Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/internal/crypto/mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
normalizeHashName,
numBitsToBytes,
truncateToBitLength,
validateKmacKeyLength,
} = require('internal/crypto/util');

const {
Expand Down Expand Up @@ -60,6 +61,9 @@ function normalizeKeyLength(handle, algorithm) {
length = algorithm.length;
}

if (algorithm.name === 'KMAC128' || algorithm.name === 'KMAC256')
validateKmacKeyLength(length);

return { handle, length };
}

Expand Down
14 changes: 14 additions & 0 deletions lib/internal/crypto/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ const {
EVP_PKEY_ML_KEM_1024,
kKeyVariantAES_OCB_128: hasAesOcbMode,
Argon2Job,
getFipsCrypto,
KmacJob,
} = internalBinding('crypto');

const isFips = getFipsCrypto() === 1;

const { getOptionValue } = require('internal/options');

const {
Expand Down Expand Up @@ -423,6 +426,8 @@ const conditionalAlgorithms = {
'Ed448': !process.features.openssl_is_boringssl,
'KMAC128': !!KmacJob,
'KMAC256': !!KmacJob,
'KT128': !isFips,
'KT256': !isFips,
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
Expand All @@ -435,6 +440,8 @@ const conditionalAlgorithms = {
ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
'SHA3-512': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
'TurboSHAKE128': !isFips,
'TurboSHAKE256': !isFips,
'X448': !process.features.openssl_is_boringssl,
};

Expand Down Expand Up @@ -579,6 +586,11 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
}
}

function validateKmacKeyLength(length) {
if ((length < 32 || length % 8) && isFips)
throw lazyDOMException('Invalid key length', 'NotSupportedError');
}

/**
* Converts a bit length to the number of bytes needed to contain it.
* Non-byte lengths are rounded up to the next byte.
Expand Down Expand Up @@ -1097,6 +1109,7 @@ module.exports = {

kNamedCurveAliases,
kSupportedAlgorithms,
isFips,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,
Expand All @@ -1106,6 +1119,7 @@ module.exports = {
jobPromiseThen,
cleanupWebCryptoResult,
prepareWebCryptoResult,
validateKmacKeyLength,
validateMaxBufferLength,
numBitsToBytes,
truncateToBitLength,
Expand Down
51 changes: 34 additions & 17 deletions lib/internal/crypto/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
TypedArrayPrototypeGetLength,
} = primordials;

const {
Expand All @@ -27,8 +26,10 @@ const {
validateMaxBufferLength,
getBufferSourceByteLength,
getBufferSourceBytes,
isFips,
kNamedCurveAliases,
numBitsToBytes,
validateKmacKeyLength,
} = require('internal/crypto/util');
const {
converters: webidl,
Expand Down Expand Up @@ -252,30 +253,39 @@ function validateCShakeOutputLength(V) {
}
}

function bufferSourceEqualsAscii(V, string) {
if (getBufferSourceByteLength(V) !== string.length) return false;

const bytes = getBufferSourceBytes(V);
const length = TypedArrayPrototypeGetLength(bytes);
for (let i = 0; i < length; i++) {
if (bytes[i] !== StringPrototypeCharCodeAt(string, i)) return false;
}
return true;
}
const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];

function validateCShakeFunctionName(V) {
if (getBufferSourceByteLength(V) === 0 ||
bufferSourceEqualsAscii(V, 'KMAC') ||
bufferSourceEqualsAscii(V, 'TupleHash') ||
bufferSourceEqualsAscii(V, 'ParallelHash')) {
return;
const length = getBufferSourceByteLength(V);
if (length === 0) return;

if (!isFips) {
const bytes = getBufferSourceBytes(V);
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
const functionName = kCShakeFunctionNames[i];
if (length !== functionName.length) continue;

let j = 0;
for (; j < length; j++) {
if (bytes[j] !== StringPrototypeCharCodeAt(functionName, j)) break;
}
if (j === length) return;
}
}

throw lazyDOMException(
'Unsupported CShakeParams functionName',
'NotSupportedError');
}

function validateCShakeCustomization(V) {
if (isFips && getBufferSourceByteLength(V) !== 0)
throw lazyDOMException(
'Unsupported CShakeParams customization',
'NotSupportedError');
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
}

converters.RsaPssParams = createDictionaryConverter(
'RsaPssParams', [
dictAlgorithm,
Expand Down Expand Up @@ -433,7 +443,7 @@ converters.CShakeParams = createDictionaryConverter(
{
key: 'customization',
converter: converters.BufferSource,
validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512),
validator: validateCShakeCustomization,
},
],
]);
Expand Down Expand Up @@ -719,6 +729,7 @@ for (let i = 0; i < kKmacDictionaries.length; i++) {
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, enforceRangeOptions(opts)),
validator: validateKmacKeyLength,
},
],
]);
Expand All @@ -732,6 +743,12 @@ converters.KmacParams = createDictionaryConverter(
key: 'outputLength',
converter: (V, opts) =>
converters['unsigned long'](V, enforceRangeOptions(opts)),
validator: (V) => {
if ((V === 0 || V % 8) && isFips)
throw lazyDOMException(
'Invalid KmacParams outputLength',
'NotSupportedError');
},
required: true,
},
{
Expand Down
5 changes: 5 additions & 0 deletions src/crypto/crypto_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,11 @@ Maybe<void> CShakeTraits::AdditionalConfig(
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();
Expand Down
2 changes: 2 additions & 0 deletions src/crypto/crypto_kmac.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ 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;

Expand Down
10 changes: 10 additions & 0 deletions src/crypto/crypto_turboshake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ Maybe<void> TurboShakeTraits::AdditionalConfig(
TurboShakeConfig* params) {
Environment* env = Environment::GetCurrent(args);

if (IsFipsEnabled()) {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return Nothing<void>();
}

// args[offset + 0] = algorithm name (string)
CHECK(args[offset]->IsString());
Utf8Value algorithm_name(env->isolate(), args[offset]);
Expand Down Expand Up @@ -535,6 +540,11 @@ Maybe<void> KangarooTwelveTraits::AdditionalConfig(
KangarooTwelveConfig* params) {
Environment* env = Environment::GetCurrent(args);

if (IsFipsEnabled()) {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return Nothing<void>();
}

// args[offset + 0] = algorithm name (string)
CHECK(args[offset]->IsString());
Utf8Value algorithm_name(env->isolate(), args[offset]);
Expand Down
8 changes: 6 additions & 2 deletions src/crypto/crypto_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ bool InitCryptoOnce(Isolate* isolate) {
// be part of a larger mutex for global OpenSSL state.
static Mutex fips_mutex;

bool IsFipsEnabled() {
Mutex::ScopedLock fips_lock(fips_mutex);
return ncrypto::isFipsEnabled();
}

void InitCryptoOnce() {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
Expand Down Expand Up @@ -223,8 +228,7 @@ void InitCryptoOnce() {

void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
Mutex::ScopedLock lock(per_process::cli_options_mutex);
Mutex::ScopedLock fips_lock(fips_mutex);
args.GetReturnValue().Set(ncrypto::isFipsEnabled() ? 1 : 0);
args.GetReturnValue().Set(IsFipsEnabled() ? 1 : 0);
}

void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ constexpr T NumBitsToBytes(T bits) {
// what went wrong, or std::nullopt when there was nothing to do or the
// options were applied successfully.
std::optional<std::string> ProcessFipsOptions();
bool IsFipsEnabled();

bool InitCryptoOnce(v8::Isolate* isolate);
void InitCryptoOnce();
Expand Down
8 changes: 2 additions & 6 deletions test/parallel/test-webcrypto-derivekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const fips4 = hasFIPS(4);
})().then(common.mustCall());
}

if (hasOpenSSL(3)) {
if (hasOpenSSL(3) && !hasFIPS()) {
(async () => {
const derivedKeyAlgorithm = { name: 'KMAC128', length: 0 };
const usages = ['sign'];
Expand Down Expand Up @@ -326,11 +326,7 @@ if (hasOpenSSL(3)) {
name: 'KMAC128',
outputLength: 256,
}, derived, new Uint8Array());
if (fips4) {
await assert.rejects(signature, { name: 'OperationError' });
} else {
assert.strictEqual((await signature).byteLength, 32);
}
assert.strictEqual((await signature).byteLength, 32);
}
})().then(common.mustCall());
}
Expand Down
5 changes: 5 additions & 0 deletions test/parallel/test-webcrypto-digest-turboshake-rfc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const { hasFIPS } = require('../common/crypto');

if (hasFIPS())
common.skip('TurboSHAKE and KangarooTwelve are not available in FIPS mode');

const assert = require('assert');
const { subtle } = globalThis.crypto;

Expand Down
5 changes: 5 additions & 0 deletions test/parallel/test-webcrypto-digest-turboshake.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const { hasFIPS } = require('../common/crypto');

if (hasFIPS())
common.skip('TurboSHAKE and KangarooTwelve are not available in FIPS mode');

const assert = require('assert');
const { subtle } = globalThis.crypto;

Expand Down
42 changes: 14 additions & 28 deletions test/parallel/test-webcrypto-digest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { Buffer } = require('buffer');
const { subtle } = globalThis.crypto;
const { createHash, getHashes } = require('crypto');
const { hasOpenSSL, hasFIPS } = require('../common/crypto');
const fips4 = hasFIPS(4);
const fips = hasFIPS();

const kTests = [
['SHA-1', ['sha1'], 160],
Expand Down Expand Up @@ -291,6 +291,8 @@ if (getHashes().includes('shake128')) {
message: 'Unsupported CShakeParams functionName',
});

if (fips) return;

await assert.rejects(
subtle.digest(
{
Expand Down Expand Up @@ -398,35 +400,19 @@ if (getHashes().includes('shake128')) {
'ca6f88db415829',
},
]) {
const digest = subtle.digest(algorithm, data);
if (fips4) {
await assert.rejects(
digest,
(err) => err.name === 'OperationError' &&
err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
} else {
assert.strictEqual(
Buffer.from(await digest).toString('hex'),
expected);
}
assert.strictEqual(
Buffer.from(await subtle.digest(algorithm, data)).toString('hex'),
expected);
}

const truncatedDigest = subtle.digest(
const truncated = Buffer.from(await subtle.digest(
{ ...nistCShakeSample1.algorithm, outputLength: 255 },
nistCShakeSample1.data);
if (fips4) {
await assert.rejects(
truncatedDigest,
(err) => err.name === 'OperationError' &&
err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED');
} else {
const truncated = Buffer.from(await truncatedDigest);
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]);
}
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]);
})().then(common.mustCall());
}
Loading
Loading