UNPKG

react-native-quick-crypto

Version:

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

443 lines (438 loc) 14.9 kB
"use strict"; import { Buffer as SBuffer } from 'safe-buffer'; import { CryptoKey, KWebCryptoKeyFormat, createSecretKey, CipherOrWrapMode } from './keys'; import { hasAnyNotIn, lazyDOMException, normalizeHashName, HashContext, validateMaxBufferLength, bufferLikeToArrayBuffer } from './Utils'; import { ecImportKey, ecExportKey, ecGenerateKey, ecdsaSignVerify } from './ec'; import { pbkdf2DeriveBits } from './pbkdf2'; import { asyncDigest } from './Hash'; import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes'; import { rsaCipher, rsaExportKey, rsaImportKey, rsaKeyGenerate } from './rsa'; import { normalizeAlgorithm } from './Algorithms'; import { hmacImportKey } from './mac'; const exportKeySpki = async key => { switch (key.algorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': if (key.type === 'public') { return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI); } break; case 'ECDSA': // Fall through case 'ECDH': if (key.type === 'public') { return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI); } break; // case 'Ed25519': // // Fall through // case 'Ed448': // // Fall through // case 'X25519': // // Fall through // case 'X448': // if (key.type === 'public') { // return cfrgExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI); // } // break; } throw new Error(`Unable to export a spki ${key.algorithm.name} ${key.type} key`); }; const exportKeyPkcs8 = async key => { switch (key.algorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': if (key.type === 'private') { return rsaExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8); } break; case 'ECDSA': // Fall through case 'ECDH': if (key.type === 'private') { return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8); } break; // case 'Ed25519': // // Fall through // case 'Ed448': // // Fall through // case 'X25519': // // Fall through // case 'X448': // if (key.type === 'private') { // return cfrgExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8); // } // break; } throw new Error(`Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`); }; const exportKeyRaw = key => { switch (key.algorithm.name) { case 'ECDSA': // Fall through case 'ECDH': if (key.type === 'public') { return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw); } break; // case 'Ed25519': // // Fall through // case 'Ed448': // // Fall through // case 'X25519': // // Fall through // case 'X448': // if (key.type === 'public') { // return require('internal/crypto/cfrg') // .cfrgExportKey(key, kWebCryptoKeyFormatRaw); // } // break; case 'AES-CTR': // Fall through case 'AES-CBC': // Fall through case 'AES-GCM': // Fall through case 'AES-KW': // Fall through case 'HMAC': return key.keyObject.export(); } throw lazyDOMException(`Unable to export a raw ${key.algorithm.name} ${key.type} key`, 'InvalidAccessError'); }; const exportKeyJWK = key => { const jwk = key.keyObject.handle.exportJwk({ key_ops: key.usages, ext: key.extractable }, true); switch (key.algorithm.name) { case 'RSASSA-PKCS1-v1_5': jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsa); return jwk; case 'RSA-PSS': jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaPss); return jwk; case 'RSA-OAEP': jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkRsaOaep); return jwk; case 'HMAC': jwk.alg = normalizeHashName(key.algorithm.hash, HashContext.JwkHmac); return jwk; case 'ECDSA': // Fall through case 'ECDH': jwk.crv ||= key.algorithm.namedCurve; return jwk; // case 'X25519': // // Fall through // case 'X448': // jwk.crv ||= key.algorithm.name; // return jwk; // case 'Ed25519': // // Fall through // case 'Ed448': // jwk.crv ||= key.algorithm.name; // return jwk; case 'AES-CTR': // Fall through case 'AES-CBC': // Fall through case 'AES-GCM': // Fall through case 'AES-KW': jwk.alg = getAlgorithmName(key.algorithm.name, key.algorithm.length); return jwk; // case 'HMAC': // jwk.alg = normalizeHashName( // key.algorithm.hash.name, // normalizeHashName.kContextJwkHmac); // return jwk; default: // Fall through } throw lazyDOMException(`JWK export not yet supported: ${key.algorithm.name}`, 'NotSupportedError'); }; const importGenericSecretKey = async ({ name, length }, format, keyData, extractable, keyUsages) => { if (extractable) { throw new Error(`${name} keys are not extractable`); } if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { throw new Error(`Unsupported key usage for a ${name} key`); } switch (format) { case 'raw': { if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) { throw new Error(`Unsupported key usage for a ${name} key`); } const checkLength = typeof keyData === 'string' || SBuffer.isBuffer(keyData) ? keyData.length * 8 : keyData.byteLength * 8; // The Web Crypto spec allows for key lengths that are not multiples of // 8. We don't. Our check here is stricter than that defined by the spec // in that we require that algorithm.length match keyData.length * 8 if // algorithm.length is specified. if (length !== undefined && length !== checkLength) { throw new Error('Invalid key length'); } const keyObject = createSecretKey(keyData); return new CryptoKey(keyObject, { name }, keyUsages, false); } } throw new Error(`Unable to import ${name} key with format ${format}`); }; // const checkCryptoKeyUsages = (key: CryptoKey) => { // if ( // (key.type === 'secret' || key.type === 'private') && // key.usages.length === 0 // ) { // throw lazyDOMException( // 'Usages cannot be empty when creating a key.', // 'SyntaxError' // ); // } // }; const checkCryptoKeyPairUsages = pair => { if (!(pair.privateKey instanceof Buffer) && pair.privateKey && Object.prototype.hasOwnProperty.call(pair.privateKey, 'keyUsages')) { const priv = pair.privateKey; if (priv.usages.length > 0) { return; } } console.log(pair.privateKey); throw lazyDOMException('Usages cannot be empty when creating a key.', 'SyntaxError'); }; const signVerify = (algorithm, key, data, signature) => { const usage = signature === undefined ? 'sign' : 'verify'; algorithm = normalizeAlgorithm(algorithm, usage); if (!key.usages.includes(usage) || algorithm.name !== key.algorithm.name) { throw lazyDOMException(`Unable to use this key to ${usage}`, 'InvalidAccessError'); } switch (algorithm.name) { // case 'RSA-PSS': // // Fall through // case 'RSASSA-PKCS1-v1_5': // return require('internal/crypto/rsa').rsaSignVerify( // key, // data, // algorithm, // signature // ); case 'ECDSA': return ecdsaSignVerify(key, data, algorithm, signature); // case 'Ed25519': // // Fall through // case 'Ed448': // return require('internal/crypto/cfrg').eddsaSignVerify( // key, // data, // algorithm, // signature // ); // case 'HMAC': // return require('internal/crypto/mac').hmacSignVerify( // key, // data, // algorithm, // signature // ); } throw lazyDOMException(`Unrecognized algorithm name '${algorithm}' for '${usage}'`, 'NotSupportedError'); }; const cipherOrWrap = async (mode, algorithm, key, data, op) => { // We use a Node.js style error here instead of a DOMException because // the WebCrypto spec is not specific what kind of error is to be thrown // in this case. Both Firefox and Chrome throw simple TypeErrors here. // The key algorithm and cipher algorithm must match, and the // key must have the proper usage. if (key.algorithm.name !== algorithm.name || !key.usages.includes(op)) { throw lazyDOMException('The requested operation is not valid for the provided key', 'InvalidAccessError'); } // While WebCrypto allows for larger input buffer sizes, we limit // those to sizes that can fit within uint32_t because of limitations // in the OpenSSL API. validateMaxBufferLength(data, 'data'); switch (algorithm.name) { case 'RSA-OAEP': return rsaCipher(mode, key, data, algorithm); case 'AES-CTR': // Fall through case 'AES-CBC': // Fall through case 'AES-GCM': return aesCipher(mode, key, data, algorithm); // case 'AES-KW': // if (op === 'wrapKey' || op === 'unwrapKey') { // return aesCipher(mode, key, data, algorithm); // } } // @ts-expect-error unreachable code throw lazyDOMException(`Unrecognized algorithm name '${algorithm}' for '${op}'`, 'NotSupportedError'); }; export class Subtle { async decrypt(algorithm, key, data) { const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt'); return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, normalizedAlgorithm, key, bufferLikeToArrayBuffer(data), 'decrypt'); } async digest(algorithm, data) { const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'digest'); return asyncDigest(normalizedAlgorithm, data); } async deriveBits(algorithm, baseKey, length) { if (!baseKey.keyUsages.includes('deriveBits')) { throw new Error('baseKey does not have deriveBits usage'); } if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch'); switch (algorithm.name) { // case 'X25519': // // Fall through // case 'X448': // // Fall through // case 'ECDH': // return require('internal/crypto/diffiehellman') // .ecdhDeriveBits(algorithm, baseKey, length); // case 'HKDF': // return require('internal/crypto/hkdf') // .hkdfDeriveBits(algorithm, baseKey, length); case 'PBKDF2': return pbkdf2DeriveBits(algorithm, baseKey, length); } throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`); } async encrypt(algorithm, key, data) { const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt'); return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, bufferLikeToArrayBuffer(data), 'encrypt'); } async exportKey(format, key) { if (!key.extractable) throw new Error('key is not extractable'); switch (format) { case 'spki': return await exportKeySpki(key); case 'pkcs8': return await exportKeyPkcs8(key); case 'jwk': return exportKeyJWK(key); case 'raw': return exportKeyRaw(key); } } async generateKey(algorithm, extractable, keyUsages) { algorithm = normalizeAlgorithm(algorithm, 'generateKey'); let result; switch (algorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': result = await rsaKeyGenerate(algorithm, extractable, keyUsages); break; // case 'Ed25519': // // Fall through // case 'Ed448': // // Fall through // case 'X25519': // // Fall through // case 'X448': // resultType = 'CryptoKeyPair'; // result = await cfrgGenerateKey(algorithm, extractable, keyUsages); // break; case 'ECDSA': // Fall through case 'ECDH': result = await ecGenerateKey(algorithm, extractable, keyUsages); checkCryptoKeyPairUsages(result); break; // case 'HMAC': // result = await hmacGenerateKey(algorithm, extractable, keyUsages); // break; case 'AES-CTR': // Fall through case 'AES-CBC': // Fall through case 'AES-GCM': // Fall through case 'AES-KW': result = await aesGenerateKey(algorithm, extractable, keyUsages); break; default: throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}. Unrecognized algorithm name`); } return result; } async importKey(format, data, algorithm, extractable, keyUsages) { const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey'); let result; switch (normalizedAlgorithm.name) { case 'RSASSA-PKCS1-v1_5': // Fall through case 'RSA-PSS': // Fall through case 'RSA-OAEP': result = rsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages); break; case 'ECDSA': // Fall through case 'ECDH': result = ecImportKey(format, data, normalizedAlgorithm, extractable, keyUsages); break; // case 'Ed25519': // // Fall through // case 'Ed448': // // Fall through // case 'X25519': // // Fall through // case 'X448': // result = await require('internal/crypto/cfrg').cfrgImportKey( // format, // keyData, // algorithm, // extractable, // keyUsages // ); // break; case 'HMAC': result = await hmacImportKey(normalizedAlgorithm, format, data, extractable, keyUsages); break; case 'AES-CTR': // Fall through case 'AES-CBC': // Fall through case 'AES-GCM': // Fall through case 'AES-KW': result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages); break; // case 'HKDF': // // Fall through case 'PBKDF2': result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages); break; default: throw new Error(`"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`); } if ((result.type === 'secret' || result.type === 'private') && result.usages.length === 0) { throw new Error(`Usages cannot be empty when importing a ${result.type} key.`); } return result; } async sign(algorithm, key, data) { return signVerify(algorithm, key, data); } async verify(algorithm, key, signature, data) { return signVerify(algorithm, key, data, signature); } } export const subtle = new Subtle(); //# sourceMappingURL=subtle.js.map