Skip to content

Commit 810a6b0

Browse files
panvaaduh95
authored andcommitted
test: update tests to run with OpenSSL >= 3.0 FIPS mode
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #64960 Backport-PR-URL: #66233 Fixes: #48379 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent c555ba0 commit 810a6b0

130 files changed

Lines changed: 2836 additions & 1018 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎lib/internal/crypto/webcrypto.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,7 @@ class SubtleCrypto {
16741674
}
16751675

16761676
// Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports
1677+
// TODO(panva): Make supports() account for the active FIPS state.
16771678
static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
16781679
emitExperimentalWarning('The supports Web Crypto API method');
16791680
if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor');

‎test/common/crypto.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,14 @@ function assertApproximateSize(key, expectedSize) {
5050
function testEncryptDecrypt(publicKey, privateKey) {
5151
const message = 'Hello Node.js world!';
5252
const plaintext = Buffer.from(message, 'utf8');
53+
const withOaepHash = (key) => {
54+
if (!hasFIPS(3)) return key;
55+
if (key?.key !== undefined) return { ...key, oaepHash: 'sha256' };
56+
return { key, oaepHash: 'sha256' };
57+
};
5358
for (const key of [publicKey, privateKey]) {
54-
const ciphertext = publicEncrypt(key, plaintext);
55-
const received = privateDecrypt(privateKey, ciphertext);
59+
const ciphertext = publicEncrypt(withOaepHash(key), plaintext);
60+
const received = privateDecrypt(withOaepHash(privateKey), ciphertext);
5661
assert.strictEqual(received.toString('utf8'), message);
5762
}
5863
}

‎test/fixtures/keys/Makefile‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ all: \
88
ca5-cert.pem \
99
ca6-cert.pem \
1010
agent1-cert.pem \
11+
agent1-fips.pfx \
1112
agent1.pfx \
1213
agent2-cert.pem \
1314
agent3-cert.pem \
@@ -39,6 +40,7 @@ all: \
3940
dsa_private_encrypted_1025.pem \
4041
dsa_public_1025.pem \
4142
ec-cert.pem \
43+
ec-fips.pfx \
4244
ec.pfx \
4345
fake-cnnic-root-cert.pem \
4446
intermediate-ca-cert.pem \
@@ -445,6 +447,20 @@ agent1.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem
445447
-out agent1.pfx \
446448
-password pass:sample
447449

450+
# PKCS12KDF is unavailable under FIPS properties. Use PBMAC1 with PBKDF2
451+
# instead, alongside AES-256/PBKDF2 key protection.
452+
agent1-fips.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem
453+
openssl pkcs12 -export \
454+
-keypbe AES-256-CBC \
455+
-certpbe AES-256-CBC \
456+
-iter 2048 \
457+
-pbmac1_pbkdf2 \
458+
-in agent1-cert.pem \
459+
-inkey agent1-key.pem \
460+
-certfile ca1-cert.pem \
461+
-out agent1-fips.pfx \
462+
-password pass:password
463+
448464
agent1-verify: agent1-cert.pem ca1-cert.pem
449465
openssl verify -CAfile ca1-cert.pem agent1-cert.pem
450466

@@ -788,6 +804,18 @@ ec.pfx: ec-cert.pem ec-key.pem
788804
-out ec.pfx \
789805
-password pass:
790806

807+
# See agent1-fips.pfx for why the FIPS fixture uses PBMAC1.
808+
ec-fips.pfx: ec-cert.pem ec-key.pem
809+
openssl pkcs12 -export \
810+
-keypbe AES-256-CBC \
811+
-certpbe AES-256-CBC \
812+
-iter 2048 \
813+
-pbmac1_pbkdf2 \
814+
-in ec-cert.pem \
815+
-inkey ec-key.pem \
816+
-out ec-fips.pfx \
817+
-password pass:password
818+
791819
dh512.pem:
792820
openssl dhparam -out dh512.pem 512
793821

‎test/fixtures/keys/agent1-fips.pfx‎

3.72 KB
Binary file not shown.

‎test/fixtures/keys/ec-fips.pfx‎

1.23 KB
Binary file not shown.

‎test/parallel/test-crypto-argon2-job.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ const common = require('../common');
44
if (!common.hasCrypto)
55
common.skip('missing crypto');
66

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

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

1214
// Exercises the native Argon2 job directly via internalBinding, bypassing
1315
// the JS validators, to ensure that if invalid parameters ever reach the

‎test/parallel/test-crypto-argon2.js‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const common = require('../common');
33
if (!common.hasCrypto)
44
common.skip('missing crypto');
55

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

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

31+
if (hasFIPS(3)) {
32+
assert.throws(() => crypto.argon2Sync('argon2id', defaults), {
33+
name: 'Error',
34+
message: /:digital envelope routines::unsupported$/,
35+
});
36+
crypto.argon2('argon2id', defaults, common.mustCall((err, result) => {
37+
assert.strictEqual(err?.name, 'Error');
38+
assert.match(err.message, /:digital envelope routines::unsupported$/);
39+
assert.strictEqual(result, undefined);
40+
}));
41+
return;
42+
}
43+
3144
const good = [
3245
// Test vectors from RFC 9106 https://www.rfc-editor.org/rfc/rfc9106.html#name-test-vectors
3346
// and OpenSSL 3.2 https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/test/recipes/30-test_evp_data/evpkdf_argon2.txt

‎test/parallel/test-crypto-async-sign-verify.js‎

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ const common = require('../common');
33
if (!common.hasCrypto)
44
common.skip('missing crypto');
55

6-
const { hasOpenSSL3 } = require('../common/crypto');
6+
const { hasOpenSSL, hasFIPS } = require('../common/crypto');
77
const assert = require('assert');
88
const util = require('util');
99
const crypto = require('crypto');
1010
const fixtures = require('../common/fixtures');
1111

12+
const fips3 = hasFIPS(3);
13+
1214
function test(
1315
publicFixture,
1416
privateFixture,
@@ -65,6 +67,17 @@ function test(
6567
}
6668
}
6769

70+
function testSignFailure(privateFixture, algorithm, options, code, message) {
71+
const key = { key: fixtures.readKey(privateFixture), ...options };
72+
const data = Buffer.from('Hello world');
73+
assert.throws(() => crypto.sign(algorithm, data, key), { code });
74+
crypto.sign(algorithm, data, key, common.mustCall((err) => {
75+
// Async crypto jobs in v24 preserve the OpenSSL message without a code.
76+
assert.strictEqual(err?.name, 'Error');
77+
assert.strictEqual(err.message, message);
78+
}));
79+
}
80+
6881
// RSA w/ default padding
6982
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true);
7083
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true,
@@ -94,14 +107,20 @@ if (!process.features.openssl_is_boringssl) {
94107
test('ed448_public.pem', 'ed448_private.pem', undefined, true);
95108

96109
// ECDSA w/ der signature encoding
97-
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
98-
false);
99-
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
100-
false, { dsaEncoding: 'der' });
101-
102-
// ECDSA w/ ieee-p1363 signature encoding
103-
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
104-
{ dsaEncoding: 'ieee-p1363' });
110+
if (fips3) {
111+
testSignFailure('ec_secp256k1_private.pem', 'sha384', {},
112+
'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE',
113+
'error:03000096:digital envelope routines::operation not supported for this keytype');
114+
} else {
115+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
116+
false);
117+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
118+
false, { dsaEncoding: 'der' });
119+
120+
// ECDSA w/ ieee-p1363 signature encoding
121+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
122+
{ dsaEncoding: 'ieee-p1363' });
123+
}
105124

106125
// DSA w/ der signature encoding
107126
test('dsa_public.pem', 'dsa_private.pem', 'sha256',
@@ -156,7 +175,7 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc=
156175
const signature = crypto.randomBytes(16);
157176

158177
let expected = /no default digest/;
159-
if (hasOpenSSL3 || process.features.openssl_is_boringssl) {
178+
if (hasOpenSSL(3) || process.features.openssl_is_boringssl) {
160179
expected = /operation[\s_]not[\s_]supported[\s_]for[\s_]this[\s_]keytype/i;
161180
}
162181

@@ -167,11 +186,21 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc=
167186
}
168187

169188
{
170-
const { privateKey } = crypto.generateKeyPairSync('rsa', {
171-
modulusLength: 512
172-
});
173-
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
174-
assert.ok(err);
175-
assert.match(err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
176-
}));
189+
if (fips3) {
190+
crypto.generateKeyPair('rsa', { modulusLength: 512 },
191+
common.mustCall((err) => {
192+
assert.strictEqual(err?.name, 'Error');
193+
assert.strictEqual(
194+
err.message, 'error:020000AE:rsa routines::invalid modulus');
195+
}));
196+
} else {
197+
const { privateKey } = crypto.generateKeyPairSync('rsa', {
198+
modulusLength: 512
199+
});
200+
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
201+
assert.ok(err);
202+
assert.match(
203+
err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
204+
}));
205+
}
177206
}

‎test/parallel/test-crypto-authenticated-stream.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ if (!common.hasCrypto)
66

77
const assert = require('assert');
88
const crypto = require('crypto');
9+
const { hasFIPS } = require('../common/crypto');
910
const fs = require('fs');
1011
const stream = require('stream');
1112
const tmpdir = require('../common/tmpdir');
@@ -120,6 +121,16 @@ function test(config) {
120121
return;
121122
}
122123

124+
if (hasFIPS(3)) {
125+
assert.throws(() => crypto.createDecipheriv(
126+
config.cipher, config.key, config.iv, {
127+
authTagLength: config.authTagLength,
128+
}), {
129+
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
130+
});
131+
return;
132+
}
133+
123134
direct(config);
124135
mstream(config);
125136
fstream(config);

‎test/parallel/test-crypto-authenticated.js‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ const assert = require('assert');
2929
const crypto = require('crypto');
3030
const { inspect } = require('util');
3131
const fixtures = require('../common/fixtures');
32-
const { hasOpenSSL, hasOpenSSL3 } = require('../common/crypto');
32+
const { hasOpenSSL, hasFIPS } = require('../common/crypto');
3333

34-
const isFipsEnabled = crypto.getFips();
34+
const isFipsEnabled = crypto.getFips() === 1;
35+
const fips3 = hasFIPS(3);
3536

3637
//
3738
// Test authenticated encryption modes.
@@ -718,6 +719,14 @@ for (const test of TEST_CASES) {
718719
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
719720
const tag = cipher.getAuthTag();
720721

722+
if (fips3 && mode === 'ccm') {
723+
assert.throws(() => crypto.createDecipheriv(
724+
`aes-128-${mode}`, key, iv, opts), {
725+
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
726+
});
727+
continue;
728+
}
729+
721730
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
722731
decipher.setAuthTag(tag);
723732
assert.throws(() => {
@@ -804,7 +813,7 @@ for (const test of TEST_CASES) {
804813
} catch (err) {
805814
// OpenSSL without https://github.com/openssl/openssl/pull/32427
806815
// cannot finalize an empty CCM message unless update() was called.
807-
if (hasOpenSSL3) {
816+
if (hasOpenSSL(3)) {
808817
assert.strictEqual(err.code, 'ERR_OSSL_TAG_NOT_SET');
809818
} else {
810819
assert.match(err.message, /Unsupported state/);
@@ -818,7 +827,14 @@ for (const test of TEST_CASES) {
818827
}
819828
}
820829

821-
if (!process.features.openssl_is_boringssl) {
830+
if (fips3) {
831+
assert.throws(() => crypto.createCipheriv(
832+
'chacha20-poly1305', Buffer.alloc(32), Buffer.alloc(12), {
833+
authTagLength: 16,
834+
}), {
835+
code: 'ERR_OSSL_EVP_UNSUPPORTED',
836+
});
837+
} else if (!process.features.openssl_is_boringssl) {
822838
const key = Buffer.alloc(32);
823839
const iv = Buffer.alloc(12);
824840

@@ -836,7 +852,7 @@ if (!process.features.openssl_is_boringssl) {
836852

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

@@ -887,7 +903,7 @@ if (!process.features.openssl_is_boringssl) {
887903
// shorter tags as long as their length was valid according to NIST SP 800-38D.
888904
// For ChaCha20-Poly1305, we intentionally deviate from that because there are
889905
// no recommended or approved authentication tag lengths below 16 bytes.
890-
if (!process.features.openssl_is_boringssl) {
906+
if (!fips3 && !process.features.openssl_is_boringssl) {
891907
const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => {
892908
return algo === 'chacha20-poly1305' && tampered === false;
893909
});
@@ -926,7 +942,7 @@ if (!process.features.openssl_is_boringssl) {
926942
}
927943

928944
// https://github.com/nodejs/node/issues/45874
929-
if (!process.features.openssl_is_boringssl) {
945+
if (!fips3 && !process.features.openssl_is_boringssl) {
930946
const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => {
931947
return algo === 'chacha20-poly1305' && tampered === false;
932948
});
@@ -973,7 +989,7 @@ if (ciphers.includes('aes-128-ccm')) {
973989
const tag = cipher.getAuthTag();
974990
assert.strictEqual(tag.length, 16);
975991

976-
if (isFipsEnabled && hasOpenSSL3) {
992+
if (fips3) {
977993
assert.throws(() => crypto.createDecipheriv(
978994
'aes-128-ccm', key, nonce, { authTagLength: 16 }), {
979995
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',

0 commit comments

Comments
 (0)