UNPKG

react-native-quick-crypto

Version:

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

645 lines (615 loc) 21.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _exportNames = { setDefaultEncoding: true, getDefaultEncoding: true, kEmptyObject: true, toArrayBuffer: true, bufferLikeToArrayBuffer: true, binaryLikeToArrayBuffer: true, ab2str: true, validateString: true, validateFunction: true, isStringOrBuffer: true, validateObject: true, validateInt32: true, validateUint32: true, hasAnyNotIn: true, lazyDOMException: true, validateMaxBufferLength: true, normalizeAlgorithm: true, validateBitLength: true, validateByteLength: true, getUsagesUnion: true, validateKeyOps: true, bigIntArrayToUnsignedInt: true, abvToArrayBuffer: true, getHashes: true, getCiphers: true }; exports.ab2str = ab2str; exports.abvToArrayBuffer = abvToArrayBuffer; exports.bigIntArrayToUnsignedInt = void 0; exports.binaryLikeToArrayBuffer = binaryLikeToArrayBuffer; exports.bufferLikeToArrayBuffer = bufferLikeToArrayBuffer; exports.getCiphers = void 0; exports.getDefaultEncoding = getDefaultEncoding; exports.getUsagesUnion = exports.getHashes = void 0; exports.hasAnyNotIn = hasAnyNotIn; exports.isStringOrBuffer = isStringOrBuffer; exports.kEmptyObject = void 0; exports.lazyDOMException = lazyDOMException; exports.normalizeAlgorithm = void 0; exports.setDefaultEncoding = setDefaultEncoding; exports.toArrayBuffer = toArrayBuffer; exports.validateByteLength = exports.validateBitLength = void 0; exports.validateFunction = validateFunction; exports.validateInt32 = validateInt32; exports.validateMaxBufferLength = exports.validateKeyOps = void 0; exports.validateObject = validateObject; exports.validateString = validateString; exports.validateUint32 = validateUint32; var _reactNativeBuffer = require("@craftzdog/react-native-buffer"); var _safeBuffer = require("safe-buffer"); var _buffer = require("buffer"); var _Hashnames = require("./Hashnames"); Object.keys(_Hashnames).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; if (key in exports && exports[key] === _Hashnames[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _Hashnames[key]; } }); }); // @types/node // TODO(osp) should buffer be part of the Encoding type? // These are for shortcomings in @types/node // Here we use "*Type" instead of "*Types" like node does. // Mimics node behavior for default global encoding let defaultEncoding = 'buffer'; function setDefaultEncoding(encoding) { defaultEncoding = encoding; } function getDefaultEncoding() { return defaultEncoding; } const kEmptyObject = exports.kEmptyObject = Object.freeze(Object.create(null)); // Should be used by Cipher (or any other module that requires valid encodings) // function slowCases(enc: string) { // switch (enc.length) { // case 4: // if (enc === 'UTF8') return 'utf8'; // if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le'; // enc = `${enc}`.toLowerCase(); // if (enc === 'utf8') return 'utf8'; // if (enc === 'ucs2') return 'utf16le'; // break; // case 3: // if (enc === 'hex' || enc === 'HEX' || `${enc}`.toLowerCase() === 'hex') // return 'hex'; // break; // case 5: // if (enc === 'ascii') return 'ascii'; // if (enc === 'ucs-2') return 'utf16le'; // if (enc === 'UTF-8') return 'utf8'; // if (enc === 'ASCII') return 'ascii'; // if (enc === 'UCS-2') return 'utf16le'; // enc = `${enc}`.toLowerCase(); // if (enc === 'utf-8') return 'utf8'; // if (enc === 'ascii') return 'ascii'; // if (enc === 'ucs-2') return 'utf16le'; // break; // case 6: // if (enc === 'base64') return 'base64'; // if (enc === 'latin1' || enc === 'binary') return 'latin1'; // if (enc === 'BASE64') return 'base64'; // if (enc === 'LATIN1' || enc === 'BINARY') return 'latin1'; // enc = `${enc}`.toLowerCase(); // if (enc === 'base64') return 'base64'; // if (enc === 'latin1' || enc === 'binary') return 'latin1'; // break; // case 7: // if ( // enc === 'utf16le' || // enc === 'UTF16LE' || // `${enc}`.toLowerCase() === 'utf16le' // ) // return 'utf16le'; // break; // case 8: // if ( // enc === 'utf-16le' || // enc === 'UTF-16LE' || // `${enc}`.toLowerCase() === 'utf-16le' // ) // return 'utf16le'; // break; // case 9: // if ( // enc === 'base64url' || // enc === 'BASE64URL' || // `${enc}`.toLowerCase() === 'base64url' // ) // return 'base64url'; // break; // default: // if (enc === '') return 'utf8'; // } // } // // Return undefined if there is no match. // // Move the "slow cases" to a separate function to make sure this function gets // // inlined properly. That prioritizes the common case. // export function normalizeEncoding(enc?: string) { // if (enc == null || enc === 'utf8' || enc === 'utf-8') return 'utf8'; // return slowCases(enc); // } function toArrayBuffer(buf) { if (_reactNativeBuffer.Buffer.isBuffer(buf) || _buffer.Buffer.isBuffer(buf) || ArrayBuffer.isView(buf)) { if (buf?.buffer?.slice) { return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); } else { return buf.buffer; } } const ab = new ArrayBuffer(buf.length); const view = new Uint8Array(ab); for (let i = 0; i < buf.length; ++i) { view[i] = _safeBuffer.Buffer.isBuffer(buf) ? buf.readUInt8(i) : buf[i]; } return ab; } function bufferLikeToArrayBuffer(buf) { if (_reactNativeBuffer.Buffer.isBuffer(buf) || _buffer.Buffer.isBuffer(buf) || _safeBuffer.Buffer.isBuffer(buf)) { return toArrayBuffer(buf); } if (ArrayBuffer.isView(buf)) { return toArrayBuffer(buf); } return buf; } function binaryLikeToArrayBuffer(input, // CipherKey adds compat with node types encoding = 'utf-8') { // string if (typeof input === 'string') { if (encoding === 'buffer') { throw new Error('Cannot create a buffer from a string with a buffer encoding'); } const buffer = _reactNativeBuffer.Buffer.from(input, encoding); return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } // Buffer if (_reactNativeBuffer.Buffer.isBuffer(input) || _buffer.Buffer.isBuffer(input) || _safeBuffer.Buffer.isBuffer(input)) { return toArrayBuffer(input); } // ArrayBufferView // TODO add further binary types to BinaryLike, UInt8Array and so for have this array as property if (ArrayBuffer.isView(input)) { return toArrayBuffer(input); } // ArrayBuffer if (input instanceof ArrayBuffer) { return input; } // if (!(input instanceof ArrayBuffer)) { // try { // // this is a strange fallback case and input is unknown at this point // const buffer = Buffer.from(input as unknown as string); // return buffer.buffer.slice( // buffer.byteOffset, // buffer.byteOffset + buffer.byteLength // ); // } catch(e: unknown) { // console.log('throwing 1'); // const err = e as Error; // throw new Error(err.message); // } // } // TODO: handle if input is KeyObject? throw new Error('input could not be converted to ArrayBuffer'); } function ab2str(buf, encoding = 'hex') { return _reactNativeBuffer.Buffer.from(buf).toString(encoding); } function validateString(str, name) { const isString = typeof str === 'string'; if (!isString) { throw new Error(`${name} is not a string`); } return isString; } function validateFunction(f) { return f !== null && typeof f === 'function'; } function isStringOrBuffer(val) { return typeof val === 'string' || ArrayBuffer.isView(val) || val instanceof ArrayBuffer; } function validateObject(value, name, options) { const useDefaultOptions = options == null; const allowArray = useDefaultOptions ? false : options.allowArray; const allowFunction = useDefaultOptions ? false : options.allowFunction; const nullable = useDefaultOptions ? false : options.nullable; if (!nullable && value === null || !allowArray && Array.isArray(value) || typeof value !== 'object' && (!allowFunction || typeof value !== 'function')) { throw new Error(`${name} is not a valid object $${value}`); } return true; } function validateInt32( // eslint-disable-next-line @typescript-eslint/no-explicit-any value, name, min = -2147483648, max = 2147483647) { // The defaults for min and max correspond to the limits of 32-bit integers. if (typeof value !== 'number') { throw new Error(`Invalid argument - ${name} is not a number: ${value}`); } if (!Number.isInteger(value)) { throw new Error(`Argument out of range - ${name} out of integer range: ${value}`); } if (value < min || value > max) { throw new Error(`Invalid argument - ${name} out of range >= ${min} && <= ${max}: ${value}`); } } function validateUint32(value, name, positive) { if (typeof value !== 'number') { // throw new ERR_INVALID_ARG_TYPE(name, 'number', value); throw new Error(`Invalid argument - ${name} is not a number: ${value}`); } if (!Number.isInteger(value)) { // throw new ERR_OUT_OF_RANGE(name, 'an integer', value); throw new Error(`Argument out of range - ${name} out of integer range: ${value}`); } const min = positive ? 1 : 0; // 2 ** 32 === 4294967296 const max = 4294967295; if (value < min || value > max) { // throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); throw new Error(`Invalid argument - ${name} out of range >= ${min} && <= ${max}: ${value}`); } } function hasAnyNotIn(set, checks) { for (const s of set) { if (!checks.includes(s)) { return true; } } return false; } function lazyDOMException(message, domName) { let cause = ''; if (typeof domName !== 'string') { cause = `\nCaused by: ${domName.cause}`; } return new Error(`[${domName}]: ${message}${cause}`); } // from lib/internal/crypto/util.js // The maximum buffer size that we'll support in the WebCrypto impl const kMaxBufferLength = 2 ** 31 - 1; // // The EC named curves that we currently support via the Web Crypto API. // const kNamedCurveAliases = { // 'P-256': 'prime256v1', // 'P-384': 'secp384r1', // 'P-521': 'secp521r1', // }; // const kAesKeyLengths = [128, 192, 256]; // // These are the only hash algorithms we currently support via // // the Web Crypto API. // const kHashTypes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; const kSupportedAlgorithms = { digest: { 'SHA-1': null, 'SHA-256': null, 'SHA-384': null, 'SHA-512': null }, generateKey: { 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', 'RSA-PSS': 'RsaHashedKeyGenParams', 'RSA-OAEP': 'RsaHashedKeyGenParams', ECDSA: 'EcKeyGenParams', ECDH: 'EcKeyGenParams', 'AES-CTR': 'AesKeyGenParams', 'AES-CBC': 'AesKeyGenParams', 'AES-GCM': 'AesKeyGenParams', 'AES-KW': 'AesKeyGenParams', HMAC: 'HmacKeyGenParams', X25519: null, Ed25519: null, X448: null, Ed448: null }, sign: { 'RSASSA-PKCS1-v1_5': null, 'RSA-PSS': 'RsaPssParams', ECDSA: 'EcdsaParams', HMAC: null, Ed25519: null, Ed448: 'Ed448Params' }, verify: { 'RSASSA-PKCS1-v1_5': null, 'RSA-PSS': 'RsaPssParams', ECDSA: 'EcdsaParams', HMAC: null, Ed25519: null, Ed448: 'Ed448Params' }, importKey: { 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', 'RSA-PSS': 'RsaHashedImportParams', 'RSA-OAEP': 'RsaHashedImportParams', ECDSA: 'EcKeyImportParams', ECDH: 'EcKeyImportParams', HMAC: 'HmacImportParams', HKDF: null, PBKDF2: null, 'AES-CTR': null, 'AES-CBC': null, 'AES-GCM': null, 'AES-KW': null, Ed25519: null, X25519: null, Ed448: null, X448: null }, deriveBits: { HKDF: 'HkdfParams', PBKDF2: 'Pbkdf2Params', ECDH: 'EcdhKeyDeriveParams', X25519: 'EcdhKeyDeriveParams', X448: 'EcdhKeyDeriveParams' }, encrypt: { 'RSA-OAEP': 'RsaOaepParams', 'AES-CBC': 'AesCbcParams', 'AES-GCM': 'AesGcmParams', 'AES-CTR': 'AesCtrParams' }, decrypt: { 'RSA-OAEP': 'RsaOaepParams', 'AES-CBC': 'AesCbcParams', 'AES-GCM': 'AesGcmParams', 'AES-CTR': 'AesCtrParams' }, 'get key length': { 'AES-CBC': 'AesDerivedKeyParams', 'AES-CTR': 'AesDerivedKeyParams', 'AES-GCM': 'AesDerivedKeyParams', 'AES-KW': 'AesDerivedKeyParams', HMAC: 'HmacImportParams', HKDF: null, PBKDF2: null }, wrapKey: { 'AES-KW': null }, unwrapKey: { 'AES-KW': null } }; const simpleAlgorithmDictionaries = { AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, EcKeyGenParams: {}, HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, RsaPssParams: {}, EcdsaParams: { hash: 'HashAlgorithmIdentifier' }, HmacImportParams: { hash: 'HashAlgorithmIdentifier' }, HkdfParams: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource', info: 'BufferSource' }, Ed448Params: { context: 'BufferSource' }, Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, RsaOaepParams: { label: 'BufferSource' }, RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, EcKeyImportParams: {} }; const validateMaxBufferLength = (data, name) => { const length = typeof data === 'string' || data instanceof _safeBuffer.Buffer ? data.length : data.byteLength; if (length > kMaxBufferLength) { throw lazyDOMException(`${name} must be less than ${kMaxBufferLength + 1} bits`, 'OperationError'); } }; // https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm // adapted for Node.js from Deno's implementation // https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195 exports.validateMaxBufferLength = validateMaxBufferLength; const normalizeAlgorithm = (algorithm, op) => { if (typeof algorithm === 'string') { return normalizeAlgorithm({ name: algorithm }, op); } // 1. const registeredAlgorithms = kSupportedAlgorithms[op]; // 2. 3. // commented, because typescript takes care of this for us 🤞👀 // const initialAlg = webidl.converters.Algorithm(algorithm, { // prefix: 'Failed to normalize algorithm', // context: 'passed algorithm', // }); // 4. let algName = algorithm.name; if (algName === undefined) return { name: 'unknown' }; // 5. let desiredType; for (const key in registeredAlgorithms) { if (!Object.prototype.hasOwnProperty.call(registeredAlgorithms, key)) { continue; } if (key.toUpperCase() === algName.toUpperCase()) { algName = key; desiredType = registeredAlgorithms[algName]; } } if (desiredType === undefined) throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); // Fast path everything below if the registered dictionary is null if (desiredType === null) return { name: algName }; // 6. const normalizedAlgorithm = algorithm; // TODO: implement this? Maybe via typescript? // webidl.converters[desiredType](algorithm, { // prefix: 'Failed to normalize algorithm', // context: 'passed algorithm', // }); // 7. normalizedAlgorithm.name = algName; // 9. const dict = simpleAlgorithmDictionaries[desiredType]; // 10. const dictKeys = dict ? Object.keys(dict) : []; for (let i = 0; i < dictKeys.length; i++) { const member = dictKeys[i] || ''; if (!Object.prototype.hasOwnProperty.call(dict, member)) continue; // TODO: implement this? Maybe via typescript? // const idlType = dict[member]; // const idlValue = normalizedAlgorithm[member]; // 3. // if (idlType === 'BufferSource' && idlValue) { // const isView = ArrayBufferIsView(idlValue); // normalizedAlgorithm[member] = TypedArrayPrototypeSlice( // new Uint8Array( // isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue, // isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0, // isView // ? getDataViewOrTypedArrayByteLength(idlValue) // : ArrayBufferPrototypeGetByteLength(idlValue) // ) // ); // } else if (idlType === 'HashAlgorithmIdentifier') { // normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest'); // } else if (idlType === 'AlgorithmIdentifier') { // // This extension point is not used by any supported algorithm (yet?) // throw lazyDOMException('Not implemented.', 'NotSupportedError'); // } } return normalizedAlgorithm; }; exports.normalizeAlgorithm = normalizeAlgorithm; const validateBitLength = (length, name, required = false) => { if (length !== undefined || required) { // validateNumber(length, name); if (length < 0) throw new Error(`${name} > 0`); if (length % 8) { throw lazyDOMException(`${name}'s length (${length}) must be a multiple of 8`, 'InvalidArgument'); } } }; exports.validateBitLength = validateBitLength; const validateByteLength = (buf, name, target) => { if (_safeBuffer.Buffer.isBuffer(buf) && buf.length !== target || buf.byteLength !== target) { throw lazyDOMException(`${name} must contain exactly ${target} bytes`, 'OperationError'); } }; exports.validateByteLength = validateByteLength; const getUsagesUnion = (usageSet, ...usages) => { const newset = []; for (let n = 0; n < usages.length; n++) { if (!usages[n] || usages[n] === undefined) continue; if (usageSet.includes(usages[n])) newset.push(usages[n]); } return newset; }; exports.getUsagesUnion = getUsagesUnion; const kKeyOps = { sign: 1, verify: 2, encrypt: 3, decrypt: 4, wrapKey: 5, unwrapKey: 6, deriveKey: 7, deriveBits: 8 }; const validateKeyOps = (keyOps, usagesSet) => { if (keyOps === undefined) return; if (!Array.isArray(keyOps)) { throw lazyDOMException('keyData.key_ops', 'InvalidArgument'); } let flags = 0; for (let n = 0; n < keyOps.length; n++) { const op = keyOps[n]; const op_flag = kKeyOps[op]; // Skipping unknown key ops if (op_flag === undefined) continue; // Have we seen it already? if so, error if (flags & 1 << op_flag) throw lazyDOMException('Duplicate key operation', 'DataError'); flags |= 1 << op_flag; // TODO(@jasnell): RFC7517 section 4.3 strong recommends validating // key usage combinations. Specifically, it says that unrelated key // ops SHOULD NOT be used together. We're not yet validating that here. } if (usagesSet !== undefined) { for (const use of usagesSet) { if (!keyOps.includes(use)) { throw lazyDOMException('Key operations and usage mismatch', 'DataError'); } } } }; // In WebCrypto, the publicExponent option in RSA is represented as a // WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary // number of leading zero bits. Our conventional APIs for reading // an unsigned int from a Buffer are not adequate. The implementation // here is adapted from the chromium implementation here: // https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript // Returns undefined if the conversion was unsuccessful. exports.validateKeyOps = validateKeyOps; const bigIntArrayToUnsignedInt = input => { let result = 0; for (let n = 0; n < input.length; ++n) { const n_reversed = input.length - n - 1; if (n_reversed >= 4 && input[n]) return; // Too large // @ts-expect-error - input[n] is possibly undefined result |= input[n] << 8 * n_reversed; } return result; }; exports.bigIntArrayToUnsignedInt = bigIntArrayToUnsignedInt; function abvToArrayBuffer(buffer) { if (_reactNativeBuffer.Buffer.isBuffer(buffer)) { return buffer.buffer; } if (ArrayBuffer.isView(buffer)) { return buffer.buffer; } return buffer; } // TODO: these used to be shipped by crypto-browserify in quickcrypto v0.6 // could instead fetch from OpenSSL if needed and handle breaking changes const getHashes = () => ['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'md5', 'rmd160', 'sha224WithRSAEncryption', 'RSA-SHA224', 'sha256WithRSAEncryption', 'RSA-SHA256', 'sha384WithRSAEncryption', 'RSA-SHA384', 'sha512WithRSAEncryption', 'RSA-SHA512', 'RSA-SHA1', 'ecdsa-with-SHA1', 'sha256', 'sha224', 'sha384', 'sha512', 'DSA-SHA', 'DSA-SHA1', 'DSA', 'DSA-WITH-SHA224', 'DSA-SHA224', 'DSA-WITH-SHA256', 'DSA-SHA256', 'DSA-WITH-SHA384', 'DSA-SHA384', 'DSA-WITH-SHA512', 'DSA-SHA512', 'DSA-RIPEMD160', 'ripemd160WithRSA', 'RSA-RIPEMD160', 'md5WithRSAEncryption', 'RSA-MD5']; // TODO: these used to be shipped by crypto-browserify in quickcrypto v0.6 // could instead fetch from OpenSSL if needed and handle breaking changes exports.getHashes = getHashes; const getCiphers = () => ['des-ecb', 'des', 'des-cbc', 'des3', 'des-ede3-cbc', 'des-ede3', 'des-ede-cbc', 'des-ede', 'aes-128-ecb', 'aes-192-ecb', 'aes-256-ecb', 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', 'aes128', 'aes192', 'aes256', 'aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb', 'aes-128-cfb8', 'aes-192-cfb8', 'aes-256-cfb8', 'aes-128-cfb1', 'aes-192-cfb1', 'aes-256-cfb1', 'aes-128-ofb', 'aes-192-ofb', 'aes-256-ofb', 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', 'aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm']; exports.getCiphers = getCiphers; //# sourceMappingURL=Utils.js.map