pq_crypto 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/GET_STARTED.md +70 -9
- data/README.md +11 -6
- data/ext/pqcrypto/pq_externalmu.c +23 -18
- data/ext/pqcrypto/pqcrypto_native_api.h +10 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +212 -48
- data/ext/pqcrypto/pqcrypto_secure.c +83 -84
- data/ext/pqcrypto/pqcrypto_secure.h +15 -10
- data/ext/pqcrypto/pqcrypto_version.h +1 -1
- data/lib/pq_crypto/hybrid_kem.rb +1 -0
- data/lib/pq_crypto/kem.rb +71 -29
- data/lib/pq_crypto/key.rb +90 -0
- data/lib/pq_crypto/pkcs8.rb +184 -10
- data/lib/pq_crypto/signature.rb +74 -37
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +6 -4
- metadata +7 -3
|
@@ -73,7 +73,8 @@ cleanup:
|
|
|
73
73
|
return ret;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
static int x25519_shared_secret_with_pkey(uint8_t *shared, const uint8_t *their_pk,
|
|
76
|
+
static int x25519_shared_secret_with_pkey(uint8_t *shared, const uint8_t *their_pk,
|
|
77
|
+
EVP_PKEY *pkey) {
|
|
77
78
|
EVP_PKEY_CTX *ctx = NULL;
|
|
78
79
|
EVP_PKEY *peer_key = NULL;
|
|
79
80
|
size_t shared_len = X25519_SHAREDSECRETBYTES;
|
|
@@ -131,7 +132,7 @@ static int x25519_shared_secret(uint8_t *shared, const uint8_t *their_pk, const
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
static int x25519_ephemeral_keypair_and_shared_secret(uint8_t *ephemeral_pk, uint8_t *shared,
|
|
134
|
-
|
|
135
|
+
const uint8_t *their_pk) {
|
|
135
136
|
EVP_PKEY_CTX *keygen_ctx = NULL;
|
|
136
137
|
EVP_PKEY *ephemeral_pkey = NULL;
|
|
137
138
|
size_t pklen = X25519_PUBLICKEYBYTES;
|
|
@@ -173,8 +174,8 @@ static int xwing_combiner(uint8_t shared_secret[HYBRID_SHAREDSECRETBYTES],
|
|
|
173
174
|
const uint8_t ss_X[X25519_SHAREDSECRETBYTES],
|
|
174
175
|
const uint8_t ct_X[X25519_PUBLICKEYBYTES],
|
|
175
176
|
const uint8_t pk_X[X25519_PUBLICKEYBYTES]) {
|
|
176
|
-
uint8_t input[MLKEM_SHAREDSECRETBYTES + X25519_SHAREDSECRETBYTES +
|
|
177
|
-
X25519_PUBLICKEYBYTES +
|
|
177
|
+
uint8_t input[MLKEM_SHAREDSECRETBYTES + X25519_SHAREDSECRETBYTES + X25519_PUBLICKEYBYTES +
|
|
178
|
+
X25519_PUBLICKEYBYTES + sizeof(XWING_LABEL)];
|
|
178
179
|
uint8_t *cur = input;
|
|
179
180
|
|
|
180
181
|
if (!shared_secret || !ss_M || !ss_X || !ct_X || !pk_X) {
|
|
@@ -232,36 +233,35 @@ cleanup:
|
|
|
232
233
|
return ret;
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
#define PQ_MLKEM_VARIANTS(X)
|
|
236
|
-
X(mlkem, pqcr_mlkem768)
|
|
237
|
-
X(mlkem512, pqcr_mlkem512)
|
|
236
|
+
#define PQ_MLKEM_VARIANTS(X) \
|
|
237
|
+
X(mlkem, pqcr_mlkem768) \
|
|
238
|
+
X(mlkem512, pqcr_mlkem512) \
|
|
238
239
|
X(mlkem1024, pqcr_mlkem1024)
|
|
239
240
|
|
|
240
|
-
#define PQ_DEFINE_MLKEM_SHIMS(prefix, native)
|
|
241
|
-
int pq_##prefix##_keypair(uint8_t *pk, uint8_t *sk) {
|
|
242
|
-
if (!pk || !sk) {
|
|
243
|
-
return PQ_ERROR_BUFFER;
|
|
244
|
-
}
|
|
245
|
-
return native##_keypair(pk, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR;
|
|
246
|
-
}
|
|
247
|
-
int pq_##prefix##_keypair_from_seed(uint8_t *pk, uint8_t *sk, const uint8_t *seed64) {\
|
|
248
|
-
if (!pk || !sk || !seed64) {
|
|
249
|
-
return PQ_ERROR_BUFFER;
|
|
250
|
-
}
|
|
251
|
-
return native##_keypair_derand(pk, sk, seed64) == 0 ? PQ_SUCCESS
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return native##_dec(ss, ct, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_DECAPSULATE; \
|
|
241
|
+
#define PQ_DEFINE_MLKEM_SHIMS(prefix, native) \
|
|
242
|
+
int pq_##prefix##_keypair(uint8_t *pk, uint8_t *sk) { \
|
|
243
|
+
if (!pk || !sk) { \
|
|
244
|
+
return PQ_ERROR_BUFFER; \
|
|
245
|
+
} \
|
|
246
|
+
return native##_keypair(pk, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR; \
|
|
247
|
+
} \
|
|
248
|
+
int pq_##prefix##_keypair_from_seed(uint8_t *pk, uint8_t *sk, const uint8_t *seed64) { \
|
|
249
|
+
if (!pk || !sk || !seed64) { \
|
|
250
|
+
return PQ_ERROR_BUFFER; \
|
|
251
|
+
} \
|
|
252
|
+
return native##_keypair_derand(pk, sk, seed64) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR; \
|
|
253
|
+
} \
|
|
254
|
+
int pq_##prefix##_encapsulate(uint8_t *ct, uint8_t *ss, const uint8_t *pk) { \
|
|
255
|
+
if (!ct || !ss || !pk) { \
|
|
256
|
+
return PQ_ERROR_BUFFER; \
|
|
257
|
+
} \
|
|
258
|
+
return native##_enc(ct, ss, pk) == 0 ? PQ_SUCCESS : PQ_ERROR_ENCAPSULATE; \
|
|
259
|
+
} \
|
|
260
|
+
int pq_##prefix##_decapsulate(uint8_t *ss, const uint8_t *ct, const uint8_t *sk) { \
|
|
261
|
+
if (!ss || !ct || !sk) { \
|
|
262
|
+
return PQ_ERROR_BUFFER; \
|
|
263
|
+
} \
|
|
264
|
+
return native##_dec(ss, ct, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_DECAPSULATE; \
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
PQ_MLKEM_VARIANTS(PQ_DEFINE_MLKEM_SHIMS)
|
|
@@ -288,30 +288,29 @@ static int pq_testing_mlkem_encapsulate_from_seed_with(
|
|
|
288
288
|
: PQ_ERROR_ENCAPSULATE;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
#define PQ_DEFINE_MLKEM_TESTING_SHIMS(prefix, native)
|
|
292
|
-
int pq_testing_##prefix##_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key,
|
|
293
|
-
const uint8_t *seed, size_t seed_len) {
|
|
294
|
-
return pq_testing_mlkem_keypair_from_seed_with(public_key, secret_key, seed, seed_len,
|
|
295
|
-
native##_keypair_derand);
|
|
296
|
-
}
|
|
297
|
-
int pq_testing_##prefix##_encapsulate_from_seed(uint8_t *ciphertext, uint8_t *shared_secret
|
|
298
|
-
const uint8_t *public_key,
|
|
299
|
-
const uint8_t *seed, size_t seed_len) {
|
|
300
|
-
return pq_testing_mlkem_encapsulate_from_seed_with(ciphertext, shared_secret, public_key
|
|
301
|
-
seed, seed_len, native##_enc_derand)
|
|
291
|
+
#define PQ_DEFINE_MLKEM_TESTING_SHIMS(prefix, native) \
|
|
292
|
+
int pq_testing_##prefix##_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key, \
|
|
293
|
+
const uint8_t *seed, size_t seed_len) { \
|
|
294
|
+
return pq_testing_mlkem_keypair_from_seed_with(public_key, secret_key, seed, seed_len, \
|
|
295
|
+
native##_keypair_derand); \
|
|
296
|
+
} \
|
|
297
|
+
int pq_testing_##prefix##_encapsulate_from_seed(uint8_t *ciphertext, uint8_t *shared_secret, \
|
|
298
|
+
const uint8_t *public_key, \
|
|
299
|
+
const uint8_t *seed, size_t seed_len) { \
|
|
300
|
+
return pq_testing_mlkem_encapsulate_from_seed_with(ciphertext, shared_secret, public_key, \
|
|
301
|
+
seed, seed_len, native##_enc_derand); \
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
PQ_MLKEM_VARIANTS(PQ_DEFINE_MLKEM_TESTING_SHIMS)
|
|
305
305
|
|
|
306
306
|
#undef PQ_DEFINE_MLKEM_TESTING_SHIMS
|
|
307
307
|
|
|
308
|
-
#define PQ_DEFINE_MLDSA_SIGN_KEYPAIR(prefix, native)
|
|
309
|
-
int pq_##prefix##_keypair(uint8_t *public_key, uint8_t *secret_key) {
|
|
310
|
-
if (!public_key || !secret_key) {
|
|
311
|
-
return PQ_ERROR_BUFFER;
|
|
312
|
-
}
|
|
313
|
-
return native##_keypair(public_key, secret_key) == 0 ? PQ_SUCCESS
|
|
314
|
-
: PQ_ERROR_KEYPAIR; \
|
|
308
|
+
#define PQ_DEFINE_MLDSA_SIGN_KEYPAIR(prefix, native) \
|
|
309
|
+
int pq_##prefix##_keypair(uint8_t *public_key, uint8_t *secret_key) { \
|
|
310
|
+
if (!public_key || !secret_key) { \
|
|
311
|
+
return PQ_ERROR_BUFFER; \
|
|
312
|
+
} \
|
|
313
|
+
return native##_keypair(public_key, secret_key) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR; \
|
|
315
314
|
}
|
|
316
315
|
|
|
317
316
|
PQ_DEFINE_MLDSA_SIGN_KEYPAIR(sign, pqcr_mldsa65)
|
|
@@ -320,16 +319,18 @@ PQ_DEFINE_MLDSA_SIGN_KEYPAIR(mldsa87_sign, pqcr_mldsa87)
|
|
|
320
319
|
|
|
321
320
|
#undef PQ_DEFINE_MLDSA_SIGN_KEYPAIR
|
|
322
321
|
|
|
323
|
-
#define PQ_DEFINE_MLDSA_SIGN(name, native)
|
|
324
|
-
int pq_##name(uint8_t *signature, size_t *signature_len, const uint8_t *message,
|
|
325
|
-
size_t message_len, const uint8_t *
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
322
|
+
#define PQ_DEFINE_MLDSA_SIGN(name, native) \
|
|
323
|
+
int pq_##name(uint8_t *signature, size_t *signature_len, const uint8_t *message, \
|
|
324
|
+
size_t message_len, const uint8_t *ctx, size_t ctx_len, \
|
|
325
|
+
const uint8_t *secret_key) { \
|
|
326
|
+
if (!signature || !signature_len || !secret_key || (message_len > 0 && !message) || \
|
|
327
|
+
(ctx_len > 0 && !ctx) || ctx_len > 255) { \
|
|
328
|
+
return PQ_ERROR_BUFFER; \
|
|
329
|
+
} \
|
|
330
|
+
return native##_signature(signature, signature_len, message, message_len, ctx, ctx_len, \
|
|
331
|
+
secret_key) == 0 \
|
|
332
|
+
? PQ_SUCCESS \
|
|
333
|
+
: PQ_ERROR_SIGN; \
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
PQ_DEFINE_MLDSA_SIGN(sign, pqcr_mldsa65)
|
|
@@ -338,16 +339,18 @@ PQ_DEFINE_MLDSA_SIGN(mldsa87_sign, pqcr_mldsa87)
|
|
|
338
339
|
|
|
339
340
|
#undef PQ_DEFINE_MLDSA_SIGN
|
|
340
341
|
|
|
341
|
-
#define PQ_DEFINE_MLDSA_VERIFY(name, native)
|
|
342
|
-
int pq_##name(const uint8_t *signature, size_t signature_len, const uint8_t *message,
|
|
343
|
-
size_t message_len, const uint8_t *
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
342
|
+
#define PQ_DEFINE_MLDSA_VERIFY(name, native) \
|
|
343
|
+
int pq_##name(const uint8_t *signature, size_t signature_len, const uint8_t *message, \
|
|
344
|
+
size_t message_len, const uint8_t *ctx, size_t ctx_len, \
|
|
345
|
+
const uint8_t *public_key) { \
|
|
346
|
+
if (!signature || !public_key || (message_len > 0 && !message) || (ctx_len > 0 && !ctx) || \
|
|
347
|
+
ctx_len > 255) { \
|
|
348
|
+
return PQ_ERROR_BUFFER; \
|
|
349
|
+
} \
|
|
350
|
+
return native##_verify(signature, signature_len, message, message_len, ctx, ctx_len, \
|
|
351
|
+
public_key) == 0 \
|
|
352
|
+
? PQ_SUCCESS \
|
|
353
|
+
: PQ_ERROR_VERIFY; \
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
PQ_DEFINE_MLDSA_VERIFY(verify, pqcr_mldsa65)
|
|
@@ -440,30 +443,27 @@ int pq_testing_mldsa_sign_from_seed(uint8_t *signature, size_t *signature_len,
|
|
|
440
443
|
const uint8_t *message, size_t message_len,
|
|
441
444
|
const uint8_t *secret_key, const uint8_t *seed,
|
|
442
445
|
size_t seed_len) {
|
|
443
|
-
return pq_testing_mldsa_sign_from_seed_with(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
pqcr_mldsa65_prepare_domain_separation_prefix);
|
|
446
|
+
return pq_testing_mldsa_sign_from_seed_with(
|
|
447
|
+
signature, signature_len, message, message_len, secret_key, seed, seed_len,
|
|
448
|
+
pqcr_mldsa65_signature_internal, pqcr_mldsa65_prepare_domain_separation_prefix);
|
|
447
449
|
}
|
|
448
450
|
|
|
449
451
|
int pq_testing_mldsa44_sign_from_seed(uint8_t *signature, size_t *signature_len,
|
|
450
452
|
const uint8_t *message, size_t message_len,
|
|
451
453
|
const uint8_t *secret_key, const uint8_t *seed,
|
|
452
454
|
size_t seed_len) {
|
|
453
|
-
return pq_testing_mldsa_sign_from_seed_with(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
pqcr_mldsa44_prepare_domain_separation_prefix);
|
|
455
|
+
return pq_testing_mldsa_sign_from_seed_with(
|
|
456
|
+
signature, signature_len, message, message_len, secret_key, seed, seed_len,
|
|
457
|
+
pqcr_mldsa44_signature_internal, pqcr_mldsa44_prepare_domain_separation_prefix);
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
int pq_testing_mldsa87_sign_from_seed(uint8_t *signature, size_t *signature_len,
|
|
460
461
|
const uint8_t *message, size_t message_len,
|
|
461
462
|
const uint8_t *secret_key, const uint8_t *seed,
|
|
462
463
|
size_t seed_len) {
|
|
463
|
-
return pq_testing_mldsa_sign_from_seed_with(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
pqcr_mldsa87_prepare_domain_separation_prefix);
|
|
464
|
+
return pq_testing_mldsa_sign_from_seed_with(
|
|
465
|
+
signature, signature_len, message, message_len, secret_key, seed, seed_len,
|
|
466
|
+
pqcr_mldsa87_signature_internal, pqcr_mldsa87_prepare_domain_separation_prefix);
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
int pq_hybrid_kem_keypair(uint8_t *public_key, uint8_t *secret_key) {
|
|
@@ -523,8 +523,7 @@ int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
|
|
|
523
523
|
goto cleanup;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
ret = x25519_ephemeral_keypair_and_shared_secret(ct.x25519_ephemeral, x25519_ss,
|
|
527
|
-
pk.x25519_pk);
|
|
526
|
+
ret = x25519_ephemeral_keypair_and_shared_secret(ct.x25519_ephemeral, x25519_ss, pk.x25519_pk);
|
|
528
527
|
if (ret != PQ_SUCCESS) {
|
|
529
528
|
ret = PQ_ERROR_ENCAPSULATE;
|
|
530
529
|
goto cleanup;
|
|
@@ -95,17 +95,17 @@ int pq_mldsa_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key,
|
|
|
95
95
|
int pq_mldsa87_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key,
|
|
96
96
|
const uint8_t *seed32);
|
|
97
97
|
int pq_sign(uint8_t *signature, size_t *signature_len, const uint8_t *message, size_t message_len,
|
|
98
|
-
const uint8_t *secret_key);
|
|
98
|
+
const uint8_t *ctx, size_t ctx_len, const uint8_t *secret_key);
|
|
99
99
|
int pq_mldsa44_sign(uint8_t *signature, size_t *signature_len, const uint8_t *message, size_t message_len,
|
|
100
|
-
const uint8_t *secret_key);
|
|
100
|
+
const uint8_t *ctx, size_t ctx_len, const uint8_t *secret_key);
|
|
101
101
|
int pq_mldsa87_sign(uint8_t *signature, size_t *signature_len, const uint8_t *message, size_t message_len,
|
|
102
|
-
const uint8_t *secret_key);
|
|
102
|
+
const uint8_t *ctx, size_t ctx_len, const uint8_t *secret_key);
|
|
103
103
|
int pq_verify(const uint8_t *signature, size_t signature_len, const uint8_t *message,
|
|
104
|
-
size_t message_len, const uint8_t *public_key);
|
|
104
|
+
size_t message_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *public_key);
|
|
105
105
|
int pq_mldsa44_verify(const uint8_t *signature, size_t signature_len, const uint8_t *message,
|
|
106
|
-
size_t message_len, const uint8_t *public_key);
|
|
106
|
+
size_t message_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *public_key);
|
|
107
107
|
int pq_mldsa87_verify(const uint8_t *signature, size_t signature_len, const uint8_t *message,
|
|
108
|
-
size_t message_len, const uint8_t *public_key);
|
|
108
|
+
size_t message_len, const uint8_t *ctx, size_t ctx_len, const uint8_t *public_key);
|
|
109
109
|
|
|
110
110
|
int pq_public_key_to_pqc_container_der(uint8_t **output, size_t *output_len,
|
|
111
111
|
const uint8_t *public_key,
|
|
@@ -189,13 +189,18 @@ const char *pq_version(void);
|
|
|
189
189
|
#define PQ_MLDSA_MUBYTES 64
|
|
190
190
|
#define PQ_MLDSA_TRBYTES 64
|
|
191
191
|
|
|
192
|
-
int pq_mldsa_extract_tr_from_secret_key(uint8_t *tr_out, const uint8_t *secret_key
|
|
193
|
-
|
|
192
|
+
int pq_mldsa_extract_tr_from_secret_key(uint8_t *tr_out, const uint8_t *secret_key,
|
|
193
|
+
size_t public_key_len,
|
|
194
|
+
int (*pk_from_sk)(uint8_t *, const uint8_t *));
|
|
195
|
+
int pq_mldsa_compute_tr_from_public_key(uint8_t *tr_out, const uint8_t *public_key,
|
|
196
|
+
size_t public_key_len);
|
|
194
197
|
|
|
195
198
|
int pq_sign_mu(uint8_t *signature, size_t *signature_len,
|
|
196
|
-
const uint8_t *mu, const uint8_t *secret_key
|
|
199
|
+
const uint8_t *mu, const uint8_t *secret_key,
|
|
200
|
+
int (*signature_extmu)(uint8_t *, size_t *, const uint8_t *, const uint8_t *));
|
|
197
201
|
int pq_verify_mu(const uint8_t *signature, size_t signature_len,
|
|
198
|
-
const uint8_t *mu, const uint8_t *public_key
|
|
202
|
+
const uint8_t *mu, const uint8_t *public_key, size_t expected_signature_len,
|
|
203
|
+
int (*verify_extmu)(const uint8_t *, size_t, const uint8_t *, const uint8_t *));
|
|
199
204
|
void *pq_mu_builder_new(void);
|
|
200
205
|
int pq_mu_builder_init(void *state, const uint8_t *tr,
|
|
201
206
|
const uint8_t *ctx, size_t ctxlen);
|
data/lib/pq_crypto/hybrid_kem.rb
CHANGED
data/lib/pq_crypto/kem.rb
CHANGED
|
@@ -44,6 +44,10 @@ module PQCrypto
|
|
|
44
44
|
SecretKey.new(resolve_algorithm!(algorithm), bytes)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
def secret_key_from_seed(algorithm, seed)
|
|
48
|
+
SecretKey.from_seed(resolve_algorithm!(algorithm), seed)
|
|
49
|
+
end
|
|
50
|
+
|
|
47
51
|
def public_key_from_pqc_container_der(der, algorithm = nil)
|
|
48
52
|
resolved_algorithm, bytes = Serialization.public_key_from_pqc_container_der(algorithm, der)
|
|
49
53
|
PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
|
|
@@ -64,12 +68,12 @@ module PQCrypto
|
|
|
64
68
|
SecretKey.new(resolve_algorithm!(resolved_algorithm), bytes)
|
|
65
69
|
end
|
|
66
70
|
|
|
67
|
-
def secret_key_from_pkcs8_der(der)
|
|
68
|
-
secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der))
|
|
71
|
+
def secret_key_from_pkcs8_der(der, passphrase: nil)
|
|
72
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der, passphrase: passphrase))
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
def secret_key_from_pkcs8_pem(pem)
|
|
72
|
-
secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem))
|
|
75
|
+
def secret_key_from_pkcs8_pem(pem, passphrase: nil)
|
|
76
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem, passphrase: passphrase))
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
def public_key_from_spki_der(der, algorithm: nil)
|
|
@@ -101,20 +105,20 @@ module PQCrypto
|
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
def secret_key_from_decoded_pkcs8(algorithm, format, material)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
algorithm = resolve_algorithm!(algorithm)
|
|
109
|
+
|
|
110
|
+
case format
|
|
111
|
+
when :seed
|
|
112
|
+
_public_key, expanded = PQCrypto.__send__(native_method_for(algorithm, :keypair_from_seed), material)
|
|
113
|
+
SecretKey.new(algorithm, expanded, seed: material)
|
|
114
|
+
when :both
|
|
115
|
+
seed, expanded = material
|
|
116
|
+
SecretKey.new(algorithm, expanded, seed: seed)
|
|
117
|
+
when :expanded
|
|
118
|
+
SecretKey.new(algorithm, material)
|
|
119
|
+
else
|
|
120
|
+
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
121
|
+
end
|
|
118
122
|
end
|
|
119
123
|
|
|
120
124
|
def native_method_for(algorithm, operation)
|
|
@@ -192,7 +196,7 @@ module PQCrypto
|
|
|
192
196
|
|
|
193
197
|
def ==(other)
|
|
194
198
|
return false unless other.is_a?(PublicKey) && other.algorithm == algorithm
|
|
195
|
-
PQCrypto.__send__(:native_ct_equals, other.
|
|
199
|
+
PQCrypto.__send__(:native_ct_equals, other.send(:bytes_for_native), @bytes)
|
|
196
200
|
end
|
|
197
201
|
|
|
198
202
|
alias eql? ==
|
|
@@ -207,6 +211,10 @@ module PQCrypto
|
|
|
207
211
|
|
|
208
212
|
private
|
|
209
213
|
|
|
214
|
+
def bytes_for_native
|
|
215
|
+
@bytes
|
|
216
|
+
end
|
|
217
|
+
|
|
210
218
|
def validate_length!
|
|
211
219
|
expected = KEM.details(@algorithm).fetch(:public_key_bytes)
|
|
212
220
|
raise InvalidKeyError, "Invalid KEM public key length" unless @bytes.bytesize == expected
|
|
@@ -216,10 +224,20 @@ module PQCrypto
|
|
|
216
224
|
class SecretKey
|
|
217
225
|
attr_reader :algorithm
|
|
218
226
|
|
|
219
|
-
def initialize(algorithm, bytes)
|
|
227
|
+
def initialize(algorithm, bytes, seed: nil)
|
|
220
228
|
@algorithm = algorithm
|
|
221
229
|
@bytes = String(bytes).b
|
|
230
|
+
@seed = seed.nil? ? nil : String(seed).b
|
|
222
231
|
validate_length!
|
|
232
|
+
validate_seed_length! if @seed
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def self.from_seed(algorithm, seed)
|
|
236
|
+
seed_bytes = String(seed).b
|
|
237
|
+
_public_key, expanded = PQCrypto.__send__(KEM.send(:native_method_for, algorithm, :keypair_from_seed), seed_bytes)
|
|
238
|
+
new(algorithm, expanded, seed: seed_bytes)
|
|
239
|
+
rescue ArgumentError => e
|
|
240
|
+
raise InvalidKeyError, e.message
|
|
223
241
|
end
|
|
224
242
|
|
|
225
243
|
def to_bytes
|
|
@@ -234,23 +252,31 @@ module PQCrypto
|
|
|
234
252
|
Serialization.secret_key_to_pqc_container_pem(@algorithm, @bytes)
|
|
235
253
|
end
|
|
236
254
|
|
|
237
|
-
def to_pkcs8_der(format: :expanded)
|
|
255
|
+
def to_pkcs8_der(format: :expanded, passphrase: nil, iterations: PKCS8::ENCRYPTED_PKCS8_DEFAULT_ITERATIONS)
|
|
238
256
|
case format
|
|
239
257
|
when :expanded
|
|
240
|
-
PKCS8.encode_der(@algorithm, @bytes, format: :expanded)
|
|
241
|
-
when :seed
|
|
242
|
-
|
|
258
|
+
PKCS8.encode_der(@algorithm, @bytes, format: :expanded, passphrase: passphrase, iterations: iterations)
|
|
259
|
+
when :seed
|
|
260
|
+
ensure_seed_available!(format)
|
|
261
|
+
PKCS8.encode_der(@algorithm, @seed, format: :seed, passphrase: passphrase, iterations: iterations)
|
|
262
|
+
when :both
|
|
263
|
+
ensure_seed_available!(format)
|
|
264
|
+
PKCS8.encode_der(@algorithm, [@seed, @bytes], format: :both, passphrase: passphrase, iterations: iterations)
|
|
243
265
|
else
|
|
244
266
|
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
245
267
|
end
|
|
246
268
|
end
|
|
247
269
|
|
|
248
|
-
def to_pkcs8_pem(format: :expanded)
|
|
270
|
+
def to_pkcs8_pem(format: :expanded, passphrase: nil, iterations: PKCS8::ENCRYPTED_PKCS8_DEFAULT_ITERATIONS)
|
|
249
271
|
case format
|
|
250
272
|
when :expanded
|
|
251
|
-
PKCS8.encode_pem(@algorithm, @bytes, format: :expanded)
|
|
252
|
-
when :seed
|
|
253
|
-
|
|
273
|
+
PKCS8.encode_pem(@algorithm, @bytes, format: :expanded, passphrase: passphrase, iterations: iterations)
|
|
274
|
+
when :seed
|
|
275
|
+
ensure_seed_available!(format)
|
|
276
|
+
PKCS8.encode_pem(@algorithm, @seed, format: :seed, passphrase: passphrase, iterations: iterations)
|
|
277
|
+
when :both
|
|
278
|
+
ensure_seed_available!(format)
|
|
279
|
+
PKCS8.encode_pem(@algorithm, [@seed, @bytes], format: :both, passphrase: passphrase, iterations: iterations)
|
|
254
280
|
else
|
|
255
281
|
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
256
282
|
end
|
|
@@ -264,12 +290,13 @@ module PQCrypto
|
|
|
264
290
|
|
|
265
291
|
def wipe!
|
|
266
292
|
PQCrypto.secure_wipe(@bytes)
|
|
293
|
+
PQCrypto.secure_wipe(@seed) if @seed
|
|
267
294
|
self
|
|
268
295
|
end
|
|
269
296
|
|
|
270
297
|
def ==(other)
|
|
271
298
|
return false unless other.is_a?(SecretKey) && other.algorithm == algorithm
|
|
272
|
-
PQCrypto.__send__(:native_ct_equals, other.
|
|
299
|
+
PQCrypto.__send__(:native_ct_equals, other.send(:bytes_for_native), @bytes)
|
|
273
300
|
end
|
|
274
301
|
|
|
275
302
|
alias eql? ==
|
|
@@ -284,10 +311,25 @@ module PQCrypto
|
|
|
284
311
|
|
|
285
312
|
private
|
|
286
313
|
|
|
314
|
+
def bytes_for_native
|
|
315
|
+
@bytes
|
|
316
|
+
end
|
|
317
|
+
|
|
287
318
|
def validate_length!
|
|
288
319
|
expected = KEM.details(@algorithm).fetch(:secret_key_bytes)
|
|
289
320
|
raise InvalidKeyError, "Invalid KEM secret key length" unless @bytes.bytesize == expected
|
|
290
321
|
end
|
|
322
|
+
|
|
323
|
+
def validate_seed_length!
|
|
324
|
+
expected = PKCS8::PRIVATE_KEY_CHOICES.fetch(@algorithm).fetch(:seed_bytes)
|
|
325
|
+
raise InvalidKeyError, "Invalid KEM seed length" unless @seed.bytesize == expected
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def ensure_seed_available!(format)
|
|
329
|
+
return if @seed
|
|
330
|
+
|
|
331
|
+
raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
|
|
332
|
+
end
|
|
291
333
|
end
|
|
292
334
|
|
|
293
335
|
class EncapsulationResult
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PQCrypto
|
|
4
|
+
module Key
|
|
5
|
+
class << self
|
|
6
|
+
def generate(algorithm)
|
|
7
|
+
algorithm = resolve_algorithm!(algorithm)
|
|
8
|
+
case AlgorithmRegistry.fetch(algorithm).fetch(:family)
|
|
9
|
+
when :ml_kem
|
|
10
|
+
KEM.generate(algorithm)
|
|
11
|
+
when :ml_dsa
|
|
12
|
+
Signature.generate(algorithm)
|
|
13
|
+
when :ml_kem_hybrid
|
|
14
|
+
HybridKEM.generate(algorithm)
|
|
15
|
+
else
|
|
16
|
+
raise UnsupportedAlgorithmError, "Unsupported key generation algorithm: #{algorithm.inspect}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def from_pem(pem, passphrase: nil)
|
|
21
|
+
text = String(pem)
|
|
22
|
+
if text.include?(SPKI::PEM_BEGIN)
|
|
23
|
+
public_key_from_spki_pem(text)
|
|
24
|
+
elsif text.include?(PKCS8::PEM_BEGIN) || text.include?(PKCS8::ENCRYPTED_PEM_BEGIN)
|
|
25
|
+
secret_key_from_pkcs8_pem(text, passphrase: passphrase)
|
|
26
|
+
else
|
|
27
|
+
raise SerializationError, "Unsupported PEM label for PQCrypto::Key.from_pem"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def from_der(der, passphrase: nil)
|
|
32
|
+
public_key_from_spki_der(der)
|
|
33
|
+
rescue SerializationError => spki_error
|
|
34
|
+
begin
|
|
35
|
+
secret_key_from_pkcs8_der(der, passphrase: passphrase)
|
|
36
|
+
rescue SerializationError => pkcs8_error
|
|
37
|
+
raise SerializationError,
|
|
38
|
+
"Unable to decode DER as SPKI or PKCS#8 (SPKI: #{spki_error.message}; PKCS#8: #{pkcs8_error.message})"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def resolve_algorithm!(algorithm)
|
|
45
|
+
AlgorithmRegistry.fetch(algorithm)
|
|
46
|
+
algorithm
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def public_key_from_spki_pem(pem)
|
|
50
|
+
algorithm, bytes = SPKI.decode_pem(pem)
|
|
51
|
+
public_key_from_algorithm_and_bytes(algorithm, bytes)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def public_key_from_spki_der(der)
|
|
55
|
+
algorithm, bytes = SPKI.decode_der(der)
|
|
56
|
+
public_key_from_algorithm_and_bytes(algorithm, bytes)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def secret_key_from_pkcs8_pem(pem, passphrase: nil)
|
|
60
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem, passphrase: passphrase))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def secret_key_from_pkcs8_der(der, passphrase: nil)
|
|
64
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der, passphrase: passphrase))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def secret_key_from_decoded_pkcs8(algorithm, format, material)
|
|
68
|
+
case AlgorithmRegistry.fetch(algorithm).fetch(:family)
|
|
69
|
+
when :ml_kem
|
|
70
|
+
KEM.send(:secret_key_from_decoded_pkcs8, algorithm, format, material)
|
|
71
|
+
when :ml_dsa
|
|
72
|
+
Signature.send(:secret_key_from_decoded_pkcs8, algorithm, format, material)
|
|
73
|
+
else
|
|
74
|
+
raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm.inspect}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def public_key_from_algorithm_and_bytes(algorithm, bytes)
|
|
79
|
+
case AlgorithmRegistry.fetch(algorithm).fetch(:family)
|
|
80
|
+
when :ml_kem
|
|
81
|
+
KEM::PublicKey.new(algorithm, bytes)
|
|
82
|
+
when :ml_dsa
|
|
83
|
+
Signature::PublicKey.new(algorithm, bytes)
|
|
84
|
+
else
|
|
85
|
+
raise SerializationError, "SPKI public key codec is not supported for #{algorithm.inspect}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|