UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

569 lines (512 loc) 16 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/crypto/util.js import { getCiphers as _getCiphers, getCurves as _getCurves, getHashes as _getHashes, setEngine as _setEngine, secureHeapUsed as _secureHeapUsed, getCachedAliases, } from "nstdlib/stub/binding/crypto"; import { getOptionValue } from "nstdlib/lib/internal/options"; import { crypto as __crypto__ } from "nstdlib/stub/binding/constants"; import * as normalizeHashName from "nstdlib/lib/internal/crypto/hashnames"; import { codes as __codes__, hideStackFrames, } from "nstdlib/lib/internal/errors"; import { validateArray, validateNumber, validateString, } from "nstdlib/lib/internal/validators"; import { Buffer } from "nstdlib/lib/buffer"; import { cachedResult, filterDuplicateStrings, lazyDOMException, } from "nstdlib/lib/internal/util"; import { namespace as __namespace__ } from "nstdlib/lib/internal/v8/startup_snapshot"; import { isDataView, isArrayBufferView, isAnyArrayBuffer, } from "nstdlib/lib/internal/util/types"; import * as __hoisted_internal_crypto_webidl__ from "nstdlib/lib/internal/crypto/webidl"; const { ENGINE_METHOD_ALL } = __crypto__; const { ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, ERR_CRYPTO_ENGINE_UNKNOWN, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, } = __codes__; const { isBuildingSnapshot, addSerializeCallback } = __namespace__; const kHandle = Symbol("kHandle"); const kKeyObject = Symbol("kKeyObject"); // This is here because many functions accepted binary strings without // any explicit encoding in older versions of node, and we don't want // to break them unnecessarily. function toBuf(val, encoding) { if (typeof val === "string") { if (encoding === "buffer") encoding = "utf8"; return Buffer.from(val, encoding); } return val; } let _hashCache; function getHashCache() { if (_hashCache === undefined) { _hashCache = getCachedAliases(); if (isBuildingSnapshot()) { // For dynamic linking, clear the map. addSerializeCallback(() => { _hashCache = undefined; }); } } return _hashCache; } function getCachedHashId(algorithm) { const result = getHashCache()[algorithm]; return result === undefined ? -1 : result; } const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers())); const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes())); const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves())); function setEngine(id, flags) { validateString(id, "id"); if (flags) validateNumber(flags, "flags"); flags = flags >>> 0; // Use provided engine for everything by default if (flags === 0) flags = ENGINE_METHOD_ALL; if (typeof _setEngine !== "function") throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); if (!_setEngine(id, flags)) throw new ERR_CRYPTO_ENGINE_UNKNOWN(id); } const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { if (isAnyArrayBuffer(buffer)) return buffer; if (typeof buffer === "string") { if (encoding === "buffer") encoding = "utf8"; return Buffer.from(buffer, encoding); } if (!isArrayBufferView(buffer)) { throw new ERR_INVALID_ARG_TYPE.HideStackFramesError( name, ["string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], buffer, ); } return buffer; }); // 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: {}, }; function validateMaxBufferLength(data, name) { if (data.byteLength > kMaxBufferLength) { throw lazyDOMException( `${name} must be less than ${kMaxBufferLength + 1} bits`, "OperationError", ); } } let webidl; // 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 function normalizeAlgorithm(algorithm, op) { if (typeof algorithm === "string") return normalizeAlgorithm({ name: algorithm }, op); webidl ??= __hoisted_internal_crypto_webidl__; // 1. const registeredAlgorithms = kSupportedAlgorithms[op]; // 2. 3. const initialAlg = webidl.converters.Algorithm(algorithm, { prefix: "Failed to normalize algorithm", context: "passed algorithm", }); // 4. let algName = initialAlg.name; // 5. let desiredType; for (const key in registeredAlgorithms) { if (!Object.prototype.hasOwnProperty.call(registeredAlgorithms, key)) { continue; } if ( String.prototype.toUpperCase.call(key) === String.prototype.toUpperCase.call(algName) ) { algName = key; desiredType = registeredAlgorithms[key]; } } 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 = 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; const idlType = dict[member]; const idlValue = normalizedAlgorithm[member]; // 3. if (idlType === "BufferSource" && idlValue) { const isView = ArrayBuffer.isView(idlValue); normalizedAlgorithm[member] = TypedArrayPrototypeSlice( new Uint8Array( isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue, isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0, isView ? getDataViewOrTypedArrayByteLength(idlValue) : Object.getOwnPropertyDescriptor( ArrayBufferView.prototype, "byteLength", ).get(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; } function getDataViewOrTypedArrayBuffer(V) { return isDataView(V) ? Object.getOwnPropertyDescriptor(DataView.prototype, "buffer").get(V) : TypedArrayPrototypeGetBuffer(V); } function getDataViewOrTypedArrayByteOffset(V) { return isDataView(V) ? Object.getOwnPropertyDescriptor(DataView.prototype, "byteOffset").get(V) : TypedArrayPrototypeGetByteOffset(V); } function getDataViewOrTypedArrayByteLength(V) { return isDataView(V) ? Object.getOwnPropertyDescriptor(DataView.prototype, "byteLength").get(V) : TypedArrayPrototypeGetByteLength(V); } function hasAnyNotIn(set, checks) { for (const s of set) if (!Array.prototype.includes.call(checks, s)) return true; return false; } function validateBitLength(length, name, required = false) { if (length !== undefined || required) { validateNumber(length, name); if (length < 0) throw new ERR_OUT_OF_RANGE(name, "> 0"); if (length % 8) { throw new ERR_INVALID_ARG_VALUE(name, length, "must be a multiple of 8"); } } } function validateByteLength(buf, name, target) { if (buf.byteLength !== target) { throw lazyDOMException( `${name} must contain exactly ${target} bytes`, "OperationError", ); } } const validateByteSource = hideStackFrames((val, name) => { val = toBuf(val); if (isAnyArrayBuffer(val) || isArrayBufferView(val)) return val; throw new ERR_INVALID_ARG_TYPE.HideStackFramesError( name, ["string", "ArrayBuffer", "TypedArray", "DataView", "Buffer"], val, ); }); function onDone(resolve, reject, err, result) { if (err) { return reject( lazyDOMException( "The operation failed for an operation-specific reason", { name: "OperationError", cause: err }, ), ); } resolve(result); } function jobPromise(getJob) { return new Promise((resolve, reject) => { try { const job = getJob(); job.ondone = Function.prototype.bind.call(onDone, job, resolve, reject); job.run(); } catch (err) { onDone(resolve, reject, err); } }); } // 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. function 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 result |= input[n] << (8 * n_reversed); } return result; } function bigIntArrayToUnsignedBigInt(input) { let result = 0n; for (let n = 0; n < input.length; ++n) { const n_reversed = input.length - n - 1; result |= BigInt(input[n]) << (8n * BigInt(n_reversed)); } return result; } function getStringOption(options, key) { let value; if (options && (value = options[key]) != null) validateString(value, `options.${key}`); return value; } function getUsagesUnion(usageSet, ...usages) { const newset = []; for (let n = 0; n < usages.length; n++) { if (usageSet.has(usages[n])) Array.prototype.push.call(newset, usages[n]); } return newset; } function getBlockSize(name) { switch (name) { case "SHA-1": // Fall through case "SHA-256": return 512; case "SHA-384": // Fall through case "SHA-512": return 1024; } } function getDigestSizeInBytes(name) { switch (name) { case "SHA-1": return 20; case "SHA-256": return 32; case "SHA-384": return 48; case "SHA-512": return 64; } } const kKeyOps = { sign: 1, verify: 2, encrypt: 3, decrypt: 4, wrapKey: 5, unwrapKey: 6, deriveKey: 7, deriveBits: 8, }; function validateKeyOps(keyOps, usagesSet) { if (keyOps === undefined) return; validateArray(keyOps, "keyData.key_ops"); 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 (!Array.prototype.includes.call(keyOps, use)) { throw lazyDOMException( "Key operations and usage mismatch", "DataError", ); } } } } function secureHeapUsed() { const val = _secureHeapUsed(); if (val === undefined) return { total: 0, used: 0, utilization: 0, min: 0 }; const used = Number(_secureHeapUsed()); const total = Number(getOptionValue("--secure-heap")); const min = Number(getOptionValue("--secure-heap-min")); const utilization = used / total; return { total, used, utilization, min }; } export { getArrayBufferOrView }; export { getCiphers }; export { getCurves }; export { getDataViewOrTypedArrayBuffer }; export { getHashes }; export { kHandle }; export { kKeyObject }; export { setEngine }; export { toBuf }; export { kHashTypes }; export { kNamedCurveAliases }; export { kAesKeyLengths }; export { normalizeAlgorithm }; export { normalizeHashName }; export { hasAnyNotIn }; export { validateBitLength }; export { validateByteLength }; export { validateByteSource }; export { validateKeyOps }; export { jobPromise }; export { validateMaxBufferLength }; export { bigIntArrayToUnsignedBigInt }; export { bigIntArrayToUnsignedInt }; export { getBlockSize }; export { getDigestSizeInBytes }; export { getStringOption }; export { getUsagesUnion }; export { secureHeapUsed }; export { getCachedHashId }; export { getHashCache };