Skip to content

Commit c41ce0f

Browse files
committed
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>
1 parent 8f51878 commit c41ce0f

19 files changed

Lines changed: 451 additions & 176 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
@@ -47,9 +47,12 @@ const {
4747
EVP_PKEY_ML_KEM_1024,
4848
kKeyVariantAES_OCB_128: hasAesOcbMode,
4949
Argon2Job,
50+
getFipsCrypto,
5051
KmacJob,
5152
} = internalBinding('crypto');
5253

54+
const isFips = getFipsCrypto() === 1;
55+
5356
const { getOptionValue } = require('internal/options');
5457

5558
const {
@@ -423,6 +426,8 @@ const conditionalAlgorithms = {
423426
'Ed448': !process.features.openssl_is_boringssl,
424427
'KMAC128': !!KmacJob,
425428
'KMAC256': !!KmacJob,
429+
'KT128': !isFips,
430+
'KT256': !isFips,
426431
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
427432
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
428433
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
@@ -435,6 +440,8 @@ const conditionalAlgorithms = {
435440
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
436441
'SHA3-512': !process.features.openssl_is_boringssl ||
437442
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
443+
'TurboSHAKE128': !isFips,
444+
'TurboSHAKE256': !isFips,
438445
'X448': !process.features.openssl_is_boringssl,
439446
};
440447

@@ -579,6 +586,11 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
579586
}
580587
}
581588

589+
function validateKmacKeyLength(length) {
590+
if ((length < 32 || length % 8) && isFips)
591+
throw lazyDOMException('Invalid key length', 'NotSupportedError');
592+
}
593+
582594
/**
583595
* Converts a bit length to the number of bytes needed to contain it.
584596
* Non-byte lengths are rounded up to the next byte.
@@ -1097,6 +1109,7 @@ module.exports = {
10971109

10981110
kNamedCurveAliases,
10991111
kSupportedAlgorithms,
1112+
isFips,
11001113
normalizeAlgorithm,
11011114
normalizeHashName,
11021115
hasAnyNotIn,
@@ -1106,6 +1119,7 @@ module.exports = {
11061119
jobPromiseThen,
11071120
cleanupWebCryptoResult,
11081121
prepareWebCryptoResult,
1122+
validateKmacKeyLength,
11091123
validateMaxBufferLength,
11101124
numBitsToBytes,
11111125
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 {
@@ -27,8 +26,10 @@ const {
2726
validateMaxBufferLength,
2827
getBufferSourceByteLength,
2928
getBufferSourceBytes,
29+
isFips,
3030
kNamedCurveAliases,
3131
numBitsToBytes,
32+
validateKmacKeyLength,
3233
} = require('internal/crypto/util');
3334
const {
3435
converters: webidl,
@@ -252,30 +253,39 @@ function validateCShakeOutputLength(V) {
252253
}
253254
}
254255

255-
function bufferSourceEqualsAscii(V, string) {
256-
if (getBufferSourceByteLength(V) !== string.length) return false;
257-
258-
const bytes = getBufferSourceBytes(V);
259-
const length = TypedArrayPrototypeGetLength(bytes);
260-
for (let i = 0; i < length; i++) {
261-
if (bytes[i] !== StringPrototypeCharCodeAt(string, i)) return false;
262-
}
263-
return true;
264-
}
256+
const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];
265257

266258
function validateCShakeFunctionName(V) {
267-
if (getBufferSourceByteLength(V) === 0 ||
268-
bufferSourceEqualsAscii(V, 'KMAC') ||
269-
bufferSourceEqualsAscii(V, 'TupleHash') ||
270-
bufferSourceEqualsAscii(V, 'ParallelHash')) {
271-
return;
259+
const length = getBufferSourceByteLength(V);
260+
if (length === 0) return;
261+
262+
if (!isFips) {
263+
const bytes = getBufferSourceBytes(V);
264+
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
265+
const functionName = kCShakeFunctionNames[i];
266+
if (length !== functionName.length) continue;
267+
268+
let j = 0;
269+
for (; j < length; j++) {
270+
if (bytes[j] !== StringPrototypeCharCodeAt(functionName, j)) break;
271+
}
272+
if (j === length) return;
273+
}
272274
}
273275

274276
throw lazyDOMException(
275277
'Unsupported CShakeParams functionName',
276278
'NotSupportedError');
277279
}
278280

281+
function validateCShakeCustomization(V) {
282+
if (isFips && getBufferSourceByteLength(V) !== 0)
283+
throw lazyDOMException(
284+
'Unsupported CShakeParams customization',
285+
'NotSupportedError');
286+
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
287+
}
288+
279289
converters.RsaPssParams = createDictionaryConverter(
280290
'RsaPssParams', [
281291
dictAlgorithm,
@@ -433,7 +443,7 @@ converters.CShakeParams = createDictionaryConverter(
433443
{
434444
key: 'customization',
435445
converter: converters.BufferSource,
436-
validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512),
446+
validator: validateCShakeCustomization,
437447
},
438448
],
439449
]);
@@ -719,6 +729,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) {
719729
key: 'length',
720730
converter: (V, opts) =>
721731
converters['unsigned long'](V, enforceRangeOptions(opts)),
732+
validator: validateKmacKeyLength,
722733
},
723734
],
724735
]);
@@ -732,6 +743,12 @@ converters.KmacParams = createDictionaryConverter(
732743
key: 'outputLength',
733744
converter: (V, opts) =>
734745
converters['unsigned long'](V, enforceRangeOptions(opts)),
746+
validator: (V) => {
747+
if ((V === 0 || V % 8) && isFips)
748+
throw lazyDOMException(
749+
'Invalid KmacParams outputLength',
750+
'NotSupportedError');
751+
},
735752
required: true,
736753
},
737754
{

src/crypto/crypto_hash.cc

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

830+
if (IsFipsEnabled()) {
831+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
832+
return Nothing<void>();
833+
}
834+
830835
CHECK(args[offset]->IsString()); // Algorithm name
831836
Utf8Value algorithm_name(env->isolate(), args[offset]);
832837
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]);
@@ -535,6 +540,11 @@ Maybe<void> KangarooTwelveTraits::AdditionalConfig(
535540
KangarooTwelveConfig* params) {
536541
Environment* env = Environment::GetCurrent(args);
537542

543+
if (IsFipsEnabled()) {
544+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
545+
return Nothing<void>();
546+
}
547+
538548
// args[offset + 0] = algorithm name (string)
539549
CHECK(args[offset]->IsString());
540550
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
@@ -146,6 +146,11 @@ bool InitCryptoOnce(Isolate* isolate) {
146146
// be part of a larger mutex for global OpenSSL state.
147147
static Mutex fips_mutex;
148148

149+
bool IsFipsEnabled() {
150+
Mutex::ScopedLock fips_lock(fips_mutex);
151+
return ncrypto::isFipsEnabled();
152+
}
153+
149154
void InitCryptoOnce() {
150155
Mutex::ScopedLock lock(per_process::cli_options_mutex);
151156
Mutex::ScopedLock fips_lock(fips_mutex);
@@ -223,8 +228,7 @@ void InitCryptoOnce() {
223228

224229
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
225230
Mutex::ScopedLock lock(per_process::cli_options_mutex);
226-
Mutex::ScopedLock fips_lock(fips_mutex);
227-
args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0);
231+
args.GetReturnValue().Set(IsFipsEnabled() ? 1 : 0);
228232
}
229233

230234
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {

src/crypto/crypto_util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ constexpr T NumBitsToBytes(T bits) {
6666
// what went wrong, or std::nullopt when there was nothing to do or the
6767
// options were applied successfully.
6868
std::optional<std::string> ProcessFipsOptions();
69+
bool IsFipsEnabled();
6970

7071
bool InitCryptoOnce(v8::Isolate* isolate);
7172
void InitCryptoOnce();

test/parallel/test-webcrypto-derivekey.js

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

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

test/parallel/test-webcrypto-digest-turboshake-rfc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ const common = require('../common');
55
if (!common.hasCrypto)
66
common.skip('missing crypto');
77

8+
const { hasFIPS } = require('../common/crypto');
9+
10+
if (hasFIPS())
11+
common.skip('TurboSHAKE and KangarooTwelve are not available in FIPS mode');
12+
813
const assert = require('assert');
914
const { subtle } = globalThis.crypto;
1015

0 commit comments

Comments
 (0)