react-native-quick-crypto 1.0.0-beta.21 → 1.0.0-beta.22

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.
Files changed (52) hide show
  1. package/QuickCrypto.podspec +11 -1
  2. package/android/CMakeLists.txt +2 -0
  3. package/cpp/cipher/GCMCipher.cpp +68 -0
  4. package/cpp/cipher/GCMCipher.hpp +14 -0
  5. package/cpp/cipher/HybridCipherFactory.hpp +8 -0
  6. package/cpp/cipher/HybridRsaCipher.cpp +229 -0
  7. package/cpp/cipher/HybridRsaCipher.hpp +23 -0
  8. package/cpp/keys/HybridKeyObjectHandle.cpp +508 -9
  9. package/cpp/keys/HybridKeyObjectHandle.hpp +10 -1
  10. package/cpp/utils/base64.h +309 -0
  11. package/lib/commonjs/ec.js +85 -17
  12. package/lib/commonjs/ec.js.map +1 -1
  13. package/lib/commonjs/specs/rsaCipher.nitro.js +6 -0
  14. package/lib/commonjs/specs/rsaCipher.nitro.js.map +1 -0
  15. package/lib/commonjs/subtle.js +420 -17
  16. package/lib/commonjs/subtle.js.map +1 -1
  17. package/lib/commonjs/utils/conversion.js +1 -1
  18. package/lib/commonjs/utils/conversion.js.map +1 -1
  19. package/lib/module/ec.js +86 -18
  20. package/lib/module/ec.js.map +1 -1
  21. package/lib/module/specs/rsaCipher.nitro.js +4 -0
  22. package/lib/module/specs/rsaCipher.nitro.js.map +1 -0
  23. package/lib/module/subtle.js +421 -18
  24. package/lib/module/subtle.js.map +1 -1
  25. package/lib/module/utils/conversion.js +1 -1
  26. package/lib/module/utils/conversion.js.map +1 -1
  27. package/lib/tsconfig.tsbuildinfo +1 -1
  28. package/lib/typescript/ec.d.ts.map +1 -1
  29. package/lib/typescript/specs/keyObjectHandle.nitro.d.ts +1 -0
  30. package/lib/typescript/specs/keyObjectHandle.nitro.d.ts.map +1 -1
  31. package/lib/typescript/specs/rsaCipher.nitro.d.ts +26 -0
  32. package/lib/typescript/specs/rsaCipher.nitro.d.ts.map +1 -0
  33. package/lib/typescript/subtle.d.ts.map +1 -1
  34. package/lib/typescript/utils/conversion.d.ts.map +1 -1
  35. package/lib/typescript/utils/types.d.ts +1 -1
  36. package/lib/typescript/utils/types.d.ts.map +1 -1
  37. package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +1 -0
  38. package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +10 -0
  39. package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +10 -0
  40. package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +104 -0
  41. package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.cpp +1 -0
  42. package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.hpp +5 -4
  43. package/nitrogen/generated/shared/c++/HybridRsaCipherSpec.cpp +22 -0
  44. package/nitrogen/generated/shared/c++/HybridRsaCipherSpec.hpp +70 -0
  45. package/package.json +1 -1
  46. package/src/ec.ts +122 -20
  47. package/src/specs/keyObjectHandle.nitro.ts +1 -0
  48. package/src/specs/rsaCipher.nitro.ts +35 -0
  49. package/src/subtle.ts +550 -45
  50. package/src/utils/conversion.ts +3 -1
  51. package/src/utils/types.ts +6 -6
  52. package/nitrogen/generated/shared/c++/CFRGKeyPairType.hpp +0 -84
@@ -1,14 +1,89 @@
1
1
  #include <stdexcept>
2
2
 
3
- #include "CFRGKeyPairType.hpp"
3
+ #include "../utils/base64.h"
4
4
  #include "HybridKeyObjectHandle.hpp"
5
5
  #include "Utils.hpp"
6
+ #include <openssl/bn.h>
6
7
  #include <openssl/ec.h>
7
8
  #include <openssl/evp.h>
8
9
  #include <openssl/obj_mac.h>
10
+ #include <openssl/rsa.h>
9
11
 
10
12
  namespace margelo::nitro::crypto {
11
13
 
14
+ // Helper functions for base64url encoding/decoding with BIGNUMs
15
+ static std::string bn_to_base64url(const BIGNUM* bn, size_t expected_size = 0) {
16
+ if (!bn)
17
+ return "";
18
+
19
+ int num_bytes = BN_num_bytes(bn);
20
+ if (num_bytes == 0)
21
+ return "";
22
+
23
+ // If expected_size is provided and larger than num_bytes, pad with leading zeros
24
+ size_t buffer_size =
25
+ (expected_size > 0 && expected_size > static_cast<size_t>(num_bytes)) ? expected_size : static_cast<size_t>(num_bytes);
26
+
27
+ std::vector<unsigned char> buffer(buffer_size, 0);
28
+
29
+ // BN_bn2bin writes to the end of the buffer if it's larger than needed
30
+ size_t offset = buffer_size - num_bytes;
31
+ BN_bn2bin(bn, buffer.data() + offset);
32
+
33
+ // Return clean base64url - RFC 7517 compliant (no padding characters)
34
+ return base64_encode<std::string>(buffer.data(), buffer.size(), true);
35
+ }
36
+
37
+ // Helper to add padding to base64url strings
38
+ static std::string add_base64_padding(const std::string& b64) {
39
+ std::string padded = b64;
40
+ // Base64 strings should be a multiple of 4 characters
41
+ // Add '=' padding to make it so
42
+ while (padded.length() % 4 != 0) {
43
+ padded += '=';
44
+ }
45
+ return padded;
46
+ }
47
+
48
+ static BIGNUM* base64url_to_bn(const std::string& b64) {
49
+ if (b64.empty())
50
+ return nullptr;
51
+
52
+ try {
53
+ // Strip trailing periods (some JWK implementations use '.' as padding)
54
+ std::string cleaned = b64;
55
+ while (!cleaned.empty() && cleaned.back() == '.') {
56
+ cleaned.pop_back();
57
+ }
58
+
59
+ // Add padding if needed for base64url
60
+ std::string padded = add_base64_padding(cleaned);
61
+ std::string decoded = base64_decode<std::string>(padded, false);
62
+ if (decoded.empty())
63
+ return nullptr;
64
+
65
+ return BN_bin2bn(reinterpret_cast<const unsigned char*>(decoded.data()), static_cast<int>(decoded.size()), nullptr);
66
+ } catch (const std::exception& e) {
67
+ throw std::runtime_error(std::string("Input is not valid base64-encoded data."));
68
+ }
69
+ }
70
+
71
+ static std::string base64url_encode(const unsigned char* data, size_t len) {
72
+ return base64_encode<std::string>(data, len, true);
73
+ }
74
+
75
+ static std::string base64url_decode(const std::string& input) {
76
+ // Strip trailing periods (some JWK implementations use '.' as padding)
77
+ std::string cleaned = input;
78
+ while (!cleaned.empty() && cleaned.back() == '.') {
79
+ cleaned.pop_back();
80
+ }
81
+
82
+ // Add padding if needed for base64url
83
+ std::string padded = add_base64_padding(cleaned);
84
+ return base64_decode<std::string>(padded, false);
85
+ }
86
+
12
87
  std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportKey(std::optional<KFormatType> format, std::optional<KeyEncoding> type,
13
88
  const std::optional<std::string>& cipher,
14
89
  const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) {
@@ -98,10 +173,124 @@ std::shared_ptr<ArrayBuffer> HybridKeyObjectHandle::exportKey(std::optional<KFor
98
173
  }
99
174
 
100
175
  JWK HybridKeyObjectHandle::exportJwk(const JWK& key, bool handleRsaPss) {
101
- throw std::runtime_error("Not yet implemented");
176
+ JWK result = key;
177
+ auto keyType = data_.GetKeyType();
178
+
179
+ // Handle secret keys (AES, HMAC)
180
+ if (keyType == KeyType::SECRET) {
181
+ auto symKey = data_.GetSymmetricKey();
182
+ result.kty = JWKkty::OCT;
183
+ // RFC 7517 compliant base64url encoding (no padding characters)
184
+ result.k = base64url_encode(reinterpret_cast<const unsigned char*>(symKey->data()), symKey->size());
185
+ return result;
186
+ }
187
+
188
+ // Handle asymmetric keys (RSA, EC)
189
+ const auto& pkey = data_.GetAsymmetricKey();
190
+ if (!pkey) {
191
+ throw std::runtime_error("Invalid key for JWK export");
192
+ }
193
+
194
+ int keyId = EVP_PKEY_id(pkey.get());
195
+
196
+ // Export RSA keys
197
+ if (keyId == EVP_PKEY_RSA || keyId == EVP_PKEY_RSA_PSS) {
198
+ const RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
199
+ if (!rsa)
200
+ throw std::runtime_error("Failed to get RSA key");
201
+
202
+ result.kty = JWKkty::RSA;
203
+
204
+ const BIGNUM *n_bn, *e_bn, *d_bn, *p_bn, *q_bn, *dmp1_bn, *dmq1_bn, *iqmp_bn;
205
+ RSA_get0_key(rsa, &n_bn, &e_bn, &d_bn);
206
+ RSA_get0_factors(rsa, &p_bn, &q_bn);
207
+ RSA_get0_crt_params(rsa, &dmp1_bn, &dmq1_bn, &iqmp_bn);
208
+
209
+ // Public components (always present)
210
+ if (n_bn)
211
+ result.n = bn_to_base64url(n_bn);
212
+ if (e_bn)
213
+ result.e = bn_to_base64url(e_bn);
214
+
215
+ // Private components (only for private keys)
216
+ if (keyType == KeyType::PRIVATE) {
217
+ if (d_bn)
218
+ result.d = bn_to_base64url(d_bn);
219
+ if (p_bn)
220
+ result.p = bn_to_base64url(p_bn);
221
+ if (q_bn)
222
+ result.q = bn_to_base64url(q_bn);
223
+ if (dmp1_bn)
224
+ result.dp = bn_to_base64url(dmp1_bn);
225
+ if (dmq1_bn)
226
+ result.dq = bn_to_base64url(dmq1_bn);
227
+ if (iqmp_bn)
228
+ result.qi = bn_to_base64url(iqmp_bn);
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ // Export EC keys
235
+ if (keyId == EVP_PKEY_EC) {
236
+ const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
237
+ if (!ec)
238
+ throw std::runtime_error("Failed to get EC key");
239
+
240
+ const EC_GROUP* group = EC_KEY_get0_group(ec);
241
+ if (!group)
242
+ throw std::runtime_error("Failed to get EC group");
243
+
244
+ int nid = EC_GROUP_get_curve_name(group);
245
+ const char* curve_name = OBJ_nid2sn(nid);
246
+ if (!curve_name)
247
+ throw std::runtime_error("Unknown curve");
248
+
249
+ // Get the field size in bytes for proper padding
250
+ size_t field_size = (EC_GROUP_get_degree(group) + 7) / 8;
251
+
252
+ result.kty = JWKkty::EC;
253
+
254
+ // Map OpenSSL curve names to JWK curve names
255
+ if (strcmp(curve_name, "prime256v1") == 0) {
256
+ result.crv = "P-256";
257
+ } else if (strcmp(curve_name, "secp384r1") == 0) {
258
+ result.crv = "P-384";
259
+ } else if (strcmp(curve_name, "secp521r1") == 0) {
260
+ result.crv = "P-521";
261
+ } else {
262
+ result.crv = curve_name;
263
+ }
264
+
265
+ const EC_POINT* pub_key = EC_KEY_get0_public_key(ec);
266
+ if (pub_key) {
267
+ BIGNUM* x_bn = BN_new();
268
+ BIGNUM* y_bn = BN_new();
269
+
270
+ if (EC_POINT_get_affine_coordinates(group, pub_key, x_bn, y_bn, nullptr) == 1) {
271
+ result.x = bn_to_base64url(x_bn, field_size);
272
+ result.y = bn_to_base64url(y_bn, field_size);
273
+ }
274
+
275
+ BN_free(x_bn);
276
+ BN_free(y_bn);
277
+ }
278
+
279
+ // Export private key if this is a private key
280
+ if (keyType == KeyType::PRIVATE) {
281
+ const BIGNUM* priv_key = EC_KEY_get0_private_key(ec);
282
+ if (priv_key) {
283
+ result.d = bn_to_base64url(priv_key, field_size);
284
+ }
285
+ }
286
+
287
+ return result;
288
+ }
289
+
290
+ throw std::runtime_error("Unsupported key type for JWK export");
102
291
  }
103
292
 
104
- CFRGKeyPairType HybridKeyObjectHandle::getAsymmetricKeyType() {
293
+ AsymmetricKeyType HybridKeyObjectHandle::getAsymmetricKeyType() {
105
294
  const auto& pkey = data_.GetAsymmetricKey();
106
295
  if (!pkey) {
107
296
  throw std::runtime_error("Key is not an asymmetric key");
@@ -110,14 +299,24 @@ CFRGKeyPairType HybridKeyObjectHandle::getAsymmetricKeyType() {
110
299
  int keyType = EVP_PKEY_id(pkey.get());
111
300
 
112
301
  switch (keyType) {
302
+ case EVP_PKEY_RSA:
303
+ return AsymmetricKeyType::RSA;
304
+ case EVP_PKEY_RSA_PSS:
305
+ return AsymmetricKeyType::RSA_PSS;
306
+ case EVP_PKEY_DSA:
307
+ return AsymmetricKeyType::DSA;
308
+ case EVP_PKEY_EC:
309
+ return AsymmetricKeyType::EC;
310
+ case EVP_PKEY_DH:
311
+ return AsymmetricKeyType::DH;
113
312
  case EVP_PKEY_X25519:
114
- return CFRGKeyPairType::X25519;
313
+ return AsymmetricKeyType::X25519;
115
314
  case EVP_PKEY_X448:
116
- return CFRGKeyPairType::X448;
315
+ return AsymmetricKeyType::X448;
117
316
  case EVP_PKEY_ED25519:
118
- return CFRGKeyPairType::ED25519;
317
+ return AsymmetricKeyType::ED25519;
119
318
  case EVP_PKEY_ED448:
120
- return CFRGKeyPairType::ED448;
319
+ return AsymmetricKeyType::ED448;
121
320
  default:
122
321
  throw std::runtime_error("Unsupported asymmetric key type");
123
322
  }
@@ -172,7 +371,218 @@ bool HybridKeyObjectHandle::init(KeyType keyType, const std::variant<std::string
172
371
  }
173
372
 
174
373
  std::optional<KeyType> HybridKeyObjectHandle::initJwk(const JWK& keyData, std::optional<NamedCurve> namedCurve) {
175
- throw std::runtime_error("Not yet implemented");
374
+ // Reset any existing data
375
+ data_ = KeyObjectData();
376
+
377
+ if (!keyData.kty.has_value()) {
378
+ throw std::runtime_error("JWK missing required 'kty' field");
379
+ }
380
+
381
+ JWKkty kty = keyData.kty.value();
382
+
383
+ // Handle symmetric keys (AES, HMAC)
384
+ if (kty == JWKkty::OCT) {
385
+ if (!keyData.k.has_value()) {
386
+ throw std::runtime_error("JWK oct key missing 'k' field");
387
+ }
388
+
389
+ std::string decoded = base64url_decode(keyData.k.value());
390
+ auto keyBuffer = ToNativeArrayBuffer(decoded);
391
+ data_ = KeyObjectData::CreateSecret(keyBuffer);
392
+ return KeyType::SECRET;
393
+ }
394
+
395
+ // Handle RSA keys
396
+ if (kty == JWKkty::RSA) {
397
+ bool isPrivate = keyData.d.has_value();
398
+
399
+ if (!keyData.n.has_value() || !keyData.e.has_value()) {
400
+ throw std::runtime_error("JWK RSA key missing required 'n' or 'e' fields");
401
+ }
402
+
403
+ RSA* rsa = RSA_new();
404
+ if (!rsa)
405
+ throw std::runtime_error("Failed to create RSA key");
406
+
407
+ // Set public components
408
+ BIGNUM* n = base64url_to_bn(keyData.n.value());
409
+ BIGNUM* e = base64url_to_bn(keyData.e.value());
410
+
411
+ if (!n || !e) {
412
+ RSA_free(rsa);
413
+ throw std::runtime_error("Failed to decode RSA public components");
414
+ }
415
+
416
+ if (isPrivate) {
417
+ // Private key
418
+ if (!keyData.d.has_value()) {
419
+ BN_free(n);
420
+ BN_free(e);
421
+ RSA_free(rsa);
422
+ throw std::runtime_error("JWK RSA private key missing 'd' field");
423
+ }
424
+
425
+ BIGNUM* d = base64url_to_bn(keyData.d.value());
426
+ if (!d) {
427
+ BN_free(n);
428
+ BN_free(e);
429
+ RSA_free(rsa);
430
+ throw std::runtime_error("Failed to decode RSA 'd' component");
431
+ }
432
+
433
+ // Set key components (RSA_set0_key takes ownership)
434
+ if (RSA_set0_key(rsa, n, e, d) != 1) {
435
+ BN_free(n);
436
+ BN_free(e);
437
+ BN_free(d);
438
+ RSA_free(rsa);
439
+ throw std::runtime_error("Failed to set RSA key components");
440
+ }
441
+
442
+ // Set optional CRT parameters if present
443
+ if (keyData.p.has_value() && keyData.q.has_value()) {
444
+ BIGNUM* p = base64url_to_bn(keyData.p.value());
445
+ BIGNUM* q = base64url_to_bn(keyData.q.value());
446
+ if (p && q) {
447
+ RSA_set0_factors(rsa, p, q);
448
+ }
449
+ }
450
+
451
+ if (keyData.dp.has_value() && keyData.dq.has_value() && keyData.qi.has_value()) {
452
+ BIGNUM* dmp1 = base64url_to_bn(keyData.dp.value());
453
+ BIGNUM* dmq1 = base64url_to_bn(keyData.dq.value());
454
+ BIGNUM* iqmp = base64url_to_bn(keyData.qi.value());
455
+ if (dmp1 && dmq1 && iqmp) {
456
+ RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
457
+ }
458
+ }
459
+
460
+ // Create EVP_PKEY from RSA
461
+ EVP_PKEY* pkey = EVP_PKEY_new();
462
+ if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) {
463
+ RSA_free(rsa);
464
+ if (pkey)
465
+ EVP_PKEY_free(pkey);
466
+ throw std::runtime_error("Failed to create EVP_PKEY from RSA");
467
+ }
468
+
469
+ data_ = KeyObjectData::CreateAsymmetric(KeyType::PRIVATE, ncrypto::EVPKeyPointer(pkey));
470
+ return KeyType::PRIVATE;
471
+
472
+ } else {
473
+ // Public key
474
+ if (RSA_set0_key(rsa, n, e, nullptr) != 1) {
475
+ BN_free(n);
476
+ BN_free(e);
477
+ RSA_free(rsa);
478
+ throw std::runtime_error("Failed to set RSA public key components");
479
+ }
480
+
481
+ EVP_PKEY* pkey = EVP_PKEY_new();
482
+ if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) {
483
+ RSA_free(rsa);
484
+ if (pkey)
485
+ EVP_PKEY_free(pkey);
486
+ throw std::runtime_error("Failed to create EVP_PKEY from RSA");
487
+ }
488
+
489
+ data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, ncrypto::EVPKeyPointer(pkey));
490
+ return KeyType::PUBLIC;
491
+ }
492
+ }
493
+
494
+ // Handle EC keys
495
+ if (kty == JWKkty::EC) {
496
+ bool isPrivate = keyData.d.has_value();
497
+
498
+ if (!keyData.crv.has_value() || !keyData.x.has_value() || !keyData.y.has_value()) {
499
+ throw std::runtime_error("JWK EC key missing required fields (crv, x, y)");
500
+ }
501
+
502
+ std::string crv = keyData.crv.value();
503
+
504
+ // Map JWK curve names to OpenSSL NIDs
505
+ int nid;
506
+ if (crv == "P-256") {
507
+ nid = NID_X9_62_prime256v1;
508
+ } else if (crv == "P-384") {
509
+ nid = NID_secp384r1;
510
+ } else if (crv == "P-521") {
511
+ nid = NID_secp521r1;
512
+ } else {
513
+ throw std::runtime_error("Unsupported EC curve: " + crv);
514
+ }
515
+
516
+ // Create EC_KEY
517
+ EC_KEY* ec = EC_KEY_new_by_curve_name(nid);
518
+ if (!ec)
519
+ throw std::runtime_error("Failed to create EC key");
520
+
521
+ const EC_GROUP* group = EC_KEY_get0_group(ec);
522
+
523
+ // Decode public key coordinates
524
+ BIGNUM* x_bn = base64url_to_bn(keyData.x.value());
525
+ BIGNUM* y_bn = base64url_to_bn(keyData.y.value());
526
+
527
+ if (!x_bn || !y_bn) {
528
+ EC_KEY_free(ec);
529
+ throw std::runtime_error("Failed to decode EC public key coordinates");
530
+ }
531
+
532
+ // Set public key
533
+ EC_POINT* pub_key = EC_POINT_new(group);
534
+ if (!pub_key || EC_POINT_set_affine_coordinates(group, pub_key, x_bn, y_bn, nullptr) != 1) {
535
+ BN_free(x_bn);
536
+ BN_free(y_bn);
537
+ if (pub_key)
538
+ EC_POINT_free(pub_key);
539
+ EC_KEY_free(ec);
540
+ throw std::runtime_error("Failed to set EC public key");
541
+ }
542
+
543
+ BN_free(x_bn);
544
+ BN_free(y_bn);
545
+
546
+ if (EC_KEY_set_public_key(ec, pub_key) != 1) {
547
+ EC_POINT_free(pub_key);
548
+ EC_KEY_free(ec);
549
+ throw std::runtime_error("Failed to set EC public key on EC_KEY");
550
+ }
551
+
552
+ EC_POINT_free(pub_key);
553
+
554
+ // Set private key if present
555
+ if (isPrivate) {
556
+ BIGNUM* d_bn = base64url_to_bn(keyData.d.value());
557
+ if (!d_bn) {
558
+ EC_KEY_free(ec);
559
+ throw std::runtime_error("Failed to decode EC private key");
560
+ }
561
+
562
+ if (EC_KEY_set_private_key(ec, d_bn) != 1) {
563
+ BN_free(d_bn);
564
+ EC_KEY_free(ec);
565
+ throw std::runtime_error("Failed to set EC private key");
566
+ }
567
+
568
+ BN_free(d_bn);
569
+ }
570
+
571
+ // Create EVP_PKEY from EC_KEY
572
+ EVP_PKEY* pkey = EVP_PKEY_new();
573
+ if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) {
574
+ EC_KEY_free(ec);
575
+ if (pkey)
576
+ EVP_PKEY_free(pkey);
577
+ throw std::runtime_error("Failed to create EVP_PKEY from EC_KEY");
578
+ }
579
+
580
+ KeyType type = isPrivate ? KeyType::PRIVATE : KeyType::PUBLIC;
581
+ data_ = KeyObjectData::CreateAsymmetric(type, ncrypto::EVPKeyPointer(pkey));
582
+ return type;
583
+ }
584
+
585
+ throw std::runtime_error("Unsupported JWK key type");
176
586
  }
177
587
 
178
588
  KeyDetail HybridKeyObjectHandle::keyDetail() {
@@ -182,8 +592,30 @@ KeyDetail HybridKeyObjectHandle::keyDetail() {
182
592
  }
183
593
 
184
594
  EVP_PKEY* pkey = pkey_ptr.get();
595
+ int keyType = EVP_PKEY_base_id(pkey);
596
+
597
+ if (keyType == EVP_PKEY_RSA) {
598
+ // Extract RSA key details
599
+ int modulusLength = EVP_PKEY_bits(pkey);
600
+
601
+ // Extract public exponent (typically 65537 = 0x10001)
602
+ const RSA* rsa = EVP_PKEY_get0_RSA(pkey);
603
+ if (rsa) {
604
+ const BIGNUM* e_bn = nullptr;
605
+ RSA_get0_key(rsa, nullptr, &e_bn, nullptr);
606
+ if (e_bn) {
607
+ unsigned long exponent_val = BN_get_word(e_bn);
608
+ return KeyDetail(std::nullopt, static_cast<double>(exponent_val), static_cast<double>(modulusLength), std::nullopt, std::nullopt,
609
+ std::nullopt, std::nullopt);
610
+ }
611
+ }
612
+
613
+ // Fallback if we couldn't extract the exponent
614
+ return KeyDetail(std::nullopt, std::nullopt, static_cast<double>(modulusLength), std::nullopt, std::nullopt, std::nullopt,
615
+ std::nullopt);
616
+ }
185
617
 
186
- if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
618
+ if (keyType == EVP_PKEY_EC) {
187
619
  // Extract EC curve name
188
620
  EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey);
189
621
  if (ec_key) {
@@ -240,4 +672,71 @@ bool HybridKeyObjectHandle::initRawKey(KeyType keyType, std::shared_ptr<ArrayBuf
240
672
  return true;
241
673
  }
242
674
 
675
+ bool HybridKeyObjectHandle::initECRaw(const std::string& namedCurve, const std::shared_ptr<ArrayBuffer>& keyData) {
676
+ // Reset any existing data
677
+ data_ = KeyObjectData();
678
+
679
+ // Map curve name to NID (same logic as HybridEcKeyPair::GetCurveFromName)
680
+ int nid = 0;
681
+ if (namedCurve == "prime256v1" || namedCurve == "P-256") {
682
+ nid = NID_X9_62_prime256v1;
683
+ } else if (namedCurve == "secp384r1" || namedCurve == "P-384") {
684
+ nid = NID_secp384r1;
685
+ } else if (namedCurve == "secp521r1" || namedCurve == "P-521") {
686
+ nid = NID_secp521r1;
687
+ } else if (namedCurve == "secp256k1") {
688
+ nid = NID_secp256k1;
689
+ } else {
690
+ // Try standard OpenSSL name resolution
691
+ nid = OBJ_txt2nid(namedCurve.c_str());
692
+ }
693
+
694
+ if (nid == 0) {
695
+ throw std::runtime_error("Unknown curve: " + namedCurve);
696
+ }
697
+
698
+ // Create EC_GROUP for the curve
699
+ ncrypto::ECGroupPointer group = ncrypto::ECGroupPointer::NewByCurveName(nid);
700
+ if (!group) {
701
+ throw std::runtime_error("Failed to create EC_GROUP for curve");
702
+ }
703
+
704
+ // Create EC_POINT from raw bytes
705
+ ncrypto::ECPointPointer point = ncrypto::ECPointPointer::New(group.get());
706
+ if (!point) {
707
+ throw std::runtime_error("Failed to create EC_POINT");
708
+ }
709
+
710
+ // Convert raw bytes to EC_POINT
711
+ ncrypto::Buffer<const unsigned char> buffer{.data = reinterpret_cast<const unsigned char*>(keyData->data()), .len = keyData->size()};
712
+
713
+ if (!point.setFromBuffer(buffer, group.get())) {
714
+ throw std::runtime_error("Failed to read DER asymmetric key");
715
+ }
716
+
717
+ // Create EC_KEY and set the public key
718
+ ncrypto::ECKeyPointer ec = ncrypto::ECKeyPointer::New(group.get());
719
+ if (!ec) {
720
+ throw std::runtime_error("Failed to create EC_KEY");
721
+ }
722
+
723
+ if (!ec.setPublicKey(point)) {
724
+ throw std::runtime_error("Failed to set public key on EC_KEY");
725
+ }
726
+
727
+ // Create EVP_PKEY from EC_KEY
728
+ ncrypto::EVPKeyPointer pkey = ncrypto::EVPKeyPointer::New();
729
+ if (!pkey) {
730
+ throw std::runtime_error("Failed to create EVP_PKEY");
731
+ }
732
+
733
+ if (!pkey.set(ec)) {
734
+ throw std::runtime_error("Failed to assign EC_KEY to EVP_PKEY");
735
+ }
736
+
737
+ // Store as public key
738
+ this->data_ = KeyObjectData::CreateAsymmetric(KeyType::PUBLIC, std::move(pkey));
739
+ return true;
740
+ }
741
+
243
742
  } // namespace margelo::nitro::crypto
@@ -24,15 +24,24 @@ class HybridKeyObjectHandle : public HybridKeyObjectHandleSpec {
24
24
 
25
25
  JWK exportJwk(const JWK& key, bool handleRsaPss) override;
26
26
 
27
- CFRGKeyPairType getAsymmetricKeyType() override;
27
+ AsymmetricKeyType getAsymmetricKeyType() override;
28
28
 
29
29
  bool init(KeyType keyType, const std::variant<std::string, std::shared_ptr<ArrayBuffer>>& key, std::optional<KFormatType> format,
30
30
  std::optional<KeyEncoding> type, const std::optional<std::shared_ptr<ArrayBuffer>>& passphrase) override;
31
31
 
32
+ bool initECRaw(const std::string& namedCurve, const std::shared_ptr<ArrayBuffer>& keyData) override;
33
+
32
34
  std::optional<KeyType> initJwk(const JWK& keyData, std::optional<NamedCurve> namedCurve) override;
33
35
 
34
36
  KeyDetail keyDetail() override;
35
37
 
38
+ KeyObjectData& getKeyObjectData() {
39
+ return data_;
40
+ }
41
+ const KeyObjectData& getKeyObjectData() const {
42
+ return data_;
43
+ }
44
+
36
45
  private:
37
46
  KeyObjectData data_;
38
47