Skip to content

Commit 193e4c0

Browse files
authored
fix: return KeyObjects from generateKeyPairSync for Ed/X curves (#971)
1 parent 47da446 commit 193e4c0

7 files changed

Lines changed: 439 additions & 209 deletions

File tree

example/.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.3.4

example/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,7 +2811,7 @@ SPEC CHECKSUMS:
28112811
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
28122812
NitroMmkv: afbc5b2fbf963be567c6c545aa1efcf6a9cec68e
28132813
NitroModules: 11bba9d065af151eae51e38a6425e04c3b223ff3
2814-
QuickCrypto: 422a7b7f5d0de18bec3837cbc2e7a865999334c0
2814+
QuickCrypto: 414c0c5f7353bc7bf87ef3ae02485cf41b4a66d1
28152815
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
28162816
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
28172817
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"react-native-mmkv": "4.0.1",
4141
"react-native-nitro-modules": "0.33.2",
4242
"react-native-quick-base64": "2.2.2",
43-
"react-native-quick-crypto": "1.0.17",
43+
"react-native-quick-crypto": "workspace:*",
4444
"react-native-safe-area-context": "5.6.2",
4545
"react-native-screens": "4.18.0",
4646
"react-native-vector-icons": "10.3.0",

example/src/hooks/useTestsList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import '../tests/hkdf/hkdf_tests';
1919
import '../tests/hmac/hmac_tests';
2020
import '../tests/jose/jose';
2121
import '../tests/keys/create_keys';
22+
import '../tests/keys/ed_keyobject';
2223
import '../tests/keys/generate_key';
2324
import '../tests/keys/generate_keypair';
2425
import '../tests/keys/keyobject_from_tocryptokey_tests';
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import {
2+
generateKeyPairSync,
3+
generateKeyPair,
4+
createPrivateKey,
5+
createPublicKey,
6+
KeyObject,
7+
AsymmetricKeyObject,
8+
Buffer,
9+
} from 'react-native-quick-crypto';
10+
import { expect } from 'chai';
11+
import { test } from '../util';
12+
13+
const SUITE = 'keys.edKeyObject';
14+
15+
// --- Ed25519 KeyObject tests ---
16+
17+
test(SUITE, 'generateKeyPairSync ed25519 returns KeyObjects', () => {
18+
const result = generateKeyPairSync('ed25519');
19+
const pub = result.publicKey as AsymmetricKeyObject;
20+
const priv = result.privateKey as AsymmetricKeyObject;
21+
22+
expect(pub).to.be.an.instanceOf(KeyObject);
23+
expect(priv).to.be.an.instanceOf(KeyObject);
24+
expect(pub.type).to.equal('public');
25+
expect(priv.type).to.equal('private');
26+
expect(pub.asymmetricKeyType).to.equal('ed25519');
27+
expect(priv.asymmetricKeyType).to.equal('ed25519');
28+
});
29+
30+
test(SUITE, 'ed25519 KeyObject.export() works for PEM', () => {
31+
const result = generateKeyPairSync('ed25519');
32+
const pub = result.publicKey as AsymmetricKeyObject;
33+
const priv = result.privateKey as AsymmetricKeyObject;
34+
35+
const pubPem = pub.export({ type: 'spki', format: 'pem' });
36+
const privPem = priv.export({ type: 'pkcs8', format: 'pem' });
37+
38+
expect(typeof pubPem).to.equal('string');
39+
expect(typeof privPem).to.equal('string');
40+
expect(pubPem as string).to.match(/^-----BEGIN PUBLIC KEY-----/);
41+
expect(privPem as string).to.match(/^-----BEGIN PRIVATE KEY-----/);
42+
});
43+
44+
test(SUITE, 'ed25519 KeyObject.export() works for DER', () => {
45+
const result = generateKeyPairSync('ed25519');
46+
const pub = result.publicKey as AsymmetricKeyObject;
47+
const priv = result.privateKey as AsymmetricKeyObject;
48+
49+
const pubDer = pub.export({ type: 'spki', format: 'der' });
50+
const privDer = priv.export({ type: 'pkcs8', format: 'der' });
51+
52+
expect(Buffer.isBuffer(pubDer)).to.equal(true);
53+
expect(Buffer.isBuffer(privDer)).to.equal(true);
54+
});
55+
56+
test(SUITE, 'ed25519 with PEM encoding returns strings', () => {
57+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
58+
publicKeyEncoding: { type: 'spki', format: 'pem' },
59+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
60+
});
61+
62+
expect(typeof publicKey).to.equal('string');
63+
expect(typeof privateKey).to.equal('string');
64+
expect(publicKey as string).to.match(/^-----BEGIN PUBLIC KEY-----/);
65+
expect(privateKey as string).to.match(/^-----BEGIN PRIVATE KEY-----/);
66+
});
67+
68+
test(SUITE, 'ed25519 with DER encoding returns ArrayBuffers', () => {
69+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
70+
publicKeyEncoding: { type: 'spki', format: 'der' },
71+
privateKeyEncoding: { type: 'pkcs8', format: 'der' },
72+
});
73+
74+
expect(publicKey instanceof ArrayBuffer).to.equal(true);
75+
expect(privateKey instanceof ArrayBuffer).to.equal(true);
76+
expect((publicKey as ArrayBuffer).byteLength).to.be.greaterThan(0);
77+
expect((privateKey as ArrayBuffer).byteLength).to.be.greaterThan(0);
78+
});
79+
80+
// --- Round-trip tests ---
81+
82+
test(
83+
SUITE,
84+
'ed25519 round-trip: KeyObject -> export DER -> createKey -> export',
85+
() => {
86+
const result = generateKeyPairSync('ed25519');
87+
const pub = result.publicKey as AsymmetricKeyObject;
88+
const priv = result.privateKey as AsymmetricKeyObject;
89+
90+
const privDer = priv.export({ type: 'pkcs8', format: 'der' });
91+
const pubDer = pub.export({ type: 'spki', format: 'der' });
92+
93+
const recreatedPriv = createPrivateKey({
94+
key: privDer,
95+
format: 'der',
96+
type: 'pkcs8',
97+
});
98+
const recreatedPub = createPublicKey({
99+
key: pubDer,
100+
format: 'der',
101+
type: 'spki',
102+
});
103+
104+
expect(recreatedPriv.type).to.equal('private');
105+
expect(recreatedPub.type).to.equal('public');
106+
expect(recreatedPriv.asymmetricKeyType).to.equal('ed25519');
107+
expect(recreatedPub.asymmetricKeyType).to.equal('ed25519');
108+
109+
const reExportedPriv = recreatedPriv.export({
110+
type: 'pkcs8',
111+
format: 'der',
112+
});
113+
const reExportedPub = recreatedPub.export({ type: 'spki', format: 'der' });
114+
115+
expect(
116+
Buffer.compare(Buffer.from(privDer), Buffer.from(reExportedPriv)),
117+
).to.equal(0);
118+
expect(
119+
Buffer.compare(Buffer.from(pubDer), Buffer.from(reExportedPub)),
120+
).to.equal(0);
121+
},
122+
);
123+
124+
test(
125+
SUITE,
126+
'ed25519 round-trip: KeyObject -> export PEM -> createKey -> export',
127+
() => {
128+
const result = generateKeyPairSync('ed25519');
129+
const pub = result.publicKey as AsymmetricKeyObject;
130+
const priv = result.privateKey as AsymmetricKeyObject;
131+
132+
const privPem = priv.export({
133+
type: 'pkcs8',
134+
format: 'pem',
135+
}) as string;
136+
const pubPem = pub.export({ type: 'spki', format: 'pem' }) as string;
137+
138+
const recreatedPriv = createPrivateKey(privPem);
139+
const recreatedPub = createPublicKey(pubPem);
140+
141+
expect(recreatedPriv.asymmetricKeyType).to.equal('ed25519');
142+
expect(recreatedPub.asymmetricKeyType).to.equal('ed25519');
143+
144+
const reExportedPriv = recreatedPriv.export({
145+
type: 'pkcs8',
146+
format: 'pem',
147+
}) as string;
148+
const reExportedPub = recreatedPub.export({
149+
type: 'spki',
150+
format: 'pem',
151+
}) as string;
152+
153+
expect(reExportedPriv).to.equal(privPem);
154+
expect(reExportedPub).to.equal(pubPem);
155+
},
156+
);
157+
158+
// --- Ed448 KeyObject tests ---
159+
160+
test(SUITE, 'generateKeyPairSync ed448 returns KeyObjects', () => {
161+
const result = generateKeyPairSync('ed448');
162+
const pub = result.publicKey as AsymmetricKeyObject;
163+
const priv = result.privateKey as AsymmetricKeyObject;
164+
165+
expect(pub).to.be.an.instanceOf(KeyObject);
166+
expect(priv).to.be.an.instanceOf(KeyObject);
167+
expect(pub.asymmetricKeyType).to.equal('ed448');
168+
expect(priv.asymmetricKeyType).to.equal('ed448');
169+
});
170+
171+
test(SUITE, 'ed448 round-trip: KeyObject -> export -> recreate', () => {
172+
const result = generateKeyPairSync('ed448');
173+
const pub = result.publicKey as AsymmetricKeyObject;
174+
const priv = result.privateKey as AsymmetricKeyObject;
175+
176+
const privDer = priv.export({ type: 'pkcs8', format: 'der' });
177+
const pubDer = pub.export({ type: 'spki', format: 'der' });
178+
179+
const recreatedPriv = createPrivateKey({
180+
key: privDer,
181+
format: 'der',
182+
type: 'pkcs8',
183+
});
184+
const recreatedPub = createPublicKey({
185+
key: pubDer,
186+
format: 'der',
187+
type: 'spki',
188+
});
189+
190+
expect(recreatedPriv.asymmetricKeyType).to.equal('ed448');
191+
expect(recreatedPub.asymmetricKeyType).to.equal('ed448');
192+
});
193+
194+
// --- X25519 KeyObject tests ---
195+
196+
test(SUITE, 'generateKeyPairSync x25519 returns KeyObjects', () => {
197+
const result = generateKeyPairSync('x25519');
198+
const pub = result.publicKey as AsymmetricKeyObject;
199+
const priv = result.privateKey as AsymmetricKeyObject;
200+
201+
expect(pub).to.be.an.instanceOf(KeyObject);
202+
expect(priv).to.be.an.instanceOf(KeyObject);
203+
expect(pub.asymmetricKeyType).to.equal('x25519');
204+
expect(priv.asymmetricKeyType).to.equal('x25519');
205+
});
206+
207+
test(SUITE, 'x25519 round-trip: KeyObject -> export -> recreate', () => {
208+
const result = generateKeyPairSync('x25519');
209+
const pub = result.publicKey as AsymmetricKeyObject;
210+
const priv = result.privateKey as AsymmetricKeyObject;
211+
212+
const privDer = priv.export({ type: 'pkcs8', format: 'der' });
213+
const pubDer = pub.export({ type: 'spki', format: 'der' });
214+
215+
const recreatedPriv = createPrivateKey({
216+
key: privDer,
217+
format: 'der',
218+
type: 'pkcs8',
219+
});
220+
const recreatedPub = createPublicKey({
221+
key: pubDer,
222+
format: 'der',
223+
type: 'spki',
224+
});
225+
226+
expect(recreatedPriv.asymmetricKeyType).to.equal('x25519');
227+
expect(recreatedPub.asymmetricKeyType).to.equal('x25519');
228+
});
229+
230+
// --- X448 KeyObject tests ---
231+
232+
test(SUITE, 'generateKeyPairSync x448 returns KeyObjects', () => {
233+
const result = generateKeyPairSync('x448');
234+
const pub = result.publicKey as AsymmetricKeyObject;
235+
const priv = result.privateKey as AsymmetricKeyObject;
236+
237+
expect(pub).to.be.an.instanceOf(KeyObject);
238+
expect(priv).to.be.an.instanceOf(KeyObject);
239+
expect(pub.asymmetricKeyType).to.equal('x448');
240+
expect(priv.asymmetricKeyType).to.equal('x448');
241+
});
242+
243+
test(SUITE, 'x448 round-trip: KeyObject -> export -> recreate', () => {
244+
const result = generateKeyPairSync('x448');
245+
const pub = result.publicKey as AsymmetricKeyObject;
246+
const priv = result.privateKey as AsymmetricKeyObject;
247+
248+
const privDer = priv.export({ type: 'pkcs8', format: 'der' });
249+
const pubDer = pub.export({ type: 'spki', format: 'der' });
250+
251+
const recreatedPriv = createPrivateKey({
252+
key: privDer,
253+
format: 'der',
254+
type: 'pkcs8',
255+
});
256+
const recreatedPub = createPublicKey({
257+
key: pubDer,
258+
format: 'der',
259+
type: 'spki',
260+
});
261+
262+
expect(recreatedPriv.asymmetricKeyType).to.equal('x448');
263+
expect(recreatedPub.asymmetricKeyType).to.equal('x448');
264+
});
265+
266+
// --- Async path tests ---
267+
268+
test(SUITE, 'generateKeyPair ed25519 async returns KeyObjects', async () => {
269+
const result = await new Promise<{
270+
publicKey: AsymmetricKeyObject;
271+
privateKey: AsymmetricKeyObject;
272+
}>((resolve, reject) => {
273+
generateKeyPair('ed25519', {}, (err, pubKey, privKey) => {
274+
if (err) reject(err);
275+
else
276+
resolve({
277+
publicKey: pubKey as AsymmetricKeyObject,
278+
privateKey: privKey as AsymmetricKeyObject,
279+
});
280+
});
281+
});
282+
283+
expect(result.publicKey).to.be.an.instanceOf(KeyObject);
284+
expect(result.privateKey).to.be.an.instanceOf(KeyObject);
285+
expect(result.publicKey.type).to.equal('public');
286+
expect(result.privateKey.type).to.equal('private');
287+
});
288+
289+
test(
290+
SUITE,
291+
'generateKeyPair ed25519 async with PEM encoding returns strings',
292+
async () => {
293+
const { publicKey, privateKey } = await new Promise<{
294+
publicKey: string;
295+
privateKey: string;
296+
}>((resolve, reject) => {
297+
generateKeyPair(
298+
'ed25519',
299+
{
300+
publicKeyEncoding: { type: 'spki', format: 'pem' },
301+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
302+
},
303+
(err, pubKey, privKey) => {
304+
if (err) reject(err);
305+
else
306+
resolve({
307+
publicKey: pubKey as string,
308+
privateKey: privKey as string,
309+
});
310+
},
311+
);
312+
});
313+
314+
expect(typeof publicKey).to.equal('string');
315+
expect(typeof privateKey).to.equal('string');
316+
expect(publicKey).to.match(/^-----BEGIN PUBLIC KEY-----/);
317+
expect(privateKey).to.match(/^-----BEGIN PRIVATE KEY-----/);
318+
},
319+
);

0 commit comments

Comments
 (0)