Skip to content

Commit c2ec40c

Browse files
panvaaduh95
authored andcommitted
crypto: disable non-FIPS WebCrypto paths in FIPS mode
Hide TurboSHAKE and KangarooTwelve when FIPS is enabled. Reject cSHAKE and KMAC parameters that require implementations outside the OpenSSL provider, while keeping provider-backed paths available. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #65172 Backport-PR-URL: #66233 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 810a6b0 commit c2ec40c

20 files changed

Lines changed: 473 additions & 183 deletions

‎lib/internal/crypto/mac.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const {
2020
normalizeHashName,
2121
numBitsToBytes,
2222
truncateToBitLength,
23+
validateKmacKeyLength,
2324
} = require('internal/crypto/util');
2425

2526
const {
@@ -60,6 +61,9 @@ function normalizeKeyLength(handle, algorithm) {
6061
length = algorithm.length;
6162
}
6263

64+
if (algorithm.name === 'KMAC128' || algorithm.name === 'KMAC256')
65+
validateKmacKeyLength(length);
66+
6367
return { handle, length };
6468
}
6569

‎lib/internal/crypto/util.js‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ const {
5050
EVP_PKEY_ML_KEM_1024,
5151
kKeyVariantAES_OCB_128: hasAesOcbMode,
5252
Argon2Job,
53+
getFipsCrypto,
5354
getFipsCryptoGeneration,
5455
KmacJob,
5556
} = internalBinding('crypto');
5657

58+
const isFips = getFipsCrypto() === 1;
59+
5760
const { getOptionValue } = require('internal/options');
5861

5962
const {
@@ -480,6 +483,8 @@ const conditionalAlgorithms = {
480483
'Ed448': !process.features.openssl_is_boringssl,
481484
'KMAC128': !!KmacJob,
482485
'KMAC256': !!KmacJob,
486+
'KT128': !isFips,
487+
'KT256': !isFips,
483488
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
484489
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
485490
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
@@ -492,6 +497,8 @@ const conditionalAlgorithms = {
492497
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
493498
'SHA3-512': !process.features.openssl_is_boringssl ||
494499
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
500+
'TurboSHAKE128': !isFips,
501+
'TurboSHAKE256': !isFips,
495502
'X448': !process.features.openssl_is_boringssl,
496503
};
497504

@@ -636,6 +643,11 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
636643
}
637644
}
638645

646+
function validateKmacKeyLength(length) {
647+
if ((length < 32 || length % 8) && isFips)
648+
throw lazyDOMException('Invalid key length', 'NotSupportedError');
649+
}
650+
639651
/**
640652
* Converts a bit length to the number of bytes needed to contain it.
641653
* Non-byte lengths are rounded up to the next byte.
@@ -1141,6 +1153,7 @@ module.exports = {
11411153

11421154
kNamedCurveAliases,
11431155
kSupportedAlgorithms,
1156+
isFips,
11441157
normalizeAlgorithm,
11451158
normalizeHashName,
11461159
hasAnyNotIn,
@@ -1150,6 +1163,7 @@ module.exports = {
11501163
jobPromiseThen,
11511164
cleanupWebCryptoResult,
11521165
prepareWebCryptoResult,
1166+
validateKmacKeyLength,
11531167
validateMaxBufferLength,
11541168
numBitsToBytes,
11551169
truncateToBitLength,

‎lib/internal/crypto/webidl.js‎

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const {
99
StringPrototypeSplit,
1010
StringPrototypeStartsWith,
1111
StringPrototypeToLowerCase,
12-
TypedArrayPrototypeGetLength,
1312
} = primordials;
1413

1514
const {
@@ -30,8 +29,10 @@ const {
3029
validateMaxBufferLength,
3130
getBufferSourceByteLength,
3231
getBufferSourceBytes,
32+
isFips,
3333
kNamedCurveAliases,
3434
numBitsToBytes,
35+
validateKmacKeyLength,
3536
} = require('internal/crypto/util');
3637
const {
3738
converters: webidl,
@@ -303,30 +304,39 @@ function validateCShakeOutputLength(V) {
303304
}
304305
}
305306

306-
function bufferSourceEqualsAscii(V, string) {
307-
if (getBufferSourceByteLength(V) !== string.length) return false;
308-
309-
const bytes = getBufferSourceBytes(V);
310-
const length = TypedArrayPrototypeGetLength(bytes);
311-
for (let i = 0; i < length; i++) {
312-
if (bytes[i] !== StringPrototypeCharCodeAt(string, i)) return false;
313-
}
314-
return true;
315-
}
307+
const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];
316308

317309
function validateCShakeFunctionName(V) {
318-
if (getBufferSourceByteLength(V) === 0 ||
319-
bufferSourceEqualsAscii(V, 'KMAC') ||
320-
bufferSourceEqualsAscii(V, 'TupleHash') ||
321-
bufferSourceEqualsAscii(V, 'ParallelHash')) {
322-
return;
310+
const length = getBufferSourceByteLength(V);
311+
if (length === 0) return;
312+
313+
if (!isFips) {
314+
const bytes = getBufferSourceBytes(V);
315+
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
316+
const functionName = kCShakeFunctionNames[i];
317+
if (length !== functionName.length) continue;
318+
319+
let j = 0;
320+
for (; j < length; j++) {
321+
if (bytes[j] !== StringPrototypeCharCodeAt(functionName, j)) break;
322+
}
323+
if (j === length) return;
324+
}
323325
}
324326

325327
throw lazyDOMException(
326328
'Unsupported CShakeParams functionName',
327329
'NotSupportedError');
328330
}
329331

332+
function validateCShakeCustomization(V) {
333+
if (isFips && getBufferSourceByteLength(V) !== 0)
334+
throw lazyDOMException(
335+
'Unsupported CShakeParams customization',
336+
'NotSupportedError');
337+
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
338+
}
339+
330340
converters.RsaPssParams = createDictionaryConverter(
331341
'RsaPssParams', [
332342
dictAlgorithm,
@@ -484,7 +494,7 @@ converters.CShakeParams = createDictionaryConverter(
484494
{
485495
key: 'customization',
486496
converter: converters.BufferSource,
487-
validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512),
497+
validator: validateCShakeCustomization,
488498
},
489499
],
490500
]);
@@ -782,6 +792,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) {
782792
key: 'length',
783793
converter: (V, opts) =>
784794
converters['unsigned long'](V, enforceRangeOptions(opts)),
795+
validator: validateKmacKeyLength,
785796
},
786797
],
787798
]);
@@ -795,6 +806,12 @@ converters.KmacParams = createDictionaryConverter(
795806
key: 'outputLength',
796807
converter: (V, opts) =>
797808
converters['unsigned long'](V, enforceRangeOptions(opts)),
809+
validator: (V) => {
810+
if ((V === 0 || V % 8) && isFips)
811+
throw lazyDOMException(
812+
'Invalid KmacParams outputLength',
813+
'NotSupportedError');
814+
},
798815
required: true,
799816
},
800817
{

‎src/crypto/crypto_hash.cc‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,11 @@ Maybe<void> CShakeTraits::AdditionalConfig(
10721072
CShakeConfig* params) {
10731073
Environment* env = Environment::GetCurrent(args);
10741074

1075+
if (IsFipsEnabled()) {
1076+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
1077+
return Nothing<void>();
1078+
}
1079+
10751080
CHECK(args[offset]->IsString()); // Algorithm name
10761081
Utf8Value algorithm_name(env->isolate(), args[offset]);
10771082
std::string_view algorithm_str = algorithm_name.ToStringView();

‎src/crypto/crypto_kmac.cc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ bool DeriveBitsWithCShake(const KmacConfig& params,
151151
const void* key_data,
152152
size_t key_size,
153153
ByteSource* out) {
154+
if (IsFipsEnabled()) return false;
155+
154156
const size_t key_length_bytes = NumBitsToBytes(params.key_length);
155157
if (key_size < key_length_bytes) return false;
156158

‎src/crypto/crypto_turboshake.cc‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ Maybe<void> TurboShakeTraits::AdditionalConfig(
428428
TurboShakeConfig* params) {
429429
Environment* env = Environment::GetCurrent(args);
430430

431+
if (IsFipsEnabled()) {
432+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
433+
return Nothing<void>();
434+
}
435+
431436
// args[offset + 0] = algorithm name (string)
432437
CHECK(args[offset]->IsString());
433438
Utf8Value algorithm_name(env->isolate(), args[offset]);
@@ -533,6 +538,11 @@ Maybe<void> KangarooTwelveTraits::AdditionalConfig(
533538
KangarooTwelveConfig* params) {
534539
Environment* env = Environment::GetCurrent(args);
535540

541+
if (IsFipsEnabled()) {
542+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
543+
return Nothing<void>();
544+
}
545+
536546
// args[offset + 0] = algorithm name (string)
537547
CHECK(args[offset]->IsString());
538548
Utf8Value algorithm_name(env->isolate(), args[offset]);

‎src/crypto/crypto_util.cc‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,11 @@ bool InitCryptoOnce(Isolate* isolate) {
495495
// be part of a larger mutex for global OpenSSL state.
496496
static Mutex fips_mutex;
497497

498+
bool IsFipsEnabled() {
499+
Mutex::ScopedLock fips_lock(fips_mutex);
500+
return ncrypto::isFipsEnabled();
501+
}
502+
498503
void InitCryptoOnce() {
499504
Mutex::ScopedLock lock(per_process::cli_options_mutex);
500505
Mutex::ScopedLock fips_lock(fips_mutex);
@@ -557,8 +562,7 @@ void InitCryptoOnce() {
557562

558563
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
559564
Mutex::ScopedLock lock(per_process::cli_options_mutex);
560-
Mutex::ScopedLock fips_lock(fips_mutex);
561-
args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0);
565+
args.GetReturnValue().Set(IsFipsEnabled() ? 1 : 0);
562566
}
563567

564568
void GetFipsCryptoGeneration(const FunctionCallbackInfo<Value>& args) {

‎src/crypto/crypto_util.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ constexpr T NumBitsToBytes(T bits) {
6868
// options were applied successfully.
6969
std::optional<std::string> ProcessFipsOptions();
7070
void InstallFipsIndicatorCallback();
71+
bool IsFipsEnabled();
7172

7273
bool InitCryptoOnce(v8::Isolate* isolate);
7374
void InitCryptoOnce();

‎test/parallel/test-crypto-key-objects-to-crypto-key.js‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
} = require('crypto');
1515
const { hasFIPS } = require('../common/crypto');
1616
const { kSupportedAlgorithms } = require('internal/crypto/util');
17+
const fips = hasFIPS();
1718
const rejectsXCurves = hasFIPS(3, 5);
1819

1920
const hashes = Object.keys(kSupportedAlgorithms.digest).filter((name) => {
@@ -135,14 +136,24 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) {
135136
const key = createSecretKey(randomBytes(32));
136137
const usages = ['sign', 'verify'];
137138

138-
if (allowZeroKey) {
139+
if (allowZeroKey && !fips) {
139140
const zeroKey = createSecretKey(Buffer.alloc(0))
140141
.toCryptoKey(algorithm, true, usages);
141142
assert.strictEqual(zeroKey.algorithm.length, 0);
142143

143144
const explicitZeroKey = createSecretKey(Buffer.alloc(0))
144145
.toCryptoKey({ ...algorithm, length: 0 }, true, usages);
145146
assert.strictEqual(explicitZeroKey.algorithm.length, 0);
147+
} else if (allowZeroKey) {
148+
for (const zeroAlgorithm of [algorithm, { ...algorithm, length: 0 }]) {
149+
assert.throws(() => {
150+
createSecretKey(Buffer.alloc(0))
151+
.toCryptoKey(zeroAlgorithm, true, usages);
152+
}, {
153+
name: 'NotSupportedError',
154+
message: 'Invalid key length',
155+
});
156+
}
146157
} else {
147158
assert.throws(() => {
148159
createSecretKey(Buffer.alloc(0)).toCryptoKey(algorithm, true, usages);
@@ -157,12 +168,15 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) {
157168
message: 'Usages cannot be empty when importing a secret key.'
158169
});
159170

160-
assert.throws(() => {
161-
key.toCryptoKey({ ...algorithm, length: 0 }, true, usages);
162-
}, {
163-
name: 'DataError',
164-
message: invalidLengthMessage,
165-
});
171+
assert.throws(
172+
() => key.toCryptoKey({ ...algorithm, length: 0 }, true, usages),
173+
allowZeroKey && fips ? {
174+
name: 'NotSupportedError',
175+
message: 'Invalid key length',
176+
} : {
177+
name: 'DataError',
178+
message: invalidLengthMessage,
179+
});
166180
}
167181

168182
function hmacVectors() {

‎test/parallel/test-webcrypto-derivekey.js‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ const fips4 = hasFIPS(4);
283283
})().then(common.mustCall());
284284
}
285285

286-
if (hasOpenSSL(3)) {
286+
if (hasOpenSSL(3) && !hasFIPS()) {
287287
(async () => {
288288
const derivedKeyAlgorithm = { name: 'KMAC128', length: 0 };
289289
const usages = ['sign'];
@@ -325,11 +325,7 @@ if (hasOpenSSL(3)) {
325325
name: 'KMAC128',
326326
outputLength: 256,
327327
}, derived, new Uint8Array());
328-
if (fips4) {
329-
await assert.rejects(signature, { name: 'OperationError' });
330-
} else {
331-
assert.strictEqual((await signature).byteLength, 32);
332-
}
328+
assert.strictEqual((await signature).byteLength, 32);
333329
}
334330
})().then(common.mustCall());
335331
}

0 commit comments

Comments
 (0)