UNPKG

react-native-quick-crypto

Version:

A fast implementation of Node's `crypto` module written in C/C++ JSI

312 lines (291 loc) 10.6 kB
"use strict"; import { NitroModules } from 'react-native-nitro-modules'; import { Buffer } from '@craftzdog/react-native-buffer'; import { CryptoKey, KeyObject } from './keys'; import { binaryLikeToArrayBuffer as toAB, hasAnyNotIn, lazyDOMException, getUsagesUnion, KFormatType, KeyEncoding } from './utils'; export class Ed { constructor(type, config) { this.type = type; this.config = config; this.native = NitroModules.createHybridObject('EdKeyPair'); this.native.setCurve(type); } /** * Computes the Diffie-Hellman secret based on a privateKey and a publicKey. * Both keys must have the same asymmetricKeyType, which must be one of 'dh' * (for Diffie-Hellman), 'ec', 'x448', or 'x25519' (for ECDH). * * @api nodejs/node * * @param options `{ privateKey, publicKey }`, both of which are `KeyObject`s * @param callback optional `(err, secret) => void` * @returns `Buffer` if no callback, or `void` if callback is provided */ diffieHellman(options, callback) { checkDiffieHellmanOptions(options); // key types must be of certain type const keyType = options.privateKey.asymmetricKeyType; switch (keyType) { case 'x25519': case 'x448': break; default: throw new Error(`Unsupported or unimplemented curve type: ${keyType}`); } // extract the private and public keys as ArrayBuffers const privateKey = toAB(options.privateKey); const publicKey = toAB(options.publicKey); try { const ret = this.native.diffieHellman(privateKey, publicKey); if (!ret) { throw new Error('No secret'); } if (callback) { callback(null, Buffer.from(ret)); } else { return Buffer.from(ret); } } catch (e) { const err = e; if (callback) { callback(err, undefined); } else { throw err; } } } async generateKeyPair() { await this.native.generateKeyPair(this.config.publicFormat ?? -1, this.config.publicType ?? -1, this.config.privateFormat ?? -1, this.config.privateType ?? -1, this.config.cipher, this.config.passphrase); } generateKeyPairSync() { this.native.generateKeyPairSync(this.config.publicFormat ?? -1, this.config.publicType ?? -1, this.config.privateFormat ?? -1, this.config.privateType ?? -1, this.config.cipher, this.config.passphrase); } getPublicKey() { return this.native.getPublicKey(); } getPrivateKey() { return this.native.getPrivateKey(); } /** * Computes the Diffie-Hellman shared secret based on a privateKey and a * publicKey for key exchange * * @api \@paulmillr/noble-curves/ed25519 * * @param privateKey * @param publicKey * @returns shared secret key */ getSharedSecret(privateKey, publicKey) { return this.native.diffieHellman(toAB(privateKey), toAB(publicKey)); } async sign(message, key) { return key ? this.native.sign(toAB(message), toAB(key)) : this.native.sign(toAB(message)); } signSync(message, key) { return key ? this.native.signSync(toAB(message), toAB(key)) : this.native.signSync(toAB(message)); } async verify(signature, message, key) { return key ? this.native.verify(toAB(signature), toAB(message), toAB(key)) : this.native.verify(toAB(signature), toAB(message)); } verifySync(signature, message, key) { return key ? this.native.verifySync(toAB(signature), toAB(message), toAB(key)) : this.native.verifySync(toAB(signature), toAB(message)); } } // Node API export function diffieHellman(options, callback) { const privateKey = options.privateKey; const type = privateKey.asymmetricKeyType; const ed = new Ed(type, {}); return ed.diffieHellman(options, callback); } // Node API export function ed_generateKeyPair(isAsync, type, encoding, callback) { const ed = new Ed(type, encoding); // Helper to convert keys to proper output format const formatKeys = () => { 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 => { return Buffer.from(new Uint8Array(ab)).toString('utf-8'); }; const publicKey = isPemPublic ? arrayBufferToString(publicKeyRaw) : publicKeyRaw; const privateKey = isPemPrivate ? arrayBufferToString(privateKeyRaw) : privateKeyRaw; return { publicKey, privateKey }; }; // 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(); callback(undefined, publicKey, privateKey); }).catch(err => { callback(err, undefined, undefined); }); return; } // Sync path let err; try { ed.generateKeyPairSync(); } catch (e) { err = e instanceof Error ? e : new Error(String(e)); } const { publicKey, privateKey } = err ? { publicKey: undefined, privateKey: undefined } : formatKeys(); if (callback) { callback(err, publicKey, privateKey); return; } return [err, publicKey, privateKey]; } function checkDiffieHellmanOptions(options) { const { privateKey, publicKey } = options; // Check if keys are KeyObject instances if (!privateKey || typeof privateKey !== 'object' || !('type' in privateKey)) { throw new Error('privateKey must be a KeyObject'); } if (!publicKey || typeof publicKey !== 'object' || !('type' in publicKey)) { throw new Error('publicKey must be a KeyObject'); } // type checks if (privateKey.type !== 'private') { throw new Error('privateKey must be a private KeyObject'); } if (publicKey.type !== 'public') { throw new Error('publicKey must be a public KeyObject'); } // For asymmetric keys, check if they have the asymmetricKeyType property const privateKeyAsym = privateKey; const publicKeyAsym = publicKey; // key types must match if (privateKeyAsym.asymmetricKeyType && publicKeyAsym.asymmetricKeyType && privateKeyAsym.asymmetricKeyType !== publicKeyAsym.asymmetricKeyType) { throw new Error('Keys must be asymmetric and their types must match'); } switch (privateKeyAsym.asymmetricKeyType) { // case 'dh': // TODO: uncomment when implemented case 'x25519': case 'x448': break; default: throw new Error(`Unknown curve type: ${privateKeyAsym.asymmetricKeyType}`); } } export async function ed_generateKeyPairWebCrypto(type, extractable, keyUsages) { if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw lazyDOMException(`Unsupported key usage for ${type}`, 'SyntaxError'); } const publicUsages = getUsagesUnion(keyUsages, 'verify'); const privateUsages = getUsagesUnion(keyUsages, 'sign'); if (privateUsages.length === 0) { throw lazyDOMException('Usages cannot be empty', 'SyntaxError'); } // Request DER-encoded SPKI for public key, PKCS8 for private key const config = { publicFormat: KFormatType.DER, publicType: KeyEncoding.SPKI, privateFormat: KFormatType.DER, privateType: KeyEncoding.PKCS8 }; const ed = new Ed(type, config); await ed.generateKeyPair(); const algorithmName = type === 'ed25519' ? 'Ed25519' : 'Ed448'; const publicKeyData = ed.getPublicKey(); const privateKeyData = ed.getPrivateKey(); const pub = KeyObject.createKeyObject('public', publicKeyData, KFormatType.DER, KeyEncoding.SPKI); const publicKey = new CryptoKey(pub, { name: algorithmName }, publicUsages, true); const priv = KeyObject.createKeyObject('private', privateKeyData, KFormatType.DER, KeyEncoding.PKCS8); const privateKey = new CryptoKey(priv, { name: algorithmName }, privateUsages, extractable); return { publicKey, privateKey }; } export async function x_generateKeyPairWebCrypto(type, extractable, keyUsages) { if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { throw lazyDOMException(`Unsupported key usage for ${type}`, 'SyntaxError'); } const publicUsages = getUsagesUnion(keyUsages); const privateUsages = getUsagesUnion(keyUsages, 'deriveKey', 'deriveBits'); if (privateUsages.length === 0) { throw lazyDOMException('Usages cannot be empty', 'SyntaxError'); } // Request DER-encoded SPKI for public key, PKCS8 for private key const config = { publicFormat: KFormatType.DER, publicType: KeyEncoding.SPKI, privateFormat: KFormatType.DER, privateType: KeyEncoding.PKCS8 }; const ed = new Ed(type, config); await ed.generateKeyPair(); const algorithmName = type === 'x25519' ? 'X25519' : 'X448'; const publicKeyData = ed.getPublicKey(); const privateKeyData = ed.getPrivateKey(); const pub = KeyObject.createKeyObject('public', publicKeyData, KFormatType.DER, KeyEncoding.SPKI); const publicKey = new CryptoKey(pub, { name: algorithmName }, publicUsages, true); const priv = KeyObject.createKeyObject('private', privateKeyData, KFormatType.DER, KeyEncoding.PKCS8); const privateKey = new CryptoKey(priv, { name: algorithmName }, privateUsages, extractable); return { publicKey, privateKey }; } export function xDeriveBits(algorithm, baseKey, length) { const publicParams = algorithm; const publicKey = publicParams.public; if (!publicKey) { throw new Error('Public key is required for X25519/X448 derivation'); } if (baseKey.algorithm.name !== publicKey.algorithm.name) { throw new Error('Keys must be of the same algorithm'); } const type = baseKey.algorithm.name.toLowerCase(); const ed = new Ed(type, {}); // Export raw keys const privateKeyBytes = baseKey.keyObject.handle.exportKey(); const publicKeyBytes = publicKey.keyObject.handle.exportKey(); const privateKeyTyped = new Uint8Array(privateKeyBytes); const publicKeyTyped = new Uint8Array(publicKeyBytes); const secret = ed.getSharedSecret(privateKeyTyped, publicKeyTyped); // If length is null, return the full secret if (length === null) { return secret; } // If length is specified, truncate const byteLength = Math.ceil(length / 8); if (secret.byteLength >= byteLength) { return secret.slice(0, byteLength); } throw new Error('Derived key is shorter than requested length'); } //# sourceMappingURL=ed.js.map