pq_crypto 0.1.0 → 0.3.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.
@@ -4,6 +4,8 @@
4
4
  #include <stdlib.h>
5
5
  #include <string.h>
6
6
 
7
+ #include <openssl/crypto.h>
8
+
7
9
  #include "pqcrypto_secure.h"
8
10
 
9
11
  typedef struct {
@@ -62,6 +64,8 @@ static VALUE mPQCrypto;
62
64
  static VALUE ePQCryptoError;
63
65
  static VALUE ePQCryptoVerificationError;
64
66
 
67
+ __attribute__((noreturn)) static void pq_raise_general_error(int err);
68
+
65
69
  static const char *pq_algorithm_symbol_to_cstr(VALUE algorithm) {
66
70
  ID id;
67
71
  if (SYMBOL_P(algorithm)) {
@@ -72,8 +76,8 @@ static const char *pq_algorithm_symbol_to_cstr(VALUE algorithm) {
72
76
  }
73
77
  if (id == rb_intern("ml_kem_768"))
74
78
  return "ml_kem_768";
75
- if (id == rb_intern("ml_kem_768_x25519_hkdf_sha256"))
76
- return "ml_kem_768_x25519_hkdf_sha256";
79
+ if (id == rb_intern("ml_kem_768_x25519_xwing"))
80
+ return "ml_kem_768_x25519_xwing";
77
81
  if (id == rb_intern("ml_dsa_65"))
78
82
  return "ml_dsa_65";
79
83
  rb_raise(rb_eArgError, "Unsupported serialization algorithm");
@@ -82,8 +86,8 @@ static const char *pq_algorithm_symbol_to_cstr(VALUE algorithm) {
82
86
  static VALUE pq_algorithm_cstr_to_symbol(const char *algorithm) {
83
87
  if (strcmp(algorithm, "ml_kem_768") == 0)
84
88
  return ID2SYM(rb_intern("ml_kem_768"));
85
- if (strcmp(algorithm, "ml_kem_768_x25519_hkdf_sha256") == 0)
86
- return ID2SYM(rb_intern("ml_kem_768_x25519_hkdf_sha256"));
89
+ if (strcmp(algorithm, "ml_kem_768_x25519_xwing") == 0)
90
+ return ID2SYM(rb_intern("ml_kem_768_x25519_xwing"));
87
91
  if (strcmp(algorithm, "ml_dsa_65") == 0)
88
92
  return ID2SYM(rb_intern("ml_dsa_65"));
89
93
  rb_raise(rb_eArgError, "Unsupported serialization algorithm");
@@ -215,238 +219,292 @@ static void pq_wipe_and_free(uint8_t *buffer, size_t len) {
215
219
  }
216
220
  }
217
221
 
218
- __attribute__((noreturn)) static void pq_raise_general_error(int err) {
219
- switch (err) {
220
- case PQ_ERROR_KEYPAIR:
221
- rb_raise(ePQCryptoError, "Keypair generation failed");
222
- break;
223
- case PQ_ERROR_ENCAPSULATE:
224
- rb_raise(ePQCryptoError, "Encapsulation failed");
225
- break;
226
- case PQ_ERROR_DECAPSULATE:
227
- rb_raise(ePQCryptoError, "Decapsulation failed");
228
- break;
229
- case PQ_ERROR_SIGN:
230
- rb_raise(ePQCryptoError, "Signing failed");
231
- break;
232
- case PQ_ERROR_VERIFY:
233
- rb_raise(ePQCryptoError, "Verification failed");
234
- break;
235
- case PQ_ERROR_RANDOM:
236
- rb_raise(ePQCryptoError, "Random number generation failed");
237
- break;
238
- case PQ_ERROR_KDF:
239
- rb_raise(ePQCryptoError, "Key derivation failed");
240
- break;
241
- case PQ_ERROR_BUFFER:
242
- rb_raise(ePQCryptoError, "Buffer error");
243
- break;
244
- case PQ_ERROR_NOMEM:
245
- rb_raise(rb_eNoMemError, "Memory allocation failed");
246
- break;
247
- case PQ_ERROR_OPENSSL:
248
- rb_raise(ePQCryptoError, "OpenSSL error");
249
- break;
250
- default:
251
- rb_raise(ePQCryptoError, "Unknown error: %d", err);
252
- break;
222
+ static void pq_validate_bytes_argument(VALUE value, size_t expected_len, const char *what) {
223
+ StringValue(value);
224
+ if ((size_t)RSTRING_LEN(value) != expected_len) {
225
+ rb_raise(rb_eArgError, "Invalid %s length", what);
253
226
  }
254
227
  }
255
228
 
256
- __attribute__((noreturn)) static void pq_raise_verification_error(int err) {
257
- switch (err) {
258
- case PQ_ERROR_VERIFY:
259
- rb_raise(ePQCryptoVerificationError, "Verification failed");
260
- break;
261
- default:
262
- pq_raise_general_error(err);
263
- }
229
+ static VALUE pq_build_binary_pair(const uint8_t *first, size_t first_len, const uint8_t *second,
230
+ size_t second_len) {
231
+ VALUE result = rb_ary_new2(2);
232
+ rb_ary_push(result, pq_string_from_buffer(first, first_len));
233
+ rb_ary_push(result, pq_string_from_buffer(second, second_len));
234
+ return result;
264
235
  }
265
236
 
266
- static VALUE pqcrypto_ml_kem_keypair(VALUE self) {
267
- (void)self;
237
+ static VALUE pq_build_algorithm_key_pair(const char *algorithm, const uint8_t *key,
238
+ size_t key_len) {
239
+ VALUE result = rb_ary_new2(2);
240
+ rb_ary_push(result, pq_algorithm_cstr_to_symbol(algorithm));
241
+ rb_ary_push(result, pq_string_from_buffer(key, key_len));
242
+ return result;
243
+ }
268
244
 
245
+ static VALUE pq_run_kem_keypair(void *(*nogvl)(void *), size_t public_key_len,
246
+ size_t secret_key_len) {
269
247
  kem_keypair_call_t call = {0};
270
- call.public_key = pq_alloc_buffer(PQ_MLKEM_PUBLICKEYBYTES);
271
- call.secret_key = pq_alloc_buffer(PQ_MLKEM_SECRETKEYBYTES);
248
+ VALUE result;
272
249
 
273
- rb_thread_call_without_gvl(pq_ml_kem_keypair_nogvl, &call, NULL, NULL);
250
+ call.public_key = pq_alloc_buffer(public_key_len);
251
+ call.secret_key = pq_alloc_buffer(secret_key_len);
252
+
253
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
274
254
 
275
255
  if (call.result != PQ_SUCCESS) {
276
- pq_wipe_and_free(call.secret_key, PQ_MLKEM_SECRETKEYBYTES);
256
+ pq_wipe_and_free(call.secret_key, secret_key_len);
277
257
  free(call.public_key);
278
258
  pq_raise_general_error(call.result);
279
259
  }
280
260
 
281
- VALUE result = rb_ary_new2(2);
282
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_MLKEM_PUBLICKEYBYTES));
283
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_MLKEM_SECRETKEYBYTES));
284
-
261
+ result = pq_build_binary_pair(call.public_key, public_key_len, call.secret_key, secret_key_len);
285
262
  free(call.public_key);
286
- pq_wipe_and_free(call.secret_key, PQ_MLKEM_SECRETKEYBYTES);
263
+ pq_wipe_and_free(call.secret_key, secret_key_len);
287
264
  return result;
288
265
  }
289
266
 
290
- static VALUE pqcrypto_ml_kem_encapsulate(VALUE self, VALUE public_key) {
291
- (void)self;
292
- StringValue(public_key);
267
+ static VALUE pq_run_kem_encapsulate(void *(*nogvl)(void *), VALUE public_key, size_t public_key_len,
268
+ size_t ciphertext_len, size_t shared_secret_len) {
269
+ kem_encapsulate_call_t call = {0};
270
+ VALUE result;
271
+ size_t copied_public_key_len = 0;
293
272
 
294
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLKEM_PUBLICKEYBYTES) {
295
- rb_raise(rb_eArgError, "Invalid public key length");
296
- }
273
+ pq_validate_bytes_argument(public_key, public_key_len, "public key");
297
274
 
298
- kem_encapsulate_call_t call = {0};
299
- size_t public_key_len = 0;
300
- call.public_key = pq_copy_ruby_string(public_key, &public_key_len);
301
- call.ciphertext = pq_alloc_buffer(PQ_MLKEM_CIPHERTEXTBYTES);
302
- call.shared_secret = pq_alloc_buffer(PQ_MLKEM_SHAREDSECRETBYTES);
275
+ call.public_key = pq_copy_ruby_string(public_key, &copied_public_key_len);
276
+ call.ciphertext = pq_alloc_buffer(ciphertext_len);
277
+ call.shared_secret = pq_alloc_buffer(shared_secret_len);
303
278
 
304
- rb_thread_call_without_gvl(pq_ml_kem_encapsulate_nogvl, &call, NULL, NULL);
305
- pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
279
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
280
+ pq_wipe_and_free((uint8_t *)call.public_key, copied_public_key_len);
306
281
 
307
282
  if (call.result != PQ_SUCCESS) {
308
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
283
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
309
284
  free(call.ciphertext);
310
285
  pq_raise_general_error(call.result);
311
286
  }
312
287
 
313
- VALUE result = rb_ary_new2(2);
314
- rb_ary_push(result, pq_string_from_buffer(call.ciphertext, PQ_MLKEM_CIPHERTEXTBYTES));
315
- rb_ary_push(result, pq_string_from_buffer(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES));
316
-
288
+ result = pq_build_binary_pair(call.ciphertext, ciphertext_len, call.shared_secret,
289
+ shared_secret_len);
317
290
  free(call.ciphertext);
318
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
291
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
319
292
  return result;
320
293
  }
321
294
 
322
- static VALUE pqcrypto_ml_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
323
- (void)self;
324
- StringValue(ciphertext);
325
- StringValue(secret_key);
295
+ static VALUE pq_run_kem_decapsulate(void *(*nogvl)(void *), VALUE ciphertext, size_t ciphertext_len,
296
+ VALUE secret_key, size_t secret_key_len,
297
+ size_t shared_secret_len) {
298
+ kem_decapsulate_call_t call = {0};
299
+ VALUE result;
300
+ size_t copied_ciphertext_len = 0;
301
+ size_t copied_secret_key_len = 0;
326
302
 
327
- if ((size_t)RSTRING_LEN(ciphertext) != PQ_MLKEM_CIPHERTEXTBYTES) {
328
- rb_raise(rb_eArgError, "Invalid ciphertext length");
329
- }
330
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLKEM_SECRETKEYBYTES) {
331
- rb_raise(rb_eArgError, "Invalid secret key length");
332
- }
303
+ pq_validate_bytes_argument(ciphertext, ciphertext_len, "ciphertext");
304
+ pq_validate_bytes_argument(secret_key, secret_key_len, "secret key");
333
305
 
334
- kem_decapsulate_call_t call = {0};
335
- size_t ciphertext_len = 0;
336
- size_t secret_key_len = 0;
337
- call.ciphertext = pq_copy_ruby_string(ciphertext, &ciphertext_len);
338
- call.secret_key = pq_copy_ruby_string(secret_key, &secret_key_len);
339
- call.shared_secret = pq_alloc_buffer(PQ_MLKEM_SHAREDSECRETBYTES);
306
+ call.ciphertext = pq_copy_ruby_string(ciphertext, &copied_ciphertext_len);
307
+ call.secret_key = pq_copy_ruby_string(secret_key, &copied_secret_key_len);
308
+ call.shared_secret = pq_alloc_buffer(shared_secret_len);
340
309
 
341
- rb_thread_call_without_gvl(pq_ml_kem_decapsulate_nogvl, &call, NULL, NULL);
342
- pq_wipe_and_free((uint8_t *)call.ciphertext, ciphertext_len);
343
- pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
310
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
311
+ pq_wipe_and_free((uint8_t *)call.ciphertext, copied_ciphertext_len);
312
+ pq_wipe_and_free((uint8_t *)call.secret_key, copied_secret_key_len);
344
313
 
345
314
  if (call.result != PQ_SUCCESS) {
346
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
315
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
347
316
  pq_raise_general_error(call.result);
348
317
  }
349
318
 
350
- VALUE result = pq_string_from_buffer(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
351
- pq_wipe_and_free(call.shared_secret, PQ_MLKEM_SHAREDSECRETBYTES);
319
+ result = pq_string_from_buffer(call.shared_secret, shared_secret_len);
320
+ pq_wipe_and_free(call.shared_secret, shared_secret_len);
352
321
  return result;
353
322
  }
354
323
 
355
- static VALUE pqcrypto_hybrid_kem_keypair(VALUE self) {
356
- (void)self;
324
+ static VALUE pq_run_sign_keypair(void *(*nogvl)(void *), size_t public_key_len,
325
+ size_t secret_key_len) {
326
+ sign_keypair_call_t call = {0};
327
+ VALUE result;
357
328
 
358
- kem_keypair_call_t call = {0};
359
- call.public_key = pq_alloc_buffer(PQ_HYBRID_PUBLICKEYBYTES);
360
- call.secret_key = pq_alloc_buffer(PQ_HYBRID_SECRETKEYBYTES);
329
+ call.public_key = pq_alloc_buffer(public_key_len);
330
+ call.secret_key = pq_alloc_buffer(secret_key_len);
361
331
 
362
- rb_thread_call_without_gvl(pq_hybrid_kem_keypair_nogvl, &call, NULL, NULL);
332
+ rb_thread_call_without_gvl(nogvl, &call, NULL, NULL);
363
333
 
364
334
  if (call.result != PQ_SUCCESS) {
365
- pq_wipe_and_free(call.secret_key, PQ_HYBRID_SECRETKEYBYTES);
335
+ pq_wipe_and_free(call.secret_key, secret_key_len);
366
336
  free(call.public_key);
367
337
  pq_raise_general_error(call.result);
368
338
  }
369
339
 
370
- VALUE result = rb_ary_new2(2);
371
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_HYBRID_PUBLICKEYBYTES));
372
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_HYBRID_SECRETKEYBYTES));
373
-
340
+ result = pq_build_binary_pair(call.public_key, public_key_len, call.secret_key, secret_key_len);
374
341
  free(call.public_key);
375
- pq_wipe_and_free(call.secret_key, PQ_HYBRID_SECRETKEYBYTES);
342
+ pq_wipe_and_free(call.secret_key, secret_key_len);
376
343
  return result;
377
344
  }
378
345
 
379
- static VALUE pqcrypto_hybrid_kem_encapsulate(VALUE self, VALUE public_key) {
380
- (void)self;
381
- StringValue(public_key);
346
+ typedef int (*pq_export_der_fn)(uint8_t **, size_t *, const uint8_t *, size_t, const char *);
347
+ typedef int (*pq_export_pem_fn)(char **, size_t *, const uint8_t *, size_t, const char *);
348
+ typedef int (*pq_import_der_fn)(char **, uint8_t **, size_t *, const uint8_t *, size_t);
349
+ typedef int (*pq_import_pem_fn)(char **, uint8_t **, size_t *, const char *, size_t);
382
350
 
383
- if ((size_t)RSTRING_LEN(public_key) != PQ_HYBRID_PUBLICKEYBYTES) {
384
- rb_raise(rb_eArgError, "Invalid public key length");
385
- }
351
+ static VALUE pq_export_container_der(VALUE algorithm, VALUE key_bytes, pq_export_der_fn fn) {
352
+ uint8_t *out = NULL;
353
+ size_t out_len = 0;
354
+ VALUE result;
355
+ int ret;
386
356
 
387
- kem_encapsulate_call_t call = {0};
388
- size_t public_key_len = 0;
389
- call.public_key = pq_copy_ruby_string(public_key, &public_key_len);
390
- call.ciphertext = pq_alloc_buffer(PQ_HYBRID_CIPHERTEXTBYTES);
391
- call.shared_secret = pq_alloc_buffer(PQ_HYBRID_SHAREDSECRETBYTES);
357
+ StringValue(key_bytes);
358
+ ret = fn(&out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes),
359
+ (size_t)RSTRING_LEN(key_bytes), pq_algorithm_symbol_to_cstr(algorithm));
360
+ if (ret != PQ_SUCCESS)
361
+ pq_raise_general_error(ret);
392
362
 
393
- rb_thread_call_without_gvl(pq_hybrid_kem_encapsulate_nogvl, &call, NULL, NULL);
394
- pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
363
+ result = pq_string_from_buffer(out, out_len);
364
+ pq_secure_wipe(out, out_len);
365
+ free(out);
366
+ return result;
367
+ }
395
368
 
396
- if (call.result != PQ_SUCCESS) {
397
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
398
- free(call.ciphertext);
399
- pq_raise_general_error(call.result);
400
- }
369
+ static VALUE pq_export_container_pem(VALUE algorithm, VALUE key_bytes, pq_export_pem_fn fn) {
370
+ char *out = NULL;
371
+ size_t out_len = 0;
372
+ VALUE result;
373
+ int ret;
401
374
 
402
- VALUE result = rb_ary_new2(2);
403
- rb_ary_push(result, pq_string_from_buffer(call.ciphertext, PQ_HYBRID_CIPHERTEXTBYTES));
404
- rb_ary_push(result, pq_string_from_buffer(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES));
375
+ StringValue(key_bytes);
376
+ ret = fn(&out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes),
377
+ (size_t)RSTRING_LEN(key_bytes), pq_algorithm_symbol_to_cstr(algorithm));
378
+ if (ret != PQ_SUCCESS)
379
+ pq_raise_general_error(ret);
405
380
 
406
- free(call.ciphertext);
407
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
381
+ result = rb_utf8_str_new(out, (long)out_len);
382
+ pq_secure_wipe(out, out_len);
383
+ free(out);
408
384
  return result;
409
385
  }
410
386
 
411
- static VALUE pqcrypto_hybrid_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
412
- (void)self;
413
- StringValue(ciphertext);
414
- StringValue(secret_key);
387
+ static VALUE pq_import_container_der(VALUE der, pq_import_der_fn fn) {
388
+ char *algorithm = NULL;
389
+ uint8_t *key = NULL;
390
+ size_t key_len = 0;
391
+ VALUE result;
392
+ int ret;
415
393
 
416
- if ((size_t)RSTRING_LEN(ciphertext) != PQ_HYBRID_CIPHERTEXTBYTES) {
417
- rb_raise(rb_eArgError, "Invalid ciphertext length");
418
- }
419
- if ((size_t)RSTRING_LEN(secret_key) != PQ_HYBRID_SECRETKEYBYTES) {
420
- rb_raise(rb_eArgError, "Invalid secret key length");
421
- }
394
+ StringValue(der);
395
+ ret =
396
+ fn(&algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
397
+ if (ret != PQ_SUCCESS)
398
+ pq_raise_general_error(ret);
422
399
 
423
- kem_decapsulate_call_t call = {0};
424
- size_t ciphertext_len = 0;
425
- size_t secret_key_len = 0;
426
- call.ciphertext = pq_copy_ruby_string(ciphertext, &ciphertext_len);
427
- call.secret_key = pq_copy_ruby_string(secret_key, &secret_key_len);
428
- call.shared_secret = pq_alloc_buffer(PQ_HYBRID_SHAREDSECRETBYTES);
400
+ result = pq_build_algorithm_key_pair(algorithm, key, key_len);
401
+ free(algorithm);
402
+ pq_secure_wipe(key, key_len);
403
+ free(key);
404
+ return result;
405
+ }
429
406
 
430
- rb_thread_call_without_gvl(pq_hybrid_kem_decapsulate_nogvl, &call, NULL, NULL);
431
- pq_wipe_and_free((uint8_t *)call.ciphertext, ciphertext_len);
432
- pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
407
+ static VALUE pq_import_container_pem(VALUE pem, pq_import_pem_fn fn) {
408
+ char *algorithm = NULL;
409
+ uint8_t *key = NULL;
410
+ size_t key_len = 0;
411
+ VALUE result;
412
+ int ret;
433
413
 
434
- if (call.result != PQ_SUCCESS) {
435
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
436
- pq_raise_general_error(call.result);
437
- }
414
+ StringValue(pem);
415
+ ret = fn(&algorithm, &key, &key_len, RSTRING_PTR(pem), (size_t)RSTRING_LEN(pem));
416
+ if (ret != PQ_SUCCESS)
417
+ pq_raise_general_error(ret);
438
418
 
439
- VALUE result = pq_string_from_buffer(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
440
- pq_wipe_and_free(call.shared_secret, PQ_HYBRID_SHAREDSECRETBYTES);
419
+ result = pq_build_algorithm_key_pair(algorithm, key, key_len);
420
+ free(algorithm);
421
+ pq_secure_wipe(key, key_len);
422
+ free(key);
441
423
  return result;
442
424
  }
443
425
 
426
+ __attribute__((noreturn)) static void pq_raise_general_error(int err) {
427
+ switch (err) {
428
+ case PQ_ERROR_KEYPAIR:
429
+ rb_raise(ePQCryptoError, "Keypair generation failed");
430
+ break;
431
+ case PQ_ERROR_ENCAPSULATE:
432
+ rb_raise(ePQCryptoError, "Encapsulation failed");
433
+ break;
434
+ case PQ_ERROR_DECAPSULATE:
435
+ rb_raise(ePQCryptoError, "Decapsulation failed");
436
+ break;
437
+ case PQ_ERROR_SIGN:
438
+ rb_raise(ePQCryptoError, "Signing failed");
439
+ break;
440
+ case PQ_ERROR_VERIFY:
441
+ rb_raise(ePQCryptoError, "Verification failed");
442
+ break;
443
+ case PQ_ERROR_RANDOM:
444
+ rb_raise(ePQCryptoError, "Random number generation failed");
445
+ break;
446
+ case PQ_ERROR_KDF:
447
+ rb_raise(ePQCryptoError, "Key derivation failed");
448
+ break;
449
+ case PQ_ERROR_BUFFER:
450
+ rb_raise(ePQCryptoError, "Buffer error");
451
+ break;
452
+ case PQ_ERROR_NOMEM:
453
+ rb_raise(rb_eNoMemError, "Memory allocation failed");
454
+ break;
455
+ case PQ_ERROR_OPENSSL:
456
+ rb_raise(ePQCryptoError, "OpenSSL error");
457
+ break;
458
+ default:
459
+ rb_raise(ePQCryptoError, "Unknown error: %d", err);
460
+ break;
461
+ }
462
+ }
463
+
464
+ static VALUE pqcrypto_ml_kem_keypair(VALUE self) {
465
+ (void)self;
466
+ return pq_run_kem_keypair(pq_ml_kem_keypair_nogvl, PQ_MLKEM_PUBLICKEYBYTES,
467
+ PQ_MLKEM_SECRETKEYBYTES);
468
+ }
469
+
470
+ static VALUE pqcrypto_ml_kem_encapsulate(VALUE self, VALUE public_key) {
471
+ (void)self;
472
+ return pq_run_kem_encapsulate(pq_ml_kem_encapsulate_nogvl, public_key, PQ_MLKEM_PUBLICKEYBYTES,
473
+ PQ_MLKEM_CIPHERTEXTBYTES, PQ_MLKEM_SHAREDSECRETBYTES);
474
+ }
475
+
476
+ static VALUE pqcrypto_ml_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
477
+ (void)self;
478
+ return pq_run_kem_decapsulate(pq_ml_kem_decapsulate_nogvl, ciphertext, PQ_MLKEM_CIPHERTEXTBYTES,
479
+ secret_key, PQ_MLKEM_SECRETKEYBYTES, PQ_MLKEM_SHAREDSECRETBYTES);
480
+ }
481
+
482
+ static VALUE pqcrypto_hybrid_kem_keypair(VALUE self) {
483
+ (void)self;
484
+ return pq_run_kem_keypair(pq_hybrid_kem_keypair_nogvl, PQ_HYBRID_PUBLICKEYBYTES,
485
+ PQ_HYBRID_SECRETKEYBYTES);
486
+ }
487
+
488
+ static VALUE pqcrypto_hybrid_kem_encapsulate(VALUE self, VALUE public_key) {
489
+ (void)self;
490
+ return pq_run_kem_encapsulate(pq_hybrid_kem_encapsulate_nogvl, public_key,
491
+ PQ_HYBRID_PUBLICKEYBYTES, PQ_HYBRID_CIPHERTEXTBYTES,
492
+ PQ_HYBRID_SHAREDSECRETBYTES);
493
+ }
494
+
495
+ static VALUE pqcrypto_hybrid_kem_decapsulate(VALUE self, VALUE ciphertext, VALUE secret_key) {
496
+ (void)self;
497
+ return pq_run_kem_decapsulate(pq_hybrid_kem_decapsulate_nogvl, ciphertext,
498
+ PQ_HYBRID_CIPHERTEXTBYTES, secret_key, PQ_HYBRID_SECRETKEYBYTES,
499
+ PQ_HYBRID_SHAREDSECRETBYTES);
500
+ }
501
+
444
502
  static VALUE pqcrypto__test_ml_kem_keypair_from_seed(VALUE self, VALUE seed) {
445
503
  (void)self;
446
504
  StringValue(seed);
447
505
 
448
- if ((size_t)RSTRING_LEN(seed) != 32 && (size_t)RSTRING_LEN(seed) != 64) {
449
- rb_raise(rb_eArgError, "Deterministic ML-KEM test seed must be 32 or 64 bytes");
506
+ if ((size_t)RSTRING_LEN(seed) != 64) {
507
+ rb_raise(rb_eArgError, "Deterministic ML-KEM test seed must be 64 bytes (FIPS 203 d||z)");
450
508
  }
451
509
 
452
510
  kem_keypair_call_t call = {0};
@@ -476,12 +534,9 @@ static VALUE pqcrypto__test_ml_kem_keypair_from_seed(VALUE self, VALUE seed) {
476
534
 
477
535
  static VALUE pqcrypto__test_ml_kem_encapsulate_from_seed(VALUE self, VALUE public_key, VALUE seed) {
478
536
  (void)self;
479
- StringValue(public_key);
537
+ pq_validate_bytes_argument(public_key, PQ_MLKEM_PUBLICKEYBYTES, "public key");
480
538
  StringValue(seed);
481
539
 
482
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLKEM_PUBLICKEYBYTES) {
483
- rb_raise(rb_eArgError, "Invalid public key length");
484
- }
485
540
  if ((size_t)RSTRING_LEN(seed) != 32) {
486
541
  rb_raise(rb_eArgError, "Deterministic test seed must be 32 bytes");
487
542
  }
@@ -550,12 +605,9 @@ static VALUE pqcrypto__test_sign_keypair_from_seed(VALUE self, VALUE seed) {
550
605
  static VALUE pqcrypto__test_sign_from_seed(VALUE self, VALUE message, VALUE secret_key,
551
606
  VALUE seed) {
552
607
  (void)self;
553
- StringValue(secret_key);
608
+ pq_validate_bytes_argument(secret_key, PQ_MLDSA_SECRETKEYBYTES, "secret key");
554
609
  StringValue(seed);
555
610
 
556
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLDSA_SECRETKEYBYTES) {
557
- rb_raise(rb_eArgError, "Invalid secret key length");
558
- }
559
611
  if ((size_t)RSTRING_LEN(seed) != 32) {
560
612
  rb_raise(rb_eArgError, "Deterministic test seed must be 32 bytes");
561
613
  }
@@ -588,35 +640,13 @@ static VALUE pqcrypto__test_sign_from_seed(VALUE self, VALUE message, VALUE secr
588
640
 
589
641
  static VALUE pqcrypto_sign_keypair(VALUE self) {
590
642
  (void)self;
591
-
592
- sign_keypair_call_t call = {0};
593
- call.public_key = pq_alloc_buffer(PQ_MLDSA_PUBLICKEYBYTES);
594
- call.secret_key = pq_alloc_buffer(PQ_MLDSA_SECRETKEYBYTES);
595
-
596
- rb_thread_call_without_gvl(pq_sign_keypair_nogvl, &call, NULL, NULL);
597
-
598
- if (call.result != PQ_SUCCESS) {
599
- pq_wipe_and_free(call.secret_key, PQ_MLDSA_SECRETKEYBYTES);
600
- free(call.public_key);
601
- pq_raise_general_error(call.result);
602
- }
603
-
604
- VALUE result = rb_ary_new2(2);
605
- rb_ary_push(result, pq_string_from_buffer(call.public_key, PQ_MLDSA_PUBLICKEYBYTES));
606
- rb_ary_push(result, pq_string_from_buffer(call.secret_key, PQ_MLDSA_SECRETKEYBYTES));
607
-
608
- free(call.public_key);
609
- pq_wipe_and_free(call.secret_key, PQ_MLDSA_SECRETKEYBYTES);
610
- return result;
643
+ return pq_run_sign_keypair(pq_sign_keypair_nogvl, PQ_MLDSA_PUBLICKEYBYTES,
644
+ PQ_MLDSA_SECRETKEYBYTES);
611
645
  }
612
646
 
613
647
  static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
614
648
  (void)self;
615
- StringValue(secret_key);
616
-
617
- if ((size_t)RSTRING_LEN(secret_key) != PQ_MLDSA_SECRETKEYBYTES) {
618
- rb_raise(rb_eArgError, "Invalid secret key length");
619
- }
649
+ pq_validate_bytes_argument(secret_key, PQ_MLDSA_SECRETKEYBYTES, "secret key");
620
650
 
621
651
  sign_call_t call = {0};
622
652
  size_t secret_key_len = 0;
@@ -625,7 +655,7 @@ static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
625
655
  call.signature = pq_alloc_buffer(PQ_MLDSA_BYTES);
626
656
  call.message = pq_copy_ruby_string(message, &call.message_len);
627
657
 
628
- rb_thread_call_without_gvl(pq_sign_nogvl, &call, NULL, NULL);
658
+ rb_nogvl(pq_sign_nogvl, &call, NULL, NULL, RB_NOGVL_OFFLOAD_SAFE);
629
659
 
630
660
  pq_wipe_and_free(call.message, call.message_len);
631
661
  pq_wipe_and_free((uint8_t *)call.secret_key, secret_key_len);
@@ -643,11 +673,7 @@ static VALUE pqcrypto_sign(VALUE self, VALUE message, VALUE secret_key) {
643
673
  static VALUE pqcrypto_verify(VALUE self, VALUE message, VALUE signature, VALUE public_key) {
644
674
  (void)self;
645
675
  StringValue(signature);
646
- StringValue(public_key);
647
-
648
- if ((size_t)RSTRING_LEN(public_key) != PQ_MLDSA_PUBLICKEYBYTES) {
649
- rb_raise(rb_eArgError, "Invalid public key length");
650
- }
676
+ pq_validate_bytes_argument(public_key, PQ_MLDSA_PUBLICKEYBYTES, "public key");
651
677
 
652
678
  verify_call_t call = {0};
653
679
  size_t public_key_len = 0;
@@ -657,17 +683,35 @@ static VALUE pqcrypto_verify(VALUE self, VALUE message, VALUE signature, VALUE p
657
683
  call.signature_len = signature_len;
658
684
  call.message = pq_copy_ruby_string(message, &call.message_len);
659
685
 
660
- rb_thread_call_without_gvl(pq_verify_nogvl, &call, NULL, NULL);
686
+ rb_nogvl(pq_verify_nogvl, &call, NULL, NULL, RB_NOGVL_OFFLOAD_SAFE);
661
687
 
662
688
  pq_wipe_and_free(call.message, call.message_len);
663
689
  pq_wipe_and_free((uint8_t *)call.public_key, public_key_len);
664
690
  pq_wipe_and_free((uint8_t *)call.signature, signature_len);
665
691
 
666
- if (call.result != PQ_SUCCESS) {
667
- pq_raise_verification_error(call.result);
692
+ if (call.result == PQ_SUCCESS) {
693
+ return Qtrue;
694
+ }
695
+ if (call.result == PQ_ERROR_VERIFY) {
696
+ return Qfalse;
668
697
  }
698
+ pq_raise_general_error(call.result);
699
+ }
669
700
 
670
- return Qtrue;
701
+ static VALUE pqcrypto_ct_equals(VALUE self, VALUE a, VALUE b) {
702
+ (void)self;
703
+ StringValue(a);
704
+ StringValue(b);
705
+ if (RSTRING_LEN(a) != RSTRING_LEN(b)) {
706
+ return Qfalse;
707
+ }
708
+ if (RSTRING_LEN(a) == 0) {
709
+ return Qtrue;
710
+ }
711
+ if (CRYPTO_memcmp(RSTRING_PTR(a), RSTRING_PTR(b), (size_t)RSTRING_LEN(a)) == 0) {
712
+ return Qtrue;
713
+ }
714
+ return Qfalse;
671
715
  }
672
716
 
673
717
  static VALUE pqcrypto_secure_wipe(VALUE self, VALUE str) {
@@ -700,151 +744,52 @@ static void define_constants(void) {
700
744
 
701
745
  static VALUE pqcrypto_public_key_to_pqc_container_der(VALUE self, VALUE algorithm,
702
746
  VALUE key_bytes) {
703
- uint8_t *out = NULL;
704
- size_t out_len = 0;
705
- VALUE result;
706
- StringValue(key_bytes);
707
- int ret = pq_public_key_to_pqc_container_der(
708
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
709
- pq_algorithm_symbol_to_cstr(algorithm));
710
- if (ret != PQ_SUCCESS)
711
- pq_raise_general_error(ret);
712
- result = pq_string_from_buffer(out, out_len);
713
- pq_secure_wipe(out, out_len);
714
- free(out);
715
- return result;
747
+ (void)self;
748
+ return pq_export_container_der(algorithm, key_bytes, pq_public_key_to_pqc_container_der);
716
749
  }
717
750
 
718
751
  static VALUE pqcrypto_public_key_to_pqc_container_pem(VALUE self, VALUE algorithm,
719
752
  VALUE key_bytes) {
720
- char *out = NULL;
721
- size_t out_len = 0;
722
- VALUE result;
723
- StringValue(key_bytes);
724
- int ret = pq_public_key_to_pqc_container_pem(
725
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
726
- pq_algorithm_symbol_to_cstr(algorithm));
727
- if (ret != PQ_SUCCESS)
728
- pq_raise_general_error(ret);
729
- result = rb_utf8_str_new(out, (long)out_len);
730
- pq_secure_wipe(out, out_len);
731
- free(out);
732
- return result;
753
+ (void)self;
754
+ return pq_export_container_pem(algorithm, key_bytes, pq_public_key_to_pqc_container_pem);
733
755
  }
734
756
 
735
757
  static VALUE pqcrypto_secret_key_to_pqc_container_der(VALUE self, VALUE algorithm,
736
758
  VALUE key_bytes) {
737
- uint8_t *out = NULL;
738
- size_t out_len = 0;
739
- VALUE result;
740
- StringValue(key_bytes);
741
- int ret = pq_secret_key_to_pqc_container_der(
742
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
743
- pq_algorithm_symbol_to_cstr(algorithm));
744
- if (ret != PQ_SUCCESS)
745
- pq_raise_general_error(ret);
746
- result = pq_string_from_buffer(out, out_len);
747
- pq_secure_wipe(out, out_len);
748
- free(out);
749
- return result;
759
+ (void)self;
760
+ return pq_export_container_der(algorithm, key_bytes, pq_secret_key_to_pqc_container_der);
750
761
  }
751
762
 
752
763
  static VALUE pqcrypto_secret_key_to_pqc_container_pem(VALUE self, VALUE algorithm,
753
764
  VALUE key_bytes) {
754
- char *out = NULL;
755
- size_t out_len = 0;
756
- VALUE result;
757
- StringValue(key_bytes);
758
- int ret = pq_secret_key_to_pqc_container_pem(
759
- &out, &out_len, (const uint8_t *)RSTRING_PTR(key_bytes), (size_t)RSTRING_LEN(key_bytes),
760
- pq_algorithm_symbol_to_cstr(algorithm));
761
- if (ret != PQ_SUCCESS)
762
- pq_raise_general_error(ret);
763
- result = rb_utf8_str_new(out, (long)out_len);
764
- pq_secure_wipe(out, out_len);
765
- free(out);
766
- return result;
765
+ (void)self;
766
+ return pq_export_container_pem(algorithm, key_bytes, pq_secret_key_to_pqc_container_pem);
767
767
  }
768
768
 
769
769
  static VALUE pqcrypto_public_key_from_pqc_container_der(VALUE self, VALUE der) {
770
- char *algorithm = NULL;
771
- uint8_t *key = NULL;
772
- size_t key_len = 0;
773
- VALUE ary;
774
- StringValue(der);
775
- int ret = pq_public_key_from_pqc_container_der(
776
- &algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
777
- if (ret != PQ_SUCCESS)
778
- pq_raise_general_error(ret);
779
- ary = rb_ary_new_capa(2);
780
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
781
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
782
- free(algorithm);
783
- pq_secure_wipe(key, key_len);
784
- free(key);
785
- return ary;
770
+ (void)self;
771
+ return pq_import_container_der(der, pq_public_key_from_pqc_container_der);
786
772
  }
787
773
 
788
774
  static VALUE pqcrypto_public_key_from_pqc_container_pem(VALUE self, VALUE pem) {
789
- char *algorithm = NULL;
790
- uint8_t *key = NULL;
791
- size_t key_len = 0;
792
- VALUE ary;
793
- StringValue(pem);
794
- int ret = pq_public_key_from_pqc_container_pem(&algorithm, &key, &key_len, RSTRING_PTR(pem),
795
- (size_t)RSTRING_LEN(pem));
796
- if (ret != PQ_SUCCESS)
797
- pq_raise_general_error(ret);
798
- ary = rb_ary_new_capa(2);
799
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
800
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
801
- free(algorithm);
802
- pq_secure_wipe(key, key_len);
803
- free(key);
804
- return ary;
775
+ (void)self;
776
+ return pq_import_container_pem(pem, pq_public_key_from_pqc_container_pem);
805
777
  }
806
778
 
807
779
  static VALUE pqcrypto_secret_key_from_pqc_container_der(VALUE self, VALUE der) {
808
- char *algorithm = NULL;
809
- uint8_t *key = NULL;
810
- size_t key_len = 0;
811
- VALUE ary;
812
- StringValue(der);
813
- int ret = pq_secret_key_from_pqc_container_der(
814
- &algorithm, &key, &key_len, (const uint8_t *)RSTRING_PTR(der), (size_t)RSTRING_LEN(der));
815
- if (ret != PQ_SUCCESS)
816
- pq_raise_general_error(ret);
817
- ary = rb_ary_new_capa(2);
818
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
819
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
820
- free(algorithm);
821
- pq_secure_wipe(key, key_len);
822
- free(key);
823
- return ary;
780
+ (void)self;
781
+ return pq_import_container_der(der, pq_secret_key_from_pqc_container_der);
824
782
  }
825
783
 
826
784
  static VALUE pqcrypto_secret_key_from_pqc_container_pem(VALUE self, VALUE pem) {
827
- char *algorithm = NULL;
828
- uint8_t *key = NULL;
829
- size_t key_len = 0;
830
- VALUE ary;
831
- StringValue(pem);
832
- int ret = pq_secret_key_from_pqc_container_pem(&algorithm, &key, &key_len, RSTRING_PTR(pem),
833
- (size_t)RSTRING_LEN(pem));
834
- if (ret != PQ_SUCCESS)
835
- pq_raise_general_error(ret);
836
- ary = rb_ary_new_capa(2);
837
- rb_ary_push(ary, pq_algorithm_cstr_to_symbol(algorithm));
838
- rb_ary_push(ary, pq_string_from_buffer(key, key_len));
839
- free(algorithm);
840
- pq_secure_wipe(key, key_len);
841
- free(key);
842
- return ary;
785
+ (void)self;
786
+ return pq_import_container_pem(pem, pq_secret_key_from_pqc_container_pem);
843
787
  }
844
788
 
845
789
  void Init_pqcrypto_secure(void) {
846
790
  mPQCrypto = rb_define_module("PQCrypto");
847
791
  ePQCryptoError = rb_define_class_under(mPQCrypto, "Error", rb_eStandardError);
792
+
848
793
  ePQCryptoVerificationError =
849
794
  rb_define_class_under(mPQCrypto, "VerificationError", ePQCryptoError);
850
795
 
@@ -866,6 +811,7 @@ void Init_pqcrypto_secure(void) {
866
811
  rb_define_module_function(mPQCrypto, "sign_keypair", pqcrypto_sign_keypair, 0);
867
812
  rb_define_module_function(mPQCrypto, "sign", pqcrypto_sign, 2);
868
813
  rb_define_module_function(mPQCrypto, "verify", pqcrypto_verify, 3);
814
+ rb_define_module_function(mPQCrypto, "ct_equals", pqcrypto_ct_equals, 2);
869
815
  rb_define_module_function(mPQCrypto, "secure_wipe", pqcrypto_secure_wipe, 1);
870
816
  rb_define_module_function(mPQCrypto, "version", pqcrypto_version, 0);
871
817
  rb_define_module_function(mPQCrypto, "public_key_to_pqc_container_der",