Skip to content

Commit d0aa98b

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 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 732c48b commit d0aa98b

20 files changed

Lines changed: 472 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
@@ -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 {
@@ -415,6 +418,8 @@ const conditionalAlgorithms = {
415418
'Ed448': !process.features.openssl_is_boringssl,
416419
'KMAC128': !!KmacJob,
417420
'KMAC256': !!KmacJob,
421+
'KT128': !isFips,
422+
'KT256': !isFips,
418423
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
419424
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
420425
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
@@ -427,6 +432,8 @@ const conditionalAlgorithms = {
427432
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
428433
'SHA3-512': !process.features.openssl_is_boringssl ||
429434
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
435+
'TurboSHAKE128': !isFips,
436+
'TurboSHAKE256': !isFips,
430437
'X448': !process.features.openssl_is_boringssl,
431438
};
432439

@@ -571,6 +578,11 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
571578
}
572579
}
573580

581+
function validateKmacKeyLength(length) {
582+
if ((length < 32 || length % 8) && isFips)
583+
throw lazyDOMException('Invalid key length', 'NotSupportedError');
584+
}
585+
574586
/**
575587
* Converts a bit length to the number of bytes needed to contain it.
576588
* Non-byte lengths are rounded up to the next byte.
@@ -1088,6 +1100,7 @@ module.exports = {
10881100

10891101
kNamedCurveAliases,
10901102
kSupportedAlgorithms,
1103+
isFips,
10911104
normalizeAlgorithm,
10921105
normalizeHashName,
10931106
hasAnyNotIn,
@@ -1097,6 +1110,7 @@ module.exports = {
10971110
jobPromiseThen,
10981111
cleanupWebCryptoResult,
10991112
prepareWebCryptoResult,
1113+
validateKmacKeyLength,
11001114
validateMaxBufferLength,
11011115
numBitsToBytes,
11021116
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 {
@@ -28,8 +27,10 @@ const {
2827
validateMaxBufferLength,
2928
getBufferSourceByteLength,
3029
getBufferSourceBytes,
30+
isFips,
3131
kNamedCurveAliases,
3232
numBitsToBytes,
33+
validateKmacKeyLength,
3334
} = require('internal/crypto/util');
3435
const {
3536
converters: webidl,
@@ -276,30 +277,39 @@ function validateCShakeOutputLength(V) {
276277
}
277278
}
278279

279-
function bufferSourceEqualsAscii(V, string) {
280-
if (getBufferSourceByteLength(V) !== string.length) return false;
281-
282-
const bytes = getBufferSourceBytes(V);
283-
const length = TypedArrayPrototypeGetLength(bytes);
284-
for (let i = 0; i < length; i++) {
285-
if (bytes[i] !== StringPrototypeCharCodeAt(string, i)) return false;
286-
}
287-
return true;
288-
}
280+
const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];
289281

290282
function validateCShakeFunctionName(V) {
291-
if (getBufferSourceByteLength(V) === 0 ||
292-
bufferSourceEqualsAscii(V, 'KMAC') ||
293-
bufferSourceEqualsAscii(V, 'TupleHash') ||
294-
bufferSourceEqualsAscii(V, 'ParallelHash')) {
295-
return;
283+
const length = getBufferSourceByteLength(V);
284+
if (length === 0) return;
285+
286+
if (!isFips) {
287+
const bytes = getBufferSourceBytes(V);
288+
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
289+
const functionName = kCShakeFunctionNames[i];
290+
if (length !== functionName.length) continue;
291+
292+
let j = 0;
293+
for (; j < length; j++) {
294+
if (bytes[j] !== StringPrototypeCharCodeAt(functionName, j)) break;
295+
}
296+
if (j === length) return;
297+
}
296298
}
297299

298300
throw lazyDOMException(
299301
'Unsupported CShakeParams functionName',
300302
'NotSupportedError');
301303
}
302304

305+
function validateCShakeCustomization(V) {
306+
if (isFips && getBufferSourceByteLength(V) !== 0)
307+
throw lazyDOMException(
308+
'Unsupported CShakeParams customization',
309+
'NotSupportedError');
310+
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
311+
}
312+
303313
converters.RsaPssParams = createDictionaryConverter(
304314
'RsaPssParams', [
305315
dictAlgorithm,
@@ -457,7 +467,7 @@ converters.CShakeParams = createDictionaryConverter(
457467
{
458468
key: 'customization',
459469
converter: converters.BufferSource,
460-
validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512),
470+
validator: validateCShakeCustomization,
461471
},
462472
],
463473
]);
@@ -743,6 +753,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) {
743753
key: 'length',
744754
converter: (V, opts) =>
745755
converters['unsigned long'](V, enforceRangeOptions(opts)),
756+
validator: validateKmacKeyLength,
746757
},
747758
],
748759
]);
@@ -756,6 +767,12 @@ converters.KmacParams = createDictionaryConverter(
756767
key: 'outputLength',
757768
converter: (V, opts) =>
758769
converters['unsigned long'](V, enforceRangeOptions(opts)),
770+
validator: (V) => {
771+
if ((V === 0 || V % 8) && isFips)
772+
throw lazyDOMException(
773+
'Invalid KmacParams outputLength',
774+
'NotSupportedError');
775+
},
759776
required: true,
760777
},
761778
{

src/crypto/crypto_hash.cc

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

815+
if (IsFipsEnabled()) {
816+
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
817+
return Nothing<void>();
818+
}
819+
815820
CHECK(args[offset]->IsString()); // Algorithm name
816821
Utf8Value algorithm_name(env->isolate(), args[offset]);
817822
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-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)