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.
- package/QuickCrypto.podspec +11 -1
- package/android/CMakeLists.txt +2 -0
- package/cpp/cipher/GCMCipher.cpp +68 -0
- package/cpp/cipher/GCMCipher.hpp +14 -0
- package/cpp/cipher/HybridCipherFactory.hpp +8 -0
- package/cpp/cipher/HybridRsaCipher.cpp +229 -0
- package/cpp/cipher/HybridRsaCipher.hpp +23 -0
- package/cpp/keys/HybridKeyObjectHandle.cpp +508 -9
- package/cpp/keys/HybridKeyObjectHandle.hpp +10 -1
- package/cpp/utils/base64.h +309 -0
- package/lib/commonjs/ec.js +85 -17
- package/lib/commonjs/ec.js.map +1 -1
- package/lib/commonjs/specs/rsaCipher.nitro.js +6 -0
- package/lib/commonjs/specs/rsaCipher.nitro.js.map +1 -0
- package/lib/commonjs/subtle.js +420 -17
- package/lib/commonjs/subtle.js.map +1 -1
- package/lib/commonjs/utils/conversion.js +1 -1
- package/lib/commonjs/utils/conversion.js.map +1 -1
- package/lib/module/ec.js +86 -18
- package/lib/module/ec.js.map +1 -1
- package/lib/module/specs/rsaCipher.nitro.js +4 -0
- package/lib/module/specs/rsaCipher.nitro.js.map +1 -0
- package/lib/module/subtle.js +421 -18
- package/lib/module/subtle.js.map +1 -1
- package/lib/module/utils/conversion.js +1 -1
- package/lib/module/utils/conversion.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/ec.d.ts.map +1 -1
- package/lib/typescript/specs/keyObjectHandle.nitro.d.ts +1 -0
- package/lib/typescript/specs/keyObjectHandle.nitro.d.ts.map +1 -1
- package/lib/typescript/specs/rsaCipher.nitro.d.ts +26 -0
- package/lib/typescript/specs/rsaCipher.nitro.d.ts.map +1 -0
- package/lib/typescript/subtle.d.ts.map +1 -1
- package/lib/typescript/utils/conversion.d.ts.map +1 -1
- package/lib/typescript/utils/types.d.ts +1 -1
- package/lib/typescript/utils/types.d.ts.map +1 -1
- package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +1 -0
- package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +10 -0
- package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +10 -0
- package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +104 -0
- package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.hpp +5 -4
- package/nitrogen/generated/shared/c++/HybridRsaCipherSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridRsaCipherSpec.hpp +70 -0
- package/package.json +1 -1
- package/src/ec.ts +122 -20
- package/src/specs/keyObjectHandle.nitro.ts +1 -0
- package/src/specs/rsaCipher.nitro.ts +35 -0
- package/src/subtle.ts +550 -45
- package/src/utils/conversion.ts +3 -1
- package/src/utils/types.ts +6 -6
- package/nitrogen/generated/shared/c++/CFRGKeyPairType.hpp +0 -84
|
@@ -1,14 +1,89 @@
|
|
|
1
1
|
#include <stdexcept>
|
|
2
2
|
|
|
3
|
-
#include "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
313
|
+
return AsymmetricKeyType::X25519;
|
|
115
314
|
case EVP_PKEY_X448:
|
|
116
|
-
return
|
|
315
|
+
return AsymmetricKeyType::X448;
|
|
117
316
|
case EVP_PKEY_ED25519:
|
|
118
|
-
return
|
|
317
|
+
return AsymmetricKeyType::ED25519;
|
|
119
318
|
case EVP_PKEY_ED448:
|
|
120
|
-
return
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|