GeezerVison is showing github.com — test: update tests to run with OpenSSL >= 3.0 FIPS mode · nodejs/node@fd7e904
File treeExpand file treeCollapse file tree Open diff view settings Some content is hiddenLarge Commits have some content hidden by default. Use the searchbox below for content that may be hidden. Dismiss bannerExpand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/webcrypto.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1617,6 +1617,7 @@ class SubtleCrypto { | 1617 | 1617 | } | 1618 | 1618 |
| 1619 | 1619 | // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-supports | | 1620 | + // TODO(panva): Make supports() account for the active FIPS state. | 1620 | 1621 | static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) { | 1621 | 1622 | emitExperimentalWarning('The supports Web Crypto API method'); | 1622 | 1623 | if (this !== SubtleCrypto) throw new ERR_INVALID_THIS('SubtleCrypto constructor'); | |
Collapse file Copy file name to clipboardExpand all lines: test/common/crypto.js +12-2Lines changed: 12 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -50,9 +50,14 @@ function assertApproximateSize(key, expectedSize) { | 50 | 50 | function testEncryptDecrypt(publicKey, privateKey) { | 51 | 51 | const message = 'Hello Node.js world!'; | 52 | 52 | 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 | + }; | 53 | 58 | 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); | 56 | 61 | assert.strictEqual(received.toString('utf8'), message); | 57 | 62 | } | 58 | 63 | } | @@ -118,6 +123,10 @@ const hasOpenSSL = (major = 0, minor = 0, patch = 0) => { | 118 | 123 | return OPENSSL_VERSION_NUMBER >= opensslVersionNumber(major, minor, patch); | 119 | 124 | }; | 120 | 125 |
| | 126 | +const hasFIPS = (major = 0, minor = 0, patch = 0) => { | | 127 | + return crypto.getFips() === 1 && hasOpenSSL(major, minor, patch); | | 128 | +}; | | 129 | + | 121 | 130 | let opensslCli = null; | 122 | 131 |
| 123 | 132 | module.exports = { | @@ -134,6 +143,7 @@ module.exports = { | 134 | 143 | sec1Exp, | 135 | 144 | sec1EncExp, | 136 | 145 | hasOpenSSL, | | 146 | + hasFIPS, | 137 | 147 | get hasOpenSSL3() { | 138 | 148 | return hasOpenSSL(3); | 139 | 149 | }, | |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/keys/Makefile +28Lines changed: 28 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 8 | 8 | ca5-cert.pem \ | 9 | 9 | ca6-cert.pem \ | 10 | 10 | agent1-cert.pem \ | | 11 | + agent1-fips.pfx \ | 11 | 12 | agent1.pfx \ | 12 | 13 | agent2-cert.pem \ | 13 | 14 | agent3-cert.pem \ | | 39 | 40 | dsa_private_encrypted_1025.pem \ | 40 | 41 | dsa_public_1025.pem \ | 41 | 42 | ec-cert.pem \ | | 43 | + ec-fips.pfx \ | 42 | 44 | ec.pfx \ | 43 | 45 | fake-cnnic-root-cert.pem \ | 44 | 46 | intermediate-ca-cert.pem \ | @@ -444,6 +446,20 @@ agent1.pfx: agent1-cert.pem agent1-key.pem ca1-cert.pem | 444 | 446 | -out agent1.pfx \ | 445 | 447 | -password pass:sample | 446 | 448 |
| | 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 | + | 447 | 463 | agent1-verify: agent1-cert.pem ca1-cert.pem | 448 | 464 | openssl verify -CAfile ca1-cert.pem agent1-cert.pem | 449 | 465 |
| @@ -787,6 +803,18 @@ ec.pfx: ec-cert.pem ec-key.pem | 787 | 803 | -out ec.pfx \ | 788 | 804 | -password pass: | 789 | 805 |
| | 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 | + | 790 | 818 | dh512.pem: | 791 | 819 | openssl dhparam -out dh512.pem 512 | 792 | 820 |
| |
Collapse file Copy file name to clipboard Collapse file Copy file name to clipboard Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-argon2-job.js +3-1Lines changed: 3 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -4,10 +4,12 @@ const common = require('../common'); | 4 | 4 | if (!common.hasCrypto) | 5 | 5 | common.skip('missing crypto'); | 6 | 6 |
| 7 | | -const { hasOpenSSL } = require('../common/crypto'); | | 7 | +const { hasFIPS, hasOpenSSL } = require('../common/crypto'); | 8 | 8 |
| 9 | 9 | if (!hasOpenSSL(3, 2)) | 10 | 10 | common.skip('requires OpenSSL >= 3.2'); | | 11 | +if (hasFIPS(3)) | | 12 | + common.skip('Argon2 is not available in FIPS mode'); | 11 | 13 |
| 12 | 14 | // Exercises the native Argon2 job directly via internalBinding, bypassing | 13 | 15 | // the JS validators, to ensure that if invalid parameters ever reach the | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-argon2.js +12-1Lines changed: 12 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -3,7 +3,7 @@ const common = require('../common'); | 3 | 3 | if (!common.hasCrypto) | 4 | 4 | common.skip('missing crypto'); | 5 | 5 |
| 6 | | -const { hasOpenSSL } = require('../common/crypto'); | | 6 | +const { hasFIPS, hasOpenSSL } = require('../common/crypto'); | 7 | 7 |
| 8 | 8 | if (!hasOpenSSL(3, 2)) | 9 | 9 | common.skip('requires OpenSSL >= 3.2'); | @@ -28,6 +28,17 @@ const secret = Buffer.alloc(8, 0x03); | 28 | 28 | const associatedData = Buffer.alloc(12, 0x04); | 29 | 29 | const defaults = { message, nonce, parallelism: 1, tagLength: 64, memory: 8, passes: 3 }; | 30 | 30 |
| | 31 | +if (hasFIPS(3)) { | | 32 | + assert.throws(() => crypto.argon2Sync('argon2id', defaults), { | | 33 | + code: 'ERR_OSSL_EVP_UNSUPPORTED', | | 34 | + }); | | 35 | + crypto.argon2('argon2id', defaults, common.mustCall((err, result) => { | | 36 | + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); | | 37 | + assert.strictEqual(result, undefined); | | 38 | + })); | | 39 | + return; | | 40 | +} | | 41 | + | 31 | 42 | const good = [ | 32 | 43 | // Test vectors from RFC 9106 https://www.rfc-editor.org/rfc/rfc9106.html#name-test-vectors | 33 | 44 | // and OpenSSL 3.2 https://github.com/openssl/openssl/blob/6dfa998f7ea150f9c6d4e4727cf6d5c82a68a8da/test/recipes/30-test_evp_data/evpkdf_argon2.txt | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-async-sign-verify.js +43-18Lines changed: 43 additions & 18 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -3,12 +3,14 @@ const common = require('../common'); | 3 | 3 | if (!common.hasCrypto) | 4 | 4 | common.skip('missing crypto'); | 5 | 5 |
| 6 | | -const { hasOpenSSL3 } = require('../common/crypto'); | | 6 | +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); | 7 | 7 | const assert = require('assert'); | 8 | 8 | const util = require('util'); | 9 | 9 | const crypto = require('crypto'); | 10 | 10 | const fixtures = require('../common/fixtures'); | 11 | 11 |
| | 12 | +const fips3 = hasFIPS(3); | | 13 | + | 12 | 14 | function test( | 13 | 15 | publicFixture, | 14 | 16 | privateFixture, | @@ -65,6 +67,15 @@ function test( | 65 | 67 | } | 66 | 68 | } | 67 | 69 |
| | 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 | + | 68 | 79 | // RSA w/ default padding | 69 | 80 | test('rsa_public.pem', 'rsa_private.pem', 'sha256', true); | 70 | 81 | test('rsa_public.pem', 'rsa_private.pem', 'sha256', true, | @@ -94,14 +105,19 @@ if (!process.features.openssl_is_boringssl) { | 94 | 105 | test('ed448_public.pem', 'ed448_private.pem', undefined, true); | 95 | 106 |
| 96 | 107 | // 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 | + } | 105 | 121 |
| 106 | 122 | // DSA w/ der signature encoding | 107 | 123 | test('dsa_public.pem', 'dsa_private.pem', 'sha256', | @@ -157,7 +173,7 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc= | 157 | 173 |
| 158 | 174 | let expected = /no default digest/; | 159 | 175 | 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) { | 161 | 177 | expected = /operation[\s_]not[\s_]supported[\s_]for[\s_]this[\s_]keytype/i; | 162 | 178 | expectedCode = 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE'; | 163 | 179 | } | @@ -170,12 +186,21 @@ MCowBQYDK2VuAyEA6pwGRbadNQAI/tYN8+/p/0/hbsdHfOEGr1ADiLVk/Gc= | 170 | 186 | } | 171 | 187 |
| 172 | 188 | { | 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 | + } | 181 | 206 | } |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-authenticated-stream.js +11Lines changed: 11 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -6,6 +6,7 @@ if (!common.hasCrypto) | 6 | 6 |
| 7 | 7 | const assert = require('assert'); | 8 | 8 | const crypto = require('crypto'); | | 9 | +const { hasFIPS } = require('../common/crypto'); | 9 | 10 | const fs = require('fs'); | 10 | 11 | const stream = require('stream'); | 11 | 12 | const tmpdir = require('../common/tmpdir'); | @@ -120,6 +121,16 @@ function test(config) { | 120 | 121 | return; | 121 | 122 | } | 122 | 123 |
| | 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 | + | 123 | 134 | direct(config); | 124 | 135 | mstream(config); | 125 | 136 | fstream(config); | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-crypto-authenticated.js +37-14Lines changed: 37 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -29,9 +29,10 @@ const assert = require('assert'); | 29 | 29 | const crypto = require('crypto'); | 30 | 30 | const { inspect } = require('util'); | 31 | 31 | const fixtures = require('../common/fixtures'); | 32 | | -const { hasOpenSSL3 } = require('../common/crypto'); | | 32 | +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); | 33 | 33 |
| 34 | | -const isFipsEnabled = crypto.getFips(); | | 34 | +const isFipsEnabled = crypto.getFips() === 1; | | 35 | +const fips3 = hasFIPS(3); | 35 | 36 |
| 36 | 37 | // | 37 | 38 | // Test authenticated encryption modes. | @@ -559,6 +560,14 @@ for (const test of TEST_CASES) { | 559 | 560 | const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]); | 560 | 561 | const tag = cipher.getAuthTag(); | 561 | 562 |
| | 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 | + | 562 | 571 | const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts); | 563 | 572 | decipher.setAuthTag(tag); | 564 | 573 | assert.throws(() => { | @@ -636,15 +645,22 @@ for (const test of TEST_CASES) { | 636 | 645 | const cipher = crypto.createCipheriv('aes-128-ccm', key, iv, opts); | 637 | 646 | assert.throws(() => { | 638 | 647 | cipher.final(); | 639 | | - }, hasOpenSSL3 ? { | | 648 | + }, hasOpenSSL(3) ? { | 640 | 649 | code: 'ERR_OSSL_TAG_NOT_SET' | 641 | 650 | } : { | 642 | 651 | message: /Unsupported state/ | 643 | 652 | }); | 644 | 653 | } | 645 | 654 | } | 646 | 655 |
| 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) { | 648 | 664 | const key = Buffer.alloc(32); | 649 | 665 | const iv = Buffer.alloc(12); | 650 | 666 |
| @@ -662,7 +678,7 @@ if (!process.features.openssl_is_boringssl) { | 662 | 678 |
| 663 | 679 | // ChaCha20-Poly1305 should respect the authTagLength option and should not | 664 | 680 | // 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) { | 666 | 682 | const key = Buffer.alloc(32); | 667 | 683 | const iv = Buffer.alloc(12); | 668 | 684 |
| @@ -713,7 +729,7 @@ if (!process.features.openssl_is_boringssl) { | 713 | 729 | // shorter tags as long as their length was valid according to NIST SP 800-38D. | 714 | 730 | // For ChaCha20-Poly1305, we intentionally deviate from that because there are | 715 | 731 | // 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) { | 717 | 733 | const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => { | 718 | 734 | return algo === 'chacha20-poly1305' && tampered === false; | 719 | 735 | }); | @@ -752,7 +768,7 @@ if (!process.features.openssl_is_boringssl) { | 752 | 768 | } | 753 | 769 |
| 754 | 770 | // https://github.com/nodejs/node/issues/45874 | 755 | | -if (!process.features.openssl_is_boringssl) { | | 771 | +if (!fips3 && !process.features.openssl_is_boringssl) { | 756 | 772 | const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => { | 757 | 773 | return algo === 'chacha20-poly1305' && tampered === false; | 758 | 774 | }); | @@ -798,13 +814,20 @@ if (ciphers.includes('aes-128-ccm')) { | 798 | 814 | const tag = cipher.getAuthTag(); | 799 | 815 | assert.strictEqual(tag.length, 16); | 800 | 816 |
| 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 | + } | 808 | 831 | } else { | 809 | 832 | common.printSkipMessage('Skipping unsupported aes-128-ccm test'); | 810 | 833 | } |
|