crypto: use current FIPS state for availability by panva · Pull Request #66160 · nodejs/node · GitHub
Skip to content
Merged
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
6 changes: 0 additions & 6 deletions deps/ncrypto/ncrypto.cc
2 changes: 0 additions & 2 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -1093,8 +1093,6 @@ class KeyAlgorithm final {
// Look up a canonical name case-insensitively, including unavailable
// algorithms.
static const KeyAlgorithm* FromName(const char* name);
using Callback = std::function<void(const KeyAlgorithm&)>;
static void ForEachPqc(Callback callback);

const char* name() const { return name_; }
const char* keyTypeName() const {
Expand Down
21 changes: 2 additions & 19 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ const {
FunctionPrototypeCall,
ObjectDefineProperty,
SafeArrayIterator,
StringPrototypeToLowerCase,
} = primordials;

const {
DhKeyPairGenJob,
DsaKeyPairGenJob,
EcKeyPairGenJob,
NamedKeyPairGenJob,
getPqcKeyTypes,
RsaKeyPairGenJob,
SecretKeyGenJob,
kCryptoJobAsync,
Expand Down Expand Up @@ -160,17 +158,6 @@ function parseKeyEncoding(keyType, options = kEmptyObject) {
];
}

const namedKeyPairs = {
'__proto__': null,
'ed25519': 'Ed25519',
'ed448': 'Ed448',
'x25519': 'X25519',
'x448': 'X448',
};
for (const name of new SafeArrayIterator(getPqcKeyTypes())) {
namedKeyPairs[StringPrototypeToLowerCase(name)] = name;
}

function createJob(mode, type, options) {
validateString(type, 'type');

Expand Down Expand Up @@ -337,12 +324,8 @@ function createJob(mode, type, options) {
generator == null ? 2 : generator,
...encoding);
}
default: {
if (namedKeyPairs[type] === undefined) {
throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
}
return new NamedKeyPairGenJob(mode, namedKeyPairs[type], ...encoding);
}
default:
return new NamedKeyPairGenJob(mode, type, ...encoding);
}
}

Expand Down
151 changes: 95 additions & 56 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,19 @@ const {
getFipsCrypto,
getFipsCryptoGeneration,
KmacJob,
getPqcKeyTypes,
isKeyAlgorithmAvailable,
} = internalBinding('crypto');

const isFips = getFipsCrypto() === 1;
let fips;
let fipsGeneration;
function isFips() {
const generation = getFipsCryptoGeneration();
if (fipsGeneration !== generation) {
fips = getFipsCrypto() === 1;
fipsGeneration = generation;
}
return fips;
}

const { getOptionValue } = require('internal/options');

Expand Down Expand Up @@ -119,6 +128,9 @@ let _hashCache;
let _macCache;
if (isBuildingSnapshot()) {
addSerializeCallback(() => {
fips = undefined;
fipsGeneration = undefined;
supportedAlgorithmsGeneration = undefined;
_hashCache = undefined;
_macCache = undefined;
});
Expand Down Expand Up @@ -495,53 +507,69 @@ const kAlgorithmDefinitions = {
},
};

// Conditionally supported algorithms
const pqcKeyTypes = getPqcKeyTypes();

const conditionalAlgorithms = {
'AES-OCB': !!hasAesOcbMode,
'Argon2d': !!Argon2Job,
'Argon2i': !!Argon2Job,
'Argon2id': !!Argon2Job,
'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
'cSHAKE128': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'shake128'),
'cSHAKE256': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'shake256'),
'Ed448': !process.features.openssl_is_boringssl,
'KMAC128': !!KmacJob,
'KMAC256': !!KmacJob,
'KT128': !isFips,
'KT256': !isFips,
'ML-DSA-44': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-44'),
'ML-DSA-65': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-65'),
'ML-DSA-87': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-87'),
'ML-KEM-512': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-512'),
'ML-KEM-768': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768'),
'ML-KEM-1024': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024'),
'MLKEM768-P256': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
(!process.features.openssl_is_boringssl ||
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
'MLKEM768-X25519': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
(!process.features.openssl_is_boringssl ||
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
'MLKEM1024-P384': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024') &&
(!process.features.openssl_is_boringssl ||
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
'SHA3-256': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
'SHA3-384': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
'SHA3-512': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
'TurboSHAKE128': !isFips,
'TurboSHAKE256': !isFips,
'X448': !process.features.openssl_is_boringssl,
};
function has(algorithms, name) {
return ArrayPrototypeIncludes(algorithms, name);
}

// Re-evaluated when the active FIPS state changes.
function getConditionalAlgorithms() {
const mlKem768 = isKeyAlgorithmAvailable('ML-KEM-768');
const mlKem1024 = isKeyAlgorithmAvailable('ML-KEM-1024');
const ciphers = getCiphers();
const hashes = getHashes();
const macs = getMacs();

const fips = isFips();

return {
'AES-OCB': !!hasAesOcbMode &&
(has(ciphers, 'aes-128-ocb') ||
has(ciphers, 'aes-192-ocb') ||
has(ciphers, 'aes-256-ocb')),
'Argon2d': !!Argon2Job && !fips,
'Argon2i': !!Argon2Job && !fips,
'Argon2id': !!Argon2Job && !fips,
'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
has(ciphers, 'chacha20-poly1305'),
'cSHAKE128': has(hashes, 'shake128'),
'cSHAKE256': has(hashes, 'shake256'),
'Ed25519': isKeyAlgorithmAvailable('Ed25519'),
'Ed448': isKeyAlgorithmAvailable('Ed448'),
'KMAC128': !!KmacJob && has(macs, 'kmac128'),
'KMAC256': !!KmacJob && has(macs, 'kmac256'),
'KT128': !fips,
'KT256': !fips,
'ML-DSA-44': isKeyAlgorithmAvailable('ML-DSA-44'),
'ML-DSA-65': isKeyAlgorithmAvailable('ML-DSA-65'),
'ML-DSA-87': isKeyAlgorithmAvailable('ML-DSA-87'),
'ML-KEM-512': isKeyAlgorithmAvailable('ML-KEM-512'),
'ML-KEM-768': mlKem768,
'ML-KEM-1024': mlKem1024,
'MLKEM768-P256': !fips && mlKem768 &&
(!process.features.openssl_is_boringssl ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'MLKEM768-X25519': !fips && mlKem768 &&
(!process.features.openssl_is_boringssl ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'MLKEM1024-P384': !fips && mlKem1024 &&
(!process.features.openssl_is_boringssl ||
(has(hashes, 'sha3-256') &&
has(hashes, 'shake256'))),
'SHA-1': has(hashes, 'sha1'),
'SHA-256': has(hashes, 'sha256'),
'SHA-384': has(hashes, 'sha384'),
'SHA-512': has(hashes, 'sha512'),
'SHA3-256': has(hashes, 'sha3-256'),
'SHA3-384': has(hashes, 'sha3-384'),
'SHA3-512': has(hashes, 'sha3-512'),
'TurboSHAKE128': !fips,
'TurboSHAKE256': !fips,
'X25519': isKeyAlgorithmAvailable('X25519'),
'X448': isKeyAlgorithmAvailable('X448'),
};
}

// Experimental algorithms
const experimentalAlgorithms = [
Expand Down Expand Up @@ -578,6 +606,7 @@ const experimentalAlgorithms = [
// Also builds a parallel Map<UPPERCASED_NAME, canonicalName> per operation
// for O(1) case-insensitive algorithm name lookup in normalizeAlgorithm.
function createSupportedAlgorithms(algorithmDefs) {
const conditionalAlgorithms = getConditionalAlgorithms();
// Detached below rather than declared `__proto__: null`: V8 puts that
// literal form in dictionary mode, slowing every registry lookup.
const result = {};
Expand Down Expand Up @@ -625,8 +654,16 @@ function createSupportedAlgorithms(algorithmDefs) {
return { algorithms: result, nameMap };
}

const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
createSupportedAlgorithms(kAlgorithmDefinitions);
let supportedAlgorithms;
let supportedAlgorithmsGeneration;
function getSupportedAlgorithms() {
const generation = getFipsCryptoGeneration();
if (supportedAlgorithmsGeneration !== generation) {
supportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
supportedAlgorithmsGeneration = generation;
}
return supportedAlgorithms;
}

const simpleAlgorithmDictionaries = {
AesCbcParams: { iv: 'BufferSource' },
Expand Down Expand Up @@ -688,7 +725,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
}

function validateKmacKeyLength(length) {
if ((length < 32 || length % 8) && isFips)
if ((length < 32 || length % 8) && isFips())
throw lazyDOMException('Invalid key length', 'NotSupportedError');
}

Expand Down Expand Up @@ -751,16 +788,16 @@ function normalizeAlgorithm(algorithm, op) {

webidl ??= require('internal/crypto/webidl');

// 1.
const registeredAlgorithms = kSupportedAlgorithms[op];
// 2. 3.
const initialAlg = webidl.converters.Algorithm(algorithm,
kNormalizeAlgorithmOpts);
const { algorithms, nameMap } = getSupportedAlgorithms();
const registeredAlgorithms = algorithms[op];
// 4.
let algName = initialAlg.name;

// 5. Case-insensitive lookup via pre-built Map (O(1) instead of O(n)).
const canonicalName = kAlgorithmNameMap[op]?.get(
const canonicalName = nameMap[op]?.get(
StringPrototypeToUpperCase(algName));
if (canonicalName === undefined)
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
Expand Down Expand Up @@ -1197,7 +1234,9 @@ module.exports = {
toBuf,

kNamedCurveAliases,
kSupportedAlgorithms,
get kSupportedAlgorithms() {
return getSupportedAlgorithms().algorithms;
},
isFips,
normalizeAlgorithm,
normalizeHashName,
Expand Down
1 change: 0 additions & 1 deletion lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,7 +1757,6 @@ class SubtleCrypto {
}

// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
// TODO(panva): Make supports() account for the active FIPS state.
static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
emitExperimentalWarning('The supports Web Crypto API method');
if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');
Expand Down
9 changes: 4 additions & 5 deletions lib/internal/crypto/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ const {
type,
} = require('internal/webidl');

const kRsaKeyGenMinimumModulusLength = isFips ? 2048 : 512;

function validateByteLength(buf, name, target) {
if (getBufferSourceByteLength(buf) !== target) {
throw lazyDOMException(
Expand Down Expand Up @@ -157,6 +155,7 @@ const dictRsaKeyGenParams = [
converter: (V, opts) =>
converters['unsigned long'](V, enforceRangeOptions(opts)),
validator: (modulusLength) => {
const kRsaKeyGenMinimumModulusLength = isFips() ? 2048 : 512;
if (modulusLength < kRsaKeyGenMinimumModulusLength) {
throw lazyDOMException(
`algorithm.modulusLength must be at least ${kRsaKeyGenMinimumModulusLength}`,
Expand Down Expand Up @@ -285,7 +284,7 @@ function validateCShakeFunctionName(V) {
const length = getBufferSourceByteLength(V);
if (length === 0) return;

if (!isFips) {
if (!isFips()) {
const bytes = getBufferSourceBytes(V);
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
const functionName = kCShakeFunctionNames[i];
Expand All @@ -305,7 +304,7 @@ function validateCShakeFunctionName(V) {
}

function validateCShakeCustomization(V) {
if (isFips && getBufferSourceByteLength(V) !== 0)
if (isFips() && getBufferSourceByteLength(V) !== 0)
throw lazyDOMException(
'Unsupported CShakeParams customization',
'NotSupportedError');
Expand Down Expand Up @@ -782,7 +781,7 @@ converters.KmacParams = createDictionaryConverter(
converter: (V, opts) =>
converters['unsigned long'](V, enforceRangeOptions(opts)),
validator: (V) => {
if ((V === 0 || V % 8) && isFips)
if ((V === 0 || V % 8) && isFips())
throw lazyDOMException(
'Invalid KmacParams outputLength',
'NotSupportedError');
Expand Down
2 changes: 0 additions & 2 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@
'src/crypto/crypto_context.cc',
'src/crypto/crypto_tls_certificates.cc',
'src/crypto/crypto_ec.cc',
'src/crypto/crypto_pqc.cc',
'src/crypto/crypto_kem.cc',
'src/crypto/crypto_hmac.cc',
'src/crypto/crypto_kmac.cc',
Expand Down Expand Up @@ -451,7 +450,6 @@
'src/crypto/crypto_context.h',
'src/crypto/crypto_tls_certificates.h',
'src/crypto/crypto_ec.h',
'src/crypto/crypto_pqc.h',
'src/crypto/crypto_hkdf.h',
'src/crypto/crypto_pbkdf2.h',
'src/crypto/crypto_sig.h',
Expand Down
10 changes: 5 additions & 5 deletions src/crypto/README.md
Loading
Loading