Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/64122 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1077 lines
29 KiB
JavaScript
1077 lines
29 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayBufferIsView,
|
|
ArrayBufferPrototypeGetByteLength,
|
|
ArrayPrototypeIncludes,
|
|
ArrayPrototypePush,
|
|
BigInt,
|
|
DataViewPrototypeGetBuffer,
|
|
DataViewPrototypeGetByteLength,
|
|
DataViewPrototypeGetByteOffset,
|
|
MathFloor,
|
|
Number,
|
|
ObjectDefineProperty,
|
|
ObjectEntries,
|
|
ObjectKeys,
|
|
ObjectPrototypeHasOwnProperty,
|
|
PromisePrototypeThen,
|
|
PromiseReject,
|
|
PromiseWithResolvers,
|
|
SafeMap,
|
|
StringPrototypeToUpperCase,
|
|
Symbol,
|
|
TypedArrayPrototypeGetBuffer,
|
|
TypedArrayPrototypeGetByteLength,
|
|
TypedArrayPrototypeGetByteOffset,
|
|
TypedArrayPrototypeSlice,
|
|
Uint8Array,
|
|
} = primordials;
|
|
|
|
const {
|
|
getCiphers: _getCiphers,
|
|
getCurves: _getCurves,
|
|
getHashes: _getHashes,
|
|
setEngine: _setEngine,
|
|
secureHeapUsed: _secureHeapUsed,
|
|
getCachedAliases,
|
|
getOpenSSLSecLevelCrypto: getOpenSSLSecLevel,
|
|
EVP_PKEY_ML_DSA_44,
|
|
EVP_PKEY_ML_DSA_65,
|
|
EVP_PKEY_ML_DSA_87,
|
|
EVP_PKEY_ML_KEM_512,
|
|
EVP_PKEY_ML_KEM_768,
|
|
EVP_PKEY_ML_KEM_1024,
|
|
kKeyVariantAES_OCB_128: hasAesOcbMode,
|
|
Argon2Job,
|
|
KmacJob,
|
|
} = internalBinding('crypto');
|
|
|
|
const { getOptionValue } = require('internal/options');
|
|
|
|
const {
|
|
crypto: {
|
|
ENGINE_METHOD_ALL,
|
|
},
|
|
} = internalBinding('constants');
|
|
|
|
const normalizeHashName = require('internal/crypto/hashnames');
|
|
|
|
const {
|
|
codes: {
|
|
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
|
|
ERR_CRYPTO_ENGINE_UNKNOWN,
|
|
ERR_INVALID_ARG_TYPE,
|
|
},
|
|
hideStackFrames,
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
validateArray,
|
|
validateNumber,
|
|
validateString,
|
|
} = require('internal/validators');
|
|
|
|
const { Buffer } = require('buffer');
|
|
|
|
const {
|
|
cachedResult,
|
|
emitExperimentalWarning,
|
|
filterDuplicateStrings,
|
|
getDeprecationWarningEmitter,
|
|
lazyDOMException,
|
|
setOwnProperty,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
namespace: {
|
|
isBuildingSnapshot,
|
|
addSerializeCallback,
|
|
},
|
|
} = require('internal/v8/startup_snapshot');
|
|
|
|
const {
|
|
isDataView,
|
|
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;
|
|
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()));
|
|
|
|
const emitOpenSSLEngineDeprecation = getDeprecationWarningEmitter(
|
|
'DEP0183',
|
|
'OpenSSL engine-based APIs are deprecated.',
|
|
);
|
|
|
|
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;
|
|
|
|
emitOpenSSLEngineDeprecation();
|
|
|
|
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',
|
|
};
|
|
|
|
// 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,
|
|
},
|
|
'ML-KEM-768': {
|
|
'generateKey': null,
|
|
'exportKey': null,
|
|
'importKey': null,
|
|
'encapsulate': null,
|
|
'decapsulate': null,
|
|
},
|
|
'ML-KEM-1024': {
|
|
'generateKey': null,
|
|
'exportKey': null,
|
|
'importKey': null,
|
|
'encapsulate': null,
|
|
'decapsulate': 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',
|
|
},
|
|
};
|
|
|
|
// Conditionally supported algorithms
|
|
const conditionalAlgorithms = {
|
|
'AES-OCB': !!hasAesOcbMode,
|
|
'Argon2d': !!Argon2Job,
|
|
'Argon2i': !!Argon2Job,
|
|
'Argon2id': !!Argon2Job,
|
|
'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
|
|
'cSHAKE128': !process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getHashes(), 'shake128'),
|
|
'cSHAKE256': !process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getHashes(), 'shake256'),
|
|
'Ed448': !process.features.openssl_is_boringssl,
|
|
'KMAC128': !!KmacJob,
|
|
'KMAC256': !!KmacJob,
|
|
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
|
|
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
|
|
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
|
|
'ML-KEM-512': !!EVP_PKEY_ML_KEM_512,
|
|
'ML-KEM-768': !!EVP_PKEY_ML_KEM_768,
|
|
'ML-KEM-1024': !!EVP_PKEY_ML_KEM_1024,
|
|
'SHA3-256': !process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
|
|
'SHA3-384': !process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
|
|
'SHA3-512': !process.features.openssl_is_boringssl ||
|
|
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
|
|
'X448': !process.features.openssl_is_boringssl,
|
|
};
|
|
|
|
// Experimental algorithms
|
|
const experimentalAlgorithms = [
|
|
'AES-OCB',
|
|
'Argon2d',
|
|
'Argon2i',
|
|
'Argon2id',
|
|
'ChaCha20-Poly1305',
|
|
'cSHAKE128',
|
|
'cSHAKE256',
|
|
'Ed448',
|
|
'KMAC128',
|
|
'KMAC256',
|
|
'ML-DSA-44',
|
|
'ML-DSA-65',
|
|
'ML-DSA-87',
|
|
'ML-KEM-512',
|
|
'ML-KEM-768',
|
|
'ML-KEM-1024',
|
|
'SHA3-256',
|
|
'SHA3-384',
|
|
'SHA3-512',
|
|
'TurboSHAKE128',
|
|
'TurboSHAKE256',
|
|
'KT128',
|
|
'KT256',
|
|
'X448',
|
|
];
|
|
|
|
// 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 result = {};
|
|
const nameMap = {};
|
|
|
|
for (const { 0: algorithmName, 1: operations } of ObjectEntries(algorithmDefs)) {
|
|
// Skip algorithms that are conditionally not supported
|
|
if (ObjectPrototypeHasOwnProperty(conditionalAlgorithms, algorithmName) &&
|
|
!conditionalAlgorithms[algorithmName]) {
|
|
continue;
|
|
}
|
|
|
|
for (const { 0: operation, 1: dict } of ObjectEntries(operations)) {
|
|
result[operation] ||= {};
|
|
nameMap[operation] ||= new SafeMap();
|
|
nameMap[operation].set(StringPrototypeToUpperCase(algorithmName), algorithmName);
|
|
|
|
// Add experimental warnings for experimental algorithms
|
|
if (ArrayPrototypeIncludes(experimentalAlgorithms, algorithmName)) {
|
|
ObjectDefineProperty(result[operation], algorithmName, {
|
|
get() {
|
|
emitExperimentalWarning(`The ${algorithmName} Web Crypto API algorithm`);
|
|
return dict;
|
|
},
|
|
__proto__: null,
|
|
enumerable: true,
|
|
});
|
|
} else {
|
|
result[operation][algorithmName] = dict;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { algorithms: result, nameMap };
|
|
}
|
|
|
|
const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
|
|
createSupportedAlgorithms(kAlgorithmDefinitions);
|
|
|
|
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.
|
|
for (const { 0: name, 1: types } of ObjectEntries(simpleAlgorithmDictionaries)) {
|
|
simpleAlgorithmDictionaries[name] = { keys: ObjectKeys(types), types };
|
|
}
|
|
|
|
function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
|
|
if (data.byteLength > max) {
|
|
throw lazyDOMException(
|
|
`${name} must be at most ${max} bytes`,
|
|
'OperationError');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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),
|
|
getDataViewOrTypedArrayByteLength(bytes),
|
|
) :
|
|
new Uint8Array(bytes, 0, ArrayBufferPrototypeGetByteLength(bytes));
|
|
const result = TypedArrayPrototypeSlice(
|
|
byteView,
|
|
0,
|
|
lengthBytes,
|
|
);
|
|
|
|
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');
|
|
|
|
// 1.
|
|
const registeredAlgorithms = kSupportedAlgorithms[op];
|
|
// 2. 3.
|
|
const initialAlg = webidl.converters.Algorithm(algorithm,
|
|
kNormalizeAlgorithmOpts);
|
|
// 4.
|
|
let algName = initialAlg.name;
|
|
|
|
// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
|
|
const canonicalName = kAlgorithmNameMap[op]?.get(
|
|
StringPrototypeToUpperCase(algName));
|
|
if (canonicalName === undefined)
|
|
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.converters[desiredType](
|
|
{ __proto__: algorithm, name: algName },
|
|
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) {
|
|
const isView = ArrayBufferIsView(idlValue);
|
|
const idlValueBytes = isView ?
|
|
new Uint8Array(
|
|
getDataViewOrTypedArrayBuffer(idlValue),
|
|
getDataViewOrTypedArrayByteOffset(idlValue),
|
|
getDataViewOrTypedArrayByteLength(idlValue),
|
|
) :
|
|
new Uint8Array(
|
|
idlValue,
|
|
0,
|
|
ArrayBufferPrototypeGetByteLength(idlValue),
|
|
);
|
|
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
|
|
idlValueBytes,
|
|
);
|
|
} 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) ?
|
|
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 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
|
|
// 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 >>> 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* 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 getDigestSizeInBytes(name) {
|
|
switch (name) {
|
|
case 'SHA-1':
|
|
return 20;
|
|
case 'SHA-256': // Fall through
|
|
case 'SHA3-256':
|
|
return 32;
|
|
case 'SHA-384': // Fall through
|
|
case 'SHA3-384':
|
|
return 48;
|
|
case 'SHA-512': // Fall through
|
|
case 'SHA3-512':
|
|
return 64;
|
|
}
|
|
}
|
|
|
|
function validateKeyOps(keyOps, usagesSet) {
|
|
if (keyOps === undefined) return;
|
|
validateArray(keyOps, 'keyData.key_ops');
|
|
let keyOpsMask = 0;
|
|
for (let n = 0; n < keyOps.length; n++) {
|
|
const op = keyOps[n];
|
|
const opMask = kUsageMasks[op];
|
|
// Skipping unknown key ops
|
|
if (opMask === undefined)
|
|
continue;
|
|
// Have we seen it already? if so, error
|
|
if (keyOpsMask & opMask)
|
|
throw lazyDOMException('Duplicate key operation', 'DataError');
|
|
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,
|
|
getCiphers,
|
|
getCurves,
|
|
getDataViewOrTypedArrayBuffer,
|
|
getHashes,
|
|
emitOpenSSLEngineDeprecation,
|
|
kHandle,
|
|
setEngine,
|
|
toBuf,
|
|
|
|
kNamedCurveAliases,
|
|
kSupportedAlgorithms,
|
|
normalizeAlgorithm,
|
|
normalizeHashName,
|
|
hasAnyNotIn,
|
|
validateByteSource,
|
|
validateKeyOps,
|
|
jobPromise,
|
|
jobPromiseThen,
|
|
cleanupWebCryptoResult,
|
|
prepareWebCryptoResult,
|
|
validateMaxBufferLength,
|
|
numBitsToBytes,
|
|
truncateToBitLength,
|
|
bigIntArrayToUnsignedBigInt,
|
|
bigIntArrayToUnsignedInt,
|
|
getBlockSize,
|
|
getDigestSizeInBytes,
|
|
getStringOption,
|
|
getUsagesMask,
|
|
getUsagesFromMask,
|
|
hasUsage,
|
|
secureHeapUsed,
|
|
getCachedHashId,
|
|
getHashCache,
|
|
getOpenSSLSecLevel,
|
|
};
|