Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const {
getHashes,
setEngine,
secureHeapUsed,
evictCipherHashCache,
} = require('internal/crypto/util');
const Certificate = require('internal/crypto/certificate');
const {
Expand Down Expand Up @@ -263,6 +264,7 @@ function setFips(val) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Calling crypto.setFips()');
}
setFipsCrypto(val);
evictCipherHashCache();
}
}

Expand Down
24 changes: 22 additions & 2 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
ArrayFrom,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSlice,
BigInt,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
Expand Down Expand Up @@ -125,8 +126,25 @@ function getCachedHashId(algorithm) {
return result === undefined ? -1 : result;
}

const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers()));
const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes()));
let _ciphersCache;
function getCiphers() {
if (_ciphersCache === undefined)
_ciphersCache = filterDuplicateStrings(_getCiphers());
return ArrayPrototypeSlice(_ciphersCache);
}

let _hashesCache;
function getHashes() {
if (_hashesCache === undefined)
_hashesCache = filterDuplicateStrings(_getHashes());
return ArrayPrototypeSlice(_hashesCache);
}

function evictCipherHashCache() {
_ciphersCache = undefined;
_hashesCache = undefined;
}

const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves()));

function setEngine(id, flags) {
Expand All @@ -143,6 +161,7 @@ function setEngine(id, flags) {
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
if (!_setEngine(id, flags))
throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);
evictCipherHashCache();
}

const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
Expand Down Expand Up @@ -855,6 +874,7 @@ module.exports = {
getCurves,
getDataViewOrTypedArrayBuffer,
getHashes,
evictCipherHashCache,
kHandle,
kKeyObject,
setEngine,
Expand Down
74 changes: 74 additions & 0 deletions test/parallel/test-crypto-ciphers-hashes-fips-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Flags: --expose-internals
'use strict';

// Verify that getCiphers() and getHashes() reflect the current FIPS state
// rather than returning a stale cached snapshot from before setFips() was
// called. Regression test for https://github.com/nodejs/node/issues/62982.

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const { internalBinding } = require('internal/test/binding');
const { testFipsCrypto } = internalBinding('crypto');

if (!testFipsCrypto())
common.skip('FIPS not supported in this build');

const assert = require('assert');
const { getCiphers, getHashes, setFips, getFips } = require('crypto');

const initialFips = getFips();

// Ensure FIPS is off so we can capture the full algorithm lists as a baseline,
// regardless of whether the system has FIPS on by default.
if (initialFips)
setFips(false);

const ciphersWithoutFips = getCiphers();
const hashesWithoutFips = getHashes();

assert.ok(ciphersWithoutFips.length > 0, 'expected at least one cipher');
assert.ok(hashesWithoutFips.length > 0, 'expected at least one hash');

// Switch to FIPS mode; the lists must be re-derived, not served from cache.
setFips(true);
assert.strictEqual(getFips(), 1);

const ciphersWithFips = getCiphers();
const hashesWithFips = getHashes();

// FIPS mode restricts the visible algorithm set — the lists must shrink
// (or at minimum differ; some platforms expose only FIPS algorithms by
// default, but in that case the full list can't be larger than the FIPS one).
assert.ok(
ciphersWithFips.length <= ciphersWithoutFips.length,
`Expected FIPS cipher list (${ciphersWithFips.length}) to be no larger ` +
`than the full list (${ciphersWithoutFips.length})`
);
assert.ok(
hashesWithFips.length <= hashesWithoutFips.length,
`Expected FIPS hash list (${hashesWithFips.length}) to be no larger ` +
`than the full list (${hashesWithoutFips.length})`
);

// Every FIPS-mode algorithm must also appear in the non-FIPS list.
for (const cipher of ciphersWithFips) {
assert.ok(
ciphersWithoutFips.includes(cipher),
`FIPS cipher '${cipher}' missing from the non-FIPS list`
);
}
for (const hash of hashesWithFips) {
assert.ok(
hashesWithoutFips.includes(hash),
`FIPS hash '${hash}' missing from the non-FIPS list`
);
}

// Turn FIPS back off; the cache must be evicted so the full lists come back.
setFips(false);
assert.strictEqual(getFips(), 0);

assert.deepStrictEqual(getCiphers(), ciphersWithoutFips);
assert.deepStrictEqual(getHashes(), hashesWithoutFips);
Loading