Skip to content

Commit 68ba143

Browse files
committed
test: update tests to run with OpenSSL >= 3.0 FIPS mode
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 30508cf commit 68ba143

127 files changed

Lines changed: 2823 additions & 1004 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
@@ -1604,6 +1604,7 @@ class SubtleCrypto {
16041604
}
16051605

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

test/common/crypto.js

Lines changed: 12 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
}
@@ -118,6 +123,10 @@ const hasOpenSSL = (major = 0, minor = 0, patch = 0) => {
118123
return OPENSSL_VERSION_NUMBER >= opensslVersionNumber(major, minor, patch);
119124
};
120125

126+
const hasFIPS = (major = 0, minor = 0, patch = 0) => {
127+
return crypto.getFips() === 1 && hasOpenSSL(major, minor, patch);
128+
};
129+
121130
let opensslCli = null;
122131

123132
module.exports = {
@@ -134,6 +143,7 @@ module.exports = {
134143
sec1Exp,
135144
sec1EncExp,
136145
hasOpenSSL,
146+
hasFIPS,
137147
get hasOpenSSL3() {
138148
return hasOpenSSL(3);
139149
},

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 \
@@ -444,6 +446,20 @@ agent1.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem
444446
-out agent1.pfx \
445447
-password pass:sample
446448

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

@@ -787,6 +803,18 @@ ec.pfx: ec-cert.pem ec-key.pem
787803
-out ec.pfx \
788804
-password pass:
789805

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

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-async-sign-verify.js

Lines changed: 43 additions & 18 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,15 @@ function test(
6567
}
6668
}
6769

70+
function testSignFailure(privateFixture, algorithm, options, code) {
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+
assert.strictEqual(err?.code, code);
76+
}));
77+
}
78+
6879
// RSA w/ default padding
6980
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true);
7081
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true,
@@ -94,14 +105,19 @@ if (!process.features.openssl_is_boringssl) {
94105
test('ed448_public.pem', 'ed448_private.pem', undefined, true);
95106

96107
// 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' });
108+
if (fips3) {
109+
testSignFailure('ec_secp256k1_private.pem', 'sha384', {},
110+
'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE');
111+
} else {
112+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
113+
false);
114+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
115+
false, { dsaEncoding: 'der' });
116+
117+
// ECDSA w/ ieee-p1363 signature encoding
118+
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
119+
{ dsaEncoding: 'ieee-p1363' });
120+
}
105121

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

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

172188
{
173-
const { privateKey } = crypto.generateKeyPairSync('rsa', {
174-
modulusLength: 512
175-
});
176-
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
177-
assert.ok(err);
178-
assert.match(err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
179-
assert.match(err.code, /^ERR_OSSL_.*DIGEST_TOO_BIG_FOR_RSA_KEY$/);
180-
}));
189+
if (fips3) {
190+
crypto.generateKeyPair('rsa', { modulusLength: 512 },
191+
common.mustCall((err) => {
192+
assert.strictEqual(
193+
err?.code, 'ERR_OSSL_RSA_INVALID_MODULUS');
194+
}));
195+
} else {
196+
const { privateKey } = crypto.generateKeyPairSync('rsa', {
197+
modulusLength: 512
198+
});
199+
crypto.sign('sha512', 'message', privateKey, common.mustCall((err) => {
200+
assert.ok(err);
201+
assert.match(
202+
err.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i);
203+
assert.match(err.code, /^ERR_OSSL_.*DIGEST_TOO_BIG_FOR_RSA_KEY$/);
204+
}));
205+
}
181206
}

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: 37 additions & 14 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 { 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.
@@ -559,6 +560,14 @@ for (const test of TEST_CASES) {
559560
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
560561
const tag = cipher.getAuthTag();
561562

563+
if (fips3 && mode === 'ccm') {
564+
assert.throws(() => crypto.createDecipheriv(
565+
`aes-128-${mode}`, key, iv, opts), {
566+
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
567+
});
568+
continue;
569+
}
570+
562571
const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
563572
decipher.setAuthTag(tag);
564573
assert.throws(() => {
@@ -636,15 +645,22 @@ for (const test of TEST_CASES) {
636645
const cipher = crypto.createCipheriv('aes-128-ccm', key, iv, opts);
637646
assert.throws(() => {
638647
cipher.final();
639-
}, hasOpenSSL3 ? {
648+
}, hasOpenSSL(3) ? {
640649
code: 'ERR_OSSL_TAG_NOT_SET'
641650
} : {
642651
message: /Unsupported state/
643652
});
644653
}
645654
}
646655

647-
if (!process.features.openssl_is_boringssl) {
656+
if (fips3) {
657+
assert.throws(() => crypto.createCipheriv(
658+
'chacha20-poly1305', Buffer.alloc(32), Buffer.alloc(12), {
659+
authTagLength: 16,
660+
}), {
661+
code: 'ERR_OSSL_EVP_UNSUPPORTED',
662+
});
663+
} else if (!process.features.openssl_is_boringssl) {
648664
const key = Buffer.alloc(32);
649665
const iv = Buffer.alloc(12);
650666

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

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

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

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

801-
const decipher = crypto.createDecipheriv('aes-128-ccm', key, nonce, {
802-
authTagLength: 16,
803-
});
804-
decipher.setAuthTag(tag);
805-
decipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 });
806-
decipher.update(new DataView(new ArrayBuffer(0)));
807-
decipher.final();
817+
if (fips3) {
818+
assert.throws(() => crypto.createDecipheriv(
819+
'aes-128-ccm', key, nonce, { authTagLength: 16 }), {
820+
code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION',
821+
});
822+
} else {
823+
const decipher = crypto.createDecipheriv('aes-128-ccm', key, nonce, {
824+
authTagLength: 16,
825+
});
826+
decipher.setAuthTag(tag);
827+
decipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 });
828+
decipher.update(new DataView(new ArrayBuffer(0)));
829+
decipher.final();
830+
}
808831
} else {
809832
common.printSkipMessage('Skipping unsupported aes-128-ccm test');
810833
}

test/parallel/test-crypto-certificate.js

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

2727
const assert = require('assert');
2828
const crypto = require('crypto');
29+
const { hasFIPS } = require('../common/crypto');
2930
const { Certificate } = crypto;
3031
const fixtures = require('../common/fixtures');
3132

@@ -42,7 +43,7 @@ function copyArrayBuffer(buf) {
4243
function checkMethods(certificate) {
4344

4445
if (!process.features.openssl_is_boringssl)
45-
assert.strictEqual(certificate.verifySpkac(spkacValid), true);
46+
assert.strictEqual(certificate.verifySpkac(spkacValid), !hasFIPS(3));
4647
assert.strictEqual(certificate.verifySpkac(spkacFail), false);
4748

4849
assert.strictEqual(
@@ -59,9 +60,10 @@ function checkMethods(certificate) {
5960

6061
if (!process.features.openssl_is_boringssl) {
6162
const ab = copyArrayBuffer(spkacValid);
62-
assert.strictEqual(certificate.verifySpkac(ab), true);
63-
assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), true);
64-
assert.strictEqual(certificate.verifySpkac(new DataView(ab)), true);
63+
const expected = !hasFIPS(3);
64+
assert.strictEqual(certificate.verifySpkac(ab), expected);
65+
assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), expected);
66+
assert.strictEqual(certificate.verifySpkac(new DataView(ab)), expected);
6567
}
6668
}
6769

0 commit comments

Comments
 (0)