diff --git a/example/.ruby-version b/example/.ruby-version new file mode 100644 index 00000000..a0891f56 --- /dev/null +++ b/example/.ruby-version @@ -0,0 +1 @@ +3.3.4 diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 00a00261..6f6895ae 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2811,7 +2811,7 @@ SPEC CHECKSUMS: MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df NitroMmkv: afbc5b2fbf963be567c6c545aa1efcf6a9cec68e NitroModules: 11bba9d065af151eae51e38a6425e04c3b223ff3 - QuickCrypto: 422a7b7f5d0de18bec3837cbc2e7a865999334c0 + QuickCrypto: 414c0c5f7353bc7bf87ef3ae02485cf41b4a66d1 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077 RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a diff --git a/example/package.json b/example/package.json index af234c48..581faf5e 100644 --- a/example/package.json +++ b/example/package.json @@ -40,7 +40,7 @@ "react-native-mmkv": "4.0.1", "react-native-nitro-modules": "0.33.2", "react-native-quick-base64": "2.2.2", - "react-native-quick-crypto": "1.0.17", + "react-native-quick-crypto": "workspace:*", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.18.0", "react-native-vector-icons": "10.3.0", diff --git a/example/src/hooks/useTestsList.ts b/example/src/hooks/useTestsList.ts index 8135e9a7..d2de08a1 100644 --- a/example/src/hooks/useTestsList.ts +++ b/example/src/hooks/useTestsList.ts @@ -19,6 +19,7 @@ import '../tests/hkdf/hkdf_tests'; import '../tests/hmac/hmac_tests'; import '../tests/jose/jose'; import '../tests/keys/create_keys'; +import '../tests/keys/ed_keyobject'; import '../tests/keys/generate_key'; import '../tests/keys/generate_keypair'; import '../tests/keys/keyobject_from_tocryptokey_tests'; diff --git a/example/src/tests/keys/ed_keyobject.ts b/example/src/tests/keys/ed_keyobject.ts new file mode 100644 index 00000000..a7baa52e --- /dev/null +++ b/example/src/tests/keys/ed_keyobject.ts @@ -0,0 +1,319 @@ +import { + generateKeyPairSync, + generateKeyPair, + createPrivateKey, + createPublicKey, + KeyObject, + AsymmetricKeyObject, + Buffer, +} from 'react-native-quick-crypto'; +import { expect } from 'chai'; +import { test } from '../util'; + +const SUITE = 'keys.edKeyObject'; + +// --- Ed25519 KeyObject tests --- + +test(SUITE, 'generateKeyPairSync ed25519 returns KeyObjects', () => { + const result = generateKeyPairSync('ed25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + expect(pub).to.be.an.instanceOf(KeyObject); + expect(priv).to.be.an.instanceOf(KeyObject); + expect(pub.type).to.equal('public'); + expect(priv.type).to.equal('private'); + expect(pub.asymmetricKeyType).to.equal('ed25519'); + expect(priv.asymmetricKeyType).to.equal('ed25519'); +}); + +test(SUITE, 'ed25519 KeyObject.export() works for PEM', () => { + const result = generateKeyPairSync('ed25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const pubPem = pub.export({ type: 'spki', format: 'pem' }); + const privPem = priv.export({ type: 'pkcs8', format: 'pem' }); + + expect(typeof pubPem).to.equal('string'); + expect(typeof privPem).to.equal('string'); + expect(pubPem as string).to.match(/^-----BEGIN PUBLIC KEY-----/); + expect(privPem as string).to.match(/^-----BEGIN PRIVATE KEY-----/); +}); + +test(SUITE, 'ed25519 KeyObject.export() works for DER', () => { + const result = generateKeyPairSync('ed25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const pubDer = pub.export({ type: 'spki', format: 'der' }); + const privDer = priv.export({ type: 'pkcs8', format: 'der' }); + + expect(Buffer.isBuffer(pubDer)).to.equal(true); + expect(Buffer.isBuffer(privDer)).to.equal(true); +}); + +test(SUITE, 'ed25519 with PEM encoding returns strings', () => { + const { publicKey, privateKey } = generateKeyPairSync('ed25519', { + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }); + + expect(typeof publicKey).to.equal('string'); + expect(typeof privateKey).to.equal('string'); + expect(publicKey as string).to.match(/^-----BEGIN PUBLIC KEY-----/); + expect(privateKey as string).to.match(/^-----BEGIN PRIVATE KEY-----/); +}); + +test(SUITE, 'ed25519 with DER encoding returns ArrayBuffers', () => { + const { publicKey, privateKey } = generateKeyPairSync('ed25519', { + publicKeyEncoding: { type: 'spki', format: 'der' }, + privateKeyEncoding: { type: 'pkcs8', format: 'der' }, + }); + + expect(publicKey instanceof ArrayBuffer).to.equal(true); + expect(privateKey instanceof ArrayBuffer).to.equal(true); + expect((publicKey as ArrayBuffer).byteLength).to.be.greaterThan(0); + expect((privateKey as ArrayBuffer).byteLength).to.be.greaterThan(0); +}); + +// --- Round-trip tests --- + +test( + SUITE, + 'ed25519 round-trip: KeyObject -> export DER -> createKey -> export', + () => { + const result = generateKeyPairSync('ed25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const privDer = priv.export({ type: 'pkcs8', format: 'der' }); + const pubDer = pub.export({ type: 'spki', format: 'der' }); + + const recreatedPriv = createPrivateKey({ + key: privDer, + format: 'der', + type: 'pkcs8', + }); + const recreatedPub = createPublicKey({ + key: pubDer, + format: 'der', + type: 'spki', + }); + + expect(recreatedPriv.type).to.equal('private'); + expect(recreatedPub.type).to.equal('public'); + expect(recreatedPriv.asymmetricKeyType).to.equal('ed25519'); + expect(recreatedPub.asymmetricKeyType).to.equal('ed25519'); + + const reExportedPriv = recreatedPriv.export({ + type: 'pkcs8', + format: 'der', + }); + const reExportedPub = recreatedPub.export({ type: 'spki', format: 'der' }); + + expect( + Buffer.compare(Buffer.from(privDer), Buffer.from(reExportedPriv)), + ).to.equal(0); + expect( + Buffer.compare(Buffer.from(pubDer), Buffer.from(reExportedPub)), + ).to.equal(0); + }, +); + +test( + SUITE, + 'ed25519 round-trip: KeyObject -> export PEM -> createKey -> export', + () => { + const result = generateKeyPairSync('ed25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const privPem = priv.export({ + type: 'pkcs8', + format: 'pem', + }) as string; + const pubPem = pub.export({ type: 'spki', format: 'pem' }) as string; + + const recreatedPriv = createPrivateKey(privPem); + const recreatedPub = createPublicKey(pubPem); + + expect(recreatedPriv.asymmetricKeyType).to.equal('ed25519'); + expect(recreatedPub.asymmetricKeyType).to.equal('ed25519'); + + const reExportedPriv = recreatedPriv.export({ + type: 'pkcs8', + format: 'pem', + }) as string; + const reExportedPub = recreatedPub.export({ + type: 'spki', + format: 'pem', + }) as string; + + expect(reExportedPriv).to.equal(privPem); + expect(reExportedPub).to.equal(pubPem); + }, +); + +// --- Ed448 KeyObject tests --- + +test(SUITE, 'generateKeyPairSync ed448 returns KeyObjects', () => { + const result = generateKeyPairSync('ed448'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + expect(pub).to.be.an.instanceOf(KeyObject); + expect(priv).to.be.an.instanceOf(KeyObject); + expect(pub.asymmetricKeyType).to.equal('ed448'); + expect(priv.asymmetricKeyType).to.equal('ed448'); +}); + +test(SUITE, 'ed448 round-trip: KeyObject -> export -> recreate', () => { + const result = generateKeyPairSync('ed448'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const privDer = priv.export({ type: 'pkcs8', format: 'der' }); + const pubDer = pub.export({ type: 'spki', format: 'der' }); + + const recreatedPriv = createPrivateKey({ + key: privDer, + format: 'der', + type: 'pkcs8', + }); + const recreatedPub = createPublicKey({ + key: pubDer, + format: 'der', + type: 'spki', + }); + + expect(recreatedPriv.asymmetricKeyType).to.equal('ed448'); + expect(recreatedPub.asymmetricKeyType).to.equal('ed448'); +}); + +// --- X25519 KeyObject tests --- + +test(SUITE, 'generateKeyPairSync x25519 returns KeyObjects', () => { + const result = generateKeyPairSync('x25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + expect(pub).to.be.an.instanceOf(KeyObject); + expect(priv).to.be.an.instanceOf(KeyObject); + expect(pub.asymmetricKeyType).to.equal('x25519'); + expect(priv.asymmetricKeyType).to.equal('x25519'); +}); + +test(SUITE, 'x25519 round-trip: KeyObject -> export -> recreate', () => { + const result = generateKeyPairSync('x25519'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const privDer = priv.export({ type: 'pkcs8', format: 'der' }); + const pubDer = pub.export({ type: 'spki', format: 'der' }); + + const recreatedPriv = createPrivateKey({ + key: privDer, + format: 'der', + type: 'pkcs8', + }); + const recreatedPub = createPublicKey({ + key: pubDer, + format: 'der', + type: 'spki', + }); + + expect(recreatedPriv.asymmetricKeyType).to.equal('x25519'); + expect(recreatedPub.asymmetricKeyType).to.equal('x25519'); +}); + +// --- X448 KeyObject tests --- + +test(SUITE, 'generateKeyPairSync x448 returns KeyObjects', () => { + const result = generateKeyPairSync('x448'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + expect(pub).to.be.an.instanceOf(KeyObject); + expect(priv).to.be.an.instanceOf(KeyObject); + expect(pub.asymmetricKeyType).to.equal('x448'); + expect(priv.asymmetricKeyType).to.equal('x448'); +}); + +test(SUITE, 'x448 round-trip: KeyObject -> export -> recreate', () => { + const result = generateKeyPairSync('x448'); + const pub = result.publicKey as AsymmetricKeyObject; + const priv = result.privateKey as AsymmetricKeyObject; + + const privDer = priv.export({ type: 'pkcs8', format: 'der' }); + const pubDer = pub.export({ type: 'spki', format: 'der' }); + + const recreatedPriv = createPrivateKey({ + key: privDer, + format: 'der', + type: 'pkcs8', + }); + const recreatedPub = createPublicKey({ + key: pubDer, + format: 'der', + type: 'spki', + }); + + expect(recreatedPriv.asymmetricKeyType).to.equal('x448'); + expect(recreatedPub.asymmetricKeyType).to.equal('x448'); +}); + +// --- Async path tests --- + +test(SUITE, 'generateKeyPair ed25519 async returns KeyObjects', async () => { + const result = await new Promise<{ + publicKey: AsymmetricKeyObject; + privateKey: AsymmetricKeyObject; + }>((resolve, reject) => { + generateKeyPair('ed25519', {}, (err, pubKey, privKey) => { + if (err) reject(err); + else + resolve({ + publicKey: pubKey as AsymmetricKeyObject, + privateKey: privKey as AsymmetricKeyObject, + }); + }); + }); + + expect(result.publicKey).to.be.an.instanceOf(KeyObject); + expect(result.privateKey).to.be.an.instanceOf(KeyObject); + expect(result.publicKey.type).to.equal('public'); + expect(result.privateKey.type).to.equal('private'); +}); + +test( + SUITE, + 'generateKeyPair ed25519 async with PEM encoding returns strings', + async () => { + const { publicKey, privateKey } = await new Promise<{ + publicKey: string; + privateKey: string; + }>((resolve, reject) => { + generateKeyPair( + 'ed25519', + { + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }, + (err, pubKey, privKey) => { + if (err) reject(err); + else + resolve({ + publicKey: pubKey as string, + privateKey: privKey as string, + }); + }, + ); + }); + + expect(typeof publicKey).to.equal('string'); + expect(typeof privateKey).to.equal('string'); + expect(publicKey).to.match(/^-----BEGIN PUBLIC KEY-----/); + expect(privateKey).to.match(/^-----BEGIN PRIVATE KEY-----/); + }, +); diff --git a/example/src/tests/subtle/deriveBits.ts b/example/src/tests/subtle/deriveBits.ts index a99b3b79..4163c066 100644 --- a/example/src/tests/subtle/deriveBits.ts +++ b/example/src/tests/subtle/deriveBits.ts @@ -82,19 +82,9 @@ test(SUITE, 'x25519 - shared secret', () => { const alice = crypto.generateKeyPairSync('x25519', {}); const bob = crypto.generateKeyPairSync('x25519', {}); - if (!alice.privateKey || !(alice.privateKey instanceof ArrayBuffer)) { - throw new Error('Failed to generate private key for Alice'); - } - if (!bob.publicKey || !(bob.publicKey instanceof ArrayBuffer)) { - throw new Error('Failed to generate public key for Bob'); - } - - const privateKey = KeyObject.createKeyObject('private', alice.privateKey); - const publicKey = KeyObject.createKeyObject('public', bob.publicKey); - const sharedSecret = crypto.diffieHellman({ - privateKey, - publicKey, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }); expect(Buffer.isBuffer(sharedSecret)).to.equal(true); }); @@ -103,31 +93,14 @@ test(SUITE, 'x25519 - shared secret symmetry', () => { const alice = crypto.generateKeyPairSync('x25519', {}); const bob = crypto.generateKeyPairSync('x25519', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const alicePublic = KeyObject.createKeyObject( - 'public', - alice.publicKey as ArrayBuffer, - ); - const bobPrivate = KeyObject.createKeyObject( - 'private', - bob.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const sharedSecretAlice = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; const sharedSecretBob = crypto.diffieHellman({ - privateKey: bobPrivate, - publicKey: alicePublic, + privateKey: bob.privateKey as KeyObject, + publicKey: alice.publicKey as KeyObject, }) as Buffer; expect(Buffer.isBuffer(sharedSecretAlice)).to.equal(true); @@ -139,18 +112,9 @@ test(SUITE, 'x25519 - shared secret properties', () => { const alice = crypto.generateKeyPairSync('x25519', {}); const bob = crypto.generateKeyPairSync('x25519', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const sharedSecret = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; expect(sharedSecret.length).to.equal(32); @@ -159,8 +123,8 @@ test(SUITE, 'x25519 - shared secret properties', () => { expect(sharedSecret.equals(allZeros)).to.equal(false); const sharedSecret2 = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; expect(sharedSecret.equals(sharedSecret2)).to.equal(true); }); @@ -170,27 +134,14 @@ test(SUITE, 'x25519 - different key pairs produce different secrets', () => { const bob = crypto.generateKeyPairSync('x25519', {}); const charlie = crypto.generateKeyPairSync('x25519', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const charliePublic = KeyObject.createKeyObject( - 'public', - charlie.publicKey as ArrayBuffer, - ); - const secretAliceBob = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; const secretAliceCharlie = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: charliePublic, + privateKey: alice.privateKey as KeyObject, + publicKey: charlie.publicKey as KeyObject, }) as Buffer; expect(secretAliceBob.equals(secretAliceCharlie)).to.equal(false); @@ -199,18 +150,9 @@ test(SUITE, 'x25519 - different key pairs produce different secrets', () => { test(SUITE, 'x25519 - error handling', () => { const alice = crypto.generateKeyPairSync('x25519', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - - expect(() => { - KeyObject.createKeyObject('public', new ArrayBuffer(16)); - }).to.throw(); - expect(() => { crypto.diffieHellman({ - privateKey: alicePrivate, + privateKey: alice.privateKey as KeyObject, publicKey: {} as KeyObject, }); }).to.throw(); @@ -337,16 +279,10 @@ test(SUITE, 'x448 - shared secret', () => { const alice = crypto.generateKeyPairSync('x448', {}); const bob = crypto.generateKeyPairSync('x448', {}); - const privateKey = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const publicKey = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - - const sharedSecret = crypto.diffieHellman({ privateKey, publicKey }); + const sharedSecret = crypto.diffieHellman({ + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, + }); expect(Buffer.isBuffer(sharedSecret)).to.equal(true); }); @@ -354,31 +290,14 @@ test(SUITE, 'x448 - shared secret symmetry', () => { const alice = crypto.generateKeyPairSync('x448', {}); const bob = crypto.generateKeyPairSync('x448', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const alicePublic = KeyObject.createKeyObject( - 'public', - alice.publicKey as ArrayBuffer, - ); - const bobPrivate = KeyObject.createKeyObject( - 'private', - bob.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const sharedSecretAlice = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; const sharedSecretBob = crypto.diffieHellman({ - privateKey: bobPrivate, - publicKey: alicePublic, + privateKey: bob.privateKey as KeyObject, + publicKey: alice.publicKey as KeyObject, }) as Buffer; expect(Buffer.isBuffer(sharedSecretAlice)).to.equal(true); @@ -390,18 +309,9 @@ test(SUITE, 'x448 - shared secret properties', () => { const alice = crypto.generateKeyPairSync('x448', {}); const bob = crypto.generateKeyPairSync('x448', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const sharedSecret = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; expect(sharedSecret.length).to.equal(56); @@ -410,8 +320,8 @@ test(SUITE, 'x448 - shared secret properties', () => { expect(sharedSecret.equals(allZeros)).to.equal(false); const sharedSecret2 = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; expect(sharedSecret.equals(sharedSecret2)).to.equal(true); }); @@ -421,27 +331,14 @@ test(SUITE, 'x448 - different key pairs produce different secrets', () => { const bob = crypto.generateKeyPairSync('x448', {}); const charlie = crypto.generateKeyPairSync('x448', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - const bobPublic = KeyObject.createKeyObject( - 'public', - bob.publicKey as ArrayBuffer, - ); - const charliePublic = KeyObject.createKeyObject( - 'public', - charlie.publicKey as ArrayBuffer, - ); - const secretAliceBob = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: bobPublic, + privateKey: alice.privateKey as KeyObject, + publicKey: bob.publicKey as KeyObject, }) as Buffer; const secretAliceCharlie = crypto.diffieHellman({ - privateKey: alicePrivate, - publicKey: charliePublic, + privateKey: alice.privateKey as KeyObject, + publicKey: charlie.publicKey as KeyObject, }) as Buffer; expect(secretAliceBob.equals(secretAliceCharlie)).to.equal(false); @@ -450,14 +347,9 @@ test(SUITE, 'x448 - different key pairs produce different secrets', () => { test(SUITE, 'x448 - error handling', () => { const alice = crypto.generateKeyPairSync('x448', {}); - const alicePrivate = KeyObject.createKeyObject( - 'private', - alice.privateKey as ArrayBuffer, - ); - expect(() => { crypto.diffieHellman({ - privateKey: alicePrivate, + privateKey: alice.privateKey as KeyObject, publicKey: {} as KeyObject, }); }).to.throw(); diff --git a/packages/react-native-quick-crypto/src/ed.ts b/packages/react-native-quick-crypto/src/ed.ts index 60270647..791e17dc 100644 --- a/packages/react-native-quick-crypto/src/ed.ts +++ b/packages/react-native-quick-crypto/src/ed.ts @@ -58,9 +58,11 @@ export class Ed { options: DiffieHellmanOptions, callback?: DiffieHellmanCallback, ): Buffer | void { - // extract the private and public keys as ArrayBuffers - const privateKey = toAB(options.privateKey); - const publicKey = toAB(options.publicKey); + // extract raw key bytes from KeyObject instances + const privKeyObj = options.privateKey as AsymmetricKeyObject; + const pubKeyObj = options.publicKey as AsymmetricKeyObject; + const privateKey = privKeyObj.handle.exportKey(); + const publicKey = pubKeyObj.handle.exportKey(); try { const ret = this.native.diffieHellman(privateKey, publicKey); @@ -178,51 +180,97 @@ export function diffieHellman( return ed.diffieHellman(options, callback); } +function ed_createKeyObjects(ed: Ed): { + pub: PublicKeyObject; + priv: PrivateKeyObjectClass; +} { + const publicKeyData = ed.getPublicKey(); + const privateKeyData = ed.getPrivateKey(); + const pub = KeyObject.createKeyObject( + 'public', + publicKeyData, + KFormatType.DER, + KeyEncoding.SPKI, + ) as PublicKeyObject; + const priv = KeyObject.createKeyObject( + 'private', + privateKeyData, + KFormatType.DER, + KeyEncoding.PKCS8, + ) as PrivateKeyObjectClass; + return { pub, priv }; +} + // Node API +function ed_formatKeyPairOutput( + ed: Ed, + encoding: KeyPairGenConfig, +): { + publicKey: PublicKeyObject | string | ArrayBuffer; + privateKey: PrivateKeyObjectClass | string | ArrayBuffer; +} { + const { publicFormat, privateFormat, cipher, passphrase } = encoding; + const { pub, priv } = ed_createKeyObjects(ed); + + let publicKey: PublicKeyObject | string | ArrayBuffer; + let privateKey: PrivateKeyObjectClass | string | ArrayBuffer; + + if (publicFormat == null || publicFormat === -1) { + publicKey = pub; + } else { + const format = + publicFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER; + const exported = pub.handle.exportKey(format, KeyEncoding.SPKI); + if (format === KFormatType.PEM) { + publicKey = Buffer.from(new Uint8Array(exported)).toString('utf-8'); + } else { + publicKey = exported; + } + } + + if (privateFormat == null || privateFormat === -1) { + privateKey = priv; + } else { + const format = + privateFormat === KFormatType.PEM ? KFormatType.PEM : KFormatType.DER; + const exported = priv.handle.exportKey( + format, + KeyEncoding.PKCS8, + cipher, + passphrase, + ); + if (format === KFormatType.PEM) { + privateKey = Buffer.from(new Uint8Array(exported)).toString('utf-8'); + } else { + privateKey = exported; + } + } + + return { publicKey, privateKey }; +} + export function ed_generateKeyPair( isAsync: boolean, type: CFRGKeyPairType, encoding: KeyPairGenConfig, callback: GenerateKeyPairCallback | undefined, ): GenerateKeyPairReturn | void { - const ed = new Ed(type, encoding); - - // Helper to convert keys to proper output format - const formatKeys = (): { - publicKey: string | ArrayBuffer; - privateKey: string | ArrayBuffer; - } => { - const publicKeyRaw = ed.getPublicKey(); - const privateKeyRaw = ed.getPrivateKey(); - - // Check if PEM format was requested (KFormatType.PEM = 1) - const isPemPublic = encoding.publicFormat === KFormatType.PEM; - const isPemPrivate = encoding.privateFormat === KFormatType.PEM; - - // Convert ArrayBuffer to string for PEM format - const arrayBufferToString = (ab: ArrayBuffer): string => { - return Buffer.from(new Uint8Array(ab)).toString('utf-8'); - }; - - const publicKey = isPemPublic - ? arrayBufferToString(publicKeyRaw) - : publicKeyRaw; - const privateKey = isPemPrivate - ? arrayBufferToString(privateKeyRaw) - : privateKeyRaw; - - return { publicKey, privateKey }; + const derConfig: KeyPairGenConfig = { + ...encoding, + publicFormat: KFormatType.DER, + publicType: KeyEncoding.SPKI, + privateFormat: KFormatType.DER, + privateType: KeyEncoding.PKCS8, }; + const ed = new Ed(type, derConfig); - // Async path if (isAsync) { if (!callback) { - // This should not happen if called from public API throw new Error('A callback is required for async key generation.'); } ed.generateKeyPair() .then(() => { - const { publicKey, privateKey } = formatKeys(); + const { publicKey, privateKey } = ed_formatKeyPairOutput(ed, encoding); callback(undefined, publicKey, privateKey); }) .catch(err => { @@ -231,7 +279,6 @@ export function ed_generateKeyPair( return; } - // Sync path let err: Error | undefined; try { ed.generateKeyPairSync(); @@ -241,7 +288,7 @@ export function ed_generateKeyPair( const { publicKey, privateKey } = err ? { publicKey: undefined, privateKey: undefined } - : formatKeys(); + : ed_formatKeyPairOutput(ed, encoding); if (callback) { callback(err, publicKey, privateKey); @@ -374,29 +421,14 @@ export async function ed_generateKeyPairWebCrypto( await ed.generateKeyPair(); const algorithmName = type === 'ed25519' ? 'Ed25519' : 'Ed448'; + const { pub, priv } = ed_createKeyObjects(ed); - const publicKeyData = ed.getPublicKey(); - const privateKeyData = ed.getPrivateKey(); - - const pub = KeyObject.createKeyObject( - 'public', - publicKeyData, - KFormatType.DER, - KeyEncoding.SPKI, - ) as PublicKeyObject; const publicKey = new CryptoKey( pub, { name: algorithmName } as SubtleAlgorithm, publicUsages, true, ); - - const priv = KeyObject.createKeyObject( - 'private', - privateKeyData, - KFormatType.DER, - KeyEncoding.PKCS8, - ) as PrivateKeyObjectClass; const privateKey = new CryptoKey( priv, { name: algorithmName } as SubtleAlgorithm, @@ -434,29 +466,14 @@ export async function x_generateKeyPairWebCrypto( await ed.generateKeyPair(); const algorithmName = type === 'x25519' ? 'X25519' : 'X448'; + const { pub, priv } = ed_createKeyObjects(ed); - const publicKeyData = ed.getPublicKey(); - const privateKeyData = ed.getPrivateKey(); - - const pub = KeyObject.createKeyObject( - 'public', - publicKeyData, - KFormatType.DER, - KeyEncoding.SPKI, - ) as PublicKeyObject; const publicKey = new CryptoKey( pub, { name: algorithmName } as SubtleAlgorithm, publicUsages, true, ); - - const priv = KeyObject.createKeyObject( - 'private', - privateKeyData, - KFormatType.DER, - KeyEncoding.PKCS8, - ) as PrivateKeyObjectClass; const privateKey = new CryptoKey( priv, { name: algorithmName } as SubtleAlgorithm,