Skip to content

Commit 67bd94d

Browse files
MegaManSecljharb
authored andcommitted
[Fix] only allow finite iterations
1 parent 8f59d96 commit 67bd94d

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

lib/precondition.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
'use strict';
22

3+
var $isFinite = isFinite;
34
var MAX_ALLOC = Math.pow(2, 30) - 1; // default in iojs
45

56
module.exports = function (iterations, keylen) {
67
if (typeof iterations !== 'number') {
78
throw new TypeError('Iterations not a number');
89
}
910

10-
if (iterations < 0) {
11+
if (iterations < 0 || !$isFinite(iterations)) {
1112
throw new TypeError('Bad iterations');
1213
}
1314

test/iteration-guard.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
'use strict';
2+
3+
var test = require('tape');
4+
var pbkdf2 = require('..');
5+
var precondition = require('../lib/precondition');
6+
var nodeCrypto = require('crypto');
7+
8+
var password = Buffer.from('password');
9+
var salt = Buffer.from('salt');
10+
var keylen = 32;
11+
var digest = 'sha256';
12+
13+
var hasBigInt = typeof BigInt === 'function';
14+
var makeBigInt = function (x) { return hasBigInt ? BigInt(x) : undefined; };
15+
16+
var RE_NOT_NUMBER = /(iterations?|rounds?)\s*(not a number|must be a number|invalid)/i;
17+
var RE_BAD_ITER = /(bad iterations?|iterations?\s*(must be finite|must be positive|non[-\s]?finite|>=?\s*0))/i;
18+
19+
test('precondition: rejects non-number iterations', function (t) {
20+
var badNotNumber = [
21+
'100',
22+
true,
23+
false,
24+
null,
25+
undefined,
26+
[],
27+
{},
28+
function () {},
29+
Object(10),
30+
Object(NaN)
31+
];
32+
var maybeBig = makeBigInt(10);
33+
if (typeof maybeBig !== 'undefined') { badNotNumber.push(maybeBig); }
34+
35+
t.plan(badNotNumber.length);
36+
37+
badNotNumber.forEach(function (v) {
38+
t['throws'](
39+
function () { precondition(v, keylen); },
40+
RE_NOT_NUMBER,
41+
'precondition: non-number (' + String(v) + ') rejected'
42+
);
43+
});
44+
});
45+
46+
test('precondition: rejects non-finite and negative iterations', function (t) {
47+
var vals = [Infinity, -Infinity, NaN, -1, -0.5];
48+
t.plan(vals.length);
49+
50+
vals.forEach(function (v) {
51+
t['throws'](
52+
function () { precondition(v, keylen); },
53+
RE_BAD_ITER,
54+
'precondition: ' + String(v) + ' rejected as bad iterations'
55+
);
56+
});
57+
});
58+
59+
test('precondition: accepts very large finite numbers (no KDF run here)', function (t) {
60+
t.plan(2);
61+
t.doesNotThrow(
62+
function () { precondition(Number.MAX_SAFE_INTEGER, keylen); },
63+
'MAX_SAFE_INTEGER passes precondition'
64+
);
65+
t.doesNotThrow(
66+
function () { precondition(Number.MAX_VALUE, keylen); },
67+
'MAX_VALUE passes precondition'
68+
);
69+
});
70+
71+
test('API: rejects non-finite iterations (no infinite loop possible)', function (t) {
72+
t.plan(6);
73+
74+
t['throws'](
75+
function () { pbkdf2.pbkdf2(password, salt, Infinity, keylen, digest, function () {}); },
76+
RE_BAD_ITER,
77+
'async: Infinity rejected'
78+
);
79+
t['throws'](
80+
function () { pbkdf2.pbkdf2(password, salt, -Infinity, keylen, digest, function () {}); },
81+
RE_BAD_ITER,
82+
'async: -Infinity rejected'
83+
);
84+
t['throws'](
85+
function () { pbkdf2.pbkdf2(password, salt, NaN, keylen, digest, function () {}); },
86+
RE_BAD_ITER,
87+
'async: NaN rejected'
88+
);
89+
90+
t['throws'](
91+
function () { pbkdf2.pbkdf2Sync(password, salt, Infinity, keylen, digest); },
92+
RE_BAD_ITER,
93+
'sync: Infinity rejected'
94+
);
95+
t['throws'](
96+
function () { pbkdf2.pbkdf2Sync(password, salt, -Infinity, keylen, digest); },
97+
RE_BAD_ITER,
98+
'sync: -Infinity rejected'
99+
);
100+
t['throws'](
101+
function () { pbkdf2.pbkdf2Sync(password, salt, NaN, keylen, digest); },
102+
RE_BAD_ITER,
103+
'sync: NaN rejected'
104+
);
105+
});
106+
107+
test('API: rejects other unwanted iteration values', function (t) {
108+
var notNumberCases = [
109+
'"100"',
110+
undefined,
111+
null,
112+
true,
113+
Object(7)
114+
];
115+
var badNumberCases = [-1, -0.25];
116+
117+
if (hasBigInt) { notNumberCases.push(makeBigInt(10)); }
118+
119+
t.plan((notNumberCases.length + badNumberCases.length) * 2);
120+
121+
notNumberCases.forEach(function (val) {
122+
t['throws'](
123+
function () { pbkdf2.pbkdf2(password, salt, val, keylen, digest, function () {}); },
124+
RE_NOT_NUMBER,
125+
'async: ' + String(val) + ' rejected (not a number)'
126+
);
127+
t['throws'](
128+
function () { pbkdf2.pbkdf2Sync(password, salt, val, keylen, digest); },
129+
RE_NOT_NUMBER,
130+
'sync: ' + String(val) + ' rejected (not a number)'
131+
);
132+
});
133+
134+
badNumberCases.forEach(function (val) {
135+
t['throws'](
136+
function () { pbkdf2.pbkdf2(password, salt, val, keylen, digest, function () {}); },
137+
RE_BAD_ITER,
138+
'async: ' + String(val) + ' rejected (bad iterations)'
139+
);
140+
t['throws'](
141+
function () { pbkdf2.pbkdf2Sync(password, salt, val, keylen, digest); },
142+
RE_BAD_ITER,
143+
'sync: ' + String(val) + ' rejected (bad iterations)'
144+
);
145+
});
146+
});
147+
148+
test('sanity: small finite iterations still match Node crypto', function (t) {
149+
t.plan(2);
150+
var iterations = 2;
151+
var expected = nodeCrypto.pbkdf2Sync(password, salt, iterations, keylen, digest);
152+
var actual = pbkdf2.pbkdf2Sync(password, salt, iterations, keylen, digest);
153+
t.deepEqual(actual, expected, 'sync derived key matches Node for finite iterations');
154+
155+
pbkdf2.pbkdf2(password, salt, iterations, keylen, digest, function (err) {
156+
t.error(err, 'async derived key succeeds for finite iterations');
157+
});
158+
});

0 commit comments

Comments
 (0)