crypto: validate normalized hash names

Check the normalized SHA algorithm instead of reading the caller hash
name before conversion. This prevents changing getters from bypassing
the supported hash check.

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>
This commit is contained in:
Filip Skokan authored and Node.js GitHub Bot committed 2026-09-25 13:23:07 +00:00
1 parent e329423cd8
commit 33a4935b84
3 files changed
+54 -20

No files matched your search

+7 -1
View File
@@ -23,6 +23,7 @@ const {
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeStartsWith,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeGetBuffer,
@@ -841,7 +842,12 @@ function normalizeAlgorithm(algorithm, op) {
getBufferSourceBytes(idlValue),
);
} else if (idlType === 'HashAlgorithmIdentifier') {
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
const hash = normalizeAlgorithm(idlValue, 'digest');
if (!StringPrototypeStartsWith(hash.name, 'SHA')) {
throw lazyDOMException(
`Only SHA hashes are supported in ${desiredType}`, 'NotSupportedError');
}
normalizedAlgorithm[member] = hash;
} else if (idlType === 'AlgorithmIdentifier') {
// This extension point is not used by any supported algorithm (yet?)
throw lazyDOMException('Not implemented.', 'NotSupportedError');
-19
View File
@@ -7,7 +7,6 @@ const {
ObjectPrototypeHasOwnProperty,
StringPrototypeCharCodeAt,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
} = primordials;
@@ -67,18 +66,6 @@ function namedCurveValidator(V, dict) {
const converters = { __proto__: null, ...webidl };
/**
* @param {string | object} V - The hash algorithm identifier (string or object).
* @param {string} label - The dictionary name for the error message.
*/
function ensureSHA(V, label) {
const name = typeof V === 'string' ? V : V.name;
if (typeof name !== 'string' ||
!StringPrototypeStartsWith(StringPrototypeToLowerCase(name), 'sha'))
throw lazyDOMException(
`Only SHA hashes are supported in ${label}`, 'NotSupportedError');
}
converters.AlgorithmIdentifier = (V, opts) => {
// Union for (object or DOMString)
if (type(V) === 'Object') {
@@ -200,7 +187,6 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'),
required: true,
},
],
@@ -213,7 +199,6 @@ converters.RsaHashedImportParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'),
required: true,
},
],
@@ -342,7 +327,6 @@ converters.EcdsaParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'EcdsaParams'),
required: true,
},
],
@@ -368,7 +352,6 @@ for (let i = 0; i < kHmacDictionaries.length; i++) {
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, name),
required: true,
},
{
@@ -432,7 +415,6 @@ converters.HkdfParams = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'HkdfParams'),
required: true,
},
{
@@ -500,7 +482,6 @@ converters.Pbkdf2Params = createDictionaryConverter(
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'),
required: true,
},
],
@@ -0,0 +1,47 @@
'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(32);
let reads = 0;
const key = await subtle.importKey('raw', data, {
name: 'HMAC',
hash: {
get name() {
reads++;
return reads === 1 ? 'SHA-384' : 'SHA-256';
},
},
}, false, ['sign']);
assert.strictEqual(reads, 1);
assert.strictEqual(key.algorithm.hash.name, 'SHA-384');
assert.strictEqual((await subtle.sign('HMAC', key, data)).byteLength, 48);
const converted = await subtle.importKey('raw', data, {
name: 'HMAC',
hash: { name: { toString() { return 'SHA-256'; } } },
}, false, ['sign']);
assert.strictEqual(converted.algorithm.hash.name, 'SHA-256');
await assert.rejects(subtle.importKey('raw', data, {
name: 'HMAC', hash: {},
}, false, ['sign']), TypeError);
const cshakeAvailable = SubtleCrypto.supports('digest', {
name: 'cSHAKE128', outputLength: 256,
});
await assert.rejects(subtle.importKey('raw', data, {
name: 'HMAC', hash: 'cSHAKE128',
}, false, ['sign']), cshakeAvailable ? TypeError : { name: 'NotSupportedError' });
await assert.rejects(subtle.importKey('raw', data, {
name: 'HMAC', hash: 'MD5', length: -1,
}, false, ['sign']), TypeError);
await assert.rejects(subtle.importKey('raw', data, {
name: 'HMAC', hash: { name: 'cSHAKE128', outputLength: 256 },
}, false, ['sign']), { name: 'NotSupportedError' });
})().then(common.mustCall());