UNPKG

react-native-quick-crypto

Version:

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

370 lines (355 loc) 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ec = void 0; exports.ecDeriveBits = ecDeriveBits; exports.ecImportKey = ecImportKey; exports.ec_generateKeyPair = ec_generateKeyPair; exports.ec_generateKeyPairNode = ec_generateKeyPairNode; exports.ec_generateKeyPairNodeSync = ec_generateKeyPairNodeSync; exports.ecdsaSignVerify = void 0; exports.getCurves = getCurves; var _reactNativeNitroModules = require("react-native-nitro-modules"); var _classes = require("./keys/classes"); var _utils = require("./utils"); var _reactNativeBuffer = require("@craftzdog/react-native-buffer"); var _ecdh = require("./ecdh"); class EcUtils { static get native() { if (!this._native) { this._native = _reactNativeNitroModules.NitroModules.createHybridObject('EcKeyPair'); } return this._native; } static getSupportedCurves() { return this.native.getSupportedCurves(); } } function getCurves() { return EcUtils.getSupportedCurves(); } class Ec { constructor(curve) { this.native = _reactNativeNitroModules.NitroModules.createHybridObject('EcKeyPair'); this.native.setCurve(curve); } async generateKeyPair() { await this.native.generateKeyPair(); return { publicKey: this.native.getPublicKey(), privateKey: this.native.getPrivateKey() }; } generateKeyPairSync() { this.native.generateKeyPairSync(); return { publicKey: this.native.getPublicKey(), privateKey: this.native.getPrivateKey() }; } } // WebCrypto API - only P-256, P-384, P-521 allowed per spec exports.Ec = Ec; function ecImportKey(format, keyData, algorithm, extractable, keyUsages) { const { name, namedCurve } = algorithm; if (!namedCurve || !_utils.kNamedCurveAliases[namedCurve]) { throw (0, _utils.lazyDOMException)('Unrecognized namedCurve', 'NotSupportedError'); } if (format === 'jwk') { const jwk = keyData; if (!jwk || typeof jwk !== 'object') { throw (0, _utils.lazyDOMException)('Invalid keyData', 'DataError'); } if (jwk.kty !== 'EC') { throw (0, _utils.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError'); } if (jwk.crv !== namedCurve) { throw (0, _utils.lazyDOMException)('JWK "crv" does not match the requested algorithm', 'DataError'); } const expectedUse = name === 'ECDH' ? 'enc' : 'sig'; (0, _utils.validateJwkStructure)(jwk, extractable, keyUsages, expectedUse); if (jwk.alg !== undefined) { let expectedAlg; if (name === 'ECDSA') { expectedAlg = namedCurve === 'P-256' ? 'ES256' : namedCurve === 'P-384' ? 'ES384' : namedCurve === 'P-521' ? 'ES512' : undefined; } else if (name === 'ECDH') { expectedAlg = 'ECDH-ES'; } if (expectedAlg && jwk.alg !== expectedAlg) { throw (0, _utils.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError'); } } const isPublicJwk = jwk.d === undefined; const validUsagesJwk = name === 'ECDSA' ? isPublicJwk ? ['verify'] : ['sign'] : isPublicJwk ? [] : ['deriveKey', 'deriveBits']; if ((0, _utils.hasAnyNotIn)(keyUsages, validUsagesJwk)) { throw (0, _utils.lazyDOMException)(`Unsupported key usage for a ${name} key`, 'SyntaxError'); } const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle'); let keyType; try { keyType = handle.initJwk(jwk, namedCurve); } catch (err) { throw (0, _utils.lazyDOMException)('Invalid keyData', { name: 'DataError', cause: err }); } if (keyType === undefined) { throw (0, _utils.lazyDOMException)('Invalid keyData', 'DataError'); } let keyObject; if (keyType === 1) { keyObject = new _classes.PublicKeyObject(handle); } else if (keyType === 2) { keyObject = new _classes.PrivateKeyObject(handle); } else { throw (0, _utils.lazyDOMException)('Invalid keyData', 'DataError'); } return new _classes.CryptoKey(keyObject, algorithm, keyUsages, extractable); } if (format !== 'spki' && format !== 'pkcs8' && format !== 'raw') { throw (0, _utils.lazyDOMException)(`Unsupported format: ${format}`, 'NotSupportedError'); } const expectedKeyType = format === 'spki' || format === 'raw' ? 'public' : 'private'; const isPublicKey = expectedKeyType === 'public'; let validUsages; if (name === 'ECDSA') { validUsages = isPublicKey ? ['verify'] : ['sign']; } else if (name === 'ECDH') { validUsages = isPublicKey ? [] : ['deriveKey', 'deriveBits']; } else { throw (0, _utils.lazyDOMException)('Unsupported algorithm', 'NotSupportedError'); } if ((0, _utils.hasAnyNotIn)(keyUsages, validUsages)) { throw (0, _utils.lazyDOMException)(`Unsupported key usage for a ${name} key`, 'SyntaxError'); } // Convert keyData to ArrayBuffer const keyBuffer = (0, _utils.bufferLikeToArrayBuffer)(keyData); // Create KeyObject directly using the appropriate format let keyObject; if (format === 'raw') { // Raw format is only for public keys - use specialized EC raw import const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle'); const curveAlias = _utils.kNamedCurveAliases[namedCurve]; // Only throw if initialization explicitly fails if (!handle.initECRaw(curveAlias, keyBuffer)) { throw (0, _utils.lazyDOMException)('Failed to import EC raw key', 'DataError'); } keyObject = new _classes.PublicKeyObject(handle); } else { // Use standard DER import for spki/pkcs8 keyObject = _classes.KeyObject.createKeyObject(expectedKeyType, keyBuffer, _utils.KFormatType.DER, format === 'spki' ? _utils.KeyEncoding.SPKI : _utils.KeyEncoding.PKCS8); // Validate the imported curve matches the requested algorithm.namedCurve. const expectedAlias = _utils.kNamedCurveAliases[namedCurve]; if (keyObject.handle.keyDetail().namedCurve !== expectedAlias) { throw (0, _utils.lazyDOMException)('Named curve mismatch', 'DataError'); } } // Verify the public/private point lies on the named curve. if (!keyObject.handle.checkEcKeyData()) { throw (0, _utils.lazyDOMException)('Invalid keyData', 'DataError'); } return new _classes.CryptoKey(keyObject, algorithm, keyUsages, extractable); } // Node API const ecdsaSignVerify = (key, data, { hash }, signature) => { const isSign = signature === undefined; const expectedKeyType = isSign ? 'private' : 'public'; if (key.type !== expectedKeyType) { throw (0, _utils.lazyDOMException)(`Key must be a ${expectedKeyType} key`, 'InvalidAccessError'); } const hashName = typeof hash === 'string' ? hash : hash?.name; if (!hashName) { throw (0, _utils.lazyDOMException)('Hash algorithm is required for ECDSA', 'InvalidAccessError'); } // Normalize hash algorithm name to WebCrypto format for C++ layer const normalizedHashName = (0, _utils.normalizeHashName)(hashName, _utils.HashContext.WebCrypto); // Create EC instance with the curve from the key const namedCurve = key.algorithm.namedCurve; const ec = new Ec(namedCurve); // Extract and import the actual key data from the CryptoKey // Export in DER format with appropriate encoding const encoding = key.type === 'private' ? _utils.KeyEncoding.PKCS8 : _utils.KeyEncoding.SPKI; const keyData = key.keyObject.handle.exportKey(_utils.KFormatType.DER, encoding); const keyBuffer = (0, _utils.bufferLikeToArrayBuffer)(keyData); ec.native.importKey('der', keyBuffer, key.algorithm.name, key.extractable, key.usages); const dataBuffer = (0, _utils.bufferLikeToArrayBuffer)(data); if (isSign) { // Sign operation return ec.native.sign(dataBuffer, normalizedHashName); } else { // Verify operation const signatureBuffer = (0, _utils.bufferLikeToArrayBuffer)(signature); return ec.native.verify(dataBuffer, signatureBuffer, normalizedHashName); } }; // WebCrypto API - only P-256, P-384, P-521 allowed per spec exports.ecdsaSignVerify = ecdsaSignVerify; async function ec_generateKeyPair(name, namedCurve, extractable, keyUsages, // eslint-disable-next-line @typescript-eslint/no-unused-vars _options // TODO: Implement format options support ) { // validation checks if (!Object.keys(_utils.kNamedCurveAliases).includes(namedCurve || '')) { throw (0, _utils.lazyDOMException)(`Unrecognized namedCurve '${namedCurve}'`, 'NotSupportedError'); } switch (name) { case 'ECDSA': if ((0, _utils.hasAnyNotIn)(keyUsages, ['sign', 'verify'])) { throw (0, _utils.lazyDOMException)('Unsupported key usage for an ECDSA key', 'SyntaxError'); } break; case 'ECDH': if ((0, _utils.hasAnyNotIn)(keyUsages, ['deriveKey', 'deriveBits'])) { throw (0, _utils.lazyDOMException)('Unsupported key usage for an ECDH key', 'SyntaxError'); } // Fall through } const ec = new Ec(namedCurve); await ec.generateKeyPair(); let publicUsages = []; let privateUsages = []; switch (name) { case 'ECDSA': publicUsages = (0, _utils.getUsagesUnion)(keyUsages, 'verify'); privateUsages = (0, _utils.getUsagesUnion)(keyUsages, 'sign'); break; case 'ECDH': publicUsages = []; privateUsages = (0, _utils.getUsagesUnion)(keyUsages, 'deriveKey', 'deriveBits'); break; } const keyAlgorithm = { name, namedCurve: namedCurve }; // Export keys directly from the EC key pair using the internal EVP_PKEY // These methods export in DER format (SPKI for public, PKCS8 for private) const publicKeyData = ec.native.getPublicKey(); const privateKeyData = ec.native.getPrivateKey(); const pub = _classes.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI); const publicKey = new _classes.CryptoKey(pub, keyAlgorithm, publicUsages, true); // All keys are now exported in PKCS8 format for consistency const priv = _classes.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8); const privateKey = new _classes.CryptoKey(priv, keyAlgorithm, privateUsages, extractable); return { publicKey, privateKey }; } function ec_prepareKeyGenParams(options) { if (!options) { throw new Error('Options are required for EC key generation'); } const { namedCurve } = options; if (!namedCurve) { throw new Error('namedCurve is required for EC key generation'); } return new Ec(namedCurve); } function ec_formatKeyPairOutput(ec, encoding) { const { publicFormat, publicType, privateFormat, privateType, cipher, passphrase } = encoding; const publicKeyData = ec.native.getPublicKey(); const privateKeyData = ec.native.getPrivateKey(); const pub = _classes.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI); const priv = _classes.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8); let publicKey; let privateKey; if (publicFormat === -1) { publicKey = pub; } else if (publicFormat === 'raw-public') { publicKey = _reactNativeBuffer.Buffer.from(pub.handle.exportECPublicRaw(publicType === 'compressed')); } else { const format = publicFormat === _utils.KFormatType.PEM ? _utils.KFormatType.PEM : _utils.KFormatType.DER; const keyEncoding = publicType === _utils.KeyEncoding.SPKI ? _utils.KeyEncoding.SPKI : _utils.KeyEncoding.SPKI; const exported = pub.handle.exportKey(format, keyEncoding); if (format === _utils.KFormatType.PEM) { publicKey = _reactNativeBuffer.Buffer.from(new Uint8Array(exported)).toString('utf-8'); } else { publicKey = exported; } } if (privateFormat === -1) { privateKey = priv; } else if (privateFormat === 'raw-private') { privateKey = _reactNativeBuffer.Buffer.from(priv.handle.exportECPrivateRaw()); } else { const format = privateFormat === _utils.KFormatType.PEM ? _utils.KFormatType.PEM : _utils.KFormatType.DER; const keyEncoding = privateType === _utils.KeyEncoding.PKCS8 ? _utils.KeyEncoding.PKCS8 : privateType === _utils.KeyEncoding.SEC1 ? _utils.KeyEncoding.SEC1 : _utils.KeyEncoding.PKCS8; const exported = priv.handle.exportKey(format, keyEncoding, cipher, passphrase); if (format === _utils.KFormatType.PEM) { privateKey = _reactNativeBuffer.Buffer.from(new Uint8Array(exported)).toString('utf-8'); } else { privateKey = exported; } } return { publicKey, privateKey }; } async function ec_generateKeyPairNode(options, encoding) { const ec = ec_prepareKeyGenParams(options); await ec.generateKeyPair(); return ec_formatKeyPairOutput(ec, encoding); } function ec_generateKeyPairNodeSync(options, encoding) { const ec = ec_prepareKeyGenParams(options); ec.generateKeyPairSync(); return ec_formatKeyPairOutput(ec, encoding); } function ecDeriveBits(algorithm, baseKey, length) { const publicKey = algorithm.public; if (!publicKey) { throw new Error('Public key is required for ECDH derivation'); } if (baseKey.algorithm.name !== publicKey.algorithm.name) { throw new Error('Keys must be of the same algorithm'); } if (baseKey.algorithm.namedCurve !== publicKey.algorithm.namedCurve) { throw new Error('Keys must use the same curve'); } const namedCurve = baseKey.algorithm.namedCurve; if (!namedCurve) { throw new Error('Curve name is missing'); } const opensslCurve = _utils.kNamedCurveAliases[namedCurve]; const ecdh = new _ecdh.ECDH(opensslCurve); const jwkPrivate = baseKey.keyObject.handle.exportJwk({}, false); if (!jwkPrivate.d) throw new Error('Invalid private key'); const privateBytes = _reactNativeBuffer.Buffer.from(jwkPrivate.d, 'base64url'); ecdh.setPrivateKey(privateBytes); const jwkPublic = publicKey.keyObject.handle.exportJwk({}, false); if (!jwkPublic.x || !jwkPublic.y) throw new Error('Invalid public key'); const x = _reactNativeBuffer.Buffer.from(jwkPublic.x, 'base64url'); const y = _reactNativeBuffer.Buffer.from(jwkPublic.y, 'base64url'); const publicBytes = _reactNativeBuffer.Buffer.concat([_reactNativeBuffer.Buffer.from([0x04]), x, y]); const secret = ecdh.computeSecret(publicBytes); const secretBuf = _reactNativeBuffer.Buffer.from(secret); // If length is null, return full secret if (length === null) { return secretBuf.buffer; } // If length is specified, truncate const byteLength = Math.ceil(length / 8); if (secretBuf.byteLength >= byteLength) { return secretBuf.buffer.slice(secretBuf.byteOffset, secretBuf.byteOffset + byteLength); } throw new Error('Derived key is shorter than requested length'); } //# sourceMappingURL=ec.js.map