Commit fb602d182d5 for nodejs
commit fb602d182d560841f30b9ec9f4b1ec124abe2106
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 22 22:23:31 2026 +0200
crypto: separate conversion from validation
Convert algorithm dictionaries on the original receiver, with name
read once. Validate normalized parameters in their operation steps
after the method-level key checks and generation usage checks.
This also makes Argon2 validation use converted parallelism and keeps
later dictionary conversion errors ahead of semantic parameter errors.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js
index 3280ddff845..dd95748dcab 100644
--- a/lib/internal/crypto/aes.js
+++ b/lib/internal/crypto/aes.js
@@ -25,6 +25,7 @@ const {
getUsagesMask,
jobPromise,
getBufferSourceByteLength,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -173,6 +174,7 @@ function asyncAesOcbCipher(mode, key, data, algorithm) {
}
function aesCipher(mode, key, data, algorithm) {
+ validateAlgorithm(algorithm, 'encrypt');
switch (algorithm.name) {
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
@@ -185,8 +187,9 @@ function aesCipher(mode, key, data, algorithm) {
function aesGenerateKey(algorithm, extractable, usages) {
const { name, length } = algorithm;
- const usagesSet = validateUsagesNotEmpty(
- validateKeyUsages(usages, kUsages[name], name));
+ const usagesSet = validateKeyUsages(usages, kUsages[name], name);
+ validateAlgorithm(algorithm, 'generateKey');
+ validateUsagesNotEmpty(usagesSet);
return jobPromise(() => new SecretKeyGenJob(
kCryptoJobWebCrypto,
diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js
index bf9eaedd85a..844f9dc94a3 100644
--- a/lib/internal/crypto/argon2.js
+++ b/lib/internal/crypto/argon2.js
@@ -29,6 +29,7 @@ const {
const {
getArrayBufferOrView,
jobPromise,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -203,6 +204,7 @@ function validateArgon2DeriveBitsLength(length) {
function argon2DeriveBits(algorithm, baseKey, length) {
validateArgon2DeriveBitsLength(length);
+ validateAlgorithm(algorithm, 'deriveBits');
const type = {
'__proto__': null,
diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js
index 3152362caa8..2e4bf9a8275 100644
--- a/lib/internal/crypto/cfrg.js
+++ b/lib/internal/crypto/cfrg.js
@@ -23,6 +23,7 @@ const {
getUsagesMask,
jobPromise,
toUsagesSet,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -184,6 +185,8 @@ function eddsaSignVerify(key, data, algorithm, signature) {
if (getCryptoKeyType(key) !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
+ validateAlgorithm(algorithm, mode === kSignJobModeSign ? 'sign' : 'verify');
+
return jobPromise(() => new SignJob(
kCryptoJobWebCrypto,
mode,
diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js
index 45071cded21..32bbcfbf0b7 100644
--- a/lib/internal/crypto/chacha20_poly1305.js
+++ b/lib/internal/crypto/chacha20_poly1305.js
@@ -9,6 +9,7 @@ const {
const {
getUsagesMask,
jobPromise,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -36,6 +37,7 @@ function validateKeyLength(length) {
}
function c20pCipher(mode, key, data, algorithm) {
+ validateAlgorithm(algorithm, 'encrypt');
return jobPromise(() => new ChaCha20Poly1305CipherJob(
kCryptoJobWebCrypto,
mode,
diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js
index 5bf9d511542..63a9451b3cc 100644
--- a/lib/internal/crypto/diffiehellman.js
+++ b/lib/internal/crypto/diffiehellman.js
@@ -61,6 +61,7 @@ const {
numBitsToBytes,
toBuf,
truncateToBitLength,
+ validateAlgorithm,
kHandle,
} = require('internal/crypto/util');
@@ -332,6 +333,7 @@ function diffieHellman(options, callback) {
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
function ecdhDeriveBits(algorithm, baseKey, length) {
+ validateAlgorithm(algorithm, 'deriveBits');
const { 'public': key } = algorithm;
if (getCryptoKeyType(baseKey) !== 'private') {
diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js
index 0f1e0555202..6f566569a25 100644
--- a/lib/internal/crypto/ec.js
+++ b/lib/internal/crypto/ec.js
@@ -31,6 +31,7 @@ const {
getUsagesMask,
jobPromise,
normalizeHashName,
+ validateAlgorithm,
kNamedCurveAliases,
toUsagesSet,
} = require('internal/crypto/util');
@@ -72,6 +73,7 @@ function ecGenerateKey(algorithm, extractable, usages) {
const { name, namedCurve } = algorithm;
const allowedUsages = kUsages[name];
const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name);
+ validateAlgorithm(algorithm, 'generateKey');
const keyAlgorithm = { name, namedCurve };
const keyUsages = getKeyPairUsages(usagesSet, allowedUsages);
@@ -139,6 +141,7 @@ function ecImportKey(
usages,
) {
const { name, namedCurve } = algorithm;
+ validateAlgorithm(algorithm, 'importKey');
let handle;
const allowedUsages = kUsages[name];
diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index 69bdcd38c17..12588b57263 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -27,6 +27,7 @@ const {
normalizeHashName,
numBitsToBytes,
truncateToBitLength,
+ validateAlgorithm,
validateMaxBufferLength,
kHandle,
getCachedHashId,
@@ -240,6 +241,7 @@ Hmac.prototype._transform = Hash.prototype._transform;
function asyncDigest(algorithm, data) {
validateMaxBufferLength(data, 'data');
+ validateAlgorithm(algorithm, 'digest');
switch (algorithm.name) {
case 'SHA-1':
diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js
index 8673f9b4681..bf8a296bc4b 100644
--- a/lib/internal/crypto/hkdf.js
+++ b/lib/internal/crypto/hkdf.js
@@ -29,6 +29,7 @@ const {
normalizeHashName,
toBuf,
validateByteSource,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -182,6 +183,7 @@ function validateHkdfDeriveBitsLength(length, hash) {
function hkdfDeriveBits(algorithm, baseKey, length) {
const { hash, salt, info } = algorithm;
validateHkdfDeriveBitsLength(length, hash);
+ validateAlgorithm(algorithm, 'deriveBits');
if (length === 0)
return PromiseResolve(new ArrayBuffer(0));
diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js
index 3297297abd5..742e1ebe590 100644
--- a/lib/internal/crypto/mac.js
+++ b/lib/internal/crypto/mac.js
@@ -21,6 +21,7 @@ const {
numBitsToBytes,
truncateToBitLength,
validateKmacKeyLength,
+ validateAlgorithm,
} = require('internal/crypto/util');
const {
@@ -71,11 +72,12 @@ function hmacGenerateKey(algorithm, extractable, usages) {
const {
hash,
name,
- length = getBlockSize(hash.name),
} = algorithm;
- const usageSet = validateUsagesNotEmpty(
- validateKeyUsages(usages, kUsages, name));
+ const usageSet = validateKeyUsages(usages, kUsages, name);
+ validateAlgorithm(algorithm, 'generateKey');
+ const { length = getBlockSize(hash.name) } = algorithm;
+ validateUsagesNotEmpty(usageSet);
return jobPromise(() => new SecretKeyGenJob(
kCryptoJobWebCrypto,
@@ -95,8 +97,9 @@ function kmacGenerateKey(algorithm, extractable, usages) {
}[name],
} = algorithm;
- const usageSet = validateUsagesNotEmpty(
- validateKeyUsages(usages, kUsages, name));
+ const usageSet = validateKeyUsages(usages, kUsages, name);
+ validateAlgorithm(algorithm, 'generateKey');
+ validateUsagesNotEmpty(usageSet);
return jobPromise(() => new SecretKeyGenJob(
kCryptoJobWebCrypto,
@@ -114,6 +117,7 @@ function macImportKey(
usages,
) {
const isHmac = algorithm.name === 'HMAC';
+ validateAlgorithm(algorithm, 'importKey');
const usagesSet = validateKeyUsages(
usages, kUsages, algorithm.name);
let handle;
@@ -181,6 +185,7 @@ function hmacSignVerify(key, data, algorithm, signature) {
}
function kmacSignVerify(key, data, algorithm, signature) {
+ validateAlgorithm(algorithm, 'sign');
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
return jobPromise(() => new KmacJob(
kCryptoJobWebCrypto,
diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js
index 71238c4726f..581e9beb5d0 100644
--- a/lib/internal/crypto/ml_dsa.js
+++ b/lib/internal/crypto/ml_dsa.js
@@ -25,6 +25,7 @@ const {
getUsagesMask,
jobPromise,
toUsagesSet,
+ validateAlgorithm,
getBufferSourceByteLength,
} = require('internal/crypto/util');
@@ -197,6 +198,8 @@ function mlDsaSignVerify(key, data, algorithm, signature) {
if (getCryptoKeyType(key) !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
+ validateAlgorithm(algorithm, mode === kSignJobModeSign ? 'sign' : 'verify');
+
return jobPromise(() => new SignJob(
kCryptoJobWebCrypto,
mode,
diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js
index 81ca6723429..56c58bf1262 100644
--- a/lib/internal/crypto/rsa.js
+++ b/lib/internal/crypto/rsa.js
@@ -31,6 +31,7 @@ const {
getUsagesMask,
jobPromise,
normalizeHashName,
+ validateAlgorithm,
validateMaxBufferLength,
toUsagesSet,
} = require('internal/crypto/util');
@@ -110,6 +111,7 @@ function rsaKeyGenerate(
const allowedUsages = kUsages[name];
const usagesSet = validateKeyUsages(usages, allowedUsages.keygen, name);
+ validateAlgorithm(algorithm, 'generateKey');
const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent);
const keyAlgorithm = {
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index 537870f25e8..a9ad6dfb62f 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -820,8 +820,8 @@ function normalizeAlgorithm(algorithm, op) {
return { name: algName };
// 6.
- const normalizedAlgorithm = webidl.converters[desiredType](
- { __proto__: algorithm, name: algName },
+ const normalizedAlgorithm = webidl.algorithmConverters[desiredType](
+ algorithm,
kNormalizeAlgorithmOpts,
);
// 7.
@@ -857,6 +857,16 @@ function normalizeAlgorithm(algorithm, op) {
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);
@@ -1283,6 +1293,7 @@ module.exports = {
hasAnyNotIn,
validateByteSource,
validateKeyOps,
+ validateAlgorithm,
jobPromise,
jobPromiseThen,
cleanupWebCryptoResult,
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index ae45d8a9ec2..7777774a217 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -41,6 +41,7 @@ const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
@@ -70,6 +71,7 @@ const {
normalizeHashName,
numBitsToBytes,
prepareWebCryptoResult,
+ validateAlgorithm,
validateMaxBufferLength,
getOptionalByteLength,
} = require('internal/crypto/util');
@@ -319,7 +321,9 @@ function deriveBitsImpl(algorithm, baseKey, length = null) {
}
}
-function getKeyLength({ name, length, hash }) {
+function getKeyLength(algorithm) {
+ validateAlgorithm(algorithm, 'get key length');
+ const { name, length, hash } = algorithm;
switch (name) {
case 'AES-CTR':
case 'AES-CBC':
@@ -331,6 +335,8 @@ function getKeyLength({ name, length, hash }) {
return length;
case 'HMAC':
+ if (length === 0)
+ throw new ERR_INVALID_ARG_VALUE('algorithm.length', length, 'must not be zero');
if (length === undefined) {
return getBlockSize(hash?.name);
}
@@ -1869,6 +1875,7 @@ class SubtleCrypto {
normalizeAlgorithm(algorithm, 'get shared key length');
sharedKeyLength = getSharedKeyLength(normalizedAlgorithm);
normalizedAdditionalAlgorithm = normalizeAlgorithm(additionalAlgorithm, 'importKey');
+ validateAlgorithm(normalizedAdditionalAlgorithm, 'importKey');
} catch {
return false;
}
@@ -1897,6 +1904,7 @@ function check(op, alg, length) {
let normalizedAlgorithm;
try {
normalizedAlgorithm = normalizeAlgorithm(alg, op);
+ validateAlgorithm(normalizedAlgorithm, op);
} catch {
if (op === 'wrapKey') {
return check('encrypt', alg);
diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js
index 1d35ce69f56..4addaf62bcc 100644
--- a/lib/internal/crypto/webidl.js
+++ b/lib/internal/crypto/webidl.js
@@ -2,9 +2,12 @@
const {
ArrayPrototypeIncludes,
+ ArrayPrototypePush,
+ ArrayPrototypeToSorted,
MathPow,
NumberParseInt,
ObjectPrototypeHasOwnProperty,
+ ObjectSetPrototypeOf,
StringPrototypeCharCodeAt,
StringPrototypeSplit,
StringPrototypeToLowerCase,
@@ -133,6 +136,41 @@ const dictAlgorithm = [
converters.Algorithm = createDictionaryConverter(
'Algorithm', dictAlgorithm);
+const validators = { __proto__: null };
+const algorithmConverters = { __proto__: null };
+
+// Algorithm.name was converted when selecting the registered dictionary.
+// Convert the remaining members on the original object, and keep operation
+// validation separate from Web IDL conversion.
+function createAlgorithmDictionaryConverter(name, dictionaries) {
+ const members = [];
+ const fullMembers = [dictAlgorithm];
+ const checks = [];
+ for (let i = 1; i < dictionaries.length; i++) {
+ const dictionary = [];
+ const sorted = ArrayPrototypeToSorted(dictionaries[i], (a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
+ for (let j = 0; j < sorted.length; j++) {
+ const member = sorted[j];
+ if (ObjectPrototypeHasOwnProperty(member, 'validator')) {
+ ArrayPrototypePush(checks, { key: member.key, validator: member.validator });
+ }
+ ArrayPrototypePush(dictionary, { ...member, validator: undefined });
+ }
+ ArrayPrototypePush(members, dictionary);
+ ArrayPrototypePush(fullMembers, dictionary);
+ }
+ validators[name] = (algorithm) => {
+ for (let i = 0; i < checks.length; i++) {
+ const check = checks[i];
+ const value = algorithm[check.key];
+ if (value !== undefined)
+ check.validator(value, algorithm);
+ }
+ };
+ algorithmConverters[name] = createDictionaryConverter(name, members);
+ return createDictionaryConverter(name, fullMembers);
+}
+
converters.BigInteger = webidl.Uint8Array;
const dictRsaKeyGenParams = [
@@ -172,13 +210,13 @@ const dictRsaKeyGenParams = [
},
];
-converters.RsaKeyGenParams = createDictionaryConverter(
+converters.RsaKeyGenParams = createAlgorithmDictionaryConverter(
'RsaKeyGenParams', [
dictAlgorithm,
dictRsaKeyGenParams,
]);
-converters.RsaHashedKeyGenParams = createDictionaryConverter(
+converters.RsaHashedKeyGenParams = createAlgorithmDictionaryConverter(
'RsaHashedKeyGenParams', [
dictAlgorithm,
dictRsaKeyGenParams,
@@ -191,7 +229,7 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter(
],
]);
-converters.RsaHashedImportParams = createDictionaryConverter(
+converters.RsaHashedImportParams = createAlgorithmDictionaryConverter(
'RsaHashedImportParams', [
dictAlgorithm,
[
@@ -205,7 +243,7 @@ converters.RsaHashedImportParams = createDictionaryConverter(
converters.NamedCurve = converters.DOMString;
-converters.EcKeyImportParams = createDictionaryConverter(
+converters.EcKeyImportParams = createAlgorithmDictionaryConverter(
'EcKeyImportParams', [
dictAlgorithm,
[
@@ -218,7 +256,7 @@ converters.EcKeyImportParams = createDictionaryConverter(
],
]);
-converters.EcKeyGenParams = createDictionaryConverter(
+converters.EcKeyGenParams = createAlgorithmDictionaryConverter(
'EcKeyGenParams', [
dictAlgorithm,
[
@@ -231,7 +269,7 @@ converters.EcKeyGenParams = createDictionaryConverter(
],
]);
-converters.AesKeyGenParams = createDictionaryConverter(
+converters.AesKeyGenParams = createAlgorithmDictionaryConverter(
'AesKeyGenParams', [
dictAlgorithm,
[
@@ -295,7 +333,7 @@ function validateCShakeCustomization(V) {
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
}
-converters.RsaPssParams = createDictionaryConverter(
+converters.RsaPssParams = createAlgorithmDictionaryConverter(
'RsaPssParams', [
dictAlgorithm,
[
@@ -308,7 +346,7 @@ converters.RsaPssParams = createDictionaryConverter(
],
]);
-converters.RsaOaepParams = createDictionaryConverter(
+converters.RsaOaepParams = createAlgorithmDictionaryConverter(
'RsaOaepParams', [
dictAlgorithm,
[
@@ -319,7 +357,7 @@ converters.RsaOaepParams = createDictionaryConverter(
],
]);
-converters.EcdsaParams = createDictionaryConverter(
+converters.EcdsaParams = createAlgorithmDictionaryConverter(
'EcdsaParams', [
dictAlgorithm,
[
@@ -344,7 +382,7 @@ const kHmacDictionaries = [
];
for (let i = 0; i < kHmacDictionaries.length; i++) {
const { 0: name, 1: zeroError } = kHmacDictionaries[i];
- converters[name] = createDictionaryConverter(
+ converters[name] = createAlgorithmDictionaryConverter(
name, [
dictAlgorithm,
[
@@ -407,7 +445,7 @@ converters.JsonWebKey = createDictionaryConverter(
simpleDomStringKey('priv'),
]);
-converters.HkdfParams = createDictionaryConverter(
+converters.HkdfParams = createAlgorithmDictionaryConverter(
'HkdfParams', [
dictAlgorithm,
[
@@ -430,7 +468,7 @@ converters.HkdfParams = createDictionaryConverter(
],
]);
-converters.CShakeParams = createDictionaryConverter(
+converters.CShakeParams = createAlgorithmDictionaryConverter(
'CShakeParams', [
dictAlgorithm,
[
@@ -454,7 +492,7 @@ converters.CShakeParams = createDictionaryConverter(
],
]);
-converters.Pbkdf2Params = createDictionaryConverter(
+converters.Pbkdf2Params = createAlgorithmDictionaryConverter(
'Pbkdf2Params', [
dictAlgorithm,
[
@@ -477,7 +515,7 @@ converters.Pbkdf2Params = createDictionaryConverter(
],
]);
-converters.AesDerivedKeyParams = createDictionaryConverter(
+converters.AesDerivedKeyParams = createAlgorithmDictionaryConverter(
'AesDerivedKeyParams', [
dictAlgorithm,
[
@@ -491,7 +529,7 @@ converters.AesDerivedKeyParams = createDictionaryConverter(
],
]);
-converters.AesCbcParams = createDictionaryConverter(
+converters.AesCbcParams = createAlgorithmDictionaryConverter(
'AesCbcParams', [
dictAlgorithm,
[
@@ -504,7 +542,7 @@ converters.AesCbcParams = createDictionaryConverter(
],
]);
-converters.AeadParams = createDictionaryConverter(
+converters.AeadParams = createAlgorithmDictionaryConverter(
'AeadParams', [
dictAlgorithm,
[
@@ -568,7 +606,7 @@ converters.AeadParams = createDictionaryConverter(
],
]);
-converters.AesCtrParams = createDictionaryConverter(
+converters.AesCtrParams = createAlgorithmDictionaryConverter(
'AesCtrParams', [
dictAlgorithm,
[
@@ -596,7 +634,7 @@ converters.AesCtrParams = createDictionaryConverter(
converters.CryptoKey = createInterfaceConverter(
'CryptoKey', isCryptoKey);
-converters.EcdhKeyDeriveParams = createDictionaryConverter(
+converters.EcdhKeyDeriveParams = createAlgorithmDictionaryConverter(
'EcdhKeyDeriveParams', [
dictAlgorithm,
[
@@ -618,7 +656,7 @@ converters.EcdhKeyDeriveParams = createDictionaryConverter(
],
]);
-converters.ContextParams = createDictionaryConverter(
+converters.ContextParams = createAlgorithmDictionaryConverter(
'ContextParams', [
dictAlgorithm,
[
@@ -652,7 +690,7 @@ converters.ContextParams = createDictionaryConverter(
],
]);
-converters.Argon2Params = createDictionaryConverter(
+converters.Argon2Params = createAlgorithmDictionaryConverter(
'Argon2Params', [
dictAlgorithm,
[
@@ -729,7 +767,7 @@ converters.Argon2Params = createDictionaryConverter(
const kKmacDictionaries = ['KmacKeyGenParams', 'KmacImportParams'];
for (let i = 0; i < kKmacDictionaries.length; i++) {
const name = kKmacDictionaries[i];
- converters[name] = createDictionaryConverter(
+ converters[name] = createAlgorithmDictionaryConverter(
name, [
dictAlgorithm,
[
@@ -743,7 +781,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) {
]);
}
-converters.KmacParams = createDictionaryConverter(
+converters.KmacParams = createAlgorithmDictionaryConverter(
'KmacParams', [
dictAlgorithm,
[
@@ -766,7 +804,7 @@ converters.KmacParams = createDictionaryConverter(
],
]);
-converters.KangarooTwelveParams = createDictionaryConverter(
+converters.KangarooTwelveParams = createAlgorithmDictionaryConverter(
'KangarooTwelveParams', [
dictAlgorithm,
[
@@ -788,7 +826,7 @@ converters.KangarooTwelveParams = createDictionaryConverter(
],
]);
-converters.TurboShakeParams = createDictionaryConverter(
+converters.TurboShakeParams = createAlgorithmDictionaryConverter(
'TurboShakeParams', [
dictAlgorithm,
[
@@ -818,6 +856,9 @@ converters.TurboShakeParams = createDictionaryConverter(
]);
module.exports = {
+ // Spread into fast-property objects before detaching their prototypes.
+ algorithmConverters: ObjectSetPrototypeOf({ ...algorithmConverters }, null),
converters,
requiredArguments,
+ validators: ObjectSetPrototypeOf({ ...validators }, null),
};
diff --git a/test/parallel/test-webcrypto-crypto-job-mode.js b/test/parallel/test-webcrypto-crypto-job-mode.js
index 5f5f1761a4c..c050ad5c26b 100644
--- a/test/parallel/test-webcrypto-crypto-job-mode.js
+++ b/test/parallel/test-webcrypto-crypto-job-mode.js
@@ -15,10 +15,8 @@ const {
} = require('internal/crypto/keys');
const {
getUsagesMask,
+ jobPromise,
} = require('internal/crypto/util');
-const {
- aesCipher,
-} = require('internal/crypto/aes');
const {
AESCipherJob,
@@ -135,11 +133,13 @@ async function withObjectPrototypeSetters(names, fn) {
Buffer.alloc(15)),
/Invalid initialization vector/);
- const promise = aesCipher(
+ const promise = jobPromise(() => new AESCipherJob(
+ kCryptoJobWebCrypto,
kWebCryptoCipherEncrypt,
- key,
+ getCryptoKeyHandle(key),
Buffer.alloc(16),
- { name: 'AES-CBC', iv: Buffer.alloc(15) });
+ kKeyVariantAES_CBC_128,
+ Buffer.alloc(15)));
assert.strictEqual(Object.getPrototypeOf(promise), Promise.prototype);
await assert.rejects(promise, (err) => {
diff --git a/test/parallel/test-webcrypto-fips-refresh.js b/test/parallel/test-webcrypto-fips-refresh.js
new file mode 100644
index 00000000000..71459099356
--- /dev/null
+++ b/test/parallel/test-webcrypto-fips-refresh.js
@@ -0,0 +1,37 @@
+// Flags: --expose-internals
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { getFips, setFips } = require('crypto');
+const { internalBinding } = require('internal/test/binding');
+const { getOptionValue } = require('internal/options');
+if (!internalBinding('crypto').testFipsCrypto())
+ common.skip('requires an active FIPS provider');
+if (getOptionValue('--force-fips'))
+ common.skip('FIPS mode cannot be changed when forced');
+
+const initial = getFips();
+try {
+ for (const fips of [false, true, false]) {
+ setFips(fips);
+ assert.strictEqual(SubtleCrypto.supports('digest', {
+ name: 'TurboSHAKE128', outputLength: 128,
+ }), !fips);
+ assert.strictEqual(SubtleCrypto.supports('digest', {
+ name: 'KT128', outputLength: 128,
+ }), !fips);
+ assert.strictEqual(SubtleCrypto.supports('digest', {
+ name: 'cSHAKE128', outputLength: 128, customization: new Uint8Array(1),
+ }), !fips);
+ assert.strictEqual(SubtleCrypto.supports('generateKey', {
+ name: 'RSA-PSS', hash: 'SHA-256', modulusLength: 1024,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ }), !fips);
+ }
+} finally {
+ setFips(initial);
+}
diff --git a/test/parallel/test-webcrypto-normalization-boundary.js b/test/parallel/test-webcrypto-normalization-boundary.js
new file mode 100644
index 00000000000..20102c32e6a
--- /dev/null
+++ b/test/parallel/test-webcrypto-normalization-boundary.js
@@ -0,0 +1,79 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+ const data = new Uint8Array(16);
+ const key = await subtle.importKey('raw', data, 'AES-CBC', false, ['encrypt']);
+ class Parameters {
+ #iv = new Uint8Array(16);
+ get name() { return 'AES-CBC'; }
+ get iv() { return this.#iv; }
+ }
+ await subtle.encrypt(new Parameters(), key, data);
+
+ const reads = [];
+ const algorithm = new Proxy({ name: 'AES-CBC', iv: data }, {
+ get: common.mustCall((target, member, receiver) => {
+ assert.strictEqual(receiver, algorithm);
+ reads.push(member);
+ return Reflect.get(target, member, receiver);
+ }, 2),
+ });
+ await subtle.encrypt(algorithm, key, data);
+ assert.deepStrictEqual(reads, ['name', 'iv']);
+
+ const decryptKey = await subtle.importKey('raw', data, 'AES-CBC', false, ['decrypt']);
+ await assert.rejects(subtle.encrypt({ name: 'AES-CBC', iv: new Uint8Array(8) },
+ decryptKey, data), { name: 'InvalidAccessError' });
+ await assert.rejects(subtle.generateKey({ name: 'AES-CBC', length: 100 }, false, ['sign']),
+ { name: 'SyntaxError' });
+ await assert.rejects(subtle.generateKey({ name: 'HMAC', hash: 'SHA-256', length: 0 }, false, ['encrypt']),
+ { name: 'SyntaxError' });
+ await assert.rejects(subtle.generateKey({ name: 'AES-CBC', length: 64 }, false, []),
+ { name: 'OperationError' });
+ await assert.rejects(subtle.generateKey({ name: 'HMAC', hash: 'SHA-256', length: 0 }, false, []),
+ { name: 'OperationError' });
+ if (SubtleCrypto.supports('digest', 'SHA3-256')) {
+ const hmac = { name: 'HMAC', hash: 'SHA3-256' };
+ await assert.rejects(subtle.generateKey(hmac, false, ['encrypt']), { name: 'SyntaxError' });
+ await assert.rejects(subtle.generateKey(hmac, false, []), { name: 'NotSupportedError' });
+ }
+ await assert.rejects(subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-999' }, false, ['encrypt']),
+ { name: 'SyntaxError' });
+ await assert.rejects(subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-999' }, false, []),
+ { name: 'NotSupportedError' });
+ await assert.rejects(subtle.generateKey({
+ name: 'RSA-PSS', modulusLength: 0,
+ publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256',
+ }, false, []), { name: 'OperationError' });
+ await assert.rejects(subtle.encrypt({ name: 'AES-CTR', counter: new Uint8Array(8), length: NaN },
+ key, data), TypeError);
+ assert.strictEqual(SubtleCrypto.supports('encrypt', { name: 'AES-CBC', iv: new Uint8Array(8) }), false);
+
+ const hkdf = { name: 'HKDF', hash: 'SHA-256', salt: data, info: data };
+ const base = await subtle.importKey('raw', data, 'HKDF', false, ['deriveKey']);
+ const noDeriveKey = await subtle.importKey('raw', data, 'HKDF', false, ['deriveBits']);
+ const hmac = { name: 'HMAC', hash: 'SHA-256', length: 0 };
+ await assert.rejects(subtle.deriveKey(hkdf, base, hmac, false, ['sign']), TypeError);
+ await assert.rejects(subtle.deriveKey(hkdf, noDeriveKey, hmac, false, ['sign']),
+ { name: 'InvalidAccessError' });
+ await assert.rejects(subtle.deriveKey(hkdf, noDeriveKey, { name: 'AES-GCM', length: 100 }, false, ['encrypt']),
+ { name: 'InvalidAccessError' });
+
+ if (SubtleCrypto.supports('importKey', 'Argon2id')) {
+ const base = await subtle.importKey('raw-secret', data, 'Argon2id', false, ['deriveBits']);
+ const parameters = { name: 'Argon2id', nonce: data, passes: 1, memory: 8, parallelism: 1 };
+ const expected = await subtle.deriveBits(parameters, base, 256);
+ let conversions = 0;
+ const parallelism = { valueOf() { conversions++; return 1.5; } };
+ assert.deepStrictEqual(await subtle.deriveBits({ ...parameters, parallelism }, base, 256), expected);
+ assert.strictEqual(conversions, 1);
+ assert.strictEqual(SubtleCrypto.supports('deriveBits', { ...parameters, parallelism: 1.5 }, 256), true);
+ }
+})().then(common.mustCall());
diff --git a/test/parallel/test-webcrypto-supports-fips.js b/test/parallel/test-webcrypto-supports-fips.js
index ae5d1d4b32d..e08551c65f3 100644
--- a/test/parallel/test-webcrypto-supports-fips.js
+++ b/test/parallel/test-webcrypto-supports-fips.js
@@ -50,21 +50,23 @@ async function check() {
}
}
}
+ const hashes = crypto.getHashes();
+ const hashError = { name: 'NotSupportedError', message: 'Unrecognized algorithm name' };
const rsa = {
name: 'RSA-PSS', modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256',
};
assert.strictEqual(SubtleCrypto.supports('generateKey', rsa), !fips);
if (fips) {
- assert.throws(() => normalizeAlgorithm(rsa, 'generateKey'), {
+ // Hash normalization precedes the RSA operation's modulus validation.
+ const error = hashes.includes('sha256') ? {
name: 'OperationError', message: 'algorithm.modulusLength must be at least 2048',
- });
+ } : hashError;
+ await assert.rejects(subtle.generateKey(rsa, true, ['sign']), error);
} else {
assert.strictEqual(normalizeAlgorithm(rsa, 'generateKey').modulusLength, 1024);
}
- const hashes = crypto.getHashes();
const salt = new Uint8Array(16);
- const hashError = { name: 'NotSupportedError', message: 'Unrecognized algorithm name' };
for (const [name, alias] of [
['SHA-1', 'sha1'], ['SHA-256', 'sha256'], ['SHA-384', 'sha384'], ['SHA-512', 'sha512'],
['SHA3-256', 'sha3-256'], ['SHA3-384', 'sha3-384'], ['SHA3-512', 'sha3-512'],
diff --git a/test/parallel/test-webcrypto-webidl.js b/test/parallel/test-webcrypto-webidl.js
index 3dcc4e7ca86..643343a77f4 100644
--- a/test/parallel/test-webcrypto-webidl.js
+++ b/test/parallel/test-webcrypto-webidl.js
@@ -643,7 +643,9 @@ function assertJsonWebKey(actual, expected) {
});
}
- assert.throws(() => converters.Argon2Params({ ...good, passes: 0 }, opts), {
+ const zeroPasses = converters.Argon2Params({ ...good, passes: 0 }, opts);
+ assert.strictEqual(zeroPasses.passes, 0);
+ assert.throws(() => webidl.validators.Argon2Params(zeroPasses), {
name: 'OperationError',
message: 'passes must be > 0',
});
@@ -728,11 +730,13 @@ function assertJsonWebKey(actual, expected) {
};
assertIdlDictionary(converters.Argon2Params({ ...good, filtered: 'out' }, opts), good);
- assert.throws(() => converters.Argon2Params({
+ const excessiveParallelism = converters.Argon2Params({
...good,
parallelism: maxParallelism + 1,
memory: 8 * (maxParallelism + 1),
- }, opts), {
+ }, opts);
+ assert.strictEqual(excessiveParallelism.parallelism, maxParallelism + 1);
+ assert.throws(() => webidl.validators.Argon2Params(excessiveParallelism), {
name: 'OperationError',
message: 'parallelism must be > 0 and <= 16777215',
});