GeezerVison is showing github.com — crypto: disable non-FIPS WebCrypto paths in FIPS mode · nodejs/node@66ee479
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/mac.js +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -20,6 +20,7 @@ const { | 20 | 20 | normalizeHashName, | 21 | 21 | numBitsToBytes, | 22 | 22 | truncateToBitLength, | | 23 | + validateKmacKeyLength, | 23 | 24 | } = require('internal/crypto/util'); | 24 | 25 |
| 25 | 26 | const { | @@ -60,6 +61,9 @@ function normalizeKeyLength(handle, algorithm) { | 60 | 61 | length = algorithm.length; | 61 | 62 | } | 62 | 63 |
| | 64 | + if (algorithm.name === 'KMAC128' || algorithm.name === 'KMAC256') | | 65 | + validateKmacKeyLength(length); | | 66 | + | 63 | 67 | return { handle, length }; | 64 | 68 | } | 65 | 69 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/util.js +14Lines changed: 14 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -47,9 +47,12 @@ const { | 47 | 47 | EVP_PKEY_ML_KEM_1024, | 48 | 48 | kKeyVariantAES_OCB_128: hasAesOcbMode, | 49 | 49 | Argon2Job, | | 50 | + getFipsCrypto, | 50 | 51 | KmacJob, | 51 | 52 | } = internalBinding('crypto'); | 52 | 53 |
| | 54 | +const isFips = getFipsCrypto() === 1; | | 55 | + | 53 | 56 | const { getOptionValue } = require('internal/options'); | 54 | 57 |
| 55 | 58 | const { | @@ -415,6 +418,8 @@ const conditionalAlgorithms = { | 415 | 418 | 'Ed448': !process.features.openssl_is_boringssl, | 416 | 419 | 'KMAC128': !!KmacJob, | 417 | 420 | 'KMAC256': !!KmacJob, | | 421 | + 'KT128': !isFips, | | 422 | + 'KT256': !isFips, | 418 | 423 | 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44, | 419 | 424 | 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65, | 420 | 425 | 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87, | @@ -427,6 +432,8 @@ const conditionalAlgorithms = { | 427 | 432 | ArrayPrototypeIncludes(getHashes(), 'sha3-384'), | 428 | 433 | 'SHA3-512': !process.features.openssl_is_boringssl || | 429 | 434 | ArrayPrototypeIncludes(getHashes(), 'sha3-512'), | | 435 | + 'TurboSHAKE128': !isFips, | | 436 | + 'TurboSHAKE256': !isFips, | 430 | 437 | 'X448': !process.features.openssl_is_boringssl, | 431 | 438 | }; | 432 | 439 |
| @@ -571,6 +578,11 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) { | 571 | 578 | } | 572 | 579 | } | 573 | 580 |
| | 581 | +function validateKmacKeyLength(length) { | | 582 | + if ((length < 32 || length % 8) && isFips) | | 583 | + throw lazyDOMException('Invalid key length', 'NotSupportedError'); | | 584 | +} | | 585 | + | 574 | 586 | /** | 575 | 587 | * Converts a bit length to the number of bytes needed to contain it. | 576 | 588 | * Non-byte lengths are rounded up to the next byte. | @@ -1088,6 +1100,7 @@ module.exports = { | 1088 | 1100 |
| 1089 | 1101 | kNamedCurveAliases, | 1090 | 1102 | kSupportedAlgorithms, | | 1103 | + isFips, | 1091 | 1104 | normalizeAlgorithm, | 1092 | 1105 | normalizeHashName, | 1093 | 1106 | hasAnyNotIn, | @@ -1097,6 +1110,7 @@ module.exports = { | 1097 | 1110 | jobPromiseThen, | 1098 | 1111 | cleanupWebCryptoResult, | 1099 | 1112 | prepareWebCryptoResult, | | 1113 | + validateKmacKeyLength, | 1100 | 1114 | validateMaxBufferLength, | 1101 | 1115 | numBitsToBytes, | 1102 | 1116 | truncateToBitLength, | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/webidl.js +34-17Lines changed: 34 additions & 17 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | StringPrototypeSplit, | 10 | 10 | StringPrototypeStartsWith, | 11 | 11 | StringPrototypeToLowerCase, | 12 | | - TypedArrayPrototypeGetLength, | 13 | 12 | } = primordials; | 14 | 13 |
| 15 | 14 | const { | @@ -28,8 +27,10 @@ const { | 28 | 27 | validateMaxBufferLength, | 29 | 28 | getBufferSourceByteLength, | 30 | 29 | getBufferSourceBytes, | | 30 | + isFips, | 31 | 31 | kNamedCurveAliases, | 32 | 32 | numBitsToBytes, | | 33 | + validateKmacKeyLength, | 33 | 34 | } = require('internal/crypto/util'); | 34 | 35 | const { | 35 | 36 | converters: webidl, | @@ -276,30 +277,39 @@ function validateCShakeOutputLength(V) { | 276 | 277 | } | 277 | 278 | } | 278 | 279 |
| 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']; | 289 | 281 |
| 290 | 282 | 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 | + } | 296 | 298 | } | 297 | 299 |
| 298 | 300 | throw lazyDOMException( | 299 | 301 | 'Unsupported CShakeParams functionName', | 300 | 302 | 'NotSupportedError'); | 301 | 303 | } | 302 | 304 |
| | 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 | + | 303 | 313 | converters.RsaPssParams = createDictionaryConverter( | 304 | 314 | 'RsaPssParams', [ | 305 | 315 | dictAlgorithm, | @@ -457,7 +467,7 @@ converters.CShakeParams = createDictionaryConverter( | 457 | 467 | { | 458 | 468 | key: 'customization', | 459 | 469 | converter: converters.BufferSource, | 460 | | - validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512), | | 470 | + validator: validateCShakeCustomization, | 461 | 471 | }, | 462 | 472 | ], | 463 | 473 | ]); | @@ -743,6 +753,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) { | 743 | 753 | key: 'length', | 744 | 754 | converter: (V, opts) => | 745 | 755 | converters['unsigned long'](V, enforceRangeOptions(opts)), | | 756 | + validator: validateKmacKeyLength, | 746 | 757 | }, | 747 | 758 | ], | 748 | 759 | ]); | @@ -756,6 +767,12 @@ converters.KmacParams = createDictionaryConverter( | 756 | 767 | key: 'outputLength', | 757 | 768 | converter: (V, opts) => | 758 | 769 | 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 | + }, | 759 | 776 | required: true, | 760 | 777 | }, | 761 | 778 | { | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_hash.cc +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -812,6 +812,11 @@ Maybe<void> CShakeTraits::AdditionalConfig( | 812 | 812 | CShakeConfig* params) { | 813 | 813 | Environment* env = Environment::GetCurrent(args); | 814 | 814 |
| | 815 | + if (IsFipsEnabled()) { | | 816 | + THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); | | 817 | + return Nothing<void>(); | | 818 | + } | | 819 | + | 815 | 820 | CHECK(args[offset]->IsString()); // Algorithm name | 816 | 821 | Utf8Value algorithm_name(env->isolate(), args[offset]); | 817 | 822 | std::string_view algorithm_str = algorithm_name.ToStringView(); | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_kmac.cc +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -151,6 +151,8 @@ bool DeriveBitsWithCShake(const KmacConfig& params, | 151 | 151 | const void* key_data, | 152 | 152 | size_t key_size, | 153 | 153 | ByteSource* out) { | | 154 | + if (IsFipsEnabled()) return false; | | 155 | + | 154 | 156 | const size_t key_length_bytes = NumBitsToBytes(params.key_length); | 155 | 157 | if (key_size < key_length_bytes) return false; | 156 | 158 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_turboshake.cc +10Lines changed: 10 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -428,6 +428,11 @@ Maybe<void> TurboShakeTraits::AdditionalConfig( | 428 | 428 | TurboShakeConfig* params) { | 429 | 429 | Environment* env = Environment::GetCurrent(args); | 430 | 430 |
| | 431 | + if (IsFipsEnabled()) { | | 432 | + THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); | | 433 | + return Nothing<void>(); | | 434 | + } | | 435 | + | 431 | 436 | // args[offset + 0] = algorithm name (string) | 432 | 437 | CHECK(args[offset]->IsString()); | 433 | 438 | Utf8Value algorithm_name(env->isolate(), args[offset]); | @@ -535,6 +540,11 @@ Maybe<void> KangarooTwelveTraits::AdditionalConfig( | 535 | 540 | KangarooTwelveConfig* params) { | 536 | 541 | Environment* env = Environment::GetCurrent(args); | 537 | 542 |
| | 543 | + if (IsFipsEnabled()) { | | 544 | + THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); | | 545 | + return Nothing<void>(); | | 546 | + } | | 547 | + | 538 | 548 | // args[offset + 0] = algorithm name (string) | 539 | 549 | CHECK(args[offset]->IsString()); | 540 | 550 | Utf8Value algorithm_name(env->isolate(), args[offset]); | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_util.cc +6-2Lines changed: 6 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -146,6 +146,11 @@ bool InitCryptoOnce(Isolate* isolate) { | 146 | 146 | // be part of a larger mutex for global OpenSSL state. | 147 | 147 | static Mutex fips_mutex; | 148 | 148 |
| | 149 | +bool IsFipsEnabled() { | | 150 | + Mutex::ScopedLock fips_lock(fips_mutex); | | 151 | + return ncrypto::isFipsEnabled(); | | 152 | +} | | 153 | + | 149 | 154 | void InitCryptoOnce() { | 150 | 155 | Mutex::ScopedLock lock(per_process::cli_options_mutex); | 151 | 156 | Mutex::ScopedLock fips_lock(fips_mutex); | @@ -223,8 +228,7 @@ void InitCryptoOnce() { | 223 | 228 |
| 224 | 229 | void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) { | 225 | 230 | 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); | 228 | 232 | } | 229 | 233 |
| 230 | 234 | void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) { | |
Collapse file Copy file name to clipboardExpand all lines: src/crypto/crypto_util.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -66,6 +66,7 @@ constexpr T NumBitsToBytes(T bits) { | 66 | 66 | // what went wrong, or std::nullopt when there was nothing to do or the | 67 | 67 | // options were applied successfully. | 68 | 68 | std::optional<std::string> ProcessFipsOptions(); | | 69 | +bool IsFipsEnabled(); | 69 | 70 |
| 70 | 71 | bool InitCryptoOnce(v8::Isolate* isolate); | 71 | 72 | void InitCryptoOnce(); | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-key-objects-to-crypto-key.js +21-7Lines changed: 21 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,6 +14,7 @@ const { | 14 | 14 | } = require('crypto'); | 15 | 15 | const { hasFIPS } = require('../common/crypto'); | 16 | 16 | const { kSupportedAlgorithms } = require('internal/crypto/util'); | | 17 | +const fips = hasFIPS(); | 17 | 18 | const rejectsXCurves = hasFIPS(3, 5); | 18 | 19 |
| 19 | 20 | const hashes = Object.keys(kSupportedAlgorithms.digest).filter((name) => { | @@ -135,14 +136,24 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) { | 135 | 136 | const key = createSecretKey(randomBytes(32)); | 136 | 137 | const usages = ['sign', 'verify']; | 137 | 138 |
| 138 | | - if (allowZeroKey) { | | 139 | + if (allowZeroKey && !fips) { | 139 | 140 | const zeroKey = createSecretKey(Buffer.alloc(0)) | 140 | 141 | .toCryptoKey(algorithm, true, usages); | 141 | 142 | assert.strictEqual(zeroKey.algorithm.length, 0); | 142 | 143 |
| 143 | 144 | const explicitZeroKey = createSecretKey(Buffer.alloc(0)) | 144 | 145 | .toCryptoKey({ ...algorithm, length: 0 }, true, usages); | 145 | 146 | 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 | + } | 146 | 157 | } else { | 147 | 158 | assert.throws(() => { | 148 | 159 | createSecretKey(Buffer.alloc(0)).toCryptoKey(algorithm, true, usages); | @@ -157,12 +168,15 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) { | 157 | 168 | message: 'Usages cannot be empty when importing a secret key.' | 158 | 169 | }); | 159 | 170 |
| 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 | + }); | 166 | 180 | } | 167 | 181 |
| 168 | 182 | function hmacVectors() { | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-webcrypto-derivekey.js +2-6Lines changed: 2 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -283,7 +283,7 @@ const fips4 = hasFIPS(4); | 283 | 283 | })().then(common.mustCall()); | 284 | 284 | } | 285 | 285 |
| 286 | | -if (hasOpenSSL(3)) { | | 286 | +if (hasOpenSSL(3) && !hasFIPS()) { | 287 | 287 | (async () => { | 288 | 288 | const derivedKeyAlgorithm = { name: 'KMAC128', length: 0 }; | 289 | 289 | const usages = ['sign']; | @@ -325,11 +325,7 @@ if (hasOpenSSL(3)) { | 325 | 325 | name: 'KMAC128', | 326 | 326 | outputLength: 256, | 327 | 327 | }, 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); | 333 | 329 | } | 334 | 330 | })().then(common.mustCall()); | 335 | 331 | } | |
|