UNPKG

react-native-quick-crypto

Version:

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

369 lines (344 loc) 13.8 kB
"use strict"; import { Buffer as CraftzdogBuffer } from '@craftzdog/react-native-buffer'; import { Buffer as SafeBuffer } from 'safe-buffer'; // @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'; export function setDefaultEncoding(encoding) { defaultEncoding = encoding; } export function getDefaultEncoding() { return defaultEncoding; } export const 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); // } /** * Converts supplied argument to an ArrayBuffer. Note this does not copy the * data so it is faster than toArrayBuffer. Not copying is important for * functions like randomFill which need to be able to write to the underlying * buffer. * @param buf * @returns ArrayBuffer */ export function abvToArrayBuffer(buffer) { if (CraftzdogBuffer.isBuffer(buffer) || ArrayBuffer.isView(buffer)) { return buffer.buffer; } return buffer; } /** * Converts supplied argument to an ArrayBuffer. Note this copies data if the * supplied buffer has the .slice() method, so can be a bit slow. * @param buf * @returns ArrayBuffer */ export function toArrayBuffer(buf) { if (CraftzdogBuffer.isBuffer(buf) || ArrayBuffer.isView(buf)) { if (buf?.buffer?.slice) { return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); } else { throw new Error('This implementation of buffer does not implement slice'); } } const ab = new ArrayBuffer(buf.length); const view = new Uint8Array(ab); for (let i = 0; i < buf.length; ++i) { view[i] = SafeBuffer.isBuffer(buf) ? buf.readUInt8(i) : buf[i]; } return ab; } export function bufferLikeToArrayBuffer(buf) { if (CraftzdogBuffer.isBuffer(buf) || SafeBuffer.isBuffer(buf)) { return toArrayBuffer(buf); } if (ArrayBuffer.isView(buf)) { return toArrayBuffer(buf); } return buf; } export 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 = CraftzdogBuffer.from(input, encoding); return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } // Buffer if (CraftzdogBuffer.isBuffer(input) || SafeBuffer.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'); } export function ab2str(buf, encoding = 'hex') { return CraftzdogBuffer.from(buf).toString(encoding); } export function validateString(str, name) { const isString = typeof str === 'string'; if (!isString) { throw new Error(`${name} is not a string`); } return isString; } export function validateFunction(f) { return f !== null && typeof f === 'function'; } export function isStringOrBuffer(val) { return typeof val === 'string' || ArrayBuffer.isView(val) || val instanceof ArrayBuffer; } export 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; } export 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}`); } } export 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}`); } } export function hasAnyNotIn(set, checks) { for (const s of set) { if (!checks.includes(s)) { return true; } } return false; } export 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']; export const validateMaxBufferLength = (data, name) => { const length = typeof data === 'string' || data instanceof SafeBuffer ? data.length : data.byteLength; if (length > kMaxBufferLength) { throw lazyDOMException(`${name} must be less than ${kMaxBufferLength + 1} bits`, 'OperationError'); } }; export 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'); } } }; export const validateByteLength = (buf, name, target) => { if (SafeBuffer.isBuffer(buf) && buf.length !== target || buf.byteLength !== target) { throw lazyDOMException(`${name} must contain exactly ${target} bytes`, 'OperationError'); } }; export 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; }; const kKeyOps = { sign: 1, verify: 2, encrypt: 3, decrypt: 4, wrapKey: 5, unwrapKey: 6, deriveKey: 7, deriveBits: 8 }; export 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. export 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; }; // TODO: these used to be shipped by crypto-browserify in quickcrypto v0.6 // could instead fetch from OpenSSL if needed and handle breaking changes export 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 export 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']; export * from './Hashnames'; //# sourceMappingURL=Utils.js.map