From 65814466615c9f9dda3d31b22328e4cc4fdd291c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 6 Aug 2026 19:57:21 +0200 Subject: [PATCH] lib: add and test [EnforceRange] in webcrypto dictionaries Signed-off-by: Filip Skokan --- lib/internal/crypto/webcrypto.js | 7 +- test/parallel/test-webcrypto-enforce-range.js | 35 +++++++ test/parallel/test-webcrypto-webidl.js | 98 +++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-webcrypto-enforce-range.js diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 4326ae3a68db..d1070a1ca182 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -255,7 +255,11 @@ function deriveBitsImpl(algorithm, baseKey, length = null) { prefix, 'AlgorithmIdentifier', algorithm, i++); baseKey = convertSubtleArgument(prefix, 'CryptoKey', baseKey, i++); if (length !== null) { - length = convertSubtleArgument(prefix, 'unsigned long', length, i++); + length = webidl.converters['unsigned long'](length, { + prefix, + context: kArgumentContexts[i++], + enforceRange: true, + }); } const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits'); @@ -1696,6 +1700,7 @@ class SubtleCrypto { length = webidl.converters['unsigned long'](length, { prefix, context: '3rd argument', + enforceRange: true, }); } } else if (operation === 'getPublicKey') { diff --git a/test/parallel/test-webcrypto-enforce-range.js b/test/parallel/test-webcrypto-enforce-range.js new file mode 100644 index 000000000000..f3b3ec74ffc5 --- /dev/null +++ b/test/parallel/test-webcrypto-enforce-range.js @@ -0,0 +1,35 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { SubtleCrypto } = globalThis; +const { subtle } = globalThis.crypto; + +const algorithm = { + name: 'HKDF', + hash: 'SHA-256', + info: new Uint8Array(), + salt: new Uint8Array(32), +}; +const invalidLength = 2 ** 32; +const expectedError = { + code: 'ERR_OUT_OF_RANGE', + name: 'TypeError', +}; + +assert.throws( + () => SubtleCrypto.supports('deriveBits', algorithm, invalidLength), + expectedError); + +(async () => { + const key = await subtle.importKey( + 'raw', new Uint8Array(32), 'HKDF', false, ['deriveBits']); + + await assert.rejects( + subtle.deriveBits(algorithm, key, invalidLength), + expectedError); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-webidl.js b/test/parallel/test-webcrypto-webidl.js index 36cd3d885755..3dcc4e7ca863 100644 --- a/test/parallel/test-webcrypto-webidl.js +++ b/test/parallel/test-webcrypto-webidl.js @@ -119,6 +119,104 @@ function assertJsonWebKey(actual, expected) { } } +// [EnforceRange] integer dictionary members +{ + const kOctetMax = 2 ** 8 - 1; + const kUnsignedShortMax = 2 ** 16 - 1; + const kUnsignedLongMax = 2 ** 32 - 1; + const empty = Buffer.alloc(0); + const rsaKeyGen = { + name: 'RSA-PSS', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + }; + const aesKeyParams = { name: 'AES-GCM', length: 128 }; + const hmacKeyParams = { + name: 'HMAC', + hash: 'SHA-256', + length: 128, + }; + const kmacKeyParams = { name: 'KMAC128', length: 128 }; + const cases = [ + ['RsaKeyGenParams', rsaKeyGen, + { modulusLength: kUnsignedLongMax }], + ['RsaHashedKeyGenParams', { ...rsaKeyGen, hash: 'SHA-256' }, + { modulusLength: kUnsignedLongMax }], + ['AesKeyGenParams', aesKeyParams, + { length: kUnsignedShortMax }], + ['RsaPssParams', { name: 'RSA-PSS', saltLength: 20 }, + { saltLength: kUnsignedLongMax }], + ['HmacKeyGenParams', hmacKeyParams, + { length: kUnsignedLongMax }], + ['HmacImportParams', hmacKeyParams, + { length: kUnsignedLongMax }], + ['CShakeParams', { name: 'cSHAKE128', outputLength: 256 }, + { outputLength: kUnsignedLongMax }], + ['Pbkdf2Params', { + name: 'PBKDF2', + salt: empty, + iterations: 1, + hash: 'SHA-256', + }, { iterations: kUnsignedLongMax }], + ['AesDerivedKeyParams', aesKeyParams, + { length: kUnsignedShortMax }], + ['AeadParams', { + name: 'AES-GCM', + iv: Buffer.alloc(12), + tagLength: 128, + }, { tagLength: kOctetMax }], + ['AesCtrParams', { + name: 'AES-CTR', + counter: Buffer.alloc(16), + length: 128, + }, { length: kOctetMax }], + ['Argon2Params', { + name: 'Argon2id', + nonce: Buffer.alloc(8), + parallelism: 1, + memory: 8, + passes: 1, + version: 0x13, + }, { + parallelism: kUnsignedLongMax, + memory: kUnsignedLongMax, + passes: kUnsignedLongMax, + version: kOctetMax, + }], + ['KmacKeyGenParams', kmacKeyParams, + { length: kUnsignedLongMax }], + ['KmacImportParams', kmacKeyParams, + { length: kUnsignedLongMax }], + ['KmacParams', { name: 'KMAC128', outputLength: 256 }, + { outputLength: kUnsignedLongMax }], + ['KangarooTwelveParams', { name: 'KT128', outputLength: 256 }, + { outputLength: kUnsignedLongMax }], + ['TurboShakeParams', { + name: 'TurboSHAKE128', + outputLength: 256, + domainSeparation: 0x1f, + }, { + outputLength: kUnsignedLongMax, + domainSeparation: kOctetMax, + }], + ]; + + for (const [dictionary, base, members] of cases) { + const converter = converters[dictionary]; + assertIdlDictionary(converter(base, opts), base); + + for (const [member, max] of Object.entries(members)) { + assert.throws( + () => converter({ ...base, [member]: -1 }, opts), { + name: 'TypeError', + code: 'ERR_OUT_OF_RANGE', + message: `${prefix}: ${member} in ${context} is outside ` + + `the expected range of 0 to ${max}.`, + }); + } + } +} + // DOMString { assert.strictEqual(converters.DOMString(1), '1');