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.
@@ -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, EVP_PKEY *pkey) {
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
- const uint8_t *their_pk) {
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 + X25519_PUBLICKEYBYTES + sizeof(XWING_LABEL)];
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
- : 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; \
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 *secret_key) { \
326
- if (!signature || !signature_len || !secret_key || (message_len > 0 && !message)) {\
327
- return PQ_ERROR_BUFFER; \
328
- } \
329
- return native##_signature(signature, signature_len, message, message_len, NULL, 0,\
330
- secret_key) == 0 \
331
- ? PQ_SUCCESS \
332
- : PQ_ERROR_SIGN; \
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 *public_key) { \
344
- if (!signature || !public_key || (message_len > 0 && !message)) { \
345
- return PQ_ERROR_BUFFER; \
346
- } \
347
- return native##_verify(signature, signature_len, message, message_len, NULL, 0, \
348
- public_key) == 0 \
349
- ? PQ_SUCCESS \
350
- : PQ_ERROR_VERIFY; \
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(signature, signature_len, message, message_len,
444
- secret_key, seed, seed_len,
445
- pqcr_mldsa65_signature_internal,
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(signature, signature_len, message, message_len,
454
- secret_key, seed, seed_len,
455
- pqcr_mldsa44_signature_internal,
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(signature, signature_len, message, message_len,
464
- secret_key, seed, seed_len,
465
- pqcr_mldsa87_signature_internal,
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
- int pq_mldsa_compute_tr_from_public_key(uint8_t *tr_out, const uint8_t *public_key);
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);
@@ -2,6 +2,6 @@
2
2
  #ifndef PQCRYPTO_VERSION_H
3
3
  #define PQCRYPTO_VERSION_H
4
4
 
5
- #define PQCRYPTO_VERSION "0.5.3"
5
+ #define PQCRYPTO_VERSION "0.6.0"
6
6
 
7
7
  #endif
@@ -85,6 +85,7 @@ module PQCrypto
85
85
  end
86
86
 
87
87
  def wipe!
88
+ PQCrypto.__send__(:native_hybrid_kem_expanded_secret_key_wipe, @expanded_key) if @expanded_key
88
89
  @expanded_key = nil
89
90
  super
90
91
  end
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
- secret_material = case format
105
- when :seed
106
- _public_key, expanded = PQCrypto.__send__(native_method_for(algorithm, :keypair_from_seed), material)
107
- expanded
108
- when :both
109
- _seed, expanded = material
110
- expanded
111
- when :expanded
112
- material
113
- else
114
- raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
115
- end
116
-
117
- SecretKey.new(resolve_algorithm!(algorithm), secret_material)
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.to_bytes, @bytes)
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, :both
242
- raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
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, :both
253
- raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
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.to_bytes, @bytes)
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