'use strict';
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeGetDetached,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSlice,
BigInt,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
MathFloor,
MathMin,
Number,
ObjectDefineProperty,
ObjectEntries,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseReject,
PromiseWithResolvers,
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeStartsWith,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetLength,
Uint8Array,
} = primordials;
const {
getCiphers: _getCiphers,
getCurves: _getCurves,
getHashes: _getHashes,
getMacs: _getMacs,
secureHeapUsed: _secureHeapUsed,
getCachedAliases,
getCachedMacAliases,
getOpenSSLSecLevelCrypto: getOpenSSLSecLevel,
kKeyVariantAES_OCB_128: hasAesOcbMode,
Argon2Job,
getFipsCrypto,
getFipsCryptoGeneration,
KmacJob,
isKeyAlgorithmAvailable,
} = internalBinding('crypto');
const { openssl_is_boringssl: isBoringSSL } = process.features;
let fips;
let fipsGeneration;
function isFips() {
const generation = getFipsCryptoGeneration();
if (fipsGeneration !== generation) {
fips = getFipsCrypto() === 1;
fipsGeneration = generation;
}
return fips;
}
const { getOptionValue } = require('internal/options');
const normalizeHashName = require('internal/crypto/hashnames');
const {
codes: {
ERR_INVALID_ARG_TYPE,
},
hideStackFrames,
} = require('internal/errors');
const {
validateArray,
validateString,
} = require('internal/validators');
const { Buffer } = require('buffer');
const {
emitExperimentalWarning,
filterDuplicateStrings,
lazyDOMException,
setOwnProperty,
} = require('internal/util');
const {
namespace: {
isBuildingSnapshot,
addSerializeCallback,
},
} = require('internal/v8/startup_snapshot');
const {
isDataView,
isArrayBuffer,
isArrayBufferView,
isAnyArrayBuffer,
isPromise,
} = require('internal/util/types');
const kHandle = Symbol('kHandle');
// 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;
let _macCache;
if (isBuildingSnapshot()) {
addSerializeCallback(() => {
fips = undefined;
fipsGeneration = undefined;
supportedAlgorithmsGeneration = undefined;
_hashCache = undefined;
_macCache = undefined;
});
}
function getHashCache() {
while (_hashCache === undefined) {
const generation = getFipsCryptoGeneration();
const cache = getCachedAliases();
if (generation !== getFipsCryptoGeneration()) continue;
_hashCache = cache;
}
return _hashCache;
}
function getMacCache() {
while (_macCache === undefined) {
const generation = getFipsCryptoGeneration();
const cache = getCachedMacAliases();
if (generation !== getFipsCryptoGeneration()) continue;
_macCache = cache;
}
return _macCache;
}
function cachedArrayByFipsGeneration(fn, onRefresh) {
let result;
let generation;
if (isBuildingSnapshot()) {
addSerializeCallback(() => {
result = undefined;
generation = undefined;
});
}
return () => {
while (true) {
const current = getFipsCryptoGeneration();
if (result === undefined || generation !== current) {
const next = fn();
if (current !== getFipsCryptoGeneration()) continue;
result = next;
generation = current;
if (onRefresh !== undefined) onRefresh();
}
return ArrayPrototypeSlice(result);
}
};
}
function getCachedHashId(algorithm) {
const result = getHashCache()[algorithm];
return result === undefined ? -1 : result;
}
function getCachedMacId(algorithm) {
const result = getMacCache()[algorithm];
return result === undefined ? -1 : result;
}
const getCiphers = cachedArrayByFipsGeneration(
() => filterDuplicateStrings(_getCiphers()));
const getHashes = cachedArrayByFipsGeneration(
() => filterDuplicateStrings(_getHashes()),
() => {
_hashCache = undefined;
});
const getMacs = cachedArrayByFipsGeneration(
() => filterDuplicateStrings(_getMacs(), true),
() => {
_macCache = undefined;
});
const getCurves = cachedArrayByFipsGeneration(
() => filterDuplicateStrings(_getCurves()));
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',
};
// Algorithm definitions organized by algorithm name
const kAlgorithmDefinitions = {
'AES-CBC': {
'generateKey': 'AesKeyGenParams',
'exportKey': null,
'importKey': null,
'encrypt': 'AesCbcParams',
'decrypt': 'AesCbcParams',
'get key length': 'AesDerivedKeyParams',
},
'AES-CTR': {
'generateKey': 'AesKeyGenParams',
'exportKey': null,
'importKey': null,
'encrypt': 'AesCtrParams',
'decrypt': 'AesCtrParams',
'get key length': 'AesDerivedKeyParams',
},
'AES-GCM': {
'generateKey': 'AesKeyGenParams',
'exportKey': null,
'importKey': null,
'encrypt': 'AeadParams',
'decrypt': 'AeadParams',
'get key length': 'AesDerivedKeyParams',
},
'AES-KW': {
'generateKey': 'AesKeyGenParams',
'exportKey': null,
'importKey': null,
'get key length': 'AesDerivedKeyParams',
'wrapKey': null,
'unwrapKey': null,
},
'AES-OCB': {
'generateKey': 'AesKeyGenParams',
'exportKey': null,
'importKey': null,
'encrypt': 'AeadParams',
'decrypt': 'AeadParams',
'get key length': 'AesDerivedKeyParams',
},
'Argon2d': {
'deriveBits': 'Argon2Params',
'get key length': null,
'importKey': null,
},
'Argon2i': {
'deriveBits': 'Argon2Params',
'get key length': null,
'importKey': null,
},
'Argon2id': {
'deriveBits': 'Argon2Params',
'get key length': null,
'importKey': null,
},
'ChaCha20-Poly1305': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encrypt': 'AeadParams',
'decrypt': 'AeadParams',
'get key length': null,
},
'cSHAKE128': { 'digest': 'CShakeParams' },
'cSHAKE256': { 'digest': 'CShakeParams' },
'KT128': { 'digest': 'KangarooTwelveParams' },
'KT256': { 'digest': 'KangarooTwelveParams' },
'TurboSHAKE128': { 'digest': 'TurboShakeParams' },
'TurboSHAKE256': { 'digest': 'TurboShakeParams' },
'ECDH': {
'generateKey': 'EcKeyGenParams',
'exportKey': null,
'importKey': 'EcKeyImportParams',
'deriveBits': 'EcdhKeyDeriveParams',
},
'ECDSA': {
'generateKey': 'EcKeyGenParams',
'exportKey': null,
'importKey': 'EcKeyImportParams',
'sign': 'EcdsaParams',
'verify': 'EcdsaParams',
},
'Ed25519': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'sign': null,
'verify': null,
},
'Ed448': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'sign': 'ContextParams',
'verify': 'ContextParams',
},
'HKDF': {
'importKey': null,
'deriveBits': 'HkdfParams',
'get key length': null,
},
'HMAC': {
'generateKey': 'HmacKeyGenParams',
'exportKey': null,
'importKey': 'HmacImportParams',
'sign': null,
'verify': null,
'get key length': 'HmacImportParams',
},
'KMAC128': {
'generateKey': 'KmacKeyGenParams',
'exportKey': null,
'importKey': 'KmacImportParams',
'sign': 'KmacParams',
'verify': 'KmacParams',
'get key length': 'KmacImportParams',
},
'KMAC256': {
'generateKey': 'KmacKeyGenParams',
'exportKey': null,
'importKey': 'KmacImportParams',
'sign': 'KmacParams',
'verify': 'KmacParams',
'get key length': 'KmacImportParams',
},
'ML-DSA-44': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'sign': 'ContextParams',
'verify': 'ContextParams',
},
'ML-DSA-65': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'sign': 'ContextParams',
'verify': 'ContextParams',
},
'ML-DSA-87': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'sign': 'ContextParams',
'verify': 'ContextParams',
},
'ML-KEM-512': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'ML-KEM-768': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'ML-KEM-1024': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'MLKEM768-P256': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'MLKEM768-X25519': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'MLKEM1024-P384': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'encapsulate': null,
'decapsulate': null,
'get shared key length': null,
},
'PBKDF2': {
'importKey': null,
'deriveBits': 'Pbkdf2Params',
'get key length': null,
},
'RSA-OAEP': {
'generateKey': 'RsaHashedKeyGenParams',
'exportKey': null,
'importKey': 'RsaHashedImportParams',
'encrypt': 'RsaOaepParams',
'decrypt': 'RsaOaepParams',
},
'RSA-PSS': {
'generateKey': 'RsaHashedKeyGenParams',
'exportKey': null,
'importKey': 'RsaHashedImportParams',
'sign': 'RsaPssParams',
'verify': 'RsaPssParams',
},
'RSASSA-PKCS1-v1_5': {
'generateKey': 'RsaHashedKeyGenParams',
'exportKey': null,
'importKey': 'RsaHashedImportParams',
'sign': null,
'verify': null,
},
'SHA-1': { 'digest': null },
'SHA-256': { 'digest': null },
'SHA-384': { 'digest': null },
'SHA-512': { 'digest': null },
'SHA3-256': { 'digest': null },
'SHA3-384': { 'digest': null },
'SHA3-512': { 'digest': null },
'X25519': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'deriveBits': 'EcdhKeyDeriveParams',
},
'X448': {
'generateKey': null,
'exportKey': null,
'importKey': null,
'deriveBits': 'EcdhKeyDeriveParams',
},
};
function has(algorithms, name) {
return ArrayPrototypeIncludes(algorithms, name);
}
// Re-evaluated when the active FIPS state changes.
function getConditionalAlgorithms() {
const mlKem768 = isKeyAlgorithmAvailable('ML-KEM-768');
const mlKem1024 = isKeyAlgorithmAvailable('ML-KEM-1024');
const ciphers = getCiphers();
const hashes = getHashes();
const macs = getMacs();
const fips = isFips();
return {
'AES-OCB': !!hasAesOcbMode &&
(has(ciphers, 'aes-128-ocb') ||
has(ciphers, 'aes-192-ocb') ||
has(ciphers, 'aes-256-ocb')),
'Argon2d': !!Argon2Job && !fips,
'Argon2i': !!Argon2Job && !fips,
'Argon2id': !!Argon2Job && !fips,
'ChaCha20-Poly1305': isBoringSSL ||
has(ciphers, 'chacha20-poly1305'),
'cSHAKE128': has(hashes, 'shake128'),
'cSHAKE256': has(hashes, 'shake256'),
'Ed25519': isKeyAlgorithmAvailable('Ed25519'),
'Ed448': isKeyAlgorithmAvailable('Ed448'),
'KMAC128': !!KmacJob && has(macs, 'kmac128'),
'KMAC256': !!KmacJob && has(macs, 'kmac256'),
'KT128': !fips,
'KT256': !fips,
'ML-DSA-44': isKeyAlgorithmAvailable('ML-DSA-44'),
'ML-DSA-65': isKeyAlgorithmAvailable('ML-DSA-65'),
'ML-DSA-87': isKeyAlgorithmAvailable('ML-DSA-87'),
'ML-KEM-512': isKeyAlgorithmAvailable('ML-KEM-512'),
'ML-KEM-768': mlKem768,
'ML-KEM-1024': mlKem1024,
'MLKEM768-P256': !fips && mlKem768 &&
(!isBoringSSL ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'MLKEM768-X25519': !fips && mlKem768 &&
(!isBoringSSL ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'MLKEM1024-P384': !fips && mlKem1024 &&
(!isBoringSSL ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'SHA-1': has(hashes, 'sha1'),
'SHA-256': has(hashes, 'sha256'),
'SHA-384': has(hashes, 'sha384'),
'SHA-512': has(hashes, 'sha512'),
'SHA3-256': has(hashes, 'sha3-256'),
'SHA3-384': has(hashes, 'sha3-384'),
'SHA3-512': has(hashes, 'sha3-512'),
'TurboSHAKE128': !fips,
'TurboSHAKE256': !fips,
'X25519': isKeyAlgorithmAvailable('X25519'),
'X448': isKeyAlgorithmAvailable('X448'),
};
}
// Experimental algorithms that emit warnings.
const experimentalAlgorithmsWithWarnings = [
'AES-OCB',
'Argon2d',
'Argon2i',
'Argon2id',
'cSHAKE128',
'cSHAKE256',
'Ed448',
'KMAC128',
'KMAC256',
'ML-KEM-512',
'MLKEM768-P256',
'MLKEM1024-P384',
'SHA3-256',
'SHA3-384',
'SHA3-512',
'TurboSHAKE128',
'TurboSHAKE256',
'KT128',
'KT256',
'X448',
// Ships unflagged in Chromium >= 155
// 'ChaCha20-Poly1305'
// 'ML-DSA-44'
// 'ML-DSA-65'
// 'ML-DSA-87'
// 'ML-KEM-768'
// 'ML-KEM-1024'
// 'MLKEM768-X25519'
];
// Transform the algorithm definitions into the operation-keyed structure
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
function createSupportedAlgorithms(algorithmDefs) {
const conditionalAlgorithms = getConditionalAlgorithms();
// Detached below rather than declared `__proto__: null`: V8 puts that
// literal form in dictionary mode, slowing every registry lookup.
const result = {};
const nameMap = {};
const algorithmEntries = ObjectEntries(algorithmDefs);
for (let i = 0; i < algorithmEntries.length; i++) {
const { 0: algorithmName, 1: operations } = algorithmEntries[i];
// Skip algorithms that are conditionally not supported
if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) &&
!conditionalAlgorithms[algorithmName]) {
continue;
}
const operationEntries = ObjectEntries(operations);
for (let j = 0; j < operationEntries.length; j++) {
const { 0: operation, 1: dict } = operationEntries[j];
result[operation] ||= {};
nameMap[operation] ||= new SafeMap();
nameMap[operation].set(StringPrototypeToUpperCase(algorithmName), algorithmName);
// Add warnings for select experimental algorithms
if (ArrayPrototypeIncludes(experimentalAlgorithmsWithWarnings, algorithmName)) {
ObjectDefineProperty(result[operation], algorithmName, {
get() {
emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`);
return dict;
},
__proto__: null,
enumerable: true,
});
} else {
result[operation][algorithmName] = dict;
}
}
}
const operations = ObjectKeys(result);
for (let i = 0; i < operations.length; i++) {
ObjectSetPrototypeOf(result[operations[i]], null);
}
ObjectSetPrototypeOf(result, null);
ObjectSetPrototypeOf(nameMap, null);
return { algorithms: result, nameMap };
}
let supportedAlgorithms;
let supportedAlgorithmsGeneration;
function getSupportedAlgorithms() {
const generation = getFipsCryptoGeneration();
if (supportedAlgorithmsGeneration !== generation) {
supportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
supportedAlgorithmsGeneration = generation;
}
return supportedAlgorithms;
}
const simpleAlgorithmDictionaries = {
AesCbcParams: { iv: 'BufferSource' },
AesCtrParams: { counter: 'BufferSource' },
AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
// publicExponent is not strictly a BufferSource but it is a Uint8Array that we normalize
// this way
RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier', publicExponent: 'BufferSource' },
EcKeyGenParams: {},
HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
RsaPssParams: {},
EcdsaParams: { hash: 'HashAlgorithmIdentifier' },
HmacImportParams: { hash: 'HashAlgorithmIdentifier' },
HkdfParams: {
hash: 'HashAlgorithmIdentifier',
salt: 'BufferSource',
info: 'BufferSource',
},
ContextParams: { context: 'BufferSource' },
Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
RsaOaepParams: { label: 'BufferSource' },
RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyImportParams: {},
CShakeParams: {
functionName: 'BufferSource',
customization: 'BufferSource',
},
Argon2Params: {
associatedData: 'BufferSource',
nonce: 'BufferSource',
secretValue: 'BufferSource',
},
KmacParams: {
customization: 'BufferSource',
},
KangarooTwelveParams: {
customization: 'BufferSource',
},
TurboShakeParams: {},
};
// Pre-compute ObjectKeys() for each dictionary entry at module init
// to avoid allocating a new keys array on every normalizeAlgorithm call.
const simpleAlgorithmDictionaryEntries =
ObjectEntries(simpleAlgorithmDictionaries);
for (let i = 0; i < simpleAlgorithmDictionaryEntries.length; i++) {
const { 0: name, 1: types } = simpleAlgorithmDictionaryEntries[i];
simpleAlgorithmDictionaries[name] = { keys: ObjectKeys(types), types };
}
// See createSupportedAlgorithms() for why this is detached here.
ObjectSetPrototypeOf(simpleAlgorithmDictionaries, null);
function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
if (getBufferSourceByteLength(data) > max) {
throw lazyDOMException(
`${name} must be at most ${max} bytes`,
'OperationError');
}
}
function validateKmacKeyLength(length) {
if (length < 32 || length % 8 !== 0)
throw lazyDOMException('Invalid key length', 'NotSupportedError');
}
/**
* Converts a bit length to the number of bytes needed to contain it.
* Non-byte lengths are rounded up to the next byte.
* @param {number} length
* @returns {number}
*/
function numBitsToBytes(length) {
return MathFloor(length / 8) + MathFloor((7 + (length % 8)) / 8);
}
/**
* Copies `bytes` up to the byte length needed for `length` bits, then clears
* unused least-significant bits in the final byte.
* @param {number} length
* @param {ArrayBuffer|ArrayBufferView} bytes
* @returns {Uint8Array}
*/
function truncateToBitLength(length, bytes) {
const lengthBytes = numBitsToBytes(length);
const isView = ArrayBufferIsView(bytes);
const byteView = isView ?
new Uint8Array(
getDataViewOrTypedArrayBuffer(bytes),
getDataViewOrTypedArrayByteOffset(bytes),
MathMin(lengthBytes, getDataViewOrTypedArrayByteLength(bytes)),
) :
new Uint8Array(bytes, 0,
MathMin(lengthBytes, ArrayBufferPrototypeGetByteLength(bytes)));
const result = new Uint8Array(byteView);
const remainder = length % 8;
if (remainder !== 0)
result[lengthBytes - 1] &= (0xff << (8 - remainder)) & 0xff;
return result;
}
let webidl;
// Keep this as a regular object. The WebIDL converters read and spread these
// options on the normalizeAlgorithm hot path, and a null-prototype object
// measurably regresses benchmark/misc/webcrypto-webidl normalizeAlgorithm-*.
const kNormalizeAlgorithmOpts = {
prefix: 'Failed to normalize algorithm',
context: 'passed algorithm',
};
// 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 ??= require('internal/crypto/webidl');
// 2. 3.
const initialAlg = webidl.converters.Algorithm(algorithm,
kNormalizeAlgorithmOpts);
const { algorithms, nameMap } = getSupportedAlgorithms();
const registeredAlgorithms = algorithms[op];
// 4.
let algName = initialAlg.name;
// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
const canonicalName = nameMap[op]?.get(
StringPrototypeToUpperCase(algName));
if (canonicalName === undefined)
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
// Registered names are ASCII. Only check characters when case folding was
// needed, so Unicode characters such as U+017F cannot match ASCII names.
if (algName !== canonicalName) {
for (let i = 0; i < algName.length; i++) {
if (StringPrototypeCharCodeAt(algName, i) > 0x7f)
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
}
algName = canonicalName;
const desiredType = registeredAlgorithms[algName];
// Fast path everything below if the registered dictionary is null
if (desiredType === null)
return { name: algName };
// 6.
const normalizedAlgorithm = webidl.algorithmConverters[desiredType](
algorithm,
kNormalizeAlgorithmOpts,
);
// 7.
normalizedAlgorithm.name = algName;
// 9. 10. Pre-computed keys and types from simpleAlgorithmDictionaries.
const dictMeta = simpleAlgorithmDictionaries[desiredType];
if (dictMeta) {
const { keys: dictKeys, types: dictTypes } = dictMeta;
for (let i = 0; i < dictKeys.length; i++) {
const member = dictKeys[i];
const idlType = dictTypes[member];
const idlValue = normalizedAlgorithm[member];
// 3.
if (idlType === 'BufferSource' && idlValue) {
normalizedAlgorithm[member] = new Uint8Array(
getBufferSourceBytes(idlValue),
);
} else if (idlType === 'HashAlgorithmIdentifier') {
const hash = normalizeAlgorithm(idlValue, 'digest');
if (!StringPrototypeStartsWith(hash.name, 'SHA')) {
throw lazyDOMException(
`Only SHA hashes are supported in ${desiredType}`, 'NotSupportedError');
}
if (isBoringSSL && StringPrototypeStartsWith(hash.name, 'SHA3-')) {
throw lazyDOMException(
'SHA-3 is only supported for digest operations',
'NotSupportedError');
}
normalizedAlgorithm[member] = hash;
} else if (idlType === 'AlgorithmIdentifier') {
// This extension point is not used by any supported algorithm (yet?)
throw lazyDOMException('Not implemented.', 'NotSupportedError');
}
}
}
return normalizedAlgorithm;
}
function validateAlgorithm(algorithm, op) {
// HMAC's get key length operation handles zero with a TypeError, while
// HmacImportParams otherwise uses the import operation's DataError.
if (op === 'get key length' && algorithm.name === 'HMAC')
return;
webidl ??= require('internal/crypto/webidl');
const desiredType = getSupportedAlgorithms().algorithms[op]?.[algorithm.name];
webidl.validators[desiredType]?.(algorithm);
}
function getDataViewOrTypedArrayBuffer(V) {
return isDataView(V) ?
DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
}
function getDataViewOrTypedArrayByteOffset(V) {
return isDataView(V) ?
DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V);
}
function getDataViewOrTypedArrayByteLength(V) {
return isDataView(V) ?
DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V);
}
function getBufferSourceByteLength(V) {
// ArrayBuffer and typed array lengths already become zero when detached.
if (!ArrayBufferIsView(V))
return ArrayBufferPrototypeGetByteLength(V);
if (!isDataView(V))
return TypedArrayPrototypeGetByteLength(V);
const buffer = DataViewPrototypeGetBuffer(V);
if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
return 0;
return DataViewPrototypeGetByteLength(V);
}
function getBufferSourceBytes(V) {
if (!ArrayBufferIsView(V)) {
const length = ArrayBufferPrototypeGetByteLength(V);
if (length === 0 && ArrayBufferPrototypeGetDetached(V))
return new Uint8Array(0);
return new Uint8Array(V, 0, length);
}
let buffer;
let offset;
let length;
if (isDataView(V)) {
buffer = DataViewPrototypeGetBuffer(V);
if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
return new Uint8Array(0);
offset = DataViewPrototypeGetByteOffset(V);
length = DataViewPrototypeGetByteLength(V);
} else {
buffer = TypedArrayPrototypeGetBuffer(V);
offset = TypedArrayPrototypeGetByteOffset(V);
length = TypedArrayPrototypeGetByteLength(V);
if (length === 0 && isArrayBuffer(buffer) &&
ArrayBufferPrototypeGetDetached(buffer))
return new Uint8Array(0);
}
return new Uint8Array(buffer, offset, length);
}
function getOptionalByteLength(V) {
return V === undefined ? 0 : TypedArrayPrototypeGetByteLength(V);
}
function hasAnyNotIn(set, checks) {
for (const s of set)
if (!ArrayPrototypeIncludes(checks, s))
return true;
return false;
}
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);
});
/**
* @template T
* @typedef {{ run(): Promise<T> }} WebCryptoJob
*/
/**
* CryptoJob constructors can synchronously throw while running their native
* AdditionalConfig hook. WebCrypto needs those operation-specific setup
* failures to reject with an OperationError.
* @template T
* @param {() => WebCryptoJob<T>} getJob
* @returns {Promise<T>}
*/
function jobPromise(getJob) {
try {
return getJob().run();
} catch (err) {
return PromiseReject(lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err }));
}
}
/**
* Temporarily shadow inherited then accessors on WebCrypto result objects.
* Promise resolution reads "then" synchronously for thenable assimilation.
* Returning an own undefined data property keeps that lookup from reaching
* user-mutated prototypes.
* @param {unknown} value
* @returns {boolean}
*/
function prepareWebCryptoResult(value) {
if ((value === null || typeof value !== 'object') &&
typeof value !== 'function') {
return false;
}
if (isPromise(value) || ObjectPrototypeHasOwnProperty(value, 'then'))
return false;
setOwnProperty(value, 'then', undefined);
return true;
}
/**
* Remove the temporary then property installed by prepareWebCryptoResult().
* @param {{ then?: unknown }} value
* @returns {void}
*/
function cleanupWebCryptoResult(value) {
delete value.then;
}
/**
* @template T
* @typedef {(value: T | PromiseLike<T>) => void} WebCryptoResolve
*/
/**
* Resolve a WebCrypto promise while inherited then accessors are shadowed.
* @template T
* @param {WebCryptoResolve<T>} resolve
* @param {T | PromiseLike<T>} value
* @returns {void}
*/
function resolveWebCryptoResult(resolve, value) {
const shouldCleanupResult = prepareWebCryptoResult(value);
try {
resolve(value);
} finally {
if (shouldCleanupResult)
cleanupWebCryptoResult(value);
}
}
/**
* Run a WebCrypto promise reaction and settle the outer promise.
* @template T
* @template TResult
* @param {((value: T) => TResult | PromiseLike<TResult>) | undefined} handler
* @param {WebCryptoResolve<T | TResult>} resolve
* @param {(reason?: unknown) => void} reject
* @param {T} value
* @param {boolean} isRejected
* @returns {void}
*/
function settleJobPromise(handler, resolve, reject, value, isRejected) {
try {
if (typeof handler === 'function') {
resolveWebCryptoResult(resolve, handler(value));
} else if (isRejected) {
reject(value);
} else {
resolveWebCryptoResult(resolve, value);
}
} catch (err) {
reject(err);
}
}
/**
* Promise.prototype.then gets promise.constructor to determine the result
* promise's species. These promises are internal WebCrypto intermediates, so
* make that lookup stay on the promise itself instead of user-mutated state.
* @template T
* @template [TResult1=T]
* @template [TResult2=never]
* @param {Promise<T>} promise
* @param {((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined} [onFulfilled]
* @param {((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined} [onRejected]
* @returns {Promise<TResult1 | TResult2>}
*/
function jobPromiseThen(promise, onFulfilled, onRejected) {
const {
promise: resultPromise,
resolve,
reject,
} = PromiseWithResolvers();
setOwnProperty(promise, 'constructor', undefined);
PromisePrototypeThen(
promise,
(value) => settleJobPromise(onFulfilled, resolve, reject, value, false),
(value) => settleJobPromise(onRejected, resolve, reject, value, true));
return resultPromise;
}
// 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
// Throws an OperationError if the value does not fit in an unsigned 32-bit integer.
function bigIntArrayToUnsignedInt(input) {
let result = 0;
const length = TypedArrayPrototypeGetLength(input);
for (let n = 0; n < length; ++n) {
const n_reversed = length - n - 1;
if (n_reversed >= 4 && input[n]) {
throw lazyDOMException(
'algorithm.publicExponent must fit in an unsigned 32-bit integer',
'OperationError');
}
result |= input[n] << 8 * n_reversed;
}
return result >>> 0;
}
function bigIntArrayToUnsignedBigInt(input) {
let result = 0n;
const length = TypedArrayPrototypeGetLength(input);
for (let n = 0; n < length; ++n) {
const n_reversed = 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;
}
// Must be at most 31 entries.
const kCanonicalUsageOrder = [
'encrypt', 'decrypt',
'sign', 'verify',
'deriveKey', 'deriveBits',
'wrapKey', 'unwrapKey',
'encapsulateKey', 'encapsulateBits',
'decapsulateKey', 'decapsulateBits',
];
const kUsageMasks = {
__proto__: null,
};
const kUsageByMask = {
__proto__: null,
};
// Derive both lookup tables from kCanonicalUsageOrder so adding a new
// usage only requires updating the canonical list above. The numeric
// mask uses the usage's canonical index as its bit position.
for (let n = 0; n < kCanonicalUsageOrder.length; n++) {
const usage = kCanonicalUsageOrder[n];
const mask = 1 << n;
kUsageMasks[usage] = mask;
kUsageByMask[mask] = usage;
}
/**
* Collects a key usage list into a set.
* @param {string[]} usages
* @returns {SafeSet<string>}
*/
function toUsagesSet(usages) {
const usagesSet = new SafeSet();
for (let n = 0; n < usages.length; n++) {
usagesSet.add(usages[n]);
}
return usagesSet;
}
/**
* Returns a bit mask representing the usages from `usageSet`.
* @param {SafeSet<string>} usageSet
* @returns {number}
*/
function getUsagesMask(usageSet) {
// No usages is a valid state for some public keys, represented by a
// zero mask.
if (usageSet.size === 0) return 0;
let mask = 0;
for (const usage of usageSet) {
mask |= kUsageMasks[usage];
}
return mask;
}
/**
* Returns whether `mask` contains `usage`.
* @param {number} mask
* @param {string} usage
* @returns {boolean}
*/
function hasUsage(mask, usage) {
return (mask & kUsageMasks[usage]) !== 0;
}
/**
* Returns the usages represented by `mask` in canonical order.
* @param {number} mask
* @returns {string[]}
*/
function getUsagesFromMask(mask) {
// Short circuit most common cases, empty and single usage
// No usages is a valid state for some public keys
if (mask === 0) return [];
// A mask with exactly one bit set maps directly to one usage
if ((mask & (mask - 1)) === 0) {
return [kUsageByMask[mask]];
}
// Multiple usages need to be expanded in canonical order.
const result = [];
for (let n = 0; n < kCanonicalUsageOrder.length; n++) {
if (mask & (1 << n))
ArrayPrototypePush(result, kCanonicalUsageOrder[n]);
}
return result;
}
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;
case 'SHA3-256':
// Fall through
case 'SHA3-384':
// Fall through
case 'SHA3-512':
// This interaction is not defined for now.
// https://github.com/WICG/webcrypto-modern-algos/issues/23
throw lazyDOMException('Explicit algorithm length member is required', 'NotSupportedError');
}
}
function validateKeyOps(keyOps, usagesSet) {
if (keyOps === undefined) return;
validateArray(keyOps, 'keyData.key_ops');
let keyOpsMask = 0;
const seen = new SafeSet();
for (let n = 0; n < keyOps.length; n++) {
const op = keyOps[n];
if (seen.has(op))
throw lazyDOMException('Duplicate key operation', 'DataError');
seen.add(op);
const opMask = kUsageMasks[op];
// Skipping unknown key ops
if (opMask === undefined)
continue;
keyOpsMask |= opMask;
// 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) {
const usagesMask = getUsagesMask(usagesSet);
if ((keyOpsMask & usagesMask) !== usagesMask) {
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 };
}
module.exports = {
getArrayBufferOrView,
getBufferSourceByteLength,
getBufferSourceBytes,
getCiphers,
getCurves,
getDataViewOrTypedArrayBuffer,
getHashes,
getMacs,
getOptionalByteLength,
kHandle,
toBuf,
kNamedCurveAliases,
get kSupportedAlgorithms() {
return getSupportedAlgorithms().algorithms;
},
isFips,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,
validateByteSource,
validateKeyOps,
validateAlgorithm,
jobPromise,
jobPromiseThen,
cleanupWebCryptoResult,
prepareWebCryptoResult,
validateKmacKeyLength,
resolveWebCryptoResult,
validateMaxBufferLength,
numBitsToBytes,
truncateToBitLength,
bigIntArrayToUnsignedBigInt,
bigIntArrayToUnsignedInt,
getBlockSize,
getStringOption,
getUsagesMask,
getUsagesFromMask,
toUsagesSet,
hasUsage,
secureHeapUsed,
getCachedHashId,
getHashCache,
getCachedMacId,
getMacCache,
getOpenSSLSecLevel,
};