UNPKG

react-native-quick-crypto

Version:

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

373 lines (339 loc) 9.74 kB
import { KeyVariantLookup } from './NativeQuickCrypto/Cipher'; import { generateKeyPairPromise } from './Cipher'; import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto'; import type { KeyObjectHandle } from './NativeQuickCrypto/webcrypto'; import { lazyDOMException, type BufferLike, validateKeyOps, normalizeHashName, HashContext, hasAnyNotIn, getUsagesUnion, bigIntArrayToUnsignedInt, validateMaxBufferLength, bufferLikeToArrayBuffer, } from './Utils'; import { CryptoKey, PrivateKeyObject, type HashAlgorithm, type ImportFormat, type JWK, type KeyUsage, type SubtleAlgorithm, PublicKeyObject, type AnyAlgorithm, KeyType, createPublicKey, type CryptoKeyPair, KWebCryptoKeyFormat, CipherOrWrapMode, type RsaOaepParams, type DigestAlgorithm, } from './keys'; // TODO: keep in in sync with C++ side (cpp/Cipher/MGLRsa.h) export enum RSAKeyVariant { RSA_SSA_PKCS1_v1_5, RSA_PSS, RSA_OAEP, } function verifyAcceptableRsaKeyUse( name: AnyAlgorithm, isPublic: boolean, usages: KeyUsage[], ): void { let checkSet; switch (name) { case 'RSA-OAEP': checkSet = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey']; break; case 'RSA-PSS': // Fall through case 'RSASSA-PKCS1-v1_5': checkSet = isPublic ? ['verify'] : ['sign']; break; default: throw lazyDOMException( 'The algorithm is not supported', 'NotSupportedError', ); } if (hasAnyNotIn(usages, checkSet)) { throw lazyDOMException( `Unsupported key usage for an ${name} key`, 'SyntaxError', ); } } const rsaOaepCipher = ( mode: CipherOrWrapMode, key: CryptoKey, data: ArrayBuffer, { label }: RsaOaepParams, ): Promise<ArrayBuffer> => { const type = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt ? 'public' : 'private'; if (key.type !== type) { throw lazyDOMException( 'The requested operation is not valid for the provided key', 'InvalidAccessError', ); } if (label !== undefined) { validateMaxBufferLength(label, 'algorithm.label'); } return NativeQuickCrypto.webcrypto.rsaCipher( mode, key.keyObject.handle, data, RSAKeyVariant.RSA_OAEP, normalizeHashName(key.algorithm.hash) as DigestAlgorithm, label !== undefined ? bufferLikeToArrayBuffer(label) : undefined, ); }; export const rsaCipher = rsaOaepCipher; export const rsaKeyGenerate = async ( algorithm: SubtleAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): Promise<CryptoKeyPair> => { const { name, modulusLength, publicExponent, hash: rawHash } = algorithm; const hash: HashAlgorithm = normalizeHashName(rawHash); // const usageSet = new SafeSet(keyUsages); const publicExponentConverted = bigIntArrayToUnsignedInt( publicExponent as Uint8Array, ); if (publicExponentConverted === undefined) { throw lazyDOMException( 'The publicExponent must be equivalent to an unsigned 32-bit value', 'OperationError', ); } switch (name) { case 'RSA-OAEP': if ( hasAnyNotIn(keyUsages, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']) ) { throw lazyDOMException( 'Unsupported key usage for a RSA key', 'SyntaxError', ); } break; default: if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) { throw lazyDOMException( 'Unsupported key usage for a RSA key', 'SyntaxError', ); } } const [err, keypair] = await generateKeyPairPromise('rsa', { modulusLength, publicExponent: publicExponentConverted, }); if (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }, ); } const keyAlgorithm = { name, modulusLength, publicExponent: publicExponentConverted, hash, }; let publicUsages: KeyUsage[] = []; let privateUsages: KeyUsage[] = []; switch (name) { case 'RSA-OAEP': { publicUsages = getUsagesUnion(keyUsages, 'encrypt', 'wrapKey'); privateUsages = getUsagesUnion(keyUsages, 'decrypt', 'unwrapKey'); break; } default: { publicUsages = getUsagesUnion(keyUsages, 'verify'); privateUsages = getUsagesUnion(keyUsages, 'sign'); break; } } 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 }; }; export const rsaExportKey = ( key: CryptoKey, format: KWebCryptoKeyFormat, ): ArrayBuffer => { const variant = KeyVariantLookup[key.algorithm.name]; if (variant === undefined) { throw lazyDOMException( `Unrecognized algorithm name '${key.algorithm.name}'`, 'NotSupportedError', ); } return NativeQuickCrypto.webcrypto.rsaExportKey( format, key.keyObject.handle, variant, ); }; export const rsaImportKey = ( format: ImportFormat, keyData: BufferLike | JWK, algorithm: SubtleAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): CryptoKey => { // const usagesSet = new SafeSet(keyUsages); let keyObject: PublicKeyObject | PrivateKeyObject; switch (format) { case 'spki': { verifyAcceptableRsaKeyUse(algorithm.name, true, keyUsages); try { keyObject = createPublicKey({ key: keyData, format: 'der', type: 'spki', }); } catch (err) { throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err, }); } break; } // case 'pkcs8': { // verifyAcceptableRsaKeyUse(algorithm.name, false, keyUsages); // try { // keyObject = createPrivateKey({ // key: keyData, // format: 'der', // type: 'pkcs8', // }); // } catch (err) { // throw lazyDOMException('Invalid keyData', { // name: 'DataError', // cause: err, // }); // } // break; // } case 'jwk': { const data = keyData as JWK; if (!data.kty) { throw lazyDOMException('Invalid keyData', 'DataError'); } if (data.kty !== 'RSA') throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); verifyAcceptableRsaKeyUse( algorithm.name, data.d === undefined, keyUsages, ); if (keyUsages.length > 0 && data.use !== undefined) { const checkUse = algorithm.name === 'RSA-OAEP' ? '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 (data.alg !== undefined) { const hash = normalizeHashName( data.alg as HashAlgorithm, HashContext.WebCrypto, ); if (hash !== algorithm.hash) throw lazyDOMException( 'JWK "alg" does not match the requested algorithm', 'DataError', ); } const handle = NativeQuickCrypto.webcrypto.createKeyObjectHandle(); const type = handle.initJwk(data); if (type === undefined) throw lazyDOMException('Invalid JWK', 'DataError'); keyObject = type === KeyType.Private ? new PrivateKeyObject(handle) : new PublicKeyObject(handle); break; } default: throw lazyDOMException( `Unable to import RSA key with format ${format}`, 'NotSupportedError', ); } if (keyObject.asymmetricKeyType !== 'rsa') { throw lazyDOMException('Invalid key type', 'DataError'); } const { modulusLength, publicExponent } = keyObject.handle.keyDetail(); if (publicExponent === undefined) { throw lazyDOMException('publicExponent is undefined', 'DataError'); } return new CryptoKey( keyObject, { name: algorithm.name, modulusLength, publicExponent: new Uint8Array(publicExponent), hash: algorithm.hash, }, keyUsages, extractable, ); }; // function rsaSignVerify(key, data, { saltLength }, signature) { // let padding; // if (key.algorithm.name === 'RSA-PSS') { // padding = RSA_PKCS1_PSS_PADDING; // // TODO(@jasnell): Validate maximum size of saltLength // // based on the key size: // // Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2 // validateInt32(saltLength, 'algorithm.saltLength', -2); // } // const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; // const type = mode === kSignJobModeSign ? 'private' : 'public'; // if (key.type !== type) // throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); // return jobPromise(() => new SignJob( // kCryptoJobAsync, // signature === undefined ? kSignJobModeSign : kSignJobModeVerify, // key[kKeyObject][kHandle], // undefined, // undefined, // undefined, // data, // normalizeHashName(key.algorithm.hash.name), // saltLength, // padding, // undefined, // signature)); // } // module.exports = { // rsaCipher: rsaOaepCipher, // rsaExportKey, // rsaImportKey, // rsaKeyGenerate, // rsaSignVerify, // };