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
JavaScript
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
;