UNPKG

react-native-quick-crypto

Version:

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

375 lines (335 loc) 9.96 kB
import { generateKeyPairPromise, type GenerateKeyPairOptions } from './Cipher'; import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto'; import { DSASigEnc, SignMode } from './NativeQuickCrypto/sig'; import { bufferLikeToArrayBuffer, type BufferLike, type BinaryLike, binaryLikeToArrayBuffer, lazyDOMException, validateKeyOps, hasAnyNotIn, ab2str, getUsagesUnion, normalizeHashName, } from './Utils'; import { type ImportFormat, type SubtleAlgorithm, type KeyUsage, kNamedCurveAliases, type NamedCurve, PublicKeyObject, KWebCryptoKeyFormat, CryptoKey, type JWK, type AnyAlgorithm, PrivateKeyObject, KeyType, type CryptoKeyPair, } from './keys'; import type { KeyObjectHandle } from './NativeQuickCrypto/webcrypto'; // const { // ArrayPrototypeIncludes, // ObjectKeys, // SafeSet, // } = primordials; // const { // ECKeyExportJob, // KeyObjectHandle, // SignJob, // kCryptoJobAsync, // kKeyTypePrivate, // kSignJobModeSign, // kSignJobModeVerify, // kSigEncP1363, // } = internalBinding('crypto'); // const { // getUsagesUnion, // hasAnyNotIn, // jobPromise, // normalizeHashName, // validateKeyOps, // kHandle, // kKeyObject, // kNamedCurveAliases, // } = require('internal/crypto/util'); // const { // lazyDOMException, // promisify, // } = require('internal/util'); // const { // generateKeyPair: _generateKeyPair, // } = require('internal/crypto/keygen'); // const { // InternalCryptoKey, // PrivateKeyObject, // PublicKeyObject, // createPrivateKey, // createPublicKey, // } = require('internal/crypto/keys'); // const generateKeyPair = promisify(_generateKeyPair); 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, keyData: ArrayBuffer, ): PublicKeyObject { if (!namedCurve) { throw new Error('Invalid namedCurve'); } const handle = NativeQuickCrypto.webcrypto.createKeyObjectHandle(); if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) { console.log('keyData', ab2str(keyData)); throw new Error('Invalid keyData 1'); } return new PublicKeyObject(handle); } export function ecExportKey( key: CryptoKey, format: KWebCryptoKeyFormat, ): ArrayBuffer { return NativeQuickCrypto.webcrypto.ecExportKey(format, key.keyObject.handle); } export function ecImportKey( format: ImportFormat, keyData: BufferLike | BinaryLike | JWK, algorithm: SubtleAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): CryptoKey { const { name, namedCurve } = algorithm; // if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) { // throw lazyDOMException('Unrecognized namedCurve', 'NotSupportedError'); // } let keyObject; // const usagesSet = new SafeSet(keyUsages); switch (format) { // case 'spki': { // // 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') 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); } export const ecdsaSignVerify = ( key: CryptoKey, data: BufferLike, { hash }: SubtleAlgorithm, signature?: BufferLike, ) => { const mode: SignMode = signature === undefined ? SignMode.kSignJobModeSign : SignMode.kSignJobModeVerify; const type = mode === SignMode.kSignJobModeSign ? 'private' : 'public'; if (key.type !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); const hashname = normalizeHashName(hash); return NativeQuickCrypto.webcrypto.signVerify( mode, key.keyObject.handle, // three undefined args because C++ uses `GetPublicOrPrivateKeyFromJs` & friends undefined, undefined, undefined, bufferLikeToArrayBuffer(data), hashname, undefined, // salt length, not used with ECDSA undefined, // pss padding, not used with ECDSA DSASigEnc.kSigEncP1363, bufferLikeToArrayBuffer(signature || new ArrayBuffer(0)), ); }; export const ecGenerateKey = async ( algorithm: SubtleAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): Promise<CryptoKeyPair> => { const { name, namedCurve } = algorithm; if (!Object.keys(kNamedCurveAliases).includes(namedCurve || '')) { throw lazyDOMException( `Unrecognized namedCurve '${namedCurve}'`, 'NotSupportedError', ); } // const usageSet = new SafeSet(keyUsages); switch (name) { case 'ECDSA': if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw lazyDOMException( 'Unsupported key usage for an ECDSA key', 'SyntaxError', ); } break; case 'ECDH': if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { throw lazyDOMException( 'Unsupported key usage for an ECDH key', 'SyntaxError', ); } // Fall through } const options: GenerateKeyPairOptions = { namedCurve }; const [err, keypair] = await generateKeyPairPromise('ec', options); if (err) { throw lazyDOMException('ecGenerateKey (generateKeyPairPromise) failed', { name: 'OperationError', cause: err, }); } let publicUsages: KeyUsage[] = []; let privateUsages: KeyUsage[] = []; switch (name) { case 'ECDSA': publicUsages = getUsagesUnion(keyUsages, 'verify'); privateUsages = getUsagesUnion(keyUsages, 'sign'); break; case 'ECDH': publicUsages = []; privateUsages = getUsagesUnion(keyUsages, 'deriveKey', 'deriveBits'); break; } const keyAlgorithm = { name, namedCurve }; const pub = new PublicKeyObject(keypair?.publicKey as KeyObjectHandle); const publicKey = new CryptoKey(pub, keyAlgorithm, publicUsages, true); const priv = new PrivateKeyObject(keypair?.privateKey as KeyObjectHandle); const privateKey = new CryptoKey( priv, keyAlgorithm, privateUsages, extractable, ); return { publicKey, privateKey }; };