Skip to content

Commit 8ed89aa

Browse files
anonrigaduh95
authored andcommitted
test: cover crypto digest and HMAC caches
Check that HMAC instances duplicated from the same cached context stay independent when their updates are interleaved, and that warmed digest and HMAC caches follow FIPS mode changes without breaking an HMAC that is already in progress. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> Assisted-by: Grok PR-URL: #67109 Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 33d28a1 commit 8ed89aa

2 files changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const { isBoringSSL } = require('../common/crypto');
8+
if (isBoringSSL)
9+
common.skip('this test requires OpenSSL provider support');
10+
11+
const { isMainThread } = require('worker_threads');
12+
if (!isMainThread)
13+
common.skip('crypto.setFips() is not supported in workers');
14+
15+
const assert = require('assert');
16+
const crypto = require('crypto');
17+
18+
// createHmac(), createSign(), createVerify() and the synchronous one-shot
19+
// functions cache digests and HMAC contexts per Environment. Changing the FIPS
20+
// state must invalidate those caches without breaking objects that already
21+
// hold a context.
22+
23+
const key = Buffer.from('Jefe');
24+
const data = 'what do ya want for nothing?';
25+
// RFC 4231, test case 2.
26+
const expectedSha256 =
27+
'5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843';
28+
29+
const { privateKey, publicKey } =
30+
crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
31+
32+
const operations = {
33+
createHmac: (hash) => crypto.createHmac(hash, key).update(data).digest('hex'),
34+
createSign: (hash) =>
35+
crypto.createSign(hash).update(data).sign(privateKey, 'hex'),
36+
createVerify: (hash) => {
37+
const signature = crypto.sign(hash, Buffer.from(data), privateKey);
38+
return crypto.createVerify(hash).update(data).verify(publicKey, signature);
39+
},
40+
sign: (hash) => crypto.sign(hash, Buffer.from(data), privateKey).toString('hex'),
41+
verify: (hash) => {
42+
const signature = crypto.sign(hash, Buffer.from(data), privateKey);
43+
return crypto.verify(hash, Buffer.from(data), publicKey, signature);
44+
},
45+
hkdfSync: (hash) =>
46+
Buffer.from(crypto.hkdfSync(hash, key, 'salt', 'info', 32)).toString('hex'),
47+
pbkdf2Sync: (hash) =>
48+
crypto.pbkdf2Sync(key, 'salt', 1, 32, hash).toString('hex'),
49+
};
50+
51+
function runAll(hash) {
52+
const results = {};
53+
for (const [name, operation] of Object.entries(operations))
54+
results[name] = operation(hash);
55+
return results;
56+
}
57+
58+
const originalFips = crypto.getFips();
59+
try {
60+
crypto.setFips(false);
61+
} catch (err) {
62+
if (err.code !== 'ERR_CRYPTO_FIPS_FORCED') throw err;
63+
common.skip('FIPS mode cannot be disabled');
64+
}
65+
66+
try {
67+
// Warm the caches with a digest that FIPS mode never allows.
68+
const md5 = runAll('md5');
69+
assert.strictEqual(md5.createVerify, true);
70+
assert.strictEqual(md5.verify, true);
71+
assert.strictEqual(runAll('sha256').createHmac, expectedSha256);
72+
73+
const live = crypto.createHmac('sha256', key).update(data.slice(0, 10));
74+
75+
crypto.setFips(true);
76+
77+
// New operations follow the current policy instead of reusing cached
78+
// implementations.
79+
for (const [name, operation] of Object.entries(operations))
80+
assert.throws(() => operation('md5'), Error, name);
81+
82+
let fipsSha256;
83+
try {
84+
fipsSha256 = crypto.createHash('sha256').update(data).digest('hex');
85+
} catch {
86+
// No FIPS provider is available, so nothing can use SHA-256 either.
87+
}
88+
if (fipsSha256 === undefined) {
89+
assert.throws(() => operations.createHmac('sha256'), Error);
90+
} else {
91+
assert.strictEqual(operations.createHmac('sha256'), expectedSha256);
92+
}
93+
94+
// An HMAC that already has its context is not affected.
95+
assert.strictEqual(live.update(data.slice(10)).digest('hex'),
96+
expectedSha256);
97+
98+
crypto.setFips(false);
99+
assert.deepStrictEqual(runAll('md5'), md5);
100+
assert.strictEqual(operations.createHmac('sha256'), expectedSha256);
101+
} finally {
102+
crypto.setFips(originalFips);
103+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('assert');
8+
const crypto = require('crypto');
9+
10+
// HMAC instances for the same digest start from one cached context. Live
11+
// instances with different keys must not affect each other.
12+
13+
// Test cases 1, 2 and 6 from RFC 4231. Case 6 uses a key longer than the block
14+
// size, which is hashed first.
15+
const vectors = [
16+
{
17+
key: Buffer.alloc(20, 0x0b),
18+
data: 'Hi There',
19+
sha256: 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7',
20+
sha512: '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde' +
21+
'daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854',
22+
},
23+
{
24+
key: Buffer.from('Jefe'),
25+
data: 'what do ya want for nothing?',
26+
sha256: '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843',
27+
sha512: '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554' +
28+
'9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737',
29+
},
30+
{
31+
key: Buffer.alloc(131, 0xaa),
32+
data: 'Test Using Larger Than Block-Size Key - Hash Key First',
33+
sha256: '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54',
34+
sha512: '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352' +
35+
'6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598',
36+
},
37+
];
38+
39+
function chunks(data, size) {
40+
const result = [];
41+
for (let i = 0; i < data.length; i += size)
42+
result.push(data.slice(i, i + size));
43+
return result;
44+
}
45+
46+
for (const hash of ['sha256', 'sha512']) {
47+
// Create every instance before updating any of them.
48+
const hmacs = vectors.map(({ key }) => crypto.createHmac(hash, key));
49+
const parts = vectors.map(({ data }) => chunks(data, 5));
50+
51+
// Interleave the updates across instances.
52+
const rounds = Math.max(...parts.map((p) => p.length));
53+
for (let round = 0; round < rounds; round++) {
54+
for (let i = 0; i < hmacs.length; i++) {
55+
if (round < parts[i].length) hmacs[i].update(parts[i][round]);
56+
}
57+
}
58+
59+
// Finish them in reverse order.
60+
for (let i = hmacs.length - 1; i >= 0; i--)
61+
assert.strictEqual(hmacs[i].digest('hex'), vectors[i][hash]);
62+
63+
// Creating and finishing another instance does not affect one that is in
64+
// progress, and later instances start from a clean context.
65+
const { key, data } = vectors[1];
66+
const early = crypto.createHmac(hash, key).update(data.slice(0, 10));
67+
const other = crypto.createHmac(hash, vectors[0].key);
68+
assert.strictEqual(other.update(vectors[0].data).digest('hex'),
69+
vectors[0][hash]);
70+
assert.strictEqual(early.update(data.slice(10)).digest('hex'),
71+
vectors[1][hash]);
72+
assert.strictEqual(crypto.createHmac(hash, key).update(data).digest('hex'),
73+
vectors[1][hash]);
74+
}

0 commit comments

Comments
 (0)