Commit 38187f81ebc for nodejs
commit 38187f81ebca290af81d560071a2ac0e661e0a39
Author: Filip Skokan <panva.ip@gmail.com>
Date: Wed Sep 23 13:34:11 2026 +0200
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 <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
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`
<!-- YAML
@@ -1858,7 +1861,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.
#### `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 <openssl/core_names.h>
#include <openssl/evp.h>
-#endif
#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK
#include <openssl/digest.h>
#endif
#include <algorithm>
-#include <array>
#include <climits>
#include <cstdio>
-#include <limits>
-#include <memory>
#include <string_view>
#include <utility>
@@ -588,9 +582,6 @@ void Hash::Initialize(Environment* env, Local<Object> 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<unsigned char, 1> kEmptyString = {};
-static constexpr size_t kKeccakKmac128Rate = 168;
-static constexpr size_t kKeccakKmac256Rate = 136;
-static constexpr size_t kMaxCShakeCustomizationSize = 512;
-
-struct EncodedLength {
- std::array<unsigned char, sizeof(size_t) + 1> 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<size_t>::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<unsigned char>(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<unsigned char>(value >> shift);
- }
- if (!left) encoded.data[value_size] = static_cast<unsigned char>(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<const void>{
- .data = data,
- .len = size,
- });
-}
-
-bool DigestUpdateZeros(ncrypto::EVPMDCtxPointer* ctx, size_t size) {
- static constexpr std::array<unsigned char, 168> 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<size_t>::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, decltype(&EVP_MD_free)>{
- 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<Value> CShakeTraits::EncodeOutput(Environment* env,
- const CShakeConfig& params,
- ByteSource* out) {
- return out->ToArrayBuffer(env);
-}
-
-Maybe<void> CShakeTraits::AdditionalConfig(
- CryptoJobMode mode,
- const FunctionCallbackInfo<Value>& args,
- unsigned int offset,
- CShakeConfig* params) {
- Environment* env = Environment::GetCurrent(args);
-
- if (IsFipsEnabled()) {
- THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
- return Nothing<void>();
- }
-
- 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<char> data(args[offset + 1]);
- if (!data.CheckSizeInt32()) [[unlikely]] {
- THROW_ERR_OUT_OF_RANGE(env, "data is too big");
- return Nothing<void>();
- }
- params->in = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource();
-
- if (!args[offset + 2]->IsUndefined()) {
- ArrayBufferOrViewContents<char> function_name(args[offset + 2]);
- if (!function_name.CheckSizeInt32()) [[unlikely]] {
- THROW_ERR_OUT_OF_RANGE(env, "functionName is too big");
- return Nothing<void>();
- }
- params->function_name = IsCryptoJobAsync(mode)
- ? function_name.ToCopy()
- : function_name.ToByteSource();
- }
-
- if (!args[offset + 3]->IsUndefined()) {
- ArrayBufferOrViewContents<char> customization(args[offset + 3]);
- if (!customization.CheckSizeInt32()) [[unlikely]] {
- THROW_ERR_OUT_OF_RANGE(env, "customization is too big");
- return Nothing<void>();
- }
- params->customization = IsCryptoJobAsync(mode)
- ? customization.ToCopy()
- : customization.ToByteSource();
- }
-
- CHECK(args[offset + 4]->IsUint32()); // Length
- params->length = args[offset + 4].As<Uint32>()->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<size_t>(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<HashTraits>;
-#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<void> AdditionalConfig(
- CryptoJobMode mode,
- const v8::FunctionCallbackInfo<v8::Value>& args,
- unsigned int offset,
- CShakeConfig* params);
-
- static bool DeriveBits(Environment* env,
- const CShakeConfig& params,
- ByteSource* out,
- CryptoJobMode mode,
- CryptoErrorStore* errors);
-
- static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
- const CShakeConfig& params,
- ByteSource* out);
-};
-
-using CShakeJob = DeriveBitsJob<CShakeTraits>;
-#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 <openssl/core_names.h>
#include <openssl/params.h>
-#include <array>
-#include <limits>
#include <utility>
#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<void> KmacTraits::AdditionalConfig(
}
// If undefined, params->customization remains uninitialized (size 0).
- CHECK(args[offset + 4]->IsNumber()); // Key length
- double key_length = args[offset + 4].As<Number>()->Value();
- if (!(key_length >= 0) ||
- key_length > static_cast<double>(std::numeric_limits<size_t>::max())) {
- THROW_ERR_OUT_OF_RANGE(env, "key length is too big");
- return Nothing<void>();
- }
- params->key_length = static_cast<size_t>(key_length);
+ CHECK(args[offset + 4]->IsUint32()); // Length
+ params->length = args[offset + 4].As<Uint32>()->Value();
- CHECK(args[offset + 5]->IsUint32()); // Length
- params->length = args[offset + 5].As<Uint32>()->Value();
-
- ArrayBufferOrViewContents<char> data(args[offset + 6]);
+ ArrayBufferOrViewContents<char> data(args[offset + 5]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->data = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource();
- if (!args[offset + 7]->IsUndefined()) {
- ArrayBufferOrViewContents<char> signature(args[offset + 7]);
+ if (!args[offset + 6]->IsUndefined()) {
+ ArrayBufferOrViewContents<char> signature(args[offset + 6]);
if (!signature.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<void>();
@@ -128,86 +114,18 @@ Maybe<void> KmacTraits::AdditionalConfig(
return JustVoid();
}
-namespace {
-
-static constexpr std::array<unsigned char, 4> 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<size_t>(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',
- });
-
- if (!hasOpenSSL(3)) return;
+ functionName: Buffer.alloc(0),
+ customization: Buffer.alloc(0),
+ }, Buffer.alloc(1)),
+ await subtle.digest({ name, outputLength: 256 }, Buffer.alloc(1)));
+
+ 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(
- '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);
+ 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' });
+ }
- 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(
- { 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);
+ 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' });
- const importedExplicit = await subtle.importKey(
+ for (const byteLength of [1, 2]) {
+ await assert.rejects(
+ subtle.importKey(
'raw-secret',
- new Uint8Array([0xff, 0xff]),
+ new Uint8Array(byteLength).fill(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<ArrayBuffer>;
}
- interface CShakeJobConstructor {
- new(
- mode: CryptoJobWebCryptoMode,
- algorithm: string,
- data: ByteSource,
- functionName: OptionalByteSource,
- customization: OptionalByteSource,
- outputLength: number,
- ): CryptoJobWebCrypto<ArrayBuffer>;
- }
-
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<S>
@@ -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;