From 38187f81ebca290af81d560071a2ac0e661e0a39 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 23 Sep 2026 13:34:11 +0200 Subject: [PATCH] crypto: use backend cSHAKE and KMAC Require cSHAKE and KMAC output lengths and KMAC key lengths to be multiples of 8 bits. KMAC keys must be at least 32 bits. Share these restrictions between operations and supports. Use OpenSSL's KMAC provider for all supported inputs and its cSHAKE implementation for non-empty function names or customization strings. Keep using SHAKE when both cSHAKE parameters are empty. Remove the custom Keccak framing, partial-bit handling, and short-key fallback. Document the OpenSSL 4.0 requirement for non-empty cSHAKE parameters and reject customization strings containing null bytes. Keep the documented 512-byte customization limit and let backend failures reach callers. Signed-off-by: Filip Skokan Assisted-by: Codex PR-URL: https://github.com/nodejs/node/pull/66237 Reviewed-By: James M Snell Reviewed-By: Aviv Keller --- doc/api/webcrypto.md | 27 +- lib/internal/crypto/hash.js | 28 +- lib/internal/crypto/mac.js | 1 - lib/internal/crypto/util.js | 2 +- lib/internal/crypto/webcrypto.js | 12 +- lib/internal/crypto/webidl.js | 25 +- src/crypto/crypto_hash.cc | 353 ------------------ src/crypto/crypto_hash.h | 68 ---- src/crypto/crypto_kmac.cc | 97 +---- src/crypto/crypto_kmac.h | 3 +- test/fixtures/crypto/kmac.js | 55 --- .../webcrypto/supports-modern-algorithms.mjs | 48 ++- .../test-crypto-key-objects-to-crypto-key.js | 15 +- .../test-webcrypto-aes-kw-short-input.js | 16 +- test/parallel/test-webcrypto-derivekey.js | 18 +- test/parallel/test-webcrypto-digest.js | 98 +++-- test/parallel/test-webcrypto-export-import.js | 86 ++--- .../test-webcrypto-fips-exceptions.mjs | 72 ++-- test/parallel/test-webcrypto-fips-refresh.js | 6 +- .../test-webcrypto-keccak-byte-alignment.js | 90 +++++ test/parallel/test-webcrypto-keygen-kmac.js | 18 +- .../test-webcrypto-prototype-pollution.mjs | 9 +- .../test-webcrypto-sign-verify-kmac.js | 126 ++----- test/parallel/test-webcrypto-wrap-unwrap.js | 38 +- typings/internalBinding/crypto.d.ts | 13 - 25 files changed, 368 insertions(+), 956 deletions(-) create mode 100644 test/parallel/test-webcrypto-keccak-byte-alignment.js diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index f6125581267..609ebf00379 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -1842,6 +1842,9 @@ changes: description: Renamed `cShakeParams.length` to `cShakeParams.outputLength`. --> +When both `functionName` and `customization` are empty or `undefined`, cSHAKE is +equivalent to plain SHAKE. + #### `cShakeParams.name` -* Type: {number} represents the requested output length in bits. +* Type: {number} represents the requested output length in bits. Must be a + multiple of 8. #### `cShakeParams.functionName` @@ -1875,9 +1879,10 @@ changes: * Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} The `functionName` member represents the NIST function-name byte string used to -domain-separate functions built on top of cSHAKE. Accepted values are: +domain-separate functions built on top of cSHAKE. Non-empty values require +OpenSSL 4.0 or later. Accepted values are: -* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE +* empty or `undefined` * the ASCII byte sequence `'KMAC'` * the ASCII byte sequence `'TupleHash'` * the ASCII byte sequence `'ParallelHash'` @@ -1896,11 +1901,11 @@ changes: * Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -The `customization` member represents the customization data. Accepted -values are: +The `customization` member represents the customization data. Non-empty values +require OpenSSL 4.0 or later. Accepted values are: -* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE -* up to 512 bytes of arbitrary data +* empty or `undefined` +* up to 512 bytes of data without null bytes ### Class: `EcdhKeyDeriveParams` @@ -2360,7 +2365,7 @@ added: v24.8.0 * Type: {number} The optional number of bits in the KMAC key. This is optional and should -be omitted for most cases. +be omitted for most cases. The key length must be at least 32 and a multiple of 8. #### `kmacImportParams.name` @@ -2410,7 +2415,8 @@ added: v24.8.0 The number of bits to generate for the KMAC key. If omitted, the length will be determined by the KMAC algorithm used. -This is optional and should be omitted for most cases. +This is optional and should be omitted for most cases. Must be at least 32 and a +multiple of 8. #### `kmacKeyGenParams.name` @@ -2448,7 +2454,8 @@ added: - v24.15.0 --> -* Type: {number} represents the requested output length in bits. +* Type: {number} represents the requested output length in bits. Must be a + multiple of 8. #### `kmacParams.customization` diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 12588b57263..97bed5aab4a 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -5,12 +5,10 @@ const { ObjectSetPrototypeOf, StringPrototypeToLowerCase, Symbol, - TypedArrayPrototypeGetBuffer, TypedArrayPrototypeIncludes, } = primordials; const { - CShakeJob, Hash: _Hash, HashJob, Hmac: _Hmac, @@ -23,10 +21,7 @@ const { const { getStringOption, jobPromise, - jobPromiseThen, normalizeHashName, - numBitsToBytes, - truncateToBitLength, validateAlgorithm, validateMaxBufferLength, kHandle, @@ -42,7 +37,6 @@ const { } = require('internal/crypto/keys'); const { - lazyDOMException, normalizeEncoding, encodingsMap, getDeprecationWarningEmitter, @@ -267,30 +261,20 @@ function asyncDigest(algorithm, data) { const outputLength = algorithm.outputLength; if (getOptionalByteLength(algorithm.functionName) || getOptionalByteLength(algorithm.customization)) { - if (CShakeJob === undefined) { - throw lazyDOMException( - 'Non-empty CShakeParams functionName or customization is not supported', - 'NotSupportedError'); - } - - return jobPromise(() => new CShakeJob( + return jobPromise(() => new HashJob( kCryptoJobWebCrypto, - algorithm.name, + StringPrototypeToLowerCase(algorithm.name), data, + outputLength, algorithm.functionName, - algorithm.customization, - outputLength)); + algorithm.customization)); } - const bits = jobPromise(() => new HashJob( + return jobPromise(() => new HashJob( kCryptoJobWebCrypto, normalizeHashName(algorithm.name), data, - numBitsToBytes(outputLength) * 8)); - if (outputLength % 8 === 0) - return bits; - return jobPromiseThen(bits, (bits) => - TypedArrayPrototypeGetBuffer(truncateToBitLength(outputLength, bits))); + outputLength)); } case 'TurboSHAKE128': // Fall through diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 742e1ebe590..ed2be2b7fdb 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -193,7 +193,6 @@ function kmacSignVerify(key, data, algorithm, signature) { getCryptoKeyHandle(key), algorithm.name, algorithm.customization, - getCryptoKeyAlgorithm(key).length, algorithm.outputLength, data, signature)); diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index a9ad6dfb62f..d1f1cdc9b4f 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -729,7 +729,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) { } function validateKmacKeyLength(length) { - if ((length < 32 || length % 8) && isFips()) + if (length < 32 || length % 8 !== 0) throw lazyDOMException('Invalid key length', 'NotSupportedError'); } diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 6585baa6fc5..7a2ff063fa1 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -23,7 +23,6 @@ const { } = primordials; const { - CShakeJob, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatSPKI, @@ -72,7 +71,6 @@ const { prepareWebCryptoResult, validateAlgorithm, validateMaxBufferLength, - getOptionalByteLength, } = require('internal/crypto/util'); const { @@ -1923,15 +1921,7 @@ function check(op, alg, length) { } switch (op) { - case 'digest': { - if ((normalizedAlgorithm.name === 'cSHAKE128' || - normalizedAlgorithm.name === 'cSHAKE256') && - (getOptionalByteLength(normalizedAlgorithm.functionName) || - getOptionalByteLength(normalizedAlgorithm.customization))) { - return CShakeJob !== undefined; - } - return true; - } + case 'digest': case 'decapsulate': case 'decrypt': case 'encapsulate': diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 4addaf62bcc..33705dbb9c3 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -11,14 +11,12 @@ const { StringPrototypeCharCodeAt, StringPrototypeSplit, StringPrototypeToLowerCase, + TypedArrayPrototypeIncludes, } = primordials; const { lazyDOMException, } = require('internal/util'); -const { - isUint32, -} = require('internal/validators'); const { getCryptoKeyAlgorithm, getCryptoKeyType, @@ -29,9 +27,9 @@ const { validateMaxBufferLength, getBufferSourceByteLength, getBufferSourceBytes, + getHashes, isFips, kNamedCurveAliases, - numBitsToBytes, validateKmacKeyLength, } = require('internal/crypto/util'); const { @@ -293,20 +291,20 @@ function validateZeroLength(parameterName) { } function validateCShakeOutputLength(V) { - if (!isUint32(numBitsToBytes(V) * 8)) { + if (V % 8 !== 0) { throw lazyDOMException( 'Invalid CShakeParams outputLength', - 'OperationError'); + 'NotSupportedError'); } } const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash']; -function validateCShakeFunctionName(V) { +function validateCShakeFunctionName(V, dict) { const length = getBufferSourceByteLength(V); if (length === 0) return; - if (!isFips()) { + if (ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name))) { const bytes = getBufferSourceBytes(V); for (let i = 0; i < kCShakeFunctionNames.length; i++) { const functionName = kCShakeFunctionNames[i]; @@ -325,12 +323,17 @@ function validateCShakeFunctionName(V) { 'NotSupportedError'); } -function validateCShakeCustomization(V) { - if (isFips() && getBufferSourceByteLength(V) !== 0) +function validateCShakeCustomization(V, dict) { + if (getBufferSourceByteLength(V) === 0) return; + if (!ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name))) throw lazyDOMException( 'Unsupported CShakeParams customization', 'NotSupportedError'); validateMaxBufferLength(V, 'CShakeParams.customization', 512); + if (TypedArrayPrototypeIncludes(getBufferSourceBytes(V), 0)) + throw lazyDOMException( + 'Unsupported CShakeParams customization', + 'NotSupportedError'); } converters.RsaPssParams = createAlgorithmDictionaryConverter( @@ -790,7 +793,7 @@ converters.KmacParams = createAlgorithmDictionaryConverter( converter: (V, opts) => converters['unsigned long'](V, enforceRangeOptions(opts)), validator: (V) => { - if ((V === 0 || V % 8) && isFips()) + if (V % 8 !== 0 || (V === 0 && isFips())) throw lazyDOMException( 'Invalid KmacParams outputLength', 'NotSupportedError'); diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index 976c921fee9..24d49a6d377 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -7,21 +7,15 @@ #include "threadpoolwork-inl.h" #include "v8.h" -#if OPENSSL_WITH_EVP_MAC -#include #include -#endif #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK #include #endif #include -#include #include #include -#include -#include #include #include @@ -588,9 +582,6 @@ void Hash::Initialize(Environment* env, Local target) { SetMethodNoSideEffect(context, target, "oneShotDigest", OneShotDigest); HashJob::Initialize(env, target); -#if OPENSSL_WITH_EVP_MAC - CShakeJob::Initialize(env, target); -#endif } void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) { @@ -602,9 +593,6 @@ void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(OneShotDigest); HashJob::RegisterExternalReferences(registry); -#if OPENSSL_WITH_EVP_MAC - CShakeJob::RegisterExternalReferences(registry); -#endif } // new Hash(algorithm, xofLen, algorithmId, algorithmCache[, functionName, @@ -895,346 +883,5 @@ bool HashTraits::DeriveBits(Environment* env, return true; } -#if OPENSSL_WITH_EVP_MAC -namespace { - -static constexpr std::array kEmptyString = {}; -static constexpr size_t kKeccakKmac128Rate = 168; -static constexpr size_t kKeccakKmac256Rate = 136; -static constexpr size_t kMaxCShakeCustomizationSize = 512; - -struct EncodedLength { - std::array data; - size_t size; -}; - -struct EncodedStringInput { - const void* data; - size_t byte_length; - size_t bit_length; -}; - -struct KeccakKmacXof { - ncrypto::EVPMDCtxPointer ctx; - size_t rate; -}; - -size_t EncodedLengthSize(size_t value) { - size_t size = 1; - size_t remaining = value; - while (remaining >>= CHAR_BIT) size++; - return size + 1; -} - -bool AddSize(size_t a, size_t b, size_t* out) { - if (a > std::numeric_limits::max() - b) return false; - *out = a + b; - return true; -} - -EncodedLength EncodeLength(size_t value, bool left) { - const size_t value_size = EncodedLengthSize(value) - 1; - EncodedLength encoded = {{}, value_size + 1}; - - if (left) encoded.data[0] = static_cast(value_size); - for (size_t n = 0; n < value_size; n++) { - const size_t shift = CHAR_BIT * (value_size - n - 1); - encoded.data[(left ? 1 : 0) + n] = - static_cast(value >> shift); - } - if (!left) encoded.data[value_size] = static_cast(value_size); - - return encoded; -} - -bool DigestUpdate(ncrypto::EVPMDCtxPointer* ctx, - const void* data, - size_t size) { - if (size == 0) return true; - return ctx->digestUpdate(ncrypto::Buffer{ - .data = data, - .len = size, - }); -} - -bool DigestUpdateZeros(ncrypto::EVPMDCtxPointer* ctx, size_t size) { - static constexpr std::array zeros = {}; - while (size > 0) { - const size_t chunk = std::min(size, zeros.size()); - if (!DigestUpdate(ctx, zeros.data(), chunk)) return false; - size -= chunk; - } - return true; -} - -bool EncodedStringSize(size_t byte_length, size_t bit_length, size_t* size) { - return AddSize(EncodedLengthSize(bit_length), byte_length, size); -} - -bool ByteLengthToBitLength(size_t byte_length, size_t* bit_length) { - if (byte_length > std::numeric_limits::max() / CHAR_BIT) { - return false; - } - *bit_length = byte_length * CHAR_BIT; - return true; -} - -KeccakKmacXof NewKeccakKmacXof(bool use_128_bits) { - // OpenSSL 3.x exposes the cSHAKE/KMAC suffix primitive as KECCAK-KMAC-*. - const char* digest_name = use_128_bits ? OSSL_DIGEST_NAME_KECCAK_KMAC128 - : OSSL_DIGEST_NAME_KECCAK_KMAC256; - auto digest = std::unique_ptr{ - EVP_MD_fetch(nullptr, digest_name, nullptr), EVP_MD_free}; - if (!digest) return {}; - - auto ctx = ncrypto::EVPMDCtxPointer::New(); - if (!ctx.digestInit(digest.get())) return {}; - - return { - .ctx = std::move(ctx), - .rate = use_128_bits ? kKeccakKmac128Rate : kKeccakKmac256Rate, - }; -} - -bool ToEncodedStringInput(const void* data, - size_t byte_length, - EncodedStringInput* input) { - if (byte_length > 0 && data == nullptr) return false; - - size_t bit_length; - if (!ByteLengthToBitLength(byte_length, &bit_length)) return false; - - *input = { - .data = byte_length == 0 ? kEmptyString.data() : data, - .byte_length = byte_length, - .bit_length = bit_length, - }; - return true; -} - -bool DigestUpdateEncodedLength(ncrypto::EVPMDCtxPointer* ctx, - size_t value, - bool left) { - const EncodedLength encoded = EncodeLength(value, left); - return DigestUpdate(ctx, encoded.data.data(), encoded.size); -} - -bool DigestUpdateEncodedString(ncrypto::EVPMDCtxPointer* ctx, - const void* data, - size_t byte_length, - size_t bit_length) { - return DigestUpdateEncodedLength(ctx, bit_length, true) && - DigestUpdate(ctx, data, byte_length); -} - -bool DigestUpdateBytepad(ncrypto::EVPMDCtxPointer* ctx, - size_t width, - const void* data, - size_t byte_length, - size_t bit_length, - const void* data2 = nullptr, - size_t byte_length2 = 0, - size_t bit_length2 = 0) { - if (width == 0) return false; - - size_t encoded_size; - size_t written = EncodedLengthSize(width); - if (!EncodedStringSize(byte_length, bit_length, &encoded_size) || - !AddSize(written, encoded_size, &written)) { - return false; - } - if (data2 != nullptr) { - if (!EncodedStringSize(byte_length2, bit_length2, &encoded_size) || - !AddSize(written, encoded_size, &written)) { - return false; - } - } - - size_t padded_size; - if (!AddSize(written, width - 1, &padded_size)) return false; - padded_size = padded_size / width * width; - DCHECK_GE(padded_size, written); - const size_t padding = padded_size - written; - - return DigestUpdateEncodedLength(ctx, width, true) && - DigestUpdateEncodedString(ctx, data, byte_length, bit_length) && - (data2 == nullptr || - DigestUpdateEncodedString(ctx, data2, byte_length2, bit_length2)) && - DigestUpdateZeros(ctx, padding); -} - -} // namespace - -CShakeConfig::CShakeConfig(CShakeConfig&& other) noexcept - : in(std::move(other.in)), - function_name(std::move(other.function_name)), - customization(std::move(other.customization)), - variant(other.variant), - length(other.length) {} - -CShakeConfig& CShakeConfig::operator=(CShakeConfig&& other) noexcept { - if (&other == this) return *this; - this->~CShakeConfig(); - return *new (this) CShakeConfig(std::move(other)); -} - -void CShakeConfig::MemoryInfo(MemoryTracker* tracker) const { - tracker->TraitTrackInline(in, "in"); - tracker->TraitTrackInline(function_name, "function_name"); - tracker->TraitTrackInline(customization, "customization"); -} - -MaybeLocal CShakeTraits::EncodeOutput(Environment* env, - const CShakeConfig& params, - ByteSource* out) { - return out->ToArrayBuffer(env); -} - -Maybe CShakeTraits::AdditionalConfig( - CryptoJobMode mode, - const FunctionCallbackInfo& args, - unsigned int offset, - CShakeConfig* params) { - Environment* env = Environment::GetCurrent(args); - - if (IsFipsEnabled()) { - THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env); - return Nothing(); - } - - CHECK(args[offset]->IsString()); // Algorithm name - Utf8Value algorithm_name(env->isolate(), args[offset]); - std::string_view algorithm_str = algorithm_name.ToStringView(); - - if (algorithm_str == "cSHAKE128") { - params->variant = CShakeVariant::CSHAKE128; - } else if (algorithm_str == "cSHAKE256") { - params->variant = CShakeVariant::CSHAKE256; - } else { - UNREACHABLE(); - } - - ArrayBufferOrViewContents data(args[offset + 1]); - if (!data.CheckSizeInt32()) [[unlikely]] { - THROW_ERR_OUT_OF_RANGE(env, "data is too big"); - return Nothing(); - } - params->in = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource(); - - if (!args[offset + 2]->IsUndefined()) { - ArrayBufferOrViewContents function_name(args[offset + 2]); - if (!function_name.CheckSizeInt32()) [[unlikely]] { - THROW_ERR_OUT_OF_RANGE(env, "functionName is too big"); - return Nothing(); - } - params->function_name = IsCryptoJobAsync(mode) - ? function_name.ToCopy() - : function_name.ToByteSource(); - } - - if (!args[offset + 3]->IsUndefined()) { - ArrayBufferOrViewContents customization(args[offset + 3]); - if (!customization.CheckSizeInt32()) [[unlikely]] { - THROW_ERR_OUT_OF_RANGE(env, "customization is too big"); - return Nothing(); - } - params->customization = IsCryptoJobAsync(mode) - ? customization.ToCopy() - : customization.ToByteSource(); - } - - CHECK(args[offset + 4]->IsUint32()); // Length - params->length = args[offset + 4].As()->Value(); - - return JustVoid(); -} - -bool CShakeTraits::DeriveBits(Environment* env, - const CShakeConfig& params, - ByteSource* out, - CryptoJobMode mode, - CryptoErrorStore*) { - CShakeParams cshake_params = { - .variant = params.variant, - .function_name_data = params.function_name.data(), - .function_name_size = params.function_name.size(), - .customization_data = params.customization.data(), - .customization_size = params.customization.size(), - .bytepad_input = nullptr, - .input_data = params.in.data(), - .input_size = params.in.size(), - .append_output_length = false, - .length = params.length, - }; - return DeriveCShakeBits(cshake_params, out); -} - -bool DeriveCShakeBits(const CShakeParams& params, ByteSource* out) { - if (params.customization_size > kMaxCShakeCustomizationSize) { - return false; - } - - if (params.length == 0) { - *out = ByteSource(); - return true; - } - - auto xof = NewKeccakKmacXof(params.variant == CShakeVariant::CSHAKE128); - if (!xof.ctx) return false; - auto ctx = std::move(xof.ctx); - - EncodedStringInput function_name; - EncodedStringInput customization; - if (!ToEncodedStringInput(params.function_name_data, - params.function_name_size, - &function_name) || - !ToEncodedStringInput(params.customization_data, - params.customization_size, - &customization)) { - return false; - } - - if (!DigestUpdateBytepad(&ctx, - xof.rate, - function_name.data, - function_name.byte_length, - function_name.bit_length, - customization.data, - customization.byte_length, - customization.bit_length)) { - return false; - } - - if (params.bytepad_input != nullptr && - !DigestUpdateBytepad(&ctx, - xof.rate, - params.bytepad_input->data, - params.bytepad_input->byte_length, - params.bytepad_input->bit_length)) { - return false; - } - - if (!DigestUpdate(&ctx, params.input_data, params.input_size)) { - return false; - } - - if (params.append_output_length && - !DigestUpdateEncodedLength(&ctx, params.length, false)) { - return false; - } - - const size_t length_bytes = - NumBitsToBytes(static_cast(params.length)); - auto data = ctx.digestFinal(length_bytes); - if (!data) [[unlikely]] - return false; - - DCHECK(!data.isSecure()); - *out = ByteSource::Allocated(data.release()); - if (params.length % CHAR_BIT != 0) TruncateToBitLength(params.length, out); - return true; -} -#endif // OPENSSL_WITH_EVP_MAC - } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h index 146e9cf55b5..1cdfa2054e4 100644 --- a/src/crypto/crypto_hash.h +++ b/src/crypto/crypto_hash.h @@ -86,74 +86,6 @@ struct HashTraits final { using HashJob = DeriveBitsJob; -#if OPENSSL_WITH_EVP_MAC -enum class CShakeVariant { CSHAKE128, CSHAKE256 }; - -struct CShakeBytepadInput final { - const void* data; - size_t byte_length; - size_t bit_length; -}; - -struct CShakeParams final { - CShakeVariant variant; - const void* function_name_data; - size_t function_name_size; - const void* customization_data; - size_t customization_size; - const CShakeBytepadInput* bytepad_input; - const void* input_data; - size_t input_size; - bool append_output_length; - uint32_t length; // Output length in bits -}; - -bool DeriveCShakeBits(const CShakeParams& params, ByteSource* out); - -struct CShakeConfig final : public MemoryRetainer { - ByteSource in; - ByteSource function_name; - ByteSource customization; - CShakeVariant variant; - uint32_t length; // Output length in bits - - CShakeConfig() = default; - - explicit CShakeConfig(CShakeConfig&& other) noexcept; - - CShakeConfig& operator=(CShakeConfig&& other) noexcept; - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(CShakeConfig) - SET_SELF_SIZE(CShakeConfig) -}; - -struct CShakeTraits final { - using AdditionalParameters = CShakeConfig; - static constexpr const char* JobName = "CShakeJob"; - static constexpr AsyncWrap::ProviderType Provider = - AsyncWrap::PROVIDER_HASHREQUEST; - - static v8::Maybe AdditionalConfig( - CryptoJobMode mode, - const v8::FunctionCallbackInfo& args, - unsigned int offset, - CShakeConfig* params); - - static bool DeriveBits(Environment* env, - const CShakeConfig& params, - ByteSource* out, - CryptoJobMode mode, - CryptoErrorStore* errors); - - static v8::MaybeLocal EncodeOutput(Environment* env, - const CShakeConfig& params, - ByteSource* out); -}; - -using CShakeJob = DeriveBitsJob; -#endif // OPENSSL_WITH_EVP_MAC - } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_kmac.cc b/src/crypto/crypto_kmac.cc index 7bdbece9627..f7a994b9e12 100644 --- a/src/crypto/crypto_kmac.cc +++ b/src/crypto/crypto_kmac.cc @@ -1,14 +1,11 @@ #include "crypto/crypto_kmac.h" #include "async_wrap-inl.h" -#include "crypto/crypto_hash.h" #include "node_internals.h" #include "threadpoolwork-inl.h" #if OPENSSL_WITH_EVP_MAC #include #include -#include -#include #include #include "crypto/crypto_keys.h" #include "crypto/crypto_sig.h" @@ -26,7 +23,6 @@ using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; -using v8::Number; using v8::Object; using v8::Uint32; using v8::Value; @@ -38,7 +34,6 @@ KmacConfig::KmacConfig(KmacConfig&& other) noexcept signature(std::move(other.signature)), customization(std::move(other.customization)), variant(other.variant), - key_length(other.key_length), length(other.length) {} KmacConfig& KmacConfig::operator=(KmacConfig&& other) noexcept { @@ -96,27 +91,18 @@ Maybe KmacTraits::AdditionalConfig( } // If undefined, params->customization remains uninitialized (size 0). - CHECK(args[offset + 4]->IsNumber()); // Key length - double key_length = args[offset + 4].As()->Value(); - if (!(key_length >= 0) || - key_length > static_cast(std::numeric_limits::max())) { - THROW_ERR_OUT_OF_RANGE(env, "key length is too big"); - return Nothing(); - } - params->key_length = static_cast(key_length); + CHECK(args[offset + 4]->IsUint32()); // Length + params->length = args[offset + 4].As()->Value(); - CHECK(args[offset + 5]->IsUint32()); // Length - params->length = args[offset + 5].As()->Value(); - - ArrayBufferOrViewContents data(args[offset + 6]); + ArrayBufferOrViewContents data(args[offset + 5]); if (!data.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "data is too big"); return Nothing(); } params->data = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource(); - if (!args[offset + 7]->IsUndefined()) { - ArrayBufferOrViewContents signature(args[offset + 7]); + if (!args[offset + 6]->IsUndefined()) { + ArrayBufferOrViewContents signature(args[offset + 6]); if (!signature.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "signature is too big"); return Nothing(); @@ -128,86 +114,18 @@ Maybe KmacTraits::AdditionalConfig( return JustVoid(); } -namespace { - -static constexpr std::array kKmacFunctionName = { - 'K', 'M', 'A', 'C'}; -static constexpr size_t kKmacMinOpenSSLKeySize = 4; -// Keep the bit-aware path within OpenSSL's KMAC provider limits. -static constexpr size_t kKmacMaxOpenSSLKeySize = 512; -static constexpr size_t kKmacMaxOpenSSLCustomizationSize = 512; -static constexpr size_t kKmacMaxOpenSSLOutputSize = 0xffffff / CHAR_BIT; - -bool KmacParamsWithinOpenSSLLimits(const KmacConfig& params, - size_t key_size, - size_t length_bytes) { - return key_size <= kKmacMaxOpenSSLKeySize && - NumBitsToBytes(params.key_length) <= kKmacMaxOpenSSLKeySize && - params.customization.size() <= kKmacMaxOpenSSLCustomizationSize && - length_bytes <= kKmacMaxOpenSSLOutputSize; -} - -bool DeriveBitsWithCShake(const KmacConfig& params, - const void* key_data, - size_t key_size, - ByteSource* out) { - if (IsFipsEnabled()) return false; - - const size_t key_length_bytes = NumBitsToBytes(params.key_length); - if (key_size < key_length_bytes) return false; - - CShakeBytepadInput key_input = { - .data = key_data, - .byte_length = key_length_bytes, - .bit_length = params.key_length, - }; - CShakeParams cshake_params = { - .variant = params.variant == KmacVariant::KMAC128 - ? CShakeVariant::CSHAKE128 - : CShakeVariant::CSHAKE256, - .function_name_data = kKmacFunctionName.data(), - .function_name_size = kKmacFunctionName.size(), - .customization_data = params.customization.data(), - .customization_size = params.customization.size(), - .bytepad_input = &key_input, - .input_data = params.data.data(), - .input_size = params.data.size(), - .append_output_length = true, - .length = params.length, - }; - return DeriveCShakeBits(cshake_params, out); -} - -} // namespace - bool KmacTraits::DeriveBits(Environment* env, const KmacConfig& params, ByteSource* out, CryptoJobMode mode, CryptoErrorStore*) { - const bool truncate_to_bit_length = params.length % CHAR_BIT != 0; - const size_t length_bytes = - NumBitsToBytes(static_cast(params.length)); + if (params.length % CHAR_BIT != 0) return false; + const size_t length_bytes = params.length / CHAR_BIT; // Get the key data. const void* key_data = params.key.GetSymmetricKey(); size_t key_size = params.key.GetSymmetricKeySize(); - if (!KmacParamsWithinOpenSSLLimits(params, key_size, length_bytes)) { - return false; - } - - if (params.length == 0) { - *out = ByteSource(); - return true; - } - - // OpenSSL's EVP_MAC provider rejects KMAC keys shorter than 4 bytes. - if (params.length % CHAR_BIT != 0 || params.key_length % CHAR_BIT != 0 || - key_size < kKmacMinOpenSSLKeySize) { - return DeriveBitsWithCShake(params, key_data, key_size, out); - } - // Fetch the KMAC algorithm auto mac = EVPMacPointer::Fetch((params.variant == KmacVariant::KMAC128) ? OSSL_MAC_NAME_KMAC128 @@ -261,7 +179,6 @@ bool KmacTraits::DeriveBits(Environment* env, auto buffer = result.release(); *out = ByteSource::Allocated(buffer.data, buffer.len); - if (truncate_to_bit_length) TruncateToBitLength(params.length, out); return true; } diff --git a/src/crypto/crypto_kmac.h b/src/crypto/crypto_kmac.h index 703b03c2c88..4f8fe27d2ff 100644 --- a/src/crypto/crypto_kmac.h +++ b/src/crypto/crypto_kmac.h @@ -21,8 +21,7 @@ struct KmacConfig final : public MemoryRetainer { ByteSource signature; ByteSource customization; KmacVariant variant; - size_t key_length; // Key length in bits - uint32_t length; // Output length in bits + uint32_t length; // Output length in bits KmacConfig() = default; diff --git a/test/fixtures/crypto/kmac.js b/test/fixtures/crypto/kmac.js index ed265bb9c9f..cc1870af2bb 100644 --- a/test/fixtures/crypto/kmac.js +++ b/test/fixtures/crypto/kmac.js @@ -114,61 +114,6 @@ module.exports = function() { 0x76, 0xfc, 0x89, 0x65, ]), }, - { - // KMAC128 with a short key, generated with OpenSSL's KECCAK-KMAC128 - // digest over independently encoded NIST SP 800-185 framing. - algorithm: 'KMAC128', - key: Buffer.from([0x00, 0x01, 0x02]), - data: Buffer.from([0x01, 0x02, 0x03]), - customization: Buffer.from('Node.js'), - outputLength: 256, - expected: Buffer.from([ - 0xfb, 0x7c, 0xbb, 0xa2, 0xa1, 0x0d, 0x1a, 0x87, 0x9a, 0x9f, 0x96, 0x8c, - 0x58, 0x9d, 0x2a, 0xfe, 0x4a, 0x9b, 0xbf, 0x03, 0x7e, 0x85, 0x2c, 0xac, - 0x05, 0xdd, 0x78, 0x5b, 0x78, 0xd6, 0x57, 0x1c, - ]), - }, - { - // KMAC256 with a short key, generated with OpenSSL's KECCAK-KMAC256 - // digest over independently encoded NIST SP 800-185 framing. - algorithm: 'KMAC256', - key: Buffer.from([0x00, 0x01, 0x02]), - data: Buffer.from([0x01, 0x02, 0x03]), - customization: Buffer.from('Node.js'), - outputLength: 512, - expected: Buffer.from([ - 0x2c, 0xcf, 0x20, 0xde, 0xd8, 0xc9, 0x6d, 0xb0, 0x5f, 0x15, 0xe0, 0xb3, - 0xce, 0x5d, 0xf0, 0x45, 0xc7, 0xd7, 0x4e, 0xfe, 0x18, 0xee, 0x36, 0xa8, - 0xe4, 0x9a, 0x37, 0xfb, 0xc2, 0xb1, 0xbb, 0xfd, 0xad, 0xf5, 0xb7, 0x89, - 0xd3, 0xa4, 0xbc, 0xb3, 0xa8, 0x28, 0x8e, 0x9f, 0x25, 0xe6, 0x8d, 0x5b, - 0x4a, 0x01, 0x0b, 0x90, 0xae, 0x6d, 0x2b, 0xfc, 0xf1, 0xb6, 0xbb, 0x82, - 0x34, 0x8b, 0x51, 0xd9, - ]), - }, - { - // KMAC128 with a non-byte-aligned output length. The second byte has its - // unused low bits cleared after squeezing a 9-bit result. - algorithm: 'KMAC128', - key: Buffer.from([0x00, 0x01, 0x02, 0x03]), - data: Buffer.from([0x01, 0x02, 0x03]), - customization: undefined, - outputLength: 9, - expected: Buffer.from([0x63, 0x80]), - }, - { - // KMAC128 with a non-byte-aligned key length. The raw key is already - // truncated to 25 bits, matching WebCrypto import semantics. - algorithm: 'KMAC128', - key: Buffer.from([0xff, 0xff, 0xff, 0x80]), - keyLength: 25, - data: Buffer.from([0x01, 0x02, 0x03]), - customization: undefined, - outputLength: 128, - expected: Buffer.from([ - 0x25, 0xea, 0xc7, 0x06, 0x82, 0x47, 0x7e, 0x3c, 0x9b, 0xf0, 0xf1, 0x51, - 0x87, 0x46, 0x40, 0x0c, - ]), - }, ]; return vectors; diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index b01ebce3e5a..8d58397783e 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -11,6 +11,8 @@ const pqc = hasOpenSSL(3, 5) || boringSSL; const argon2 = hasOpenSSL(3, 2) && !fips; const shake128 = crypto.getHashes().includes('shake128'); const shake256 = crypto.getHashes().includes('shake256'); +const cshake128 = crypto.getHashes().includes('cshake128'); +const cshake256 = crypto.getHashes().includes('cshake256'); const sha3 = crypto.getHashes().includes('sha3-256'); const ocb = hasOpenSSL(3) && crypto.getCiphers().includes('aes-128-ocb'); const kmac = hasOpenSSL(3) && crypto.getMacs().includes('kmac128'); @@ -22,19 +24,21 @@ export const vectors = { [false, 'cSHAKE128'], [shake128, { name: 'cSHAKE128', outputLength: 128 }], [shake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], - [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }], + [cshake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('KMAC') }], [false, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.from('SHAKE') }], - [shake128 && kmac && !fips, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }], + [cshake128, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1, 1) }], + [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }], [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(513) }], - [shake128, { name: 'cSHAKE128', outputLength: 127 }], + [false, { name: 'cSHAKE128', outputLength: 127 }], [false, 'cSHAKE256'], [shake256, { name: 'cSHAKE256', outputLength: 256 }], [shake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], - [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }], + [cshake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('KMAC') }], [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.from('SHAKE') }], - [shake256 && kmac && !fips, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }], + [cshake256, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1, 1) }], + [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }], [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(513) }], - [shake256, { name: 'cSHAKE256', outputLength: 255 }], + [false, { name: 'cSHAKE256', outputLength: 255 }], [false, 'TurboSHAKE128'], [!fips, { name: 'TurboSHAKE128', outputLength: 128 }], [!fips, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }], @@ -86,9 +90,9 @@ export const vectors = { [false, 'KMAC128'], [false, 'KMAC256'], [kmac, { name: 'KMAC128', outputLength: 256 }], - [kmac && !fips, { name: 'KMAC128', outputLength: 255 }], + [false, { name: 'KMAC128', outputLength: 255 }], [kmac, { name: 'KMAC256', outputLength: 256 }], - [kmac && !fips, { name: 'KMAC256', outputLength: 255 }], + [false, { name: 'KMAC256', outputLength: 255 }], ], 'generateKey': [ [pqc, 'ML-DSA-44'], @@ -109,10 +113,14 @@ export const vectors = { [kmac, 'KMAC256'], [kmac, { name: 'KMAC128', length: 256 }], [kmac, { name: 'KMAC256', length: 128 }], - [kmac && !fips, { name: 'KMAC128', length: 0 }], - [kmac && !fips, { name: 'KMAC256', length: 0 }], - [kmac && !fips, { name: 'KMAC128', length: 1 }], - [kmac && !fips, { name: 'KMAC256', length: 1 }], + [false, { name: 'KMAC128', length: 0 }], + [false, { name: 'KMAC256', length: 0 }], + [false, { name: 'KMAC128', length: 1 }], + [false, { name: 'KMAC128', length: 24 }], + [kmac, { name: 'KMAC128', length: 32 }], + [false, { name: 'KMAC256', length: 1 }], + [false, { name: 'KMAC256', length: 24 }], + [kmac, { name: 'KMAC256', length: 32 }], ], 'importKey': [ [pqc, 'ML-DSA-44'], @@ -133,10 +141,14 @@ export const vectors = { [kmac, 'KMAC256'], [kmac, { name: 'KMAC128', length: 256 }], [kmac, { name: 'KMAC256', length: 128 }], - [kmac && !fips, { name: 'KMAC128', length: 0 }], - [kmac && !fips, { name: 'KMAC256', length: 0 }], - [kmac && !fips, { name: 'KMAC128', length: 1 }], - [kmac && !fips, { name: 'KMAC256', length: 1 }], + [false, { name: 'KMAC128', length: 0 }], + [false, { name: 'KMAC256', length: 0 }], + [false, { name: 'KMAC128', length: 1 }], + [false, { name: 'KMAC128', length: 24 }], + [kmac, { name: 'KMAC128', length: 32 }], + [false, { name: 'KMAC256', length: 1 }], + [false, { name: 'KMAC256', length: 24 }], + [kmac, { name: 'KMAC256', length: 32 }], ], 'exportKey': [ [pqc, 'ML-DSA-44'], @@ -251,7 +263,7 @@ export const vectors = { [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }], [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }], [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }], - [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }], + [false, 'ML-KEM-768', { name: 'KMAC128', length: 255 }], [hybridKems, 'MLKEM768-P256', 'HKDF'], [hybridKems, 'MLKEM768-X25519', 'HKDF'], [hybridKems, 'MLKEM1024-P384', 'HKDF'], @@ -283,7 +295,7 @@ export const vectors = { [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 256 }], [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 255 }], [pqc, 'ML-KEM-768', { name: 'HMAC', hash: 'SHA-256', length: 249 }], - [pqc && kmac && !fips, 'ML-KEM-768', { name: 'KMAC128', length: 255 }], + [false, 'ML-KEM-768', { name: 'KMAC128', length: 255 }], [hybridKems, 'MLKEM768-P256', 'HKDF'], [hybridKems, 'MLKEM768-X25519', 'HKDF'], [hybridKems, 'MLKEM1024-P384', 'HKDF'], diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js index ba41eb64eb2..a5430df9e0f 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -14,7 +14,6 @@ const { } = require('crypto'); const { hasFIPS } = require('../common/crypto'); const { kSupportedAlgorithms } = require('internal/crypto/util'); -const fips = hasFIPS(); const rejectsXCurves = hasFIPS(3, 5); const hashes = Object.keys(kSupportedAlgorithms.digest).filter((name) => { @@ -139,19 +138,11 @@ function genericSecretVectors(name) { ]; } -function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) { +function macInvalid(algorithm, invalidLengthMessage, isKmac = false) { const key = createSecretKey(randomBytes(32)); const usages = ['sign', 'verify']; - if (allowZeroKey && !fips) { - const zeroKey = createSecretKey(Buffer.alloc(0)) - .toCryptoKey(algorithm, true, usages); - assert.strictEqual(zeroKey.algorithm.length, 0); - - const explicitZeroKey = createSecretKey(Buffer.alloc(0)) - .toCryptoKey({ ...algorithm, length: 0 }, true, usages); - assert.strictEqual(explicitZeroKey.algorithm.length, 0); - } else if (allowZeroKey) { + if (isKmac) { for (const zeroAlgorithm of [algorithm, { ...algorithm, length: 0 }]) { assert.throws(() => { createSecretKey(Buffer.alloc(0)) @@ -177,7 +168,7 @@ function macInvalid(algorithm, invalidLengthMessage, allowZeroKey = false) { assert.throws( () => key.toCryptoKey({ ...algorithm, length: 0 }, true, usages), - allowZeroKey && fips ? { + isKmac ? { name: 'NotSupportedError', message: 'Invalid key length', } : { diff --git a/test/parallel/test-webcrypto-aes-kw-short-input.js b/test/parallel/test-webcrypto-aes-kw-short-input.js index 09fe0268ab9..e11e19360de 100644 --- a/test/parallel/test-webcrypto-aes-kw-short-input.js +++ b/test/parallel/test-webcrypto-aes-kw-short-input.js @@ -6,18 +6,13 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); -const { getFips } = require('crypto'); -const { hasOpenSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; (async () => { const keyToWrap = await subtle.importKey( 'raw', new Uint8Array(16), 'AES-GCM', true, ['encrypt']); - let emptyKey; - if (hasOpenSSL(3) && getFips() !== 1) { - emptyKey = await subtle.importKey( - 'raw-secret', new Uint8Array(0), 'KMAC128', true, ['sign']); - } + const shortKey = await subtle.importKey( + 'raw', new Uint8Array(8), { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']); for (const length of [128, 192, 256]) { const wrappingKey = await subtle.generateKey( @@ -31,11 +26,8 @@ const { subtle } = globalThis.crypto; 'HKDF', false, ['deriveBits']), { name: 'OperationError' }); } - if (emptyKey !== undefined) { - await assert.rejects(subtle.wrapKey( - 'raw-secret', emptyKey, wrappingKey, 'AES-KW'), - { name: 'OperationError' }); - } + await assert.rejects(subtle.wrapKey( + 'raw', shortKey, wrappingKey, 'AES-KW'), { name: 'OperationError' }); const wrapped = await subtle.wrapKey( 'raw', keyToWrap, wrappingKey, 'AES-KW'); diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index e1b663fa30f..318ca8d2500 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -284,7 +284,7 @@ const fips4 = hasFIPS(4); })().then(common.mustCall()); } -if (hasOpenSSL(3) && !hasFIPS()) { +if (hasOpenSSL(3)) { (async () => { const derivedKeyAlgorithm = { name: 'KMAC128', length: 0 }; const usages = ['sign']; @@ -314,19 +314,9 @@ if (hasOpenSSL(3) && !hasFIPS()) { baseKeyAlgorithm, false, ['deriveKey']); - const derived = await subtle.deriveKey( - algorithm, - baseKey, - derivedKeyAlgorithm, - false, - usages); - assert.strictEqual(derived.algorithm.length, 0); - - const signature = subtle.sign({ - name: 'KMAC128', - outputLength: 256, - }, derived, new Uint8Array()); - assert.strictEqual((await signature).byteLength, 32); + await assert.rejects( + subtle.deriveKey(algorithm, baseKey, derivedKeyAlgorithm, false, usages), + { name: 'NotSupportedError', message: 'Invalid key length' }); } })().then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index 47a56a912d6..89bd86bd928 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -9,8 +9,7 @@ const assert = require('assert'); const { Buffer } = require('buffer'); const { subtle } = globalThis.crypto; const { createHash, getHashes } = require('crypto'); -const { hasOpenSSL, hasFIPS, isBoringSSL } = require('../common/crypto'); -const fips = hasFIPS(); +const { isBoringSSL } = require('../common/crypto'); const kTests = [ ['SHA-1', ['sha1'], 160], @@ -265,16 +264,16 @@ if (getHashes().includes('shake128')) { new Uint8Array(0), ); - const digest = await subtle.digest({ name: 'cSHAKE128', outputLength: 7 }, Buffer.alloc(1)); - assert.strictEqual(digest.byteLength, 1); - assert.strictEqual(new Uint8Array(digest)[0] & 0b00000001, 0); + await assert.rejects( + subtle.digest({ name: 'cSHAKE128', outputLength: 7 }, Buffer.alloc(1)), + { name: 'NotSupportedError', message: 'Invalid CShakeParams outputLength' }); await assert.rejects( subtle.digest( { name: 'cSHAKE128', outputLength: 0xffffffff }, Buffer.alloc(1)), { - name: 'OperationError', + name: 'NotSupportedError', message: 'Invalid CShakeParams outputLength', }); @@ -291,22 +290,53 @@ if (getHashes().includes('shake128')) { message: 'Unsupported CShakeParams functionName', }); - if (fips) return; - - await assert.rejects( - subtle.digest( - { - name: 'cSHAKE128', + for (const name of ['cSHAKE128', 'cSHAKE256']) { + const supported = getHashes().includes(name.toLowerCase()); + assert.deepStrictEqual( + await subtle.digest({ + name, outputLength: 256, - customization: Buffer.alloc(513), - }, - Buffer.alloc(1)), - { - name: 'OperationError', - message: 'CShakeParams.customization must be at most 512 bytes', - }); + functionName: Buffer.alloc(0), + customization: Buffer.alloc(0), + }, Buffer.alloc(1)), + await subtle.digest({ name, outputLength: 256 }, Buffer.alloc(1))); - if (!hasOpenSSL(3)) return; + await assert.rejects( + subtle.digest({ + name, + outputLength: 256, + customization: Buffer.alloc(513, 1), + }, Buffer.alloc(1)), + supported ? { + name: 'OperationError', + message: 'CShakeParams.customization must be at most 512 bytes', + } : { + name: 'NotSupportedError', + message: 'Unsupported CShakeParams customization', + }); + + await assert.rejects( + subtle.digest({ + name, + outputLength: 256, + customization: Buffer.from([0x61, 0x00, 0x62]), + }, Buffer.alloc(1)), + { name: 'NotSupportedError', message: 'Unsupported CShakeParams customization' }); + + for (const params of [ + { functionName: Buffer.from('KMAC') }, + { customization: Buffer.from('Node.js') }, + ]) { + const algorithm = { name, outputLength: 256, ...params }; + if (supported) { + assert.strictEqual((await subtle.digest(algorithm, Buffer.alloc(1))).byteLength, 32); + } else { + await assert.rejects(subtle.digest(algorithm, Buffer.alloc(1)), { + name: 'NotSupportedError', + }); + } + } + } const nistCShakeShortInput = Buffer.from('00010203', 'hex'); const nistCShakeLongInput = @@ -400,19 +430,23 @@ if (getHashes().includes('shake128')) { 'ca6f88db415829', }, ]) { - assert.strictEqual( - Buffer.from(await subtle.digest(algorithm, data)).toString('hex'), - expected); + if (getHashes().includes(algorithm.name.toLowerCase())) { + assert.strictEqual( + Buffer.from(await subtle.digest(algorithm, data)).toString('hex'), + expected); + } else { + await assert.rejects(subtle.digest(algorithm, data), { + name: 'NotSupportedError', + }); + } } - const truncated = Buffer.from(await subtle.digest( - { ...nistCShakeSample1.algorithm, outputLength: 255 }, - nistCShakeSample1.data)); - const expected = Buffer.from(nistCShakeSample1.expected, 'hex'); - assert.strictEqual(truncated.byteLength, expected.byteLength); - assert.deepStrictEqual( - truncated.subarray(0, 31), expected.subarray(0, 31)); - assert.strictEqual(truncated[31] & 0b00000001, 0); - assert.strictEqual(truncated[31] | 0b00000001, expected[31]); + if (getHashes().includes('cshake128')) { + await assert.rejects( + subtle.digest( + { ...nistCShakeSample1.algorithm, outputLength: 255 }, + nistCShakeSample1.data), + { name: 'NotSupportedError', message: 'Invalid CShakeParams outputLength' }); + } })().then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index 385ae8c65e9..63fdde05f1e 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -285,69 +285,35 @@ if (hasOpenSSL(3)) { [/* empty usages */]), { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' }); - { - if (getFips() !== 1) { - const importedZeroImplicit = await subtle.importKey( + for (const algorithm of [name, { name, length: 0 }]) { + await assert.rejects( + subtle.importKey( + 'raw-secret', new Uint8Array(), algorithm, true, ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); + } + + await assert.rejects( + subtle.importKey( + 'raw-secret', + new Uint8Array([0xff]), + { name, length: 0 }, + true, + ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); + + await assert.rejects( + subtle.generateKey({ name, length: 9 }, true, ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); + + for (const byteLength of [1, 2]) { + await assert.rejects( + subtle.importKey( 'raw-secret', - new Uint8Array(), - name, - true, - ['sign', 'verify']); - const importedZeroImplicitRaw = - await subtle.exportKey('raw-secret', importedZeroImplicit); - assert.strictEqual(importedZeroImplicit.algorithm.length, 0); - assert.strictEqual(importedZeroImplicitRaw.byteLength, 0); - - const importedZeroExplicit = await subtle.importKey( - 'raw-secret', - new Uint8Array(), - { name, length: 0 }, - true, - ['sign', 'verify']); - const importedZeroExplicitRaw = - await subtle.exportKey('raw-secret', importedZeroExplicit); - assert.strictEqual(importedZeroExplicit.algorithm.length, 0); - assert.strictEqual(importedZeroExplicitRaw.byteLength, 0); - - await assert.rejects( - subtle.importKey( - 'raw-secret', - new Uint8Array([0xff]), - { name, length: 0 }, - true, - ['sign', 'verify']), - { name: 'DataError', message: 'Invalid key length' }); - - const generated = await subtle.generateKey( + new Uint8Array(byteLength).fill(0xff), { name, length: 9 }, true, - ['sign', 'verify']); - const generatedRaw = await subtle.exportKey('raw-secret', generated); - assert.strictEqual(generated.algorithm.length, 9); - assert.strictEqual(generatedRaw.byteLength, 2); - assert.strictEqual(new Uint8Array(generatedRaw)[1] & 0b01111111, 0); - - const importedExplicit = await subtle.importKey( - 'raw-secret', - new Uint8Array([0xff, 0xff]), - { name, length: 9 }, - true, - ['sign', 'verify']); - const importedExplicitRaw = await subtle.exportKey('raw-secret', importedExplicit); - assert.strictEqual(importedExplicit.algorithm.length, 9); - assert.deepStrictEqual( - new Uint8Array(importedExplicitRaw), - new Uint8Array([0xff, 0x80])); - - await assert.rejects( - subtle.importKey( - 'raw-secret', - new Uint8Array([0xff]), - { name, length: 9 }, - true, - ['sign', 'verify']), - { name: 'DataError', message: 'Invalid key length' }); - } + ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); } } diff --git a/test/parallel/test-webcrypto-fips-exceptions.mjs b/test/parallel/test-webcrypto-fips-exceptions.mjs index ecc0f3c6989..b45740dc508 100644 --- a/test/parallel/test-webcrypto-fips-exceptions.mjs +++ b/test/parallel/test-webcrypto-fips-exceptions.mjs @@ -12,10 +12,10 @@ if (!hasFIPS(3)) common.skip('requires OpenSSL >= 3 in FIPS mode'); const require = createRequire(import.meta.url); +const { getHashes } = require('node:crypto'); const { internalBinding } = require('internal/test/binding'); const { getCryptoKeyHandle } = require('internal/crypto/keys'); const { - CShakeJob, KangarooTwelveJob, KmacJob, TurboShakeJob, @@ -52,13 +52,6 @@ for (const createJob of [ kCryptoJobWebCrypto, 'TurboSHAKE128', 0x1f, 16, data), () => new KangarooTwelveJob( kCryptoJobWebCrypto, 'KT128', undefined, 16, data), - () => new CShakeJob( - kCryptoJobWebCrypto, - 'cSHAKE128', - data, - Buffer.from('KMAC'), - undefined, - 128), ]) { assert.throws(createJob, { code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', @@ -66,48 +59,34 @@ for (const createJob of [ }); } -const emptyCShake = { - name: 'cSHAKE128', - outputLength: 256, - customization: data, - functionName: data, -}; -assert.strictEqual(SubtleCrypto.supports('digest', emptyCShake), true); - -for (const length of [1, 513]) { - const algorithm = { - name: 'cSHAKE128', +for (const name of ['cSHAKE128', 'cSHAKE256']) { + const emptyCShake = { + name, outputLength: 256, - customization: new Uint8Array(length), + customization: data, + functionName: data, }; - await assertFipsException( - 'digest', - algorithm, - () => subtle.digest(algorithm, data), - 'Unsupported CShakeParams customization'); + assert.strictEqual(SubtleCrypto.supports('digest', emptyCShake), true); + assert.strictEqual((await subtle.digest(emptyCShake, data)).byteLength, 32); + + for (const params of [ + { customization: Buffer.from('Node.js') }, + { functionName: Buffer.from('KMAC') }, + { functionName: Buffer.from('KMAC'), customization: Buffer.from('Node.js') }, + ]) { + const algorithm = { name, outputLength: 256, ...params }; + const supported = getHashes().includes(name.toLowerCase()); + assert.strictEqual(SubtleCrypto.supports('digest', algorithm), supported); + if (supported) { + assert.strictEqual((await subtle.digest(algorithm, data)).byteLength, 32); + } else { + await assert.rejects(subtle.digest(algorithm, data), { + name: 'NotSupportedError', + }); + } + } } -const functionName = { - name: 'cSHAKE256', - outputLength: 256, - functionName: Buffer.from('KMAC'), -}; -await assertFipsException( - 'digest', - functionName, - () => subtle.digest(functionName, data), - 'Unsupported CShakeParams functionName'); - -const bothCShakeParams = { - ...functionName, - customization: new Uint8Array(1), -}; -await assertFipsException( - 'digest', - bothCShakeParams, - () => subtle.digest(bothCShakeParams, data), - 'Unsupported CShakeParams customization'); - for (const length of [0, 24, 33]) { const algorithm = { name: 'KMAC128', length }; await assertFipsException( @@ -170,7 +149,6 @@ await assert.rejects( getCryptoKeyHandle(key), 'KMAC128', undefined, - 32, 9, data, undefined).run(), diff --git a/test/parallel/test-webcrypto-fips-refresh.js b/test/parallel/test-webcrypto-fips-refresh.js index 71459099356..4eff258b72b 100644 --- a/test/parallel/test-webcrypto-fips-refresh.js +++ b/test/parallel/test-webcrypto-fips-refresh.js @@ -6,7 +6,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); -const { getFips, setFips } = require('crypto'); +const { getFips, getHashes, setFips } = require('crypto'); const { internalBinding } = require('internal/test/binding'); const { getOptionValue } = require('internal/options'); if (!internalBinding('crypto').testFipsCrypto()) @@ -25,8 +25,8 @@ try { name: 'KT128', outputLength: 128, }), !fips); assert.strictEqual(SubtleCrypto.supports('digest', { - name: 'cSHAKE128', outputLength: 128, customization: new Uint8Array(1), - }), !fips); + name: 'cSHAKE128', outputLength: 128, customization: new Uint8Array([1]), + }), getHashes().includes('cshake128')); assert.strictEqual(SubtleCrypto.supports('generateKey', { name: 'RSA-PSS', hash: 'SHA-256', modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), diff --git a/test/parallel/test-webcrypto-keccak-byte-alignment.js b/test/parallel/test-webcrypto-keccak-byte-alignment.js new file mode 100644 index 00000000000..ce3201210eb --- /dev/null +++ b/test/parallel/test-webcrypto-keccak-byte-alignment.js @@ -0,0 +1,90 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { createSecretKey, subtle } = require('crypto'); +const { SubtleCrypto } = globalThis; +const unsupported = { name: 'NotSupportedError' }; + +(async () => { + const data = new Uint8Array([1, 2, 3]); + for (const name of ['cSHAKE128', 'cSHAKE256']) { + if (!SubtleCrypto.supports('digest', { name, outputLength: 128 })) + continue; + + for (const customization of [undefined, new Uint8Array([1])]) { + if (!SubtleCrypto.supports('digest', { name, outputLength: 128, customization })) + continue; + + for (let remainder = 1; remainder < 8; remainder++) { + const algorithm = { name, outputLength: 128 + remainder, customization }; + assert.strictEqual(SubtleCrypto.supports('digest', algorithm), false); + await assert.rejects(subtle.digest(algorithm, data), unsupported); + } + + const digest = await subtle.digest({ name, outputLength: 128, customization }, data); + assert.strictEqual(digest.byteLength, 16); + } + + // Check the uint32 boundary without allocating a large digest. + assert.strictEqual(SubtleCrypto.supports('digest', { name, outputLength: 0xfffffff8 }), true); + for (const outputLength of [0xfffffff9, 0xffffffff]) { + assert.strictEqual(SubtleCrypto.supports('digest', { name, outputLength }), false); + await assert.rejects(subtle.digest({ name, outputLength }, data), unsupported); + } + } + + const baseKey = await subtle.importKey('raw-secret', data, 'HKDF', false, ['deriveKey']); + const derivation = { name: 'HKDF', hash: 'SHA-256', salt: data, info: data }; + const raw = Buffer.alloc(32, 1); + const secretKey = createSecretKey(raw); + + for (const name of ['KMAC128', 'KMAC256']) { + if (!SubtleCrypto.supports('importKey', name)) + continue; + + const key = await subtle.importKey('raw-secret', raw, name, false, ['sign', 'verify']); + for (let remainder = 1; remainder < 8; remainder++) { + const algorithm = { name, length: 248 + remainder }; + assert.strictEqual(SubtleCrypto.supports('generateKey', algorithm), false); + assert.strictEqual(SubtleCrypto.supports('importKey', algorithm), false); + assert.strictEqual(SubtleCrypto.supports('deriveKey', derivation, algorithm), false); + await assert.rejects(subtle.generateKey(algorithm, false, ['sign']), unsupported); + await assert.rejects(subtle.importKey('raw-secret', raw, algorithm, false, ['sign']), unsupported); + await assert.rejects(subtle.importKey( + 'jwk', { kty: 'oct', k: raw.toString('base64url') }, algorithm, false, ['sign']), unsupported); + await assert.rejects(subtle.deriveKey(derivation, baseKey, algorithm, false, ['sign']), unsupported); + assert.throws(() => secretKey.toCryptoKey(algorithm, false, ['sign']), unsupported); + + const params = { name, outputLength: 248 + remainder }; + assert.strictEqual(SubtleCrypto.supports('sign', params), false); + assert.strictEqual(SubtleCrypto.supports('verify', params), false); + await assert.rejects(subtle.sign(params, key, data), unsupported); + await assert.rejects(subtle.verify(params, key, raw, data), unsupported); + } + } + + if (SubtleCrypto.supports('generateKey', 'ML-KEM-768')) { + const { publicKey, privateKey } = await subtle.generateKey( + 'ML-KEM-768', false, ['encapsulateBits', 'encapsulateKey', 'decapsulateKey']); + const { ciphertext } = await subtle.encapsulateBits('ML-KEM-768', publicKey); + for (const name of ['KMAC128', 'KMAC256']) { + if (!SubtleCrypto.supports('importKey', name)) + continue; + + const algorithm = { name, length: 255 }; + assert.strictEqual(SubtleCrypto.supports('encapsulateKey', 'ML-KEM-768', algorithm), false); + assert.strictEqual(SubtleCrypto.supports('decapsulateKey', 'ML-KEM-768', algorithm), false); + await assert.rejects(subtle.encapsulateKey( + 'ML-KEM-768', publicKey, algorithm, false, ['sign']), unsupported); + await assert.rejects(subtle.decapsulateKey( + 'ML-KEM-768', privateKey, ciphertext, algorithm, false, ['sign']), unsupported); + + assert.strictEqual(SubtleCrypto.supports('encapsulateKey', 'ML-KEM-768', { name, length: 256 }), true); + assert.strictEqual(SubtleCrypto.supports('decapsulateKey', 'ML-KEM-768', { name, length: 256 }), true); + } + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-keygen-kmac.js b/test/parallel/test-webcrypto-keygen-kmac.js index 33716095751..999e70df6a6 100644 --- a/test/parallel/test-webcrypto-keygen-kmac.js +++ b/test/parallel/test-webcrypto-keygen-kmac.js @@ -5,7 +5,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasFIPS, hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); if (!hasOpenSSL(3)) common.skip('requires OpenSSL >= 3'); @@ -13,7 +13,6 @@ if (!hasOpenSSL(3)) const assert = require('assert'); const { types: { isCryptoKey } } = require('util'); const { subtle } = globalThis.crypto; -const fips = hasFIPS(); const usages = ['sign', 'verify']; @@ -23,9 +22,6 @@ async function test(name, length) { if (length !== undefined) algorithm.length = length; - if (fips && length !== undefined && - (length < 32 || length % 8 !== 0)) return; - const generatedKey = await subtle.generateKey(algorithm, true, usages); assert(generatedKey); @@ -45,12 +41,10 @@ async function test(name, length) { } const kTests = [ - ['KMAC128', 0], ['KMAC128', 32], ['KMAC128', 128], ['KMAC128', 256], ['KMAC128'], - ['KMAC256', 0], ['KMAC256', 32], ['KMAC256', 128], ['KMAC256', 256], @@ -60,3 +54,13 @@ const kTests = [ const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); + +(async () => { + for (const name of ['KMAC128', 'KMAC256']) { + for (const length of [0, 8, 16, 24]) { + await assert.rejects( + subtle.generateKey({ name, length }, true, usages), + { name: 'NotSupportedError', message: 'Invalid key length' }); + } + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-prototype-pollution.mjs b/test/parallel/test-webcrypto-prototype-pollution.mjs index 9f0b41ac10b..f485cb076f1 100644 --- a/test/parallel/test-webcrypto-prototype-pollution.mjs +++ b/test/parallel/test-webcrypto-prototype-pollution.mjs @@ -13,8 +13,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const require = createRequire(import.meta.url); const { kSupportedAlgorithms } = require('internal/crypto/util'); -const { getFips } = require('node:crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { getFips, getHashes } = require('node:crypto'); const { subtle } = globalThis.crypto; const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype); @@ -134,15 +133,15 @@ if (supports('digest', 'cSHAKE128')) { message: /Unsupported CShakeParams functionName/, }))); - // asyncDigest() picks the cSHAKE job over plain SHAKE on a non-empty + // asyncDigest() picks cSHAKE over plain SHAKE on a non-empty // customization. - if (hasOpenSSL(3)) { + { const algorithm = { name: 'cSHAKE128', outputLength: 256, customization: new Uint8Array([1, 2, 3]), }; - if (getFips() === 1) { + if (!getHashes().includes('cshake128')) { await withPoisoned(poisonTypedArrayByteLength(0), common.mustCall(() => assert.rejects(subtle.digest(algorithm, data), { name: 'NotSupportedError', diff --git a/test/parallel/test-webcrypto-sign-verify-kmac.js b/test/parallel/test-webcrypto-sign-verify-kmac.js index ac0b738bcd5..5829ab3685c 100644 --- a/test/parallel/test-webcrypto-sign-verify-kmac.js +++ b/test/parallel/test-webcrypto-sign-verify-kmac.js @@ -5,38 +5,16 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasFIPS, hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); if (!hasOpenSSL(3)) common.skip('requires OpenSSL >= 3'); const assert = require('assert'); const { subtle } = globalThis.crypto; -const fips = hasFIPS(); -const fips4 = hasFIPS(4); const vectors = require('../fixtures/crypto/kmac')(); -function isFipsProviderUnsupported(err) { - return err.name === 'OperationError' && - err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'; -} - -function usesNonFipsImplementation({ key, keyLength, outputLength }) { - const keyLengthInBits = keyLength ?? key.byteLength * 8; - return outputLength === 0 || - outputLength % 8 !== 0 || - keyLengthInBits < 32 || - keyLengthInBits % 8 !== 0; -} - -function isFips4Incompatible({ key, keyLength, outputLength }) { - const keyLengthInBits = keyLength ?? key.byteLength * 8; - return keyLengthInBits < 128 || - keyLengthInBits % 8 !== 0 || - outputLength % 8 !== 0; -} - async function testVerify({ algorithm, key, keyLength, @@ -215,17 +193,8 @@ async function testSign({ algorithm, const variations = []; for (const vector of vectors) { - if (fips && usesNonFipsImplementation(vector)) continue; - - if (fips4 && isFips4Incompatible(vector)) { - variations.push(assert.rejects( - testVerify(vector), isFipsProviderUnsupported)); - variations.push(assert.rejects( - testSign(vector), isFipsProviderUnsupported)); - } else { - variations.push(testVerify(vector)); - variations.push(testSign(vector)); - } + variations.push(testVerify(vector)); + variations.push(testSign(vector)); } await Promise.all(variations); @@ -240,87 +209,54 @@ async function testSign({ algorithm, ['sign', 'verify']); const algorithm = { name: 'KMAC128', - outputLength: fips ? 16 : 9, + outputLength: 16, customization: new Uint8Array(), }; const data = new Uint8Array([1, 2, 3]); const signature = await subtle.sign(algorithm, key, data); assert.strictEqual(signature.byteLength, 2); - if (!fips) - assert.strictEqual(new Uint8Array(signature)[1] & 0b01111111, 0); assert(await subtle.verify(algorithm, key, signature, data)); - if (fips) { - const signature128 = await subtle.sign({ - ...algorithm, - outputLength: 128, - }, key, data); - assert.strictEqual(signature128.byteLength, 16); - assert(await subtle.verify({ - ...algorithm, - outputLength: 128, - }, key, signature128, data)); - } else { - const signature16 = new Uint8Array(await subtle.sign({ - ...algorithm, - outputLength: 16, - }, key, data)); - signature16[1] &= 0b10000000; - assert.notDeepStrictEqual(new Uint8Array(signature), signature16); - } + const signature128 = await subtle.sign({ + ...algorithm, + outputLength: 128, + }, key, data); + assert.strictEqual(signature128.byteLength, 16); + assert(await subtle.verify({ + ...algorithm, + outputLength: 128, + }, key, signature128, data)); const invalidSignature = new Uint8Array(signature); - if (fips) - invalidSignature[0] ^= 0b00000001; - else - invalidSignature[1] |= 0b00000001; + invalidSignature[0] ^= 0b00000001; assert(!(await subtle.verify(algorithm, key, invalidSignature, data))); - if (!fips) { - const nonByteKey = await subtle.importKey( + const nonByteOutput = { ...algorithm, outputLength: 9 }; + await assert.rejects( + subtle.sign(nonByteOutput, key, data), + { name: 'NotSupportedError', message: 'Invalid KmacParams outputLength' }); + await assert.rejects( + subtle.verify(nonByteOutput, key, signature, data), + { name: 'NotSupportedError', message: 'Invalid KmacParams outputLength' }); + + await assert.rejects( + subtle.importKey( 'raw-secret', new Uint8Array([0xff, 0xff, 0xff, 0xff]), { name: 'KMAC128', length: 25 }, false, - ['sign', 'verify']); - const nonByteKeySignature = subtle.sign({ - ...algorithm, - outputLength: 16, - }, nonByteKey, data); - const result = await nonByteKeySignature; - assert.strictEqual(result.byteLength, 2); - assert(await subtle.verify({ - ...algorithm, - outputLength: 16, - }, nonByteKey, result, data)); - } + ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); })().then(common.mustCall()); (async function() { - if (fips) return; - - const data = new Uint8Array([1, 2, 3]); - for (const name of ['KMAC128', 'KMAC256']) { - for (const keyData of [ - new Uint8Array(), - new Uint8Array([1]), - new Uint8Array([1, 2, 3]), - ]) { - const key = await subtle.importKey( - 'raw-secret', - keyData, - { name }, - true, - ['sign', 'verify']); - assert.strictEqual(key.algorithm.length, keyData.byteLength * 8); - - const algorithm = { name, outputLength: 256 }; - const signature = subtle.sign(algorithm, key, data); - const result = await signature; - assert.strictEqual(result.byteLength, 32); - assert(await subtle.verify(algorithm, key, result, data)); + for (const byteLength of [0, 1, 2, 3]) { + await assert.rejects( + subtle.importKey( + 'raw-secret', new Uint8Array(byteLength), name, true, ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); } } })().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index b899e4f712d..1f8d45f02c2 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -485,20 +485,30 @@ async function testNonByteLengthWrapUnwrap({ }); if (hasOpenSSL(3) && getFips() !== 1) { - const kmacAlgorithm = { name: 'KMAC128' }; - const kmacKey = await subtle.importKey( - 'raw-secret', - new Uint8Array([0xff, 0xff]), - { ...kmacAlgorithm, length: 9 }, - true, - ['sign', 'verify']); - await testNonByteLengthWrapUnwrap({ - key: kmacKey, - formats: ['raw-secret', 'jwk'], - rawFormat: 'raw-secret', - explicitAlgorithm: { ...kmacAlgorithm, length: 9 }, - implicitAlgorithm: kmacAlgorithm, - }); + for (const name of ['KMAC128', 'KMAC256']) { + const keyData = new Uint8Array(32).fill(0xff); + const kmacKey = await subtle.importKey( + 'raw-secret', keyData, name, true, ['sign', 'verify']); + const wrappingKey = await subtle.generateKey( + { name: 'AES-GCM', length: 128 }, true, ['wrapKey', 'unwrapKey']); + + for (const [i, format] of ['raw-secret', 'jwk'].entries()) { + const wrapAlgorithm = { name: 'AES-GCM', iv: new Uint8Array(12).fill(i) }; + const wrapped = await subtle.wrapKey(format, kmacKey, wrappingKey, wrapAlgorithm); + await assert.rejects( + subtle.unwrapKey( + format, wrapped, wrappingKey, wrapAlgorithm, + { name, length: 255 }, true, ['sign', 'verify']), + { name: 'NotSupportedError', message: 'Invalid key length' }); + + const unwrapped = await subtle.unwrapKey( + format, wrapped, wrappingKey, wrapAlgorithm, + { name, length: 256 }, true, ['sign', 'verify']); + assert.strictEqual(unwrapped.algorithm.length, 256); + assert.deepStrictEqual( + new Uint8Array(await subtle.exportKey('raw-secret', unwrapped)), keyData); + } + } } })().then(common.mustCall()); diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index af6455f886f..e12016ec916 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -182,17 +182,6 @@ declare namespace InternalCryptoBinding { ): CryptoJobWebCrypto; } - interface CShakeJobConstructor { - new( - mode: CryptoJobWebCryptoMode, - algorithm: string, - data: ByteSource, - functionName: OptionalByteSource, - customization: OptionalByteSource, - outputLength: number, - ): CryptoJobWebCrypto; - } - interface ChaCha20Poly1305CipherJobConstructor { new( mode: CryptoJobWebCryptoMode, @@ -375,7 +364,6 @@ declare namespace InternalCryptoBinding { key: KeyObjectHandle, algorithm: string, customization: OptionalByteSource, - keyLength: number, outputLength: number, data: ByteSource, ...signature: MacJobSignatureArgs @@ -818,7 +806,6 @@ declare namespace InternalCryptoBinding { export interface CryptoBinding { AESCipherJob: InternalCryptoBinding.AESCipherJobConstructor; Argon2Job: InternalCryptoBinding.Argon2JobConstructor; - CShakeJob?: InternalCryptoBinding.CShakeJobConstructor; ChaCha20Poly1305CipherJob: InternalCryptoBinding.ChaCha20Poly1305CipherJobConstructor; CheckPrimeJob: InternalCryptoBinding.CheckPrimeJobConstructor; DHBitsJob: InternalCryptoBinding.DHBitsJobConstructor;