From d2dc5f052cacadf3d4a09d87164158da875ca740 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 23 Sep 2025 15:51:32 -0700 Subject: [PATCH 1/4] [Fix] check parameters before the "no Promise" bailout --- lib/async.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/async.js b/lib/async.js index a24bb58..21aee16 100644 --- a/lib/async.js +++ b/lib/async.js @@ -88,6 +88,13 @@ module.exports = function (password, salt, iterations, keylen, digest, callback) digest = undefined; } + checkParameters(iterations, keylen); + password = toBuffer(password, defaultEncoding, 'Password'); + salt = toBuffer(salt, defaultEncoding, 'Salt'); + if (typeof callback !== 'function') { + throw new Error('No callback provided to pbkdf2'); + } + digest = digest || 'sha1'; var algo = toBrowser[digest.toLowerCase()]; @@ -105,13 +112,6 @@ module.exports = function (password, salt, iterations, keylen, digest, callback) return; } - checkParameters(iterations, keylen); - password = toBuffer(password, defaultEncoding, 'Password'); - salt = toBuffer(salt, defaultEncoding, 'Salt'); - if (typeof callback !== 'function') { - throw new Error('No callback provided to pbkdf2'); - } - resolvePromise(checkNative(algo).then(function (resp) { if (resp) { return browserPbkdf2(password, salt, iterations, keylen, algo); From 8f59d962f71dcb2cc14067d7f514ff96e3406f81 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 23 Sep 2025 15:55:59 -0700 Subject: [PATCH 2/4] [Fix] restore node 0.10 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …in which algorithms are not specifiable and only HMAC-SHA1 is supported --- .eslintrc | 1 + package.json | 2 +- test/index.js | 32 ++++++++++++++++++++------------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.eslintrc b/.eslintrc index 1e1940e..9788295 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,7 @@ }, "rules": { + "complexity": "off", "func-style": "warn", "max-params": "warn", "multiline-comment-style": "off", diff --git a/package.json b/package.json index 57c0cf0..ebbf444 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "to-buffer": "^1.2.1" }, "engines": { - "node": ">=0.12" + "node": ">= 0.10" }, "auto-changelog": { "output": "CHANGELOG.md", diff --git a/test/index.js b/test/index.js index b788eb7..181af67 100644 --- a/test/index.js +++ b/test/index.js @@ -231,23 +231,26 @@ function runTests(name, compat) { } tape('does not return all zeroes for any algorithm', function (t) { - var algos2 = [ + // node 0.10 only supports HMAC-SHA1 + var is010 = node.pbkdf2Sync.length < 5; + + var algos2 = [].concat( // 'sha3-512', // 'sha3-256', // 'SHA3-384', // 'blake2b512', - 'Sha256', - 'ShA256', - 'Sha512', - 'sha512-256', - 'SHA512', + is010 ? [] : 'Sha256', + is010 ? [] : 'ShA256', + is010 ? [] : 'Sha512', + is010 ? [] : 'sha512-256', + is010 ? [] : 'SHA512', 'SHA1', - 's-h-a-1', + is010 ? [] : 's-h-a-1', 'sha-1', - 'RMD160', - 'RIPEMD-160', - 'ripemd-160' - ]; + is010 ? [] : 'RMD160', + is010 ? [] : 'RIPEMD-160', + is010 ? [] : 'ripemd-160' + ); algos2.forEach(function (algo) { var throwCount = 0; var impls = { __proto__: null, node: node.pbkdf2Sync, lib: js.pbkdf2Sync, browser: browserImpl }; @@ -324,7 +327,12 @@ tape('does not return all zeroes for any algorithm', function (t) { throws, expected, 'all implementations throw for ' + algo, - { todo: throwCount === 1 && algo === 'sha512-256' && 'sha.js does not yet support sha512-256' } + { + // https://nodejs.org/download/release/v0.10.0/docs/api/crypto.html#crypto_crypto_pbkdf2_password_salt_iterations_keylen_callback + todo: is010 + ? 'node 0.10 does not support the algo argument' + : throwCount === 1 && algo === 'sha512-256' && 'sha.js does not yet support sha512-256' + } ); } }); From 67bd94dbbf21b93f5e282ee910728945c8ef2827 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Fri, 5 Sep 2025 22:25:48 +0200 Subject: [PATCH 3/4] [Fix] only allow finite iterations --- lib/precondition.js | 3 +- test/iteration-guard.js | 158 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 test/iteration-guard.js diff --git a/lib/precondition.js b/lib/precondition.js index 1781449..1274dbb 100644 --- a/lib/precondition.js +++ b/lib/precondition.js @@ -1,5 +1,6 @@ 'use strict'; +var $isFinite = isFinite; var MAX_ALLOC = Math.pow(2, 30) - 1; // default in iojs module.exports = function (iterations, keylen) { @@ -7,7 +8,7 @@ module.exports = function (iterations, keylen) { throw new TypeError('Iterations not a number'); } - if (iterations < 0) { + if (iterations < 0 || !$isFinite(iterations)) { throw new TypeError('Bad iterations'); } diff --git a/test/iteration-guard.js b/test/iteration-guard.js new file mode 100644 index 0000000..b25b3c8 --- /dev/null +++ b/test/iteration-guard.js @@ -0,0 +1,158 @@ +'use strict'; + +var test = require('tape'); +var pbkdf2 = require('..'); +var precondition = require('../lib/precondition'); +var nodeCrypto = require('crypto'); + +var password = Buffer.from('password'); +var salt = Buffer.from('salt'); +var keylen = 32; +var digest = 'sha256'; + +var hasBigInt = typeof BigInt === 'function'; +var makeBigInt = function (x) { return hasBigInt ? BigInt(x) : undefined; }; + +var RE_NOT_NUMBER = /(iterations?|rounds?)\s*(not a number|must be a number|invalid)/i; +var RE_BAD_ITER = /(bad iterations?|iterations?\s*(must be finite|must be positive|non[-\s]?finite|>=?\s*0))/i; + +test('precondition: rejects non-number iterations', function (t) { + var badNotNumber = [ + '100', + true, + false, + null, + undefined, + [], + {}, + function () {}, + Object(10), + Object(NaN) + ]; + var maybeBig = makeBigInt(10); + if (typeof maybeBig !== 'undefined') { badNotNumber.push(maybeBig); } + + t.plan(badNotNumber.length); + + badNotNumber.forEach(function (v) { + t['throws']( + function () { precondition(v, keylen); }, + RE_NOT_NUMBER, + 'precondition: non-number (' + String(v) + ') rejected' + ); + }); +}); + +test('precondition: rejects non-finite and negative iterations', function (t) { + var vals = [Infinity, -Infinity, NaN, -1, -0.5]; + t.plan(vals.length); + + vals.forEach(function (v) { + t['throws']( + function () { precondition(v, keylen); }, + RE_BAD_ITER, + 'precondition: ' + String(v) + ' rejected as bad iterations' + ); + }); +}); + +test('precondition: accepts very large finite numbers (no KDF run here)', function (t) { + t.plan(2); + t.doesNotThrow( + function () { precondition(Number.MAX_SAFE_INTEGER, keylen); }, + 'MAX_SAFE_INTEGER passes precondition' + ); + t.doesNotThrow( + function () { precondition(Number.MAX_VALUE, keylen); }, + 'MAX_VALUE passes precondition' + ); +}); + +test('API: rejects non-finite iterations (no infinite loop possible)', function (t) { + t.plan(6); + + t['throws']( + function () { pbkdf2.pbkdf2(password, salt, Infinity, keylen, digest, function () {}); }, + RE_BAD_ITER, + 'async: Infinity rejected' + ); + t['throws']( + function () { pbkdf2.pbkdf2(password, salt, -Infinity, keylen, digest, function () {}); }, + RE_BAD_ITER, + 'async: -Infinity rejected' + ); + t['throws']( + function () { pbkdf2.pbkdf2(password, salt, NaN, keylen, digest, function () {}); }, + RE_BAD_ITER, + 'async: NaN rejected' + ); + + t['throws']( + function () { pbkdf2.pbkdf2Sync(password, salt, Infinity, keylen, digest); }, + RE_BAD_ITER, + 'sync: Infinity rejected' + ); + t['throws']( + function () { pbkdf2.pbkdf2Sync(password, salt, -Infinity, keylen, digest); }, + RE_BAD_ITER, + 'sync: -Infinity rejected' + ); + t['throws']( + function () { pbkdf2.pbkdf2Sync(password, salt, NaN, keylen, digest); }, + RE_BAD_ITER, + 'sync: NaN rejected' + ); +}); + +test('API: rejects other unwanted iteration values', function (t) { + var notNumberCases = [ + '"100"', + undefined, + null, + true, + Object(7) + ]; + var badNumberCases = [-1, -0.25]; + + if (hasBigInt) { notNumberCases.push(makeBigInt(10)); } + + t.plan((notNumberCases.length + badNumberCases.length) * 2); + + notNumberCases.forEach(function (val) { + t['throws']( + function () { pbkdf2.pbkdf2(password, salt, val, keylen, digest, function () {}); }, + RE_NOT_NUMBER, + 'async: ' + String(val) + ' rejected (not a number)' + ); + t['throws']( + function () { pbkdf2.pbkdf2Sync(password, salt, val, keylen, digest); }, + RE_NOT_NUMBER, + 'sync: ' + String(val) + ' rejected (not a number)' + ); + }); + + badNumberCases.forEach(function (val) { + t['throws']( + function () { pbkdf2.pbkdf2(password, salt, val, keylen, digest, function () {}); }, + RE_BAD_ITER, + 'async: ' + String(val) + ' rejected (bad iterations)' + ); + t['throws']( + function () { pbkdf2.pbkdf2Sync(password, salt, val, keylen, digest); }, + RE_BAD_ITER, + 'sync: ' + String(val) + ' rejected (bad iterations)' + ); + }); +}); + +test('sanity: small finite iterations still match Node crypto', function (t) { + t.plan(2); + var iterations = 2; + var expected = nodeCrypto.pbkdf2Sync(password, salt, iterations, keylen, digest); + var actual = pbkdf2.pbkdf2Sync(password, salt, iterations, keylen, digest); + t.deepEqual(actual, expected, 'sync derived key matches Node for finite iterations'); + + pbkdf2.pbkdf2(password, salt, iterations, keylen, digest, function (err) { + t.error(err, 'async derived key succeeds for finite iterations'); + }); +}); From 36879052911703147a6dfa5e97422126bf3cda5b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 23 Sep 2025 23:25:13 -0700 Subject: [PATCH 4/4] v3.1.5 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 951f041..3fd6d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v3.1.5](https://github.com/browserify/pbkdf2/compare/v3.1.4...v3.1.5) - 2025-09-23 + +### Commits + +- [Fix] only allow finite iterations [`67bd94d`](https://github.com/browserify/pbkdf2/commit/67bd94dbbf21b93f5e282ee910728945c8ef2827) +- [Fix] restore node 0.10 support [`8f59d96`](https://github.com/browserify/pbkdf2/commit/8f59d962f71dcb2cc14067d7f514ff96e3406f81) +- [Fix] check parameters before the "no Promise" bailout [`d2dc5f0`](https://github.com/browserify/pbkdf2/commit/d2dc5f052cacadf3d4a09d87164158da875ca740) + ## [v3.1.4](https://github.com/browserify/pbkdf2/compare/v3.1.3...v3.1.4) - 2025-09-22 ### Commits diff --git a/package.json b/package.json index ebbf444..a24e1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pbkdf2", - "version": "3.1.4", + "version": "3.1.5", "description": "This library provides the functionality of PBKDF2 with the ability to use any supported hashing algorithm returned from crypto.getHashes()", "keywords": [ "pbkdf2",