Skip to content

Commit d44c983

Browse files
committed
feat: subtle.generateKey for ec
1 parent cacd165 commit d44c983

27 files changed

Lines changed: 1101 additions & 95 deletions

bun.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"react-native-bouncy-checkbox": "4.1.2",
3939
"react-native-fast-encoder": "0.2.0",
4040
"react-native-nitro-modules": "0.29.1",
41-
"react-native-quick-base64": "2.2.1",
41+
"react-native-quick-base64": "2.2.2",
4242
"react-native-quick-crypto": "workspace:*",
4343
"react-native-safe-area-context": "^5.2.2",
4444
"react-native-screens": "4.15.4",

example/src/tests/subtle/sign_verify.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { expect } from 'chai';
55

66
const encoder = new TextEncoder();
77

8+
const SUITE = 'subtle.sign/verify';
9+
810
// // Test Sign/Verify RSASSA-PKCS1-v1_5
911
// {
1012
// async function test(data) {
@@ -53,7 +55,7 @@ const encoder = new TextEncoder();
5355
// test('hello world').then(common.mustCall());
5456
// }
5557

56-
test('subtle.sign_verify', 'ECDSA P-384', async () => {
58+
test(SUITE, 'ECDSA P-384', async () => {
5759
const pair = await subtle.generateKey(
5860
{ name: 'ECDSA', namedCurve: 'P-384' },
5961
true,
@@ -78,7 +80,7 @@ test('subtle.sign_verify', 'ECDSA P-384', async () => {
7880
).to.equal(true);
7981
});
8082

81-
test('subtle.sign_verify', 'ECDSA with HashAlgorithmIdentifier', async () => {
83+
test(SUITE, 'ECDSA with HashAlgorithmIdentifier', async () => {
8284
const pair = await subtle.generateKey(
8385
{ name: 'ECDSA', namedCurve: 'P-256' },
8486
true,
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include <NitroModules/ArrayBuffer.hpp>
2+
#include <NitroModules/Promise.hpp>
3+
#include <memory>
4+
#include <openssl/bio.h>
5+
#include <openssl/buffer.h>
6+
#include <openssl/ec.h>
7+
#include <openssl/err.h>
8+
#include <openssl/evp.h>
9+
#include <openssl/obj_mac.h>
10+
#include <openssl/pem.h>
11+
#include <stdexcept>
12+
#include <string>
13+
14+
// OpenSSL EC parameter encoding constants
15+
#ifndef OPENSSL_EC_EXPLICIT_CURVE
16+
#define OPENSSL_EC_EXPLICIT_CURVE 0x000
17+
#endif
18+
#ifndef OPENSSL_EC_NAMED_CURVE
19+
#define OPENSSL_EC_NAMED_CURVE 0x001
20+
#endif
21+
22+
#include "HybridEcKeyPair.hpp"
23+
#include "Utils.hpp"
24+
25+
namespace margelo::nitro::crypto {
26+
27+
std::shared_ptr<Promise<void>> HybridEcKeyPair::generateKeyPair() {
28+
return Promise<void>::async([this]() { this->generateKeyPairSync(); });
29+
}
30+
31+
void HybridEcKeyPair::generateKeyPairSync() {
32+
if (this->curve.empty()) {
33+
throw std::runtime_error("EC curve not set. Call setCurve() first.");
34+
}
35+
36+
// Clean up existing key if any
37+
if (this->pkey != nullptr) {
38+
EVP_PKEY_free(this->pkey);
39+
this->pkey = nullptr;
40+
}
41+
42+
// Get curve NID from curve name
43+
int curve_nid = GetCurveFromName(this->curve.c_str());
44+
if (curve_nid == NID_undef) {
45+
throw std::runtime_error("Invalid or unsupported curve: " + this->curve);
46+
}
47+
48+
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> key_ctx(nullptr, EVP_PKEY_CTX_free);
49+
50+
// Handle special curves (Ed25519, X25519, etc.)
51+
switch (curve_nid) {
52+
case EVP_PKEY_ED25519:
53+
case EVP_PKEY_ED448:
54+
case EVP_PKEY_X25519:
55+
case EVP_PKEY_X448:
56+
key_ctx.reset(EVP_PKEY_CTX_new_id(curve_nid, nullptr));
57+
break;
58+
default: {
59+
// Standard EC curves
60+
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free);
61+
62+
if (!param_ctx) {
63+
throw std::runtime_error("Failed to create parameter context");
64+
}
65+
66+
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0) {
67+
throw std::runtime_error("Failed to initialize parameter generation");
68+
}
69+
70+
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(param_ctx.get(), curve_nid) <= 0) {
71+
throw std::runtime_error("Failed to set curve NID");
72+
}
73+
74+
if (EVP_PKEY_CTX_set_ec_param_enc(param_ctx.get(), OPENSSL_EC_NAMED_CURVE) <= 0) {
75+
throw std::runtime_error("Failed to set parameter encoding");
76+
}
77+
78+
EVP_PKEY* raw_params = nullptr;
79+
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) {
80+
throw std::runtime_error("Failed to generate parameters");
81+
}
82+
83+
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> key_params(raw_params, EVP_PKEY_free);
84+
key_ctx.reset(EVP_PKEY_CTX_new(key_params.get(), nullptr));
85+
break;
86+
}
87+
}
88+
89+
if (!key_ctx) {
90+
throw std::runtime_error("Failed to create key generation context");
91+
}
92+
93+
if (EVP_PKEY_keygen_init(key_ctx.get()) <= 0) {
94+
throw std::runtime_error("Failed to initialize key generation");
95+
}
96+
97+
EVP_PKEY* raw_pkey = nullptr;
98+
if (EVP_PKEY_keygen(key_ctx.get(), &raw_pkey) <= 0) {
99+
throw std::runtime_error("Failed to generate EC key pair");
100+
}
101+
102+
this->pkey = raw_pkey;
103+
}
104+
105+
KeyObject HybridEcKeyPair::importKey(const std::string& format, const std::shared_ptr<ArrayBuffer>& keyData, const std::string& algorithm,
106+
bool extractable, const std::vector<std::string>& keyUsages) {
107+
throw std::runtime_error("HybridEcKeyPair::importKey() is not yet implemented");
108+
}
109+
110+
std::shared_ptr<ArrayBuffer> HybridEcKeyPair::exportKey(const KeyObject& key, const std::string& format) {
111+
throw std::runtime_error("HybridEcKeyPair::exportKey() is not yet implemented");
112+
}
113+
114+
std::shared_ptr<ArrayBuffer> HybridEcKeyPair::getPublicKey() {
115+
this->checkKeyPair();
116+
117+
// Export as DER format using direct OpenSSL calls
118+
BIO* bio = BIO_new(BIO_s_mem());
119+
if (!bio) {
120+
throw std::runtime_error("Failed to create BIO for public key export");
121+
}
122+
123+
if (i2d_PUBKEY_bio(bio, this->pkey) != 1) {
124+
BIO_free(bio);
125+
throw std::runtime_error("Failed to export public key to DER format");
126+
}
127+
128+
BUF_MEM* mem;
129+
BIO_get_mem_ptr(bio, &mem);
130+
131+
// Create a string from the DER data and use ToNativeArrayBuffer utility
132+
std::string derData(mem->data, mem->length);
133+
BIO_free(bio);
134+
135+
return ToNativeArrayBuffer(derData);
136+
}
137+
138+
std::shared_ptr<ArrayBuffer> HybridEcKeyPair::getPrivateKey() {
139+
this->checkKeyPair();
140+
141+
// Export as DER format in PKCS8 format using direct OpenSSL calls
142+
BIO* bio = BIO_new(BIO_s_mem());
143+
if (!bio) {
144+
throw std::runtime_error("Failed to create BIO for private key export");
145+
}
146+
147+
if (i2d_PKCS8PrivateKey_bio(bio, this->pkey, nullptr, nullptr, 0, nullptr, nullptr) != 1) {
148+
BIO_free(bio);
149+
throw std::runtime_error("Failed to export private key to DER PKCS8 format");
150+
}
151+
152+
BUF_MEM* mem;
153+
BIO_get_mem_ptr(bio, &mem);
154+
155+
// Create a string from the DER data and use ToNativeArrayBuffer utility
156+
std::string derData(mem->data, mem->length);
157+
BIO_free(bio);
158+
159+
return ToNativeArrayBuffer(derData);
160+
}
161+
162+
void HybridEcKeyPair::setCurve(const std::string& curve) {
163+
this->curve = curve;
164+
}
165+
166+
int HybridEcKeyPair::GetCurveFromName(const char* name) {
167+
// Handle NIST curve name mappings first
168+
std::string curve_name(name);
169+
if (curve_name == "P-256") {
170+
return NID_X9_62_prime256v1;
171+
} else if (curve_name == "P-384") {
172+
return NID_secp384r1;
173+
} else if (curve_name == "P-521") {
174+
return NID_secp521r1;
175+
} else if (curve_name == "secp256k1") {
176+
return NID_secp256k1;
177+
}
178+
179+
// Try standard OpenSSL name resolution
180+
int nid = OBJ_txt2nid(name);
181+
if (nid == NID_undef) {
182+
// Try short names
183+
nid = OBJ_sn2nid(name);
184+
}
185+
if (nid == NID_undef) {
186+
// Try long names
187+
nid = OBJ_ln2nid(name);
188+
}
189+
return nid;
190+
}
191+
192+
void HybridEcKeyPair::checkKeyPair() {
193+
if (this->pkey == nullptr) {
194+
throw std::runtime_error("EC KeyPair not initialized");
195+
}
196+
}
197+
198+
} // namespace margelo::nitro::crypto
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <memory>
2+
#include <openssl/ec.h>
3+
#include <openssl/err.h>
4+
#include <openssl/evp.h>
5+
#include <openssl/obj_mac.h>
6+
#include <string>
7+
8+
#include "HybridEcKeyPairSpec.hpp"
9+
#include "Utils.hpp"
10+
11+
namespace margelo::nitro::crypto {
12+
13+
class HybridEcKeyPair : public HybridEcKeyPairSpec {
14+
public:
15+
HybridEcKeyPair() : HybridObject(TAG) {}
16+
~HybridEcKeyPair() {
17+
if (pkey != nullptr) {
18+
EVP_PKEY_free(pkey);
19+
pkey = nullptr;
20+
}
21+
}
22+
23+
public:
24+
// Methods
25+
std::shared_ptr<Promise<void>> generateKeyPair() override;
26+
void generateKeyPairSync() override;
27+
KeyObject importKey(const std::string& format, const std::shared_ptr<ArrayBuffer>& keyData, const std::string& algorithm,
28+
bool extractable, const std::vector<std::string>& keyUsages) override;
29+
std::shared_ptr<ArrayBuffer> exportKey(const KeyObject& key, const std::string& format) override;
30+
std::shared_ptr<ArrayBuffer> getPublicKey() override;
31+
std::shared_ptr<ArrayBuffer> getPrivateKey() override;
32+
void setCurve(const std::string& curve) override;
33+
34+
protected:
35+
void checkKeyPair();
36+
37+
private:
38+
std::string curve;
39+
EVP_PKEY* pkey = nullptr;
40+
41+
static int GetCurveFromName(const char* name);
42+
};
43+
44+
} // namespace margelo::nitro::crypto

packages/react-native-quick-crypto/cpp/keys/HybridKeyObjectHandle.cpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include "CFRGKeyPairType.hpp"
44
#include "HybridKeyObjectHandle.hpp"
55
#include "Utils.hpp"
6+
#include <openssl/ec.h>
67
#include <openssl/evp.h>
8+
#include <openssl/obj_mac.h>
79

810
namespace margelo::nitro::crypto {
911

@@ -132,9 +134,16 @@ bool HybridKeyObjectHandle::init(KeyType keyType, const std::variant<std::string
132134
ab = std::get<std::shared_ptr<ArrayBuffer>>(key);
133135
}
134136

135-
// Handle raw asymmetric key material (curves only)
137+
// Handle raw asymmetric key material - only for special curves with known raw sizes
136138
if (!format.has_value() && !type.has_value() && (keyType == KeyType::PUBLIC || keyType == KeyType::PRIVATE)) {
137-
return initRawKey(keyType, ab);
139+
size_t keySize = ab->size();
140+
// Only route to initRawKey for exact special curve sizes:
141+
// X25519/Ed25519: 32 bytes, X448: 56 bytes, Ed448: 57 bytes
142+
// DER-encoded keys will be much larger and should use standard parsing
143+
if ((keySize == 32) || (keySize == 56) || (keySize == 57)) {
144+
return initRawKey(keyType, ab);
145+
}
146+
// For larger sizes (DER-encoded keys), fall through to standard parsing
138147
}
139148

140149
switch (keyType) {
@@ -159,16 +168,37 @@ bool HybridKeyObjectHandle::init(KeyType keyType, const std::variant<std::string
159168
return true;
160169
}
161170

162-
bool HybridKeyObjectHandle::initECRaw(const std::string& curveName, const std::shared_ptr<ArrayBuffer>& keyData) {
163-
throw std::runtime_error("Not yet implemented");
164-
}
165-
166171
std::optional<KeyType> HybridKeyObjectHandle::initJwk(const JWK& keyData, std::optional<NamedCurve> namedCurve) {
167172
throw std::runtime_error("Not yet implemented");
168173
}
169174

170175
KeyDetail HybridKeyObjectHandle::keyDetail() {
171-
throw std::runtime_error("Not yet implemented");
176+
const auto& pkey_ptr = data_.GetAsymmetricKey();
177+
if (!pkey_ptr) {
178+
return KeyDetail{};
179+
}
180+
181+
EVP_PKEY* pkey = pkey_ptr.get();
182+
183+
if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
184+
// Extract EC curve name
185+
EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey);
186+
if (ec_key) {
187+
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
188+
if (group) {
189+
int nid = EC_GROUP_get_curve_name(group);
190+
const char* curve_name = OBJ_nid2sn(nid);
191+
if (curve_name) {
192+
std::string namedCurve(curve_name);
193+
EC_KEY_free(ec_key);
194+
return KeyDetail(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, namedCurve);
195+
}
196+
}
197+
EC_KEY_free(ec_key);
198+
}
199+
}
200+
201+
return KeyDetail{};
172202
}
173203

174204
bool HybridKeyObjectHandle::initRawKey(KeyType keyType, std::shared_ptr<ArrayBuffer> keyData) {

0 commit comments

Comments
 (0)