react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
480 lines (442 loc) • 17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Ec = void 0;
exports.ecImportKey = ecImportKey;
exports.ec_generateKeyPair = ec_generateKeyPair;
exports.ec_generateKeyPairNode = ec_generateKeyPairNode;
exports.ec_generateKeyPairNodeSync = ec_generateKeyPairNodeSync;
exports.ecdsaSignVerify = void 0;
var _reactNativeNitroModules = require("react-native-nitro-modules");
var _keys = require("./keys");
var _utils = require("./utils");
var _buffer = require("buffer");
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()
};
}
}
// function verifyAcceptableEcKeyUse(
// name: AnyAlgorithm,
// isPublic: boolean,
// usages: KeyUsage[],
// ): void {
// let checkSet;
// switch (name) {
// case 'ECDH':
// checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
// break;
// case 'ECDSA':
// checkSet = isPublic ? ['verify'] : ['sign'];
// break;
// default:
// throw lazyDOMException(
// 'The algorithm is not supported',
// 'NotSupportedError',
// );
// }
// if (hasAnyNotIn(usages, checkSet)) {
// throw lazyDOMException(
// `Unsupported key usage for a ${name} key`,
// 'SyntaxError',
// );
// }
// }
// function createECPublicKeyRaw(
// namedCurve: NamedCurve | undefined,
// keyDataBuffer: ArrayBuffer,
// ): PublicKeyObject {
// if (!namedCurve) {
// throw new Error('Invalid namedCurve');
// }
// const handle = NitroModules.createHybridObject(
// 'KeyObjectHandle',
// ) as KeyObjectHandle;
// if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyDataBuffer)) {
// console.log('keyData', ab2str(keyDataBuffer));
// throw new Error('Invalid keyData 1');
// }
// return new PublicKeyObject(handle);
// }
// // Node API
// export function ec_exportKey(key: CryptoKey, format: KeyFormat): ArrayBuffer {
// return ec.native.exportKey(format, key.keyObject.handle);
// }
// Node API
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');
}
// Handle JWK format
if (format === 'jwk') {
const jwk = keyData;
// Validate JWK
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');
}
// Check use parameter if present
if (jwk.use !== undefined) {
const expectedUse = name === 'ECDH' ? 'enc' : 'sig';
if (jwk.use !== expectedUse) {
throw (0, _utils.lazyDOMException)('Invalid JWK "use" Parameter', 'DataError');
}
}
// Check alg parameter if present
if (jwk.alg !== undefined) {
let expectedAlg;
if (name === 'ECDSA') {
// Map namedCurve to expected ECDSA algorithm
expectedAlg = namedCurve === 'P-256' ? 'ES256' : namedCurve === 'P-384' ? 'ES384' : namedCurve === 'P-521' ? 'ES512' : undefined;
} else if (name === 'ECDH') {
// ECDH uses ECDH-ES algorithm
expectedAlg = 'ECDH-ES';
}
if (expectedAlg && jwk.alg !== expectedAlg) {
throw (0, _utils.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError');
}
}
// Import using C++ layer
const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
const keyType = handle.initJwk(jwk, namedCurve);
if (keyType === undefined) {
throw (0, _utils.lazyDOMException)('Invalid JWK', 'DataError');
}
// Create the appropriate KeyObject based on type
let keyObject;
if (keyType === 1) {
keyObject = new _keys.PublicKeyObject(handle);
} else if (keyType === 2) {
keyObject = new _keys.PrivateKeyObject(handle);
} else {
throw (0, _utils.lazyDOMException)('Unexpected key type from JWK import', 'DataError');
}
return new _keys.CryptoKey(keyObject, algorithm, keyUsages, extractable);
}
// Handle binary formats (spki, pkcs8, raw)
if (format !== 'spki' && format !== 'pkcs8' && format !== 'raw') {
throw (0, _utils.lazyDOMException)(`Unsupported format: ${format}`, 'NotSupportedError');
}
// Determine expected key type based on format
const expectedKeyType = format === 'spki' || format === 'raw' ? 'public' : 'private';
// Validate usages for the key type
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];
if (!handle.initECRaw(curveAlias, keyBuffer)) {
throw (0, _utils.lazyDOMException)('Failed to import EC raw key', 'DataError');
}
keyObject = new _keys.PublicKeyObject(handle);
} else {
// Use standard DER import for spki/pkcs8
keyObject = _keys.KeyObject.createKeyObject(expectedKeyType, keyBuffer, _utils.KFormatType.DER, format === 'spki' ? _utils.KeyEncoding.SPKI : _utils.KeyEncoding.PKCS8);
}
return new _keys.CryptoKey(keyObject, algorithm, keyUsages, extractable);
// // // verifyAcceptableEcKeyUse(name, true, usagesSet);
// // try {
// // keyObject = createPublicKey({
// // key: keyData,
// // format: 'der',
// // type: 'spki',
// // });
// // } catch (err) {
// // throw new Error(`Invalid keyData 2: ${err}`);
// // }
// // break;
// // }
// // case 'pkcs8': {
// // // verifyAcceptableEcKeyUse(name, false, usagesSet);
// // try {
// // keyObject = createPrivateKey({
// // key: keyData,
// // format: 'der',
// // type: 'pkcs8',
// // });
// // } catch (err) {
// // throw new Error(`Invalid keyData 3 ${err}`);
// // }
// // break;
// // }
}
// case 'jwk': {
// const data = keyData as JWK;
// if (!data.kty) throw lazyDOMException('Invalid keyData 4', 'DataError');
// if (data.kty !== 'EC')
// throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
// if (data.crv !== namedCurve)
// throw lazyDOMException(
// 'JWK "crv" does not match the requested algorithm',
// 'DataError',
// );
// verifyAcceptableEcKeyUse(name, data.d === undefined, keyUsages);
// if (keyUsages.length > 0 && data.use !== undefined) {
// const checkUse = name === 'ECDH' ? 'enc' : 'sig';
// if (data.use !== checkUse)
// throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
// }
// validateKeyOps(data.key_ops, keyUsages);
// if (
// data.ext !== undefined &&
// data.ext === false &&
// extractable === true
// ) {
// throw lazyDOMException(
// 'JWK "ext" Parameter and extractable mismatch',
// 'DataError',
// );
// }
// if (algorithm.name === 'ECDSA' && data.alg !== undefined) {
// let algNamedCurve;
// switch (data.alg) {
// case 'ES256':
// algNamedCurve = 'P-256';
// break;
// case 'ES384':
// algNamedCurve = 'P-384';
// break;
// case 'ES512':
// algNamedCurve = 'P-521';
// break;
// }
// if (algNamedCurve !== namedCurve)
// throw lazyDOMException(
// 'JWK "alg" does not match the requested algorithm',
// 'DataError',
// );
// }
// const handle = NativeQuickCrypto.webcrypto.createKeyObjectHandle();
// const type = handle.initJwk(data, namedCurve);
// if (type === undefined)
// throw lazyDOMException('Invalid JWK', 'DataError');
// keyObject =
// type === KeyType.PRIVATE
// ? new PrivateKeyObject(handle)
// : new PublicKeyObject(handle);
// break;
// }
// case 'raw': {
// const data = keyData as BufferLike | BinaryLike;
// verifyAcceptableEcKeyUse(name, true, keyUsages);
// const buffer =
// typeof data === 'string'
// ? binaryLikeToArrayBuffer(data)
// : bufferLikeToArrayBuffer(data);
// keyObject = createECPublicKeyRaw(namedCurve, buffer);
// break;
// }
// default: {
// throw new Error(`Unknown EC import format: ${format}`);
// }
// }
// switch (algorithm.name) {
// case 'ECDSA':
// // Fall through
// case 'ECDH':
// if (keyObject.asymmetricKeyType !== ('ec' as AsymmetricKeyType))
// throw new Error('Invalid key type');
// break;
// }
// // if (!keyObject[kHandle].checkEcKeyData()) {
// // throw new Error('Invalid keyData 5');
// // }
// // const { namedCurve: checkNamedCurve } = keyObject[kHandle].keyDetail({});
// // if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
// // throw new Error('Named curve mismatch');
// return new CryptoKey(keyObject, { name, namedCurve }, 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);
}
};
// Node API
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');
}
// const usageSet = new SafeSet(keyUsages);
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 = _keys.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
const publicKey = new _keys.CryptoKey(pub, keyAlgorithm, publicUsages, true);
// All keys are now exported in PKCS8 format for consistency
const priv = _keys.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
const privateKey = new _keys.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 || !_utils.kNamedCurveAliases[namedCurve]) {
throw new Error(`Invalid or unsupported named curve: ${namedCurve}`);
}
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 = _keys.KeyObject.createKeyObject('public', publicKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
const priv = _keys.KeyObject.createKeyObject('private', privateKeyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
let publicKey;
let privateKey;
if (publicFormat === -1) {
publicKey = pub;
} 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 = _buffer.Buffer.from(new Uint8Array(exported)).toString('utf-8');
} else {
publicKey = exported;
}
}
if (privateFormat === -1) {
privateKey = priv;
} 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 = _buffer.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);
}
//# sourceMappingURL=ec.js.map