Skip to content

Commit 18c2b33

Browse files
authored
crypto: use current FIPS state for availability
Refresh conditional algorithm registration on FIPS changes and use the current state for parameter restrictions. Use per-algorithm availability checks in normalization and native named key generation. Remove getPqcKeyTypes(). Enable supports() tests under FIPS and cover state transitions in warmed workers. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: #66160 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 3d85c94 commit 18c2b33

42 files changed

Lines changed: 607 additions & 400 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3101,12 +3101,6 @@ const KeyAlgorithm* KeyAlgorithm::FromName(const char* name) {
31013101
return nullptr;
31023102
}
31033103

3104-
void KeyAlgorithm::ForEachPqc(Callback callback) {
3105-
for (const auto* algorithm : kKeyAlgorithms) {
3106-
if (algorithm->isPqc() && algorithm->isAvailable()) callback(*algorithm);
3107-
}
3108-
}
3109-
31103104
bool KeyAlgorithm::isRsa() const {
31113105
return this == &RSA || this == &RSA_PSS;
31123106
}

‎deps/ncrypto/ncrypto.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,8 +1093,6 @@ class KeyAlgorithm final {
10931093
// Look up a canonical name case-insensitively, including unavailable
10941094
// algorithms.
10951095
static const KeyAlgorithm* FromName(const char* name);
1096-
using Callback = std::function<void(const KeyAlgorithm&)>;
1097-
static void ForEachPqc(Callback callback);
10981096

10991097
const char* name() const { return name_; }
11001098
const char* keyTypeName() const {

‎lib/internal/crypto/keygen.js‎

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ const {
44
FunctionPrototypeCall,
55
ObjectDefineProperty,
66
SafeArrayIterator,
7-
StringPrototypeToLowerCase,
87
} = primordials;
98

109
const {
1110
DhKeyPairGenJob,
1211
DsaKeyPairGenJob,
1312
EcKeyPairGenJob,
1413
NamedKeyPairGenJob,
15-
getPqcKeyTypes,
1614
RsaKeyPairGenJob,
1715
SecretKeyGenJob,
1816
kCryptoJobAsync,
@@ -160,17 +158,6 @@ function parseKeyEncoding(keyType, options = kEmptyObject) {
160158
];
161159
}
162160

163-
const namedKeyPairs = {
164-
'__proto__': null,
165-
'ed25519': 'Ed25519',
166-
'ed448': 'Ed448',
167-
'x25519': 'X25519',
168-
'x448': 'X448',
169-
};
170-
for (const name of new SafeArrayIterator(getPqcKeyTypes())) {
171-
namedKeyPairs[StringPrototypeToLowerCase(name)] = name;
172-
}
173-
174161
function createJob(mode, type, options) {
175162
validateString(type, 'type');
176163

@@ -337,12 +324,8 @@ function createJob(mode, type, options) {
337324
generator == null ? 2 : generator,
338325
...encoding);
339326
}
340-
default: {
341-
if (namedKeyPairs[type] === undefined) {
342-
throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
343-
}
344-
return new NamedKeyPairGenJob(mode, namedKeyPairs[type], ...encoding);
345-
}
327+
default:
328+
return new NamedKeyPairGenJob(mode, type, ...encoding);
346329
}
347330
}
348331

‎lib/internal/crypto/util.js‎

Lines changed: 95 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,19 @@ const {
4747
getFipsCrypto,
4848
getFipsCryptoGeneration,
4949
KmacJob,
50-
getPqcKeyTypes,
50+
isKeyAlgorithmAvailable,
5151
} = internalBinding('crypto');
5252

53-
const isFips = getFipsCrypto() === 1;
53+
let fips;
54+
let fipsGeneration;
55+
function isFips() {
56+
const generation = getFipsCryptoGeneration();
57+
if (fipsGeneration !== generation) {
58+
fips = getFipsCrypto() === 1;
59+
fipsGeneration = generation;
60+
}
61+
return fips;
62+
}
5463

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

@@ -119,6 +128,9 @@ let _hashCache;
119128
let _macCache;
120129
if (isBuildingSnapshot()) {
121130
addSerializeCallback(() => {
131+
fips = undefined;
132+
fipsGeneration = undefined;
133+
supportedAlgorithmsGeneration = undefined;
122134
_hashCache = undefined;
123135
_macCache = undefined;
124136
});
@@ -495,53 +507,69 @@ const kAlgorithmDefinitions = {
495507
},
496508
};
497509

498-
// Conditionally supported algorithms
499-
const pqcKeyTypes = getPqcKeyTypes();
500-
501-
const conditionalAlgorithms = {
502-
'AES-OCB': !!hasAesOcbMode,
503-
'Argon2d': !!Argon2Job,
504-
'Argon2i': !!Argon2Job,
505-
'Argon2id': !!Argon2Job,
506-
'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
507-
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
508-
'cSHAKE128': !process.features.openssl_is_boringssl ||
509-
ArrayPrototypeIncludes(getHashes(), 'shake128'),
510-
'cSHAKE256': !process.features.openssl_is_boringssl ||
511-
ArrayPrototypeIncludes(getHashes(), 'shake256'),
512-
'Ed448': !process.features.openssl_is_boringssl,
513-
'KMAC128': !!KmacJob,
514-
'KMAC256': !!KmacJob,
515-
'KT128': !isFips,
516-
'KT256': !isFips,
517-
'ML-DSA-44': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-44'),
518-
'ML-DSA-65': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-65'),
519-
'ML-DSA-87': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-87'),
520-
'ML-KEM-512': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-512'),
521-
'ML-KEM-768': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768'),
522-
'ML-KEM-1024': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024'),
523-
'MLKEM768-P256': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
524-
(!process.features.openssl_is_boringssl ||
525-
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
526-
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
527-
'MLKEM768-X25519': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-768') &&
528-
(!process.features.openssl_is_boringssl ||
529-
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
530-
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
531-
'MLKEM1024-P384': !isFips && ArrayPrototypeIncludes(pqcKeyTypes, 'ML-KEM-1024') &&
532-
(!process.features.openssl_is_boringssl ||
533-
(ArrayPrototypeIncludes(getHashes(), 'sha3-256') &&
534-
ArrayPrototypeIncludes(getHashes(), 'shake256'))),
535-
'SHA3-256': !process.features.openssl_is_boringssl ||
536-
ArrayPrototypeIncludes(getHashes(), 'sha3-256'),
537-
'SHA3-384': !process.features.openssl_is_boringssl ||
538-
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
539-
'SHA3-512': !process.features.openssl_is_boringssl ||
540-
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
541-
'TurboSHAKE128': !isFips,
542-
'TurboSHAKE256': !isFips,
543-
'X448': !process.features.openssl_is_boringssl,
544-
};
510+
function has(algorithms, name) {
511+
return ArrayPrototypeIncludes(algorithms, name);
512+
}
513+
514+
// Re-evaluated when the active FIPS state changes.
515+
function getConditionalAlgorithms() {
516+
const mlKem768 = isKeyAlgorithmAvailable('ML-KEM-768');
517+
const mlKem1024 = isKeyAlgorithmAvailable('ML-KEM-1024');
518+
const ciphers = getCiphers();
519+
const hashes = getHashes();
520+
const macs = getMacs();
521+
522+
const fips = isFips();
523+
524+
return {
525+
'AES-OCB': !!hasAesOcbMode &&
526+
(has(ciphers, 'aes-128-ocb') ||
527+
has(ciphers, 'aes-192-ocb') ||
528+
has(ciphers, 'aes-256-ocb')),
529+
'Argon2d': !!Argon2Job && !fips,
530+
'Argon2i': !!Argon2Job && !fips,
531+
'Argon2id': !!Argon2Job && !fips,
532+
'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
533+
has(ciphers, 'chacha20-poly1305'),
534+
'cSHAKE128': has(hashes, 'shake128'),
535+
'cSHAKE256': has(hashes, 'shake256'),
536+
'Ed25519': isKeyAlgorithmAvailable('Ed25519'),
537+
'Ed448': isKeyAlgorithmAvailable('Ed448'),
538+
'KMAC128': !!KmacJob && has(macs, 'kmac128'),
539+
'KMAC256': !!KmacJob && has(macs, 'kmac256'),
540+
'KT128': !fips,
541+
'KT256': !fips,
542+
'ML-DSA-44': isKeyAlgorithmAvailable('ML-DSA-44'),
543+
'ML-DSA-65': isKeyAlgorithmAvailable('ML-DSA-65'),
544+
'ML-DSA-87': isKeyAlgorithmAvailable('ML-DSA-87'),
545+
'ML-KEM-512': isKeyAlgorithmAvailable('ML-KEM-512'),
546+
'ML-KEM-768': mlKem768,
547+
'ML-KEM-1024': mlKem1024,
548+
'MLKEM768-P256': !fips && mlKem768 &&
549+
(!process.features.openssl_is_boringssl ||
550+
(has(hashes, 'sha3-256') &&
551+
has(hashes, 'shake256'))),
552+
'MLKEM768-X25519': !fips && mlKem768 &&
553+
(!process.features.openssl_is_boringssl ||
554+
(has(hashes, 'sha3-256') &&
555+
has(hashes, 'shake256'))),
556+
'MLKEM1024-P384': !fips && mlKem1024 &&
557+
(!process.features.openssl_is_boringssl ||
558+
(has(hashes, 'sha3-256') &&
559+
has(hashes, 'shake256'))),
560+
'SHA-1': has(hashes, 'sha1'),
561+
'SHA-256': has(hashes, 'sha256'),
562+
'SHA-384': has(hashes, 'sha384'),
563+
'SHA-512': has(hashes, 'sha512'),
564+
'SHA3-256': has(hashes, 'sha3-256'),
565+
'SHA3-384': has(hashes, 'sha3-384'),
566+
'SHA3-512': has(hashes, 'sha3-512'),
567+
'TurboSHAKE128': !fips,
568+
'TurboSHAKE256': !fips,
569+
'X25519': isKeyAlgorithmAvailable('X25519'),
570+
'X448': isKeyAlgorithmAvailable('X448'),
571+
};
572+
}
545573

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

628-
const { algorithms: kSupportedAlgorithms, nameMap: kAlgorithmNameMap } =
629-
createSupportedAlgorithms(kAlgorithmDefinitions);
657+
let supportedAlgorithms;
658+
let supportedAlgorithmsGeneration;
659+
function getSupportedAlgorithms() {
660+
const generation = getFipsCryptoGeneration();
661+
if (supportedAlgorithmsGeneration !== generation) {
662+
supportedAlgorithms = createSupportedAlgorithms(kAlgorithmDefinitions);
663+
supportedAlgorithmsGeneration = generation;
664+
}
665+
return supportedAlgorithms;
666+
}
630667

631668
const simpleAlgorithmDictionaries = {
632669
AesCbcParams: { iv: 'BufferSource' },
@@ -688,7 +725,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
688725
}
689726

690727
function validateKmacKeyLength(length) {
691-
if ((length < 32 || length % 8) && isFips)
728+
if ((length < 32 || length % 8) && isFips())
692729
throw lazyDOMException('Invalid key length', 'NotSupportedError');
693730
}
694731

@@ -751,16 +788,16 @@ function normalizeAlgorithm(algorithm, op) {
751788

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

754-
// 1.
755-
const registeredAlgorithms = kSupportedAlgorithms[op];
756791
// 2. 3.
757792
const initialAlg = webidl.converters.Algorithm(algorithm,
758793
kNormalizeAlgorithmOpts);
794+
const { algorithms, nameMap } = getSupportedAlgorithms();
795+
const registeredAlgorithms = algorithms[op];
759796
// 4.
760797
let algName = initialAlg.name;
761798

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

11991236
kNamedCurveAliases,
1200-
kSupportedAlgorithms,
1237+
get kSupportedAlgorithms() {
1238+
return getSupportedAlgorithms().algorithms;
1239+
},
12011240
isFips,
12021241
normalizeAlgorithm,
12031242
normalizeHashName,

‎lib/internal/crypto/webcrypto.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1757,7 +1757,6 @@ class SubtleCrypto {
17571757
}
17581758

17591759
// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
1760-
// TODO(panva): Make supports() account for the active FIPS state.
17611760
static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
17621761
emitExperimentalWarning('The supports Web Crypto API method');
17631762
if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');

‎lib/internal/crypto/webidl.js‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ const {
4343
type,
4444
} = require('internal/webidl');
4545

46-
const kRsaKeyGenMinimumModulusLength = isFips ? 2048 : 512;
47-
4846
function validateByteLength(buf, name, target) {
4947
if (getBufferSourceByteLength(buf) !== target) {
5048
throw lazyDOMException(
@@ -157,6 +155,7 @@ const dictRsaKeyGenParams = [
157155
converter: (V, opts) =>
158156
converters['unsigned long'](V, enforceRangeOptions(opts)),
159157
validator: (modulusLength) => {
158+
const kRsaKeyGenMinimumModulusLength = isFips() ? 2048 : 512;
160159
if (modulusLength < kRsaKeyGenMinimumModulusLength) {
161160
throw lazyDOMException(
162161
`algorithm.modulusLength must be at least ${kRsaKeyGenMinimumModulusLength}`,
@@ -285,7 +284,7 @@ function validateCShakeFunctionName(V) {
285284
const length = getBufferSourceByteLength(V);
286285
if (length === 0) return;
287286

288-
if (!isFips) {
287+
if (!isFips()) {
289288
const bytes = getBufferSourceBytes(V);
290289
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
291290
const functionName = kCShakeFunctionNames[i];
@@ -305,7 +304,7 @@ function validateCShakeFunctionName(V) {
305304
}
306305

307306
function validateCShakeCustomization(V) {
308-
if (isFips && getBufferSourceByteLength(V) !== 0)
307+
if (isFips() && getBufferSourceByteLength(V) !== 0)
309308
throw lazyDOMException(
310309
'Unsupported CShakeParams customization',
311310
'NotSupportedError');
@@ -782,7 +781,7 @@ converters.KmacParams = createDictionaryConverter(
782781
converter: (V, opts) =>
783782
converters['unsigned long'](V, enforceRangeOptions(opts)),
784783
validator: (V) => {
785-
if ((V === 0 || V % 8) && isFips)
784+
if ((V === 0 || V % 8) && isFips())
786785
throw lazyDOMException(
787786
'Invalid KmacParams outputLength',
788787
'NotSupportedError');

‎node.gyp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,6 @@
410410
'src/crypto/crypto_context.cc',
411411
'src/crypto/crypto_tls_certificates.cc',
412412
'src/crypto/crypto_ec.cc',
413-
'src/crypto/crypto_pqc.cc',
414413
'src/crypto/crypto_kem.cc',
415414
'src/crypto/crypto_hmac.cc',
416415
'src/crypto/crypto_kmac.cc',
@@ -451,7 +450,6 @@
451450
'src/crypto/crypto_context.h',
452451
'src/crypto/crypto_tls_certificates.h',
453452
'src/crypto/crypto_ec.h',
454-
'src/crypto/crypto_pqc.h',
455453
'src/crypto/crypto_hkdf.h',
456454
'src/crypto/crypto_pbkdf2.h',
457455
'src/crypto/crypto_sig.h',

‎src/crypto/README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ following table:
4646
| `crypto_keys` | Utilities for using and generating secret, private, and public keys. |
4747
| `crypto_mac` | Provider-generic MAC implementations. |
4848
| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. |
49-
| `crypto_pqc` | Post-quantum algorithm enumeration. |
5049
| `crypto_rsa` | RSA Key Generation functions. |
5150
| `crypto_scrypt` | Scrypt key / bit generation implementation. |
5251
| `crypto_sig` | General digital signature and verification utilities. |
@@ -205,10 +204,11 @@ Public input validation remains specific to each API: PQC JWK `alg` values use
205204
exact canonical names such as `ML-DSA-44`, while raw imports require exact public
206205
`asymmetricKeyType` values such as `ml-dsa-44`.
207206

208-
The internal JavaScript binding exposes `getPqcKeyTypes()` for the available
209-
known PQC algorithm names in their canonical spelling. Named key generation
210-
passes algorithm names to `NamedKeyPairGenJob`, which resolves the name to a static
211-
`KeyAlgorithm` descriptor. Asymmetric key IDs are not exposed to JavaScript.
207+
The internal JavaScript binding exposes `isKeyAlgorithmAvailable()` to check
208+
whether a known key algorithm is available from the current backend. Named key
209+
generation passes algorithm names to `NamedKeyPairGenJob`, which resolves the
210+
name to a static `KeyAlgorithm` descriptor. Asymmetric key IDs are not exposed to
211+
JavaScript.
212212

213213
Real EC curve and ASN.1/OID NIDs still have their own uses. The EC generation
214214
path keeps Ed/X algorithm descriptors separate from curve NIDs while preserving

0 commit comments

Comments
 (0)