Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,7 @@ class SubtleCrypto {
}

// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
// TODO(panva): Make supports() account for the active FIPS state.
static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
emitExperimentalWarning('The supports Web Crypto API method');
if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');
Expand Down
14 changes: 12 additions & 2 deletions test/common/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@ function assertApproximateSize(key, expectedSize) {
function testEncryptDecrypt(publicKey, privateKey) {
const message = 'Hello Node.js world!';
const plaintext = Buffer.from(message, 'utf8');
const withOaepHash = (key) => {
if (!hasFIPS(3)) return key;
if (key?.key !== undefined) return { ...key, oaepHash: 'sha256' };
return { key, oaepHash: 'sha256' };
};
for (const key of [publicKey, privateKey]) {
const ciphertext = publicEncrypt(key, plaintext);
const received = privateDecrypt(privateKey, ciphertext);
const ciphertext = publicEncrypt(withOaepHash(key), plaintext);
const received = privateDecrypt(withOaepHash(privateKey), ciphertext);
assert.strictEqual(received.toString('utf8'), message);
}
}
Expand Down Expand Up @@ -118,6 +123,10 @@ const hasOpenSSL = (major = 0, minor = 0, patch = 0) => {
return OPENSSL_VERSION_NUMBER >= opensslVersionNumber(major, minor, patch);
};

const hasFIPS = (major = 0, minor = 0, patch = 0) => {
return crypto.getFips() === 1 && hasOpenSSL(major, minor, patch);
};

let opensslCli = null;

module.exports = {
Expand All @@ -134,6 +143,7 @@ module.exports = {
sec1Exp,
sec1EncExp,
hasOpenSSL,
hasFIPS,
get hasOpenSSL3() {
return hasOpenSSL(3);
},
Expand Down
28 changes: 28 additions & 0 deletions test/fixtures/keys/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ all: \
ca5-cert.pem \
ca6-cert.pem \
agent1-cert.pem \
agent1-fips.pfx \
agent1.pfx \
agent2-cert.pem \
agent3-cert.pem \
Expand Down Expand Up @@ -39,6 +40,7 @@ all: \
dsa_private_encrypted_1025.pem \
dsa_public_1025.pem \
ec-cert.pem \
ec-fips.pfx \
ec.pfx \
fake-cnnic-root-cert.pem \
intermediate-ca-cert.pem \
Expand Down Expand Up @@ -444,6 +446,20 @@ agent1.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem
-out agent1.pfx \
-password pass:sample

# PKCS12KDF is unavailable under FIPS properties. Use PBMAC1 with PBKDF2
# instead, alongside AES-256/PBKDF2 key protection.
agent1-fips.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem
openssl pkcs12 -export \
-keypbe AES-256-CBC \
-certpbe AES-256-CBC \
-iter 2048 \
-pbmac1_pbkdf2 \
-in agent1-cert.pem \
-inkey agent1-key.pem \
-certfile ca1-cert.pem \
-out agent1-fips.pfx \
-password pass:password

agent1-verify: agent1-cert.pem ca1-cert.pem
openssl verify -CAfile ca1-cert.pem agent1-cert.pem

Expand Down Expand Up @@ -787,6 +803,18 @@ ec.pfx: ec-cert.pem ec-key.pem
-out ec.pfx \
-password pass:

# See agent1-fips.pfx for why the FIPS fixture uses PBMAC1.
ec-fips.pfx: ec-cert.pem ec-key.pem
openssl pkcs12 -export \
-keypbe AES-256-CBC \
-certpbe AES-256-CBC \
-iter 2048 \
-pbmac1_pbkdf2 \
-in ec-cert.pem \
-inkey ec-key.pem \
-out ec-fips.pfx \
-password pass:password

dh512.pem:
openssl dhparam -out dh512.pem 512

Expand Down
Binary file added test/fixtures/keys/agent1-fips.pfx
Binary file not shown.
Binary file added test/fixtures/keys/ec-fips.pfx
Binary file not shown.
4 changes: 3 additions & 1 deletion test/parallel/test-crypto-argon2-job.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

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

if (!hasOpenSSL(3, 2))
common.skip('requires OpenSSL >= 3.2');
if (hasFIPS(3))
common.skip('Argon2 is not available in FIPS mode');

// Exercises the native Argon2 job directly via internalBinding, bypassing
// the JS validators, to ensure that if invalid parameters ever reach the
Expand Down
13 changes: 12 additions & 1 deletion test/parallel/test-crypto-argon2.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

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

if (!hasOpenSSL(3, 2))
common.skip('requires OpenSSL >= 3.2');
Expand All @@ -28,6 +28,17 @@ const secret = Buffer.alloc(8, 0x03);
const associatedData = Buffer.alloc(12, 0x04);
const defaults = { message, nonce, parallelism: 1, tagLength: 64, memory: 8, passes: 3 };

if (hasFIPS(3)) {
assert.throws(() => crypto.argon2Sync('argon2id', defaults), {
code: 'ERR_OSSL_EVP_UNSUPPORTED',
});
crypto.argon2('argon2id', defaults, common.mustCall((err, result) => {
assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED');
assert.strictEqual(result, undefined);
}));
return;
}

const good = [
// Test vectors from RFC 9106 https://www.rfc-editor.org/rfc/rfc9106.html#name-test-vectors
// and OpenSSL 3.2 https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/test/recipes/30-test_evp_data/evpkdf_argon2.txt
Expand Down
61 changes: 43 additions & 18 deletions test/parallel/test-crypto-async-sign-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const { hasOpenSSL3 } = require('../common/crypto');
const { hasOpenSSL, hasFIPS } = require('../common/crypto');
const assert = require('assert');
const util = require('util');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');

const fips3 = hasFIPS(3);

function test(
publicFixture,
privateFixture,
Expand Down Expand Up @@ -65,6 +67,15 @@ function test(
}
}

function testSignFailure(privateFixture, algorithm, options, code) {
const key = { key: fixtures.readKey(privateFixture), ...options };
const data = Buffer.from('Hello world');
assert.throws(() => crypto.sign(algorithm, data, key), { code });
crypto.sign(algorithm, data, key, common.mustCall((err) => {
assert.strictEqual(err?.code, code);
}));
}

// RSA w/ default padding
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true);
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true,
Expand Down Expand Up @@ -94,14 +105,19 @@ if (!process.features.openssl_is_boringssl) {
test('ed448_public.pem', 'ed448_private.pem', undefined, true);

// ECDSA w/ der signature encoding
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
false);
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
false, { dsaEncoding: 'der' });

// ECDSA w/ ieee-p1363 signature encoding
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
{ dsaEncoding: 'ieee-p1363' });
if (fips3) {
testSignFailure('ec_secp256k1_private.pem', 'sha384', {},
'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE');
} else {
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
false);
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
false, { dsaEncoding: 'der' });

// ECDSA w/ ieee-p1363 signature encoding
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
{ dsaEncoding: 'ieee-p1363' });
}

// DSA w/ der signature encoding
test('dsa_public.pem', 'dsa_private.pem', 'sha256',
Expand Down Expand Up @@ -157,7 +173,7 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc=

let expected = /no default digest/;
let expectedCode = 'ERR_OSSL_EVP_NO_DEFAULT_DIGEST';
if (hasOpenSSL3 || process.features.openssl_is_boringssl) {
if (hasOpenSSL(3) || process.features.openssl_is_boringssl) {
expected = /operation[\s_]not[\s_]supported[\s_]for[\s_]this[\s_]keytype/i;
expectedCode = 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE';
}
Expand All @@ -170,12 +186,21 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc=
}

{
const { privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 512
});
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
assert.ok(err);
assert.match(err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
assert.match(err.code, /^ERR_OSSL_.*DIGEST_TOO_BIG_FOR_RSA_KEY$/);
}));
if (fips3) {
crypto.generateKeyPair('rsa', { modulusLength: 512 },
common.mustCall((err) => {
assert.strictEqual(
err?.code, 'ERR_OSSL_RSA_INVALID_MODULUS');
}));
} else {
const { privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 512
});
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
assert.ok(err);
assert.match(
err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
assert.match(err.code, /^ERR_OSSL_.*DIGEST_TOO_BIG_FOR_RSA_KEY$/);
}));
}
}
11 changes: 11 additions & 0 deletions test/parallel/test-crypto-authenticated-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ if (!common.hasCrypto)

const assert = require('assert');
const crypto = require('crypto');
const { hasFIPS } = require('../common/crypto');
const fs = require('fs');
const stream = require('stream');
const tmpdir = require('../common/tmpdir');
Expand Down Expand Up @@ -120,6 +121,16 @@ function test(config) {
return;
}

if (hasFIPS(3)) {
assert.throws(() => crypto.createDecipheriv(
config.cipher, config.key, config.iv, {
authTagLength: config.authTagLength,
}), {
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
});
return;
}

direct(config);
mstream(config);
fstream(config);
Expand Down
51 changes: 37 additions & 14 deletions test/parallel/test-crypto-authenticated.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ const assert = require('assert');
const crypto = require('crypto');
const { inspect } = require('util');
const fixtures = require('../common/fixtures');
const { hasOpenSSL3 } = require('../common/crypto');
const { hasOpenSSL, hasFIPS } = require('../common/crypto');

const isFipsEnabled = crypto.getFips();
const isFipsEnabled = crypto.getFips() === 1;
const fips3 = hasFIPS(3);

//
// Test authenticated encryption modes.
Expand Down Expand Up @@ -559,6 +560,14 @@ for (const test of TEST_CASES) {
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
const tag = cipher.getAuthTag();

if (fips3 && mode === 'ccm') {
assert.throws(() => crypto.createDecipheriv(
`aes-128-${mode}`, key, iv, opts), {
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
});
continue;
}

const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
decipher.setAuthTag(tag);
assert.throws(() => {
Expand Down Expand Up @@ -636,15 +645,22 @@ for (const test of TEST_CASES) {
const cipher = crypto.createCipheriv('aes-128-ccm', key, iv, opts);
assert.throws(() => {
cipher.final();
}, hasOpenSSL3 ? {
}, hasOpenSSL(3) ? {
code: 'ERR_OSSL_TAG_NOT_SET'
} : {
message: /Unsupported state/
});
}
}

if (!process.features.openssl_is_boringssl) {
if (fips3) {
assert.throws(() => crypto.createCipheriv(
'chacha20-poly1305', Buffer.alloc(32), Buffer.alloc(12), {
authTagLength: 16,
}), {
code: 'ERR_OSSL_EVP_UNSUPPORTED',
});
} else if (!process.features.openssl_is_boringssl) {
const key = Buffer.alloc(32);
const iv = Buffer.alloc(12);

Expand All @@ -662,7 +678,7 @@ if (!process.features.openssl_is_boringssl) {

// ChaCha20-Poly1305 should respect the authTagLength option and should not
// require the authentication tag before calls to update() during decryption.
if (!process.features.openssl_is_boringssl) {
if (!fips3 && !process.features.openssl_is_boringssl) {
const key = Buffer.alloc(32);
const iv = Buffer.alloc(12);

Expand Down Expand Up @@ -713,7 +729,7 @@ if (!process.features.openssl_is_boringssl) {
// shorter tags as long as their length was valid according to NIST SP 800-38D.
// For ChaCha20-Poly1305, we intentionally deviate from that because there are
// no recommended or approved authentication tag lengths below 16 bytes.
if (!process.features.openssl_is_boringssl) {
if (!fips3 && !process.features.openssl_is_boringssl) {
const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => {
return algo === 'chacha20-poly1305' && tampered === false;
});
Expand Down Expand Up @@ -752,7 +768,7 @@ if (!process.features.openssl_is_boringssl) {
}

// https://github.com/nodejs/node/issues/45874
if (!process.features.openssl_is_boringssl) {
if (!fips3 && !process.features.openssl_is_boringssl) {
const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => {
return algo === 'chacha20-poly1305' && tampered === false;
});
Expand Down Expand Up @@ -798,13 +814,20 @@ if (ciphers.includes('aes-128-ccm')) {
const tag = cipher.getAuthTag();
assert.strictEqual(tag.length, 16);

const decipher = crypto.createDecipheriv('aes-128-ccm', key, nonce, {
authTagLength: 16,
});
decipher.setAuthTag(tag);
decipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 });
decipher.update(new DataView(new ArrayBuffer(0)));
decipher.final();
if (fips3) {
assert.throws(() => crypto.createDecipheriv(
'aes-128-ccm', key, nonce, { authTagLength: 16 }), {
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
});
} else {
const decipher = crypto.createDecipheriv('aes-128-ccm', key, nonce, {
authTagLength: 16,
});
decipher.setAuthTag(tag);
decipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 });
decipher.update(new DataView(new ArrayBuffer(0)));
decipher.final();
}
} else {
common.printSkipMessage('Skipping unsupported aes-128-ccm test');
}
Loading