diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index fcff7070164f..8673f9b46817 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -4,6 +4,8 @@ const { ArrayBuffer, FunctionPrototypeCall, PromiseResolve, + TypedArrayPrototypeGetByteLength, + Uint8Array, } = primordials; const { @@ -22,6 +24,7 @@ const { const { kMaxLength } = require('buffer'); const { + getBufferSourceByteLength, jobPromise, normalizeHashName, toBuf, @@ -41,6 +44,7 @@ const { const { isAnyArrayBuffer, isArrayBufferView, + isSharedArrayBuffer, } = require('internal/util/types'); const { @@ -51,6 +55,13 @@ const { hideStackFrames, } = require('internal/errors'); +function getByteSourceByteLength(source) { + if (isSharedArrayBuffer(source)) { + return TypedArrayPrototypeGetByteLength(new Uint8Array(source)); + } + return getBufferSourceByteLength(source); +} + const validateParameters = hideStackFrames((hash, key, salt, info, length) => { validateString.withoutStackTrace(hash, 'digest'); key = prepareKey(key); @@ -61,11 +72,12 @@ const validateParameters = hideStackFrames((hash, key, salt, info, length) => { // Coerce -0 to +0. length += 0; - if (info.byteLength > 1024) { + const infoByteLength = getByteSourceByteLength(info); + if (infoByteLength > 1024) { throw new ERR_OUT_OF_RANGE.HideStackFramesError( 'info', 'must not contain more than 1024 bytes', - info.byteLength); + infoByteLength); } return { @@ -103,18 +115,20 @@ function prepareKey(key) { return key; } -function hkdf(hash, key, salt, info, length, callback) { - ({ - hash, - key, - salt, - info, - length, - } = validateParameters(hash, key, salt, info, length)); +function createHkdfJob(mode, params) { + return new HKDFJob( + mode, + params.hash, + params.key, + params.salt, + params.info, + params.length); +} +function hkdf(hash, key, salt, info, length, callback) { + const params = validateParameters(hash, key, salt, info, length); validateFunction(callback, 'callback'); - - const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length); + const job = createHkdfJob(kCryptoJobAsync, params); job.ondone = (error, bits) => { if (error) return FunctionPrototypeCall(callback, job, error); @@ -125,15 +139,8 @@ function hkdf(hash, key, salt, info, length, callback) { } function hkdfSync(hash, key, salt, info, length) { - ({ - hash, - key, - salt, - info, - length, - } = validateParameters(hash, key, salt, info, length)); - - const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length); + const params = validateParameters(hash, key, salt, info, length); + const job = createHkdfJob(kCryptoJobSync, params); const { 0: err, 1: bits } = job.run(); if (err !== undefined) throw err; diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 7f73d1bd66de..8ec772ba4cc5 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -15,6 +15,7 @@ const { lazyDOMException, } = require('internal/util'); const { + isInt32, isUint32, } = require('internal/validators'); const { CryptoKey } = require('internal/crypto/webcrypto'); @@ -489,6 +490,11 @@ converters.Pbkdf2Params = createDictionaryConverter( validator: (V, dict) => { if (V === 0) throw lazyDOMException('iterations cannot be zero', 'OperationError'); + if (!isInt32(V)) { + throw lazyDOMException( + 'iterations exceeds the implementation limit', + 'NotSupportedError'); + } }, required: true, }, diff --git a/test/fixtures/webcrypto/supports-level-2.mjs b/test/fixtures/webcrypto/supports-level-2.mjs index 3d0f8a630394..deec07dda904 100644 --- a/test/fixtures/webcrypto/supports-level-2.mjs +++ b/test/fixtures/webcrypto/supports-level-2.mjs @@ -124,6 +124,9 @@ export const vectors = { [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, { name: 'HMAC', hash: 'SHA-256' }], + [false, + { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 2 ** 31 }, + { name: 'AES-CBC', length: 128 }], [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 'HKDF'], @@ -183,6 +186,7 @@ export const vectors = { [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 8], [true, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 0], [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 0 }, 8], + [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 2 ** 31 }, 8], [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, null], [false, { name: 'PBKDF2', hash: 'SHA-256', salt: Buffer.alloc(0), iterations: 1 }, 7], [false, { name: 'PBKDF2', hash: 'Invalid', salt: Buffer.alloc(0), iterations: 1 }, 8], diff --git a/test/parallel/test-crypto-hkdf.js b/test/parallel/test-crypto-hkdf.js index bfde3b324331..1e2069d2e701 100644 --- a/test/parallel/test-crypto-hkdf.js +++ b/test/parallel/test-crypto-hkdf.js @@ -108,6 +108,23 @@ const { hasOpenSSL } = require('../common/crypto'); code: 'ERR_OUT_OF_RANGE' }); + { + const info = new Uint8Array(2048); + Object.defineProperty(info, 'byteLength', { + __proto__: null, + get() { + return 1; + }, + }); + + const calls = [ + () => hkdf('sha256', 'a', '', info, 10, common.mustNotCall()), + () => hkdfSync('sha256', 'a', '', info, 10), + ]; + for (const call of calls) + assert.throws(call, { code: 'ERR_OUT_OF_RANGE' }); + } + assert.throws( () => hkdf('sha512', 'a', '', '', 64 * 255 + 1, common.mustNotCall()), { code: 'ERR_CRYPTO_INVALID_KEYLEN' diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js index 6ef2227ab2d2..6b0b9d0e36e5 100644 --- a/test/parallel/test-webcrypto-derivebits.js +++ b/test/parallel/test-webcrypto-derivebits.js @@ -120,6 +120,28 @@ const rejectsXCurves = hasFIPS(3, 5); } } +// Test PBKDF2 rejects iteration counts beyond the native signed int range +{ + async function test() { + const key = await subtle.importKey( + 'raw', + new Uint8Array([1]), + 'PBKDF2', + false, + ['deriveBits']); + await assert.rejects( + subtle.deriveBits({ + name: 'PBKDF2', + hash: 'SHA-256', + salt: new Uint8Array([2]), + iterations: 2 ** 31, + }, key, 8), + { name: 'NotSupportedError' }); + } + + test().then(common.mustCall()); +} + // Test X25519 and X448 bit derivation { async function test(name) {