From 42f10023852372b44d58c96af6b6e1186e879072 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 2 Aug 2026 16:09:14 +0200 Subject: [PATCH 1/3] test: update tests to run with OpenSSL >= 3.0 FIPS mode Signed-off-by: Filip Skokan --- lib/internal/crypto/webcrypto.js | 1 + test/common/crypto.js | 14 +- test/fixtures/keys/Makefile | 28 ++ test/fixtures/keys/agent1-fips.pfx | Bin 0 -> 3808 bytes test/fixtures/keys/ec-fips.pfx | Bin 0 -> 1261 bytes .../parallel/test-crypto-async-sign-verify.js | 61 ++-- .../test-crypto-authenticated-stream.js | 11 + test/parallel/test-crypto-authenticated.js | 51 ++- test/parallel/test-crypto-certificate.js | 10 +- .../test-crypto-cipheriv-decipheriv.js | 31 +- test/parallel/test-crypto-classes.js | 15 +- test/parallel/test-crypto-dep0181.js | 2 +- test/parallel/test-crypto-dep0206.js | 26 +- test/parallel/test-crypto-dh-constructor.js | 7 +- test/parallel/test-crypto-dh-curves.js | 182 ++++++----- test/parallel/test-crypto-dh-generate-keys.js | 5 +- test/parallel/test-crypto-dh-leak.js | 5 +- test/parallel/test-crypto-dh-modp2-views.js | 34 +- test/parallel/test-crypto-dh-modp2.js | 53 +-- test/parallel/test-crypto-dh-odd-key.js | 15 +- test/parallel/test-crypto-dh-shared.js | 6 +- test/parallel/test-crypto-dh.js | 8 +- test/parallel/test-crypto-ecdh-convert-key.js | 17 +- test/parallel/test-crypto-eddsa-variants.js | 14 +- test/parallel/test-crypto-encap-decap.js | 13 +- test/parallel/test-crypto-getcipherinfo.js | 8 +- test/parallel/test-crypto-hkdf.js | 8 +- test/parallel/test-crypto-hmac.js | 30 +- test/parallel/test-crypto-job-error-parity.js | 32 +- .../test-crypto-key-objects-messageport.js | 3 +- .../test-crypto-key-objects-to-crypto-key.js | 21 +- test/parallel/test-crypto-key-objects.js | 304 ++++++++++++------ test/parallel/test-crypto-keygen-async-dsa.js | 12 +- ...ypto-keygen-async-elliptic-curve-jwk-ec.js | 8 +- ...pto-keygen-async-elliptic-curve-jwk-rsa.js | 3 +- ...-crypto-keygen-async-elliptic-curve-jwk.js | 9 +- ...-keygen-async-encrypted-private-key-der.js | 6 +- ...ypto-keygen-async-encrypted-private-key.js | 11 +- ...nc-explicit-elliptic-curve-encrypted.js.js | 12 +- ...en-async-named-elliptic-curve-encrypted.js | 12 +- test/parallel/test-crypto-keygen-async-rsa.js | 15 +- .../parallel/test-crypto-keygen-bit-length.js | 27 +- .../parallel/test-crypto-keygen-dh-classic.js | 3 +- test/parallel/test-crypto-keygen-eddsa.js | 9 +- ...crypto-keygen-empty-passphrase-no-error.js | 12 +- ...rypto-keygen-empty-passphrase-no-prompt.js | 23 +- ...o-keygen-invalid-parameter-encoding-dsa.js | 8 +- ...ypto-keygen-key-object-without-encoding.js | 5 +- .../test-crypto-keygen-key-objects.js | 8 +- .../test-crypto-keygen-missing-oid.js | 5 +- ...test-crypto-keygen-no-rsassa-pss-params.js | 9 +- ...pto-keygen-non-standard-public-exponent.js | 46 +-- test/parallel/test-crypto-keygen-promisify.js | 8 +- test/parallel/test-crypto-keygen-raw.js | 32 +- .../test-crypto-keygen-rfc8017-9-1.js | 7 +- .../test-crypto-keygen-rfc8017-a-2-3.js | 10 +- test/parallel/test-crypto-keygen-rsa-pss.js | 8 +- test/parallel/test-crypto-keygen-sync.js | 10 +- .../test-crypto-keyobject-brand-check.js | 5 +- .../test-crypto-keyobject-clone-transfer.js | 3 +- .../test-crypto-keyobject-hidden-slots.js | 16 +- .../test-crypto-keyobject-no-own-symbols.js | 3 +- test/parallel/test-crypto-pbkdf2.js | 185 ++++++++--- .../test-crypto-pqc-encrypted-pkcs8.js | 25 +- .../test-crypto-private-decrypt-gh32240.js | 42 ++- ...t-crypto-publicDecrypt-fails-first-time.js | 10 +- test/parallel/test-crypto-rsa-dsa.js | 146 ++++++--- test/parallel/test-crypto-scrypt.js | 47 ++- test/parallel/test-crypto-secure-heap.js | 5 +- test/parallel/test-crypto-sign-verify.js | 214 ++++++++---- test/parallel/test-crypto-worker-thread.js | 3 +- test/parallel/test-crypto.js | 65 ++-- .../test-https-agent-additional-options.js | 15 +- ...test-https-agent-pfx-object-array-reuse.js | 55 +++- .../test-https-agent-session-eviction.js | 31 +- test/parallel/test-https-pfx.js | 21 +- ...ttps-selfsigned-no-keycertsign-no-crash.js | 3 +- test/parallel/test-tls-alert.js | 21 +- .../test-tls-client-getephemeralkeyinfo.js | 82 +++-- test/parallel/test-tls-client-mindhsize.js | 15 +- test/parallel/test-tls-dhe.js | 55 +++- test/parallel/test-tls-ecdh-multiple.js | 27 +- .../test-tls-env-extra-ca-with-options.js | 25 +- test/parallel/test-tls-getprotocol.js | 42 ++- test/parallel/test-tls-honorcipherorder.js | 3 +- test/parallel/test-tls-invalid-pfx.js | 7 +- test/parallel/test-tls-min-max-version.js | 45 ++- test/parallel/test-tls-multi-key.js | 12 + test/parallel/test-tls-multi-pfx.js | 39 ++- test/parallel/test-tls-passphrase.js | 28 ++ .../test-tls-pfx-authorizationerror.js | 30 +- test/parallel/test-tls-session-cache.js | 9 +- test/parallel/test-tls-set-ciphers.js | 136 ++++---- test/parallel/test-tls-write-error.js | 19 +- ...-webcrypto-aead-decrypt-detached-buffer.js | 38 ++- test/parallel/test-webcrypto-constructors.js | 44 ++- .../test-webcrypto-cryptokey-hidden-slots.js | 3 +- .../test-webcrypto-deduplicate-usages.js | 21 +- .../test-webcrypto-derivebits-cfrg.js | 11 + test/parallel/test-webcrypto-derivebits.js | 61 +++- .../parallel/test-webcrypto-derivekey-cfrg.js | 11 + test/parallel/test-webcrypto-derivekey.js | 124 ++++--- test/parallel/test-webcrypto-digest.js | 40 ++- .../test-webcrypto-encrypt-decrypt-aes.js | 9 + ...rypto-encrypt-decrypt-chacha20-poly1305.js | 11 + .../test-webcrypto-encrypt-decrypt.js | 11 +- .../test-webcrypto-export-import-cfrg.js | 16 +- test/parallel/test-webcrypto-export-import.js | 9 +- .../test-webcrypto-get-public-key.mjs | 11 + test/parallel/test-webcrypto-keygen.js | 79 ++++- ...-webcrypto-promise-prototype-pollution.mjs | 48 ++- .../test-webcrypto-raw-format-aliases.js | 20 +- .../test-webcrypto-sign-verify-ecdsa.js | 33 +- .../test-webcrypto-sign-verify-eddsa.js | 4 +- .../test-webcrypto-sign-verify-hmac.js | 5 +- .../test-webcrypto-sign-verify-kmac.js | 92 ++++-- .../test-webcrypto-sign-verify-ml-dsa.js | 4 +- .../test-webcrypto-sign-verify-rsa.js | 48 ++- test/parallel/test-webcrypto-sign-verify.js | 3 +- test/parallel/test-webcrypto-supports.mjs | 5 + test/parallel/test-webcrypto-wrap-unwrap.js | 63 +++- test/pummel/test-crypto-dh-keys.js | 12 +- test/pummel/test-dh-regr.js | 27 +- .../test-webcrypto-derivebits-pbkdf2.js | 26 +- test/sequential/test-async-wrap-getasyncid.js | 4 +- test/wpt/status/WebCryptoAPI.cjs | 84 ++++- test/wpt/test-webcrypto.js | 28 ++ 127 files changed, 2823 insertions(+), 1004 deletions(-) create mode 100644 test/fixtures/keys/agent1-fips.pfx create mode 100644 test/fixtures/keys/ec-fips.pfx diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 25622d94f6b2..4326ae3a68db 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -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'); diff --git a/test/common/crypto.js b/test/common/crypto.js index f50d3895a178..63f7487f15d3 100644 --- a/test/common/crypto.js +++ b/test/common/crypto.js @@ -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); } } @@ -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 = { @@ -134,6 +143,7 @@ module.exports = { sec1Exp, sec1EncExp, hasOpenSSL, + hasFIPS, get hasOpenSSL3() { return hasOpenSSL(3); }, diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index 128e928a5916..3ef61d00afff 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -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 \ @@ -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 \ @@ -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 @@ -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 diff --git a/test/fixtures/keys/agent1-fips.pfx b/test/fixtures/keys/agent1-fips.pfx new file mode 100644 index 0000000000000000000000000000000000000000..5613cfae430b4c83b74d8c7c0aac3fa4d1449d7f GIT binary patch literal 3808 zcmai%XHXOBwuTc(=tKx*(*@~06qT-YY0^6wy3(6Sl@fXak>0C-2#BGGAPUl}Kq%6Y zD!m7!8MvOgclMrp=FFTQ->iAxcg?r{t$7v<0huQN62cG=RU%S>m`5>Zlt5x&5dv}# zgn)?uu|;4AlAM1cq9Ozd>W>vm00jIg;r}E+82oPmri2;5#Qu>KFfI@kyfDIJK%_oS zsnm6}r+WiSKtKY3Q4o>*?^FU(FaX9%L~0ZB2~zZENUjcT(wNg2ggO-*mp0S-n15$N-dFw2Po zrjmI+mdXR_eZ{cwQ!xflkQurX{myXG3EB(q=bL%BO#j(SpW9#gzGwHavV_pQLMZIg zrL(Dv)YUazv|7%06^@{bof6D&d(}W@HR0MwvuVD){h+BAwG{53^}QuD#WrJ5TZ0T` z%VIvxj#06w-6%9kAnN{HKLO}qtX#SkCd#HL_!m&rcq$Pq#9Y?X5RmLveQMGV0>2V% zU!h<{dy+)cb6#}raJjf=cx~D)oNsGLY?$Z9S``J}nv=vX<|h*~%r`ZMg&L(j4GXy% zk#X;dQtZkhNMjSDJ14w@v_5C6>2vK553F6RY^b7dFW~-+cwsU_pNsplV6f|>T`a$h zUPH2o_V?VZg+7R>wjJhFh#h{?yhJRQF}MDr{zPN^lqVM(Xt+mLuuLYwYxYRe4zs@$ zq;Hv8ZkO+y+p1~vl9Zss%kL_B^+ND{n;C&A+L1E3Hb*e!FJU3@tRGO#k9h z{5f0?4#gZ`$W!x%JFH?=#96dT=|pw|WFW%)X1Y(sVgODLq3$ig)@_y5=l1k!b8rQM zaiiLZBzDgu;U9{Mofa#S;;NR&iJwKiOcEID4fN{(bP8z1lv)i16vwLk%C)YnXXF z-;BI`jx54`oxfH=isk*Lr@d5Ahq!$#fQ?YXtxMx0QX5O*vm9-{UL@lexWMW)ogKZ~ z-~EJhF!$y0voO|nc?mCrLD5+?J?>`tT4Uo+FU%k$0^_Q~I_wDY*fL_y;KQiT@~@0# zAenLoif{J=?LXUuamhr21mmAoz?GL+>nUfOMQn8D=g=2k)i6fuVWdekd=KZ&SRwnPUnwxMb2!E zw9x%?o?^GWtK4nci^zt6#P1DTq*9qJ&F8IMs_4%(tYscin(STTi!cV+))e*^=jLU` zqGTw%uIbxc)h_4}PwZL)>zjvnEI@VPl7fT>LPGcCsnOHrS!C3I4ay96k?~Dvb_Mod z-7!$6E=rI1X<9>Vy=ZqK75wwl`oLuQyO`D>{=&k+qZv%W2wG6KShOdxte?nbC{uK$ z`m9O^jm>gGz2Q*OX!CID;&)O zvNGh@Y_a)}wgt4*!hA3A(Ig1lYcnejVvGcF^C73?pt0d#W&GaPN`gSawpR6C=m|IOHlrTHg9Hs20D?U2MF{KGKJlnqyE|G(A{zG zz2igCB*M8tsI%7Hog}OGw4l7;gtT;0dMzSByda~%bMD%MjOve(B~9ANbdM=vSxh36 z7oVs3c@lE2eORINAgk@gdfRI{gOBWJo3B0E&2s!hL+|C7lPd)rCbD;dcv~l@Q}Gm= zDB~?-eY=x-)+1WeBQl;{@T#Z3iVu^-6~le?gZxuP{b9#lXiIbN)UuLlnVES|CBkf` zpK~CkDqzO(6Sf39P}dX8%W3)!vt`@bERHvwp*(@XcBV~Bu0|TXiFV4#R$Z3!`-Ecgm!$Aa{ zk8K4glJOT7@9{WjJRCOG>%p08AkkfqVKZPj`oe6?CDlYO@ubXV&ikBgBDA+WLU+`x zm5XWNv1f5uu>P%>GQCdqC{Xe;GN22p9oesNxlqT7SCVzw(+K6XI|F!zyC`44Cu_t* zvXTym!OYSxF}2k``v9cf`Vfm_!Hy5^y!j3I!75vG=lJ@%zfKcw9kPg3>Bsym8qn4>-_(QW5H#IF2+RZ?1x?sncNP-G!l3P!Fva=MwLxlo``m*$ox1%@g(o6`x zvZ`2@7}QGs?i3nR8XH=8sH;&wvptymOCu6*@}Qi1IGc$E3qybt{^pE-feZL02mucK zWBdJi-bCd8$21gVK!QJ^^dIx?e*=}JH?@^EUPD|ae&Q{C!w9|q3M!ZjVWKG4lKb=I z$a1z&oE``P!iG3OXkJUnrV9WSysvkjIhQ*}E>kyhS1M~thN@0i!wU&}yj_x641tvk z4~ehjN#SyG?pZVxCjRHDtmD8YRHIb4TXyWv?R3v|Di{0PS~pp$v^&@jmkwI9r>WEF zYZv<+Nqxor1?j@xcHSXVJ#CIz0TR?N1MvklYw^}j8OgN?=a7>2^rA1494>p#yR*2w zqW#5XKIwzeoRz3OwVsN`6l|01rDcAC<`)SI!io$Jr*c31SMp6#;Tlh_Te7v{slX=$Md52NtF6D99 zIt4qI{$W*(Uk8b%AP+Cz7-Lm=W}$q250_UjVSITnu{Dn4(HyQ4CHrZKoh{5tE&6Rc zqo<4i=B&oAxM%dQ`#v07xM0jswQriOI20EalVp|~X+D6CvfqLA1-XT~Y-V+{`a{t4 zZ!NG(XC3o`Q{ZR`2QJ5oR~DBQTFtyuDzDmBi`_FdNXL19!$Y+)H}+^9Xcj@ppCjI? zR{*BQ=WJO~Ax*<%vt|IC1b4Qt9_O>S4^;M7r;y@5A89E7gc?bK8*#zo@Dx9GZ&mJ6zBTZ%|#5Y4XUoCsI1*)uSeQZI-of?g`LS ziR-?|Yi?R(>5@BfcYbac_K~C|Ekz>d>CYz2d)F$%VzXCIXLYgt-Iu(A zF%v)Sj$A0iI;%QE<4gE$-B{o2o54ITQ{yPj&ho_fyOa}&eLIu|qbH?l5VEQ`rF_#s zQ(Y0zJeEHu&nUgrk~Q=Bd>#}Zj)Jvj+~RRI{HSf1Z{{3O*)(nhh*#c*Z=||Cu6!L_ zkfHl>tMGh!-kxk_#}Gb#VSGYp)|R+2tNg6$1O6MsaVaz{fw!vH3`TgoFIRBNs5ZzhWi(&0XjH_ii2IjqsTFB$ouDQK()rPIo(Od-ye0rG>QiY}>V}DDkS9eZYgUUWqI|VvlZN4*@ z&z?rAvQ-EDu8QM6S=aWtU2msmvOBk?hxufkSox*r+fQkRb%r8q0Q=>P-x~?vADre8 zBd-trPyD(rOZljMbw_(wuTH<7+~qBjX~56#p55{~PB~a|XV9E{g(fthN0to%E+r}-y%-rv3DT31r9<5# zcf4-=J;jbO>qctS%vj?jzB*673az8b4C}eND87yOtJFDu#?TEcZOozFwVah zEh(KCJ%|<+Pv4atJ4VCgkakb1OdNQaE^7#jNQC*p^#ASzLtrW}$$umTj2A>@6ilTH z=^AzUk}`rymbncgDE3cs81K&y!TNg)www Oi}QF-1I&N^_J082g(BYo literal 0 HcmV?d00001 diff --git a/test/fixtures/keys/ec-fips.pfx b/test/fixtures/keys/ec-fips.pfx new file mode 100644 index 0000000000000000000000000000000000000000..1c26d641b9fc78bc68993098dee50019150d6390 GIT binary patch literal 1261 zcmXqLVtL8L$ZXKW62!)-)#lOmotKfFaX}M{GfNYTJy6)%po!@fLW*rc6Vq*=&~+w8 z1|W3-A;V}859hG*8Uz^_BDg#TDl7s|lDkUY3*M~$d)_kP?ddrOn3y;i40zZ$Aht4b zvN9ORvT-J~c`&9jvoLD02zXmWU1#}p;NYjTtLzUuiWN3AF(qi@7lRJU)=;!=C!07r5l)TNC*s1^t-Y%Ea=Oj$r^n2NA7L>HcPa@p{a|1$;ZgE zA7zdwuC09kMB`dT{(}i$gaae^KjyiAZkulREjY77e@P?j``ZUL+-7cCrMGL}Tc6D6 zpKs?*W^TIhEX#Kp&&5ur-omhhJvOWhw^;1*Wq-A=DEd~X{zSF+Wooyp7 z>FGLK=WKrS*FV*L9)`TDKcwdR$g)nd`DD3r}lNfh56-F4xh`e zf{_PL)EXtbe_&~}dBYMLye(u&g6bJ><7IId7HgXO9?d-@`TN(JlRxEWtu?sZE~-_o zS+#`a2xnRD!X497xh9J|Jb8CRjYiX@V=af~M92Fc{=M(W#lH3vq4PSrBUu03-_a@_ zS|8f5PI7D7$;f~DQ!LLrDX#m!<&5sz+eh~58V7b(m#Kd}em0`*TiDtkzjJuH9+;{e zyBui6;$RO@D(d=NE;k0vEsTS>BA=g6>q;cobRC=^oX1!B zL}6m>eLLfi>aO;Qq83j#CeGaAWdGw|u$5gS7yq|{36e&4R6;IKvE864z+#cf!C=gr zSs1tC*VDa*TXk!7XQ@?rUkG#jc45N7)!K_Q6?|<}oqj6nesD?_|=GK8@fzb`gw*oeQ zJU27p}){EC33o5$^y1 literal 0 HcmV?d00001 diff --git a/test/parallel/test-crypto-async-sign-verify.js b/test/parallel/test-crypto-async-sign-verify.js index bee83eaf8de0..96b4b5d90679 100644 --- a/test/parallel/test-crypto-async-sign-verify.js +++ b/test/parallel/test-crypto-async-sign-verify.js @@ -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, @@ -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, @@ -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', @@ -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'; } @@ -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$/); + })); + } } diff --git a/test/parallel/test-crypto-authenticated-stream.js b/test/parallel/test-crypto-authenticated-stream.js index 51b928ec36be..45736d3303dc 100644 --- a/test/parallel/test-crypto-authenticated-stream.js +++ b/test/parallel/test-crypto-authenticated-stream.js @@ -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'); @@ -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); diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index 2a4e2a1520a3..4062a5b5495f 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -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. @@ -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(() => { @@ -636,7 +645,7 @@ 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/ @@ -644,7 +653,14 @@ for (const test of TEST_CASES) { } } -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); @@ -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); @@ -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; }); @@ -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; }); @@ -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'); } diff --git a/test/parallel/test-crypto-certificate.js b/test/parallel/test-crypto-certificate.js index 28d20ba61c75..6462654680ea 100644 --- a/test/parallel/test-crypto-certificate.js +++ b/test/parallel/test-crypto-certificate.js @@ -26,6 +26,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { Certificate } = crypto; const fixtures = require('../common/fixtures'); @@ -42,7 +43,7 @@ function copyArrayBuffer(buf) { function checkMethods(certificate) { if (!process.features.openssl_is_boringssl) - assert.strictEqual(certificate.verifySpkac(spkacValid), true); + assert.strictEqual(certificate.verifySpkac(spkacValid), !hasFIPS(3)); assert.strictEqual(certificate.verifySpkac(spkacFail), false); assert.strictEqual( @@ -59,9 +60,10 @@ function checkMethods(certificate) { if (!process.features.openssl_is_boringssl) { const ab = copyArrayBuffer(spkacValid); - assert.strictEqual(certificate.verifySpkac(ab), true); - assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), true); - assert.strictEqual(certificate.verifySpkac(new DataView(ab)), true); + const expected = !hasFIPS(3); + assert.strictEqual(certificate.verifySpkac(ab), expected); + assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), expected); + assert.strictEqual(certificate.verifySpkac(new DataView(ab)), expected); } } diff --git a/test/parallel/test-crypto-cipheriv-decipheriv.js b/test/parallel/test-crypto-cipheriv-decipheriv.js index 095458e7d0b4..b1965c5a80c1 100644 --- a/test/parallel/test-crypto-cipheriv-decipheriv.js +++ b/test/parallel/test-crypto-cipheriv-decipheriv.js @@ -5,8 +5,9 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); -const isFipsEnabled = crypto.getFips(); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); +const isFipsEnabled = crypto.getFips() === 1; +const fips3 = hasFIPS(3); function testCipher1(key, iv) { // Test encryption and decryption with explicit key and iv @@ -86,10 +87,12 @@ function testCipher3(key, iv) { { const Cipheriv = crypto.Cipheriv; - const key = '123456789012345678901234'; - const iv = '12345678'; + const algorithm = fips3 ? 'aes-128-cbc' : 'des-ede3-cbc'; + const key = fips3 ? + '1234567890123456' : '123456789012345678901234'; + const iv = fips3 ? '1234567890123456' : '12345678'; - const instance = Cipheriv('des-ede3-cbc', key, iv); + const instance = Cipheriv(algorithm, key, iv); assert(instance instanceof Cipheriv, 'Cipheriv is expected to return a new ' + 'instance when called without `new`'); @@ -119,10 +122,12 @@ function testCipher3(key, iv) { { const Decipheriv = crypto.Decipheriv; - const key = '123456789012345678901234'; - const iv = '12345678'; + const algorithm = fips3 ? 'aes-128-cbc' : 'des-ede3-cbc'; + const key = fips3 ? + '1234567890123456' : '123456789012345678901234'; + const iv = fips3 ? '1234567890123456' : '12345678'; - const instance = Decipheriv('des-ede3-cbc', key, iv); + const instance = Decipheriv(algorithm, key, iv); assert(instance instanceof Decipheriv, 'Decipheriv expected to return a new' + ' instance when called without `new`'); @@ -153,8 +158,10 @@ function testCipher3(key, iv) { testCipher1('0123456789abcd0123456789', '12345678'); testCipher1('0123456789abcd0123456789', Buffer.from('12345678')); testCipher1(Buffer.from('0123456789abcd0123456789'), '12345678'); -testCipher1(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); -testCipher2(Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); +testCipher1( + Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); +testCipher2( + Buffer.from('0123456789abcd0123456789'), Buffer.from('12345678')); if (!isFipsEnabled) { testCipher3(Buffer.from('000102030405060708090A0B0C0D0E0F', 'hex'), @@ -207,8 +214,8 @@ assert.throws( errMessage); // But all other IV lengths should be accepted. -const minIvLength = hasOpenSSL3 ? 8 : 1; -const maxIvLength = hasOpenSSL3 ? 64 : 256; +const minIvLength = hasOpenSSL(3) ? 8 : 1; +const maxIvLength = hasOpenSSL(3) ? 64 : 256; for (let n = minIvLength; n < maxIvLength; n += 1) { if (isFipsEnabled && n < 12) continue; crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(n)); diff --git a/test/parallel/test-crypto-classes.js b/test/parallel/test-crypto-classes.js index 429bc91d4412..48d68c93fb62 100644 --- a/test/parallel/test-crypto-classes.js +++ b/test/parallel/test-crypto-classes.js @@ -6,7 +6,7 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); // 'ClassName' : ['args', 'for', 'constructor'] const TEST_CASES = { @@ -21,8 +21,17 @@ const TEST_CASES = { 'ECDH': ['prime256v1'], }; -if (!crypto.getFips()) { - TEST_CASES.DiffieHellman = [hasOpenSSL3 ? 1024 : 256]; +if (hasFIPS(3)) { + TEST_CASES.Hmac = ['sha1', '0123456789abcdef']; + TEST_CASES.Cipheriv = [ + 'aes-128-cbc', '0123456789abcdef', '1234567890abcdef']; + TEST_CASES.Decipheriv = TEST_CASES.Cipheriv; + TEST_CASES.Sign = ['RSA-SHA256']; + TEST_CASES.Verify = ['RSA-SHA256']; + TEST_CASES.DiffieHellman = [2048]; + TEST_CASES.DiffieHellmanGroup = ['modp14']; +} else if (crypto.getFips() !== 1) { + TEST_CASES.DiffieHellman = [hasOpenSSL(3) ? 1024 : 256]; } for (const [clazz, args] of Object.entries(TEST_CASES)) { diff --git a/test/parallel/test-crypto-dep0181.js b/test/parallel/test-crypto-dep0181.js index 96e8119dfdb6..ed7dfa93469f 100644 --- a/test/parallel/test-crypto-dep0181.js +++ b/test/parallel/test-crypto-dep0181.js @@ -14,6 +14,6 @@ common.expectWarning({ }); const Hmac = crypto.Hmac; -const instance = crypto.Hmac('sha256', 'Node'); +const instance = crypto.Hmac('sha256', '0123456789abcdef'); assert(instance instanceof Hmac, 'Hmac is expected to return a new instance' + ' when called without `new`'); diff --git a/test/parallel/test-crypto-dep0206.js b/test/parallel/test-crypto-dep0206.js index 4c48ddad6f9e..ea26eb35509c 100644 --- a/test/parallel/test-crypto-dep0206.js +++ b/test/parallel/test-crypto-dep0206.js @@ -13,9 +13,11 @@ common.expectWarning({ }, }); +const key = '0123456789abcdef'; + // Verify runtime deprecation warning for calling digest() more than once. { - const h = crypto.createHmac('sha1', 'key').update('data'); + const h = crypto.createHmac('sha1', key).update('data'); h.digest('hex'); h.digest('hex'); } @@ -23,16 +25,15 @@ common.expectWarning({ // Check initialized -> uninitialized state transition after calling digest(). { const expected = - '\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033' + - '\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d'; + Buffer.from('91768485754a2ca0c93be78a6cfe02a37af32ba3', 'hex'); { - const h = crypto.createHmac('sha1', 'key').update('data'); - assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + const h = crypto.createHmac('sha1', key).update('data'); + assert.deepStrictEqual(h.digest('buffer'), expected); assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); } { - const h = crypto.createHmac('sha1', 'key').update('data'); - assert.strictEqual(h.digest('latin1'), expected); + const h = crypto.createHmac('sha1', key).update('data'); + assert.strictEqual(h.digest('latin1'), expected.toString('latin1')); assert.strictEqual(h.digest('latin1'), ''); } } @@ -41,16 +42,15 @@ common.expectWarning({ // Calls to update() omitted intentionally. { const expected = - '\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097' + - '\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f'; + Buffer.from('804df868948c143aee0946c72d272ea557eaafcc', 'hex'); { - const h = crypto.createHmac('sha1', 'key'); - assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + const h = crypto.createHmac('sha1', key); + assert.deepStrictEqual(h.digest('buffer'), expected); assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); } { - const h = crypto.createHmac('sha1', 'key'); - assert.strictEqual(h.digest('latin1'), expected); + const h = crypto.createHmac('sha1', key); + assert.strictEqual(h.digest('latin1'), expected.toString('latin1')); assert.strictEqual(h.digest('latin1'), ''); } } diff --git a/test/parallel/test-crypto-dh-constructor.js b/test/parallel/test-crypto-dh-constructor.js index eb8674932484..28747ac3a726 100644 --- a/test/parallel/test-crypto-dh-constructor.js +++ b/test/parallel/test-crypto-dh-constructor.js @@ -5,9 +5,10 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); -const size = crypto.getFips() || hasOpenSSL3 ? 1024 : 256; +const size = hasFIPS(3) ? + 2048 : (crypto.getFips() === 1 || hasOpenSSL(3) ? 1024 : 256); const dh1 = crypto.createDiffieHellman(size); const p1 = dh1.getPrime('buffer'); @@ -21,7 +22,7 @@ const p1 = dh1.getPrime('buffer'); { const DiffieHellmanGroup = crypto.DiffieHellmanGroup; - const dhg = DiffieHellmanGroup('modp5'); + const dhg = DiffieHellmanGroup(hasFIPS(3) ? 'modp14' : 'modp5'); assert(dhg instanceof DiffieHellmanGroup, 'DiffieHellmanGroup is expected ' + 'to return a new instance when ' + 'called without `new`'); diff --git a/test/parallel/test-crypto-dh-curves.js b/test/parallel/test-crypto-dh-curves.js index f14c58e7c200..c2449a292894 100644 --- a/test/parallel/test-crypto-dh-curves.js +++ b/test/parallel/test-crypto-dh-curves.js @@ -5,7 +5,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const { DH_CHECK_P_NOT_PRIME, DH_CHECK_P_NOT_SAFE_PRIME, @@ -123,112 +123,118 @@ if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { // ECDH should check that point is on curve const ecdh3 = crypto.createECDH('secp256k1'); - const key3 = ecdh3.generateKeys(); - - assert.throws( - () => ecdh2.computeSecret(key3, 'latin1', 'buffer'), - { - code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY', - name: 'Error', - message: 'Public key is not valid for specified curve' + if (hasFIPS(3)) { + assert.throws(() => ecdh3.generateKeys(), { + code: 'ERR_CRYPTO_OPERATION_FAILED', }); + } else { + const key3 = ecdh3.generateKeys(); - // ECDH should allow .setPrivateKey()/.setPublicKey() - const ecdh4 = crypto.createECDH('prime256v1'); + assert.throws( + () => ecdh2.computeSecret(key3, 'latin1', 'buffer'), + { + code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY', + name: 'Error', + message: 'Public key is not valid for specified curve' + }); - ecdh4.setPrivateKey(ecdh1.getPrivateKey()); - ecdh4.setPublicKey(ecdh1.getPublicKey()); + // ECDH should allow .setPrivateKey()/.setPublicKey() + const ecdh4 = crypto.createECDH('prime256v1'); - assert.throws(() => { - ecdh4.setPublicKey(ecdh3.getPublicKey()); - }, { message: 'Failed to convert Buffer to EC_POINT' }); + ecdh4.setPrivateKey(ecdh1.getPrivateKey()); + ecdh4.setPublicKey(ecdh1.getPublicKey()); - // Verify that we can use ECDH without having to use newly generated keys. - const ecdh5 = crypto.createECDH('secp256k1'); + assert.throws(() => { + ecdh4.setPublicKey(ecdh3.getPublicKey()); + }, { message: 'Failed to convert Buffer to EC_POINT' }); - // Verify errors are thrown when retrieving keys from an uninitialized object. - assert.throws(() => { - ecdh5.getPublicKey(); - }, /^Error: Failed to get ECDH public key$/); + // Verify that we can use ECDH without having to use newly generated keys. + const ecdh5 = crypto.createECDH('secp256k1'); - assert.throws(() => { - ecdh5.getPrivateKey(); - }, /^Error: Failed to get ECDH private key$/); + // Verify errors are thrown when retrieving keys from an uninitialized object. + assert.throws(() => { + ecdh5.getPublicKey(); + }, /^Error: Failed to get ECDH public key$/); + + assert.throws(() => { + ecdh5.getPrivateKey(); + }, /^Error: Failed to get ECDH private key$/); - // A valid private key for the secp256k1 curve. - const cafebabeKey = 'cafebabe'.repeat(8); - // Associated compressed and uncompressed public keys (points). - const cafebabePubPtComp = + // A valid private key for the secp256k1 curve. + const cafebabeKey = 'cafebabe'.repeat(8); + // Associated compressed and uncompressed public keys (points). + const cafebabePubPtComp = '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3'; - const cafebabePubPtUnComp = + const cafebabePubPtUnComp = '04672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3' + '2e02c7f93d13dc2732b760ca377a5897b9dd41a1c1b29dc0442fdce6d0a04d1d'; - ecdh5.setPrivateKey(cafebabeKey, 'hex'); - assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); - // Show that the public point (key) is generated while setting the - // private key. - assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); - - // Compressed and uncompressed public points/keys for other party's - // private key. - // 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF - const peerPubPtComp = + ecdh5.setPrivateKey(cafebabeKey, 'hex'); + assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); + // Show that the public point (key) is generated while setting the + // private key. + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + + // Compressed and uncompressed public points/keys for other party's + // private key. + // 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF + const peerPubPtComp = '02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae'; - const peerPubPtUnComp = + const peerPubPtUnComp = '04c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae' + 'b651944a574a362082a77e3f2b5d9223eb54d7f2f76846522bf75f3bedb8178e'; - const sharedSecret = + const sharedSecret = '1da220b5329bbe8bfd19ceef5a5898593f411a6f12ea40f2a8eead9a5cf59970'; - assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'), - sharedSecret); - assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'), - sharedSecret); - - // Verify that we still have the same key pair as before the computation. - assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); - assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'), + sharedSecret); + assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'), + sharedSecret); - // Verify setting and getting compressed and non-compressed serializations. - ecdh5.setPublicKey(cafebabePubPtComp, 'hex'); - assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); - assert.strictEqual( - ecdh5.getPublicKey('hex', 'compressed'), - cafebabePubPtComp - ); - ecdh5.setPublicKey(cafebabePubPtUnComp, 'hex'); - assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); - assert.strictEqual( - ecdh5.getPublicKey('hex', 'compressed'), - cafebabePubPtComp - ); - - // Show why allowing the public key to be set on this type - // does not make sense. - ecdh5.setPublicKey(peerPubPtComp, 'hex'); - assert.strictEqual(ecdh5.getPublicKey('hex'), peerPubPtUnComp); - assert.throws(() => { - // Error because the public key does not match the private key anymore. - ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'); - }, /Invalid key pair/); - - // Set to a valid key to show that later attempts to set an invalid key are - // rejected. - ecdh5.setPrivateKey(cafebabeKey, 'hex'); - - // Some invalid private keys for the secp256k1 curve. - const errMessage = /Private key is not valid for specified curve/; - ['0000000000000000000000000000000000000000000000000000000000000000', - 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', - 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', - ].forEach((element) => { - assert.throws(() => { - ecdh5.setPrivateKey(element, 'hex'); - }, errMessage); - // Verify object state did not change. + // Verify that we still have the same key pair as before the computation. assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); - }); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + + // Verify setting and getting compressed and non-compressed serializations. + ecdh5.setPublicKey(cafebabePubPtComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + assert.strictEqual( + ecdh5.getPublicKey('hex', 'compressed'), + cafebabePubPtComp + ); + ecdh5.setPublicKey(cafebabePubPtUnComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp); + assert.strictEqual( + ecdh5.getPublicKey('hex', 'compressed'), + cafebabePubPtComp + ); + + // Show why allowing the public key to be set on this type + // does not make sense. + ecdh5.setPublicKey(peerPubPtComp, 'hex'); + assert.strictEqual(ecdh5.getPublicKey('hex'), peerPubPtUnComp); + assert.throws(() => { + // Error because the public key does not match the private key anymore. + ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'); + }, /Invalid key pair/); + + // Set to a valid key to show that later attempts to set an invalid key are + // rejected. + ecdh5.setPrivateKey(cafebabeKey, 'hex'); + + // Some invalid private keys for the secp256k1 curve. + const errMessage = /Private key is not valid for specified curve/; + ['0000000000000000000000000000000000000000000000000000000000000000', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', + 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + ].forEach((element) => { + assert.throws(() => { + ecdh5.setPrivateKey(element, 'hex'); + }, errMessage); + // Verify object state did not change. + assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); + }); + } } // Use of invalid keys was not cleaning up ERR stack, and was causing diff --git a/test/parallel/test-crypto-dh-generate-keys.js b/test/parallel/test-crypto-dh-generate-keys.js index acf7e2d09b2b..d074ba957516 100644 --- a/test/parallel/test-crypto-dh-generate-keys.js +++ b/test/parallel/test-crypto-dh-generate-keys.js @@ -6,10 +6,11 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); { - const size = crypto.getFips() || hasOpenSSL3 ? 1024 : 256; + const size = hasFIPS(3) ? + 2048 : (crypto.getFips() === 1 || hasOpenSSL(3) ? 1024 : 256); function unlessInvalidState(f) { try { diff --git a/test/parallel/test-crypto-dh-leak.js b/test/parallel/test-crypto-dh-leak.js index df1ba89737c6..8d5141eef4b1 100644 --- a/test/parallel/test-crypto-dh-leak.js +++ b/test/parallel/test-crypto-dh-leak.js @@ -9,11 +9,12 @@ if (common.isASan) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const before = process.memoryUsage.rss(); { - const size = crypto.getFips() || hasOpenSSL3 ? 1024 : 256; + const size = hasFIPS(3) ? + 2048 : (crypto.getFips() === 1 || hasOpenSSL(3) ? 1024 : 256); const dh = crypto.createDiffieHellman(size); const publicKey = dh.generateKeys(); const privateKey = dh.getPrivateKey(); diff --git a/test/parallel/test-crypto-dh-modp2-views.js b/test/parallel/test-crypto-dh-modp2-views.js index a28e615b7f35..e32c515e1536 100644 --- a/test/parallel/test-crypto-dh-modp2-views.js +++ b/test/parallel/test-crypto-dh-modp2-views.js @@ -5,24 +5,30 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { modp2buf } = require('../common/crypto'); +const { hasFIPS, modp2buf } = require('../common/crypto'); if (process.features.openssl_is_boringssl) { common.skip('Skipping unsupported Diffie-Hellman tests'); } -const modp2 = crypto.createDiffieHellmanGroup('modp2'); +if (hasFIPS(3)) { + assert.throws(() => crypto.createDiffieHellman(1024), { + code: 'ERR_INVALID_ARG_VALUE', + }); +} else { + const modp2 = crypto.createDiffieHellmanGroup('modp2'); -const views = common.getArrayBufferViews(modp2buf); -for (const buf of [modp2buf, ...views]) { - // Ensure specific generator (string with encoding) works as expected with - // any ArrayBufferViews as the first argument to createDiffieHellman(). - const exmodp2 = crypto.createDiffieHellman(buf, '02', 'hex'); - modp2.generateKeys(); - exmodp2.generateKeys(); - const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) - .toString('hex'); - const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) - .toString('hex'); - assert.strictEqual(modp2Secret, exmodp2Secret); + const views = common.getArrayBufferViews(modp2buf); + for (const buf of [modp2buf, ...views]) { + // Ensure specific generator (string with encoding) works as expected with + // any ArrayBufferViews as the first argument to createDiffieHellman(). + const exmodp2 = crypto.createDiffieHellman(buf, '02', 'hex'); + modp2.generateKeys(); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + .toString('hex'); + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + .toString('hex'); + assert.strictEqual(modp2Secret, exmodp2Secret); + } } diff --git a/test/parallel/test-crypto-dh-modp2.js b/test/parallel/test-crypto-dh-modp2.js index eb262f235ff3..0bf36f93a39a 100644 --- a/test/parallel/test-crypto-dh-modp2.js +++ b/test/parallel/test-crypto-dh-modp2.js @@ -5,44 +5,51 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { modp2buf } = require('../common/crypto'); +const { hasFIPS, modp2buf } = require('../common/crypto'); if (process.features.openssl_is_boringssl) { common.skip('Skipping unsupported Diffie-Hellman tests'); } -const modp2 = crypto.createDiffieHellmanGroup('modp2'); +if (hasFIPS(3)) { + assert.throws(() => crypto.createDiffieHellman(1024), { + code: 'ERR_INVALID_ARG_VALUE', + }); +} else { + const modp2 = crypto.createDiffieHellmanGroup('modp2'); -{ + { // Ensure specific generator (buffer) works as expected. - const exmodp2 = crypto.createDiffieHellman(modp2buf, Buffer.from([2])); - modp2.generateKeys(); - exmodp2.generateKeys(); - const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + const exmodp2 = crypto.createDiffieHellman(modp2buf, Buffer.from([2])); + modp2.generateKeys(); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) .toString('hex'); - const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) .toString('hex'); - assert.strictEqual(modp2Secret, exmodp2Secret); -} + assert.strictEqual(modp2Secret, exmodp2Secret); + } -{ + { // Ensure specific generator (string without encoding) works as expected. - const exmodp2 = crypto.createDiffieHellman(modp2buf, '\x02'); - exmodp2.generateKeys(); - const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + const exmodp2 = crypto.createDiffieHellman(modp2buf, '\x02'); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) .toString('hex'); - const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) .toString('hex'); - assert.strictEqual(modp2Secret, exmodp2Secret); -} + assert.strictEqual(modp2Secret, exmodp2Secret); + } -{ + { // Ensure specific generator (numeric) works as expected. - const exmodp2 = crypto.createDiffieHellman(modp2buf, 2); - exmodp2.generateKeys(); - const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) + const exmodp2 = crypto.createDiffieHellman(modp2buf, 2); + exmodp2.generateKeys(); + const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey()) .toString('hex'); - const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) + const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey()) .toString('hex'); - assert.strictEqual(modp2Secret, exmodp2Secret); + assert.strictEqual(modp2Secret, exmodp2Secret); + } + } diff --git a/test/parallel/test-crypto-dh-odd-key.js b/test/parallel/test-crypto-dh-odd-key.js index fbe42be425ed..c96227770e3d 100644 --- a/test/parallel/test-crypto-dh-odd-key.js +++ b/test/parallel/test-crypto-dh-odd-key.js @@ -27,19 +27,24 @@ if (!common.hasCrypto) { const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); function test() { const odd = Buffer.alloc(39, 'A'); - const c = crypto.createDiffieHellman(hasOpenSSL3 ? 1024 : 32); + const size = hasFIPS(3) ? 2048 : (hasOpenSSL(3) ? 1024 : 32); + const c = crypto.createDiffieHellman(size); c.setPrivateKey(odd); c.generateKeys(); } -// FIPS requires a length of at least 1024 -if (!crypto.getFips()) { +if (hasFIPS(3)) { + test(); + assert.throws(() => crypto.createDiffieHellman(1024), { + code: 'ERR_INVALID_ARG_VALUE', + }); +} else if (crypto.getFips() !== 1) { test(); } else { - assert.throws(function() { test(); }, /key size too small/); + assert.throws(test, /key size too small/); } diff --git a/test/parallel/test-crypto-dh-shared.js b/test/parallel/test-crypto-dh-shared.js index 515405034d76..5e6db278324d 100644 --- a/test/parallel/test-crypto-dh-shared.js +++ b/test/parallel/test-crypto-dh-shared.js @@ -5,9 +5,11 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); -const alice = crypto.createDiffieHellmanGroup('modp5'); -const bob = crypto.createDiffieHellmanGroup('modp5'); +const group = hasFIPS(3) ? 'modp14' : 'modp5'; +const alice = crypto.createDiffieHellmanGroup(group); +const bob = crypto.createDiffieHellmanGroup(group); alice.generateKeys(); bob.generateKeys(); const aSecret = alice.computeSecret(bob.getPublicKey()).toString('hex'); diff --git a/test/parallel/test-crypto-dh.js b/test/parallel/test-crypto-dh.js index 8a3dee5b0756..dc55c5226efb 100644 --- a/test/parallel/test-crypto-dh.js +++ b/test/parallel/test-crypto-dh.js @@ -7,11 +7,13 @@ if (!common.hasCrypto) { const assert = require('assert'); const crypto = require('crypto'); const { - hasOpenSSL3, + hasOpenSSL, + hasFIPS, } = require('../common/crypto'); { - const size = crypto.getFips() || hasOpenSSL3 ? 1024 : 256; + const size = hasFIPS(3) ? + 2048 : (crypto.getFips() === 1 || hasOpenSSL(3) ? 1024 : 256); const dh1 = crypto.createDiffieHellman(size); const p1 = dh1.getPrime('buffer'); const dh2 = crypto.createDiffieHellman(p1, 'buffer'); @@ -57,7 +59,7 @@ const { assert.strictEqual(secret1, secret4); let wrongBlockLength; - if (hasOpenSSL3) { + if (hasOpenSSL(3)) { wrongBlockLength = { message: /wrong[\s_]final[\s_]block[\s_]length/i, code: /ERR_OSSL_(EVP_)?WRONG_FINAL_BLOCK_LENGTH/, diff --git a/test/parallel/test-crypto-ecdh-convert-key.js b/test/parallel/test-crypto-ecdh-convert-key.js index c0046099df9e..8c910b1b6052 100644 --- a/test/parallel/test-crypto-ecdh-convert-key.js +++ b/test/parallel/test-crypto-ecdh-convert-key.js @@ -6,6 +6,7 @@ if (!common.hasCrypto) const assert = require('assert'); const { ECDH, createSign, getCurves } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // A valid private key for the secp256k1 curve. const cafebabeKey = 'cafebabe'.repeat(8); @@ -93,11 +94,17 @@ if (getCurves().includes('secp256k1')) { // Compare to getPublicKey. const ecdh1 = ECDH('secp256k1'); - ecdh1.generateKeys(); - ecdh1.setPrivateKey(cafebabeKey, 'hex'); - assert.strictEqual(ecdh1.getPublicKey('hex', 'uncompressed'), uncompressed); - assert.strictEqual(ecdh1.getPublicKey('hex', 'compressed'), compressed); - assert.strictEqual(ecdh1.getPublicKey('hex', 'hybrid'), hybrid); + if (hasFIPS(3)) { + assert.throws(() => ecdh1.generateKeys(), { + code: 'ERR_CRYPTO_OPERATION_FAILED', + }); + } else { + ecdh1.generateKeys(); + ecdh1.setPrivateKey(cafebabeKey, 'hex'); + assert.strictEqual(ecdh1.getPublicKey('hex', 'uncompressed'), uncompressed); + assert.strictEqual(ecdh1.getPublicKey('hex', 'compressed'), compressed); + assert.strictEqual(ecdh1.getPublicKey('hex', 'hybrid'), hybrid); + } } // See https://github.com/nodejs/node/issues/26133, failed ConvertKey diff --git a/test/parallel/test-crypto-eddsa-variants.js b/test/parallel/test-crypto-eddsa-variants.js index 691534f13f5b..3a75a7bf4f6f 100644 --- a/test/parallel/test-crypto-eddsa-variants.js +++ b/test/parallel/test-crypto-eddsa-variants.js @@ -7,6 +7,7 @@ const assert = require('assert'); const crypto = require('crypto'); const { hasOpenSSL, + hasFIPS, } = require('../common/crypto'); // RFC 8032 Section 7 test vectors for Ed25519, Ed25519ctx, and Ed448. @@ -191,7 +192,18 @@ for (const v of vectors) { const signKey = context ? { key: privateKey, context } : privateKey; const verifyKey = context ? { key: publicKey, context } : publicKey; - const sig = crypto.sign(null, message, signKey); + let sig; + try { + sig = crypto.sign(null, message, signKey); + } catch (err) { + if (!hasFIPS(3) || + (!v.algorithm.endsWith('ctx') && !v.context)) { + throw err; + } + assert.strictEqual( + err.code, 'ERR_OSSL_INVALID_EDDSA_INSTANCE_FOR_ATTEMPTED_OPERATION'); + continue; + } assert.deepStrictEqual(sig, expectedSig); assert.strictEqual( crypto.verify(null, message, verifyKey, expectedSig), true); diff --git a/test/parallel/test-crypto-encap-decap.js b/test/parallel/test-crypto-encap-decap.js index f2259194a9e1..7cafa0f2aa93 100644 --- a/test/parallel/test-crypto-encap-decap.js +++ b/test/parallel/test-crypto-encap-decap.js @@ -6,10 +6,11 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const { promisify } = require('util'); const isBoringSSL = process.features.openssl_is_boringssl; +const isFips = hasFIPS(3); if (!hasOpenSSL(3) && !isBoringSSL) { assert.throws(() => crypto.encapsulate(), { code: 'ERR_CRYPTO_KEM_NOT_SUPPORTED' }); @@ -36,7 +37,7 @@ const keys = { privateKey: fixtures.readKey('rsa_pss_private_2048.pem', 'ascii'), }, 'p-256': { - supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + supported: hasOpenSSL(3, 2) && !isFips, // DHKEM was added in 3.2 publicKey: fixtures.readKey('ec_p256_public.pem', 'ascii'), privateKey: fixtures.readKey('ec_p256_private.pem', 'ascii'), sharedSecretLength: 32, @@ -44,7 +45,7 @@ const keys = { raw: true, }, 'p-384': { - supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + supported: hasOpenSSL(3, 2) && !isFips, // DHKEM was added in 3.2 publicKey: fixtures.readKey('ec_p384_public.pem', 'ascii'), privateKey: fixtures.readKey('ec_p384_private.pem', 'ascii'), sharedSecretLength: 48, @@ -52,7 +53,7 @@ const keys = { raw: true, }, 'p-521': { - supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + supported: hasOpenSSL(3, 2) && !isFips, // DHKEM was added in 3.2 publicKey: fixtures.readKey('ec_p521_public.pem', 'ascii'), privateKey: fixtures.readKey('ec_p521_private.pem', 'ascii'), sharedSecretLength: 64, @@ -65,7 +66,7 @@ const keys = { privateKey: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'), }, 'x25519': { - supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + supported: hasOpenSSL(3, 2) && !isFips, // DHKEM was added in 3.2 publicKey: fixtures.readKey('x25519_public.pem', 'ascii'), privateKey: fixtures.readKey('x25519_private.pem', 'ascii'), sharedSecretLength: 32, @@ -73,7 +74,7 @@ const keys = { raw: true, }, 'x448': { - supported: hasOpenSSL(3, 2), // DHKEM was added in 3.2 + supported: hasOpenSSL(3, 2) && !isFips, // DHKEM was added in 3.2 publicKey: fixtures.readKey('x448_public.pem', 'ascii'), privateKey: fixtures.readKey('x448_private.pem', 'ascii'), sharedSecretLength: 64, diff --git a/test/parallel/test-crypto-getcipherinfo.js b/test/parallel/test-crypto-getcipherinfo.js index d55985aa3c7f..4f9f2975ad6d 100644 --- a/test/parallel/test-crypto-getcipherinfo.js +++ b/test/parallel/test-crypto-getcipherinfo.js @@ -6,8 +6,9 @@ if (!common.hasCrypto) const { getCiphers, - getCipherInfo + getCipherInfo, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const assert = require('assert'); @@ -76,7 +77,10 @@ if (!process.features.openssl_is_boringssl) { } assert(!getCipherInfo('aes-128-ocb', { ivLength: 16 })); -if (!process.features.openssl_is_boringssl) { +if (hasFIPS(3)) { + assert.strictEqual( + getCipherInfo('aes-128-ocb', { ivLength: 12 }), undefined); +} else if (!process.features.openssl_is_boringssl) { for (let n = 1; n < 16; n++) assert(getCipherInfo('aes-128-ocb', { ivLength: n })); } else { diff --git a/test/parallel/test-crypto-hkdf.js b/test/parallel/test-crypto-hkdf.js index 242e278707d7..bfde3b324331 100644 --- a/test/parallel/test-crypto-hkdf.js +++ b/test/parallel/test-crypto-hkdf.js @@ -13,7 +13,7 @@ const { hkdfSync, getHashes } = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); { assert.throws(() => hkdf(), { @@ -120,12 +120,12 @@ const { hasOpenSSL3 } = require('../common/crypto'); } const algorithms = [ - ['sha256', 'secret', 'salt', 'info', 10], + ['sha256', '0123456789abcdef', '0123456789abcdef', 'info', 10], ['sha256', '', '', '', 10], ['sha256', '', 'salt', '', 10], ['sha512', 'secret', 'salt', '', 15], ]; -if (!hasOpenSSL3 && !process.features.openssl_is_boringssl) +if (!hasOpenSSL(3) && !process.features.openssl_is_boringssl) algorithms.push(['whirlpool', 'secret', '', 'info', 20]); algorithms.forEach(([ hash, secret, salt, info, length ]) => { @@ -216,7 +216,7 @@ algorithms.forEach(([ hash, secret, salt, info, length ]) => { }); -if (!hasOpenSSL3) { +if (!hasOpenSSL(3)) { const kKnownUnsupported = ['shake128', 'shake256']; for (const hash of getHashes()) { if (kKnownUnsupported.includes(hash)) continue; diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js index 9ddc4a4b880f..653a6a4f33ec 100644 --- a/test/parallel/test-crypto-hmac.js +++ b/test/parallel/test-crypto-hmac.js @@ -6,6 +6,9 @@ if (!common.hasCrypto) { const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); + +const fips3 = hasFIPS(3); assert.throws( () => crypto.createHmac(null), @@ -17,7 +20,7 @@ assert.throws( // This used to segfault. See: https://github.com/nodejs/node/issues/9819 assert.throws( - () => crypto.createHmac('sha256', 'key').digest({ + () => crypto.createHmac('sha256', '0123456789abcdef').digest({ toString: () => { throw new Error('boom'); }, }), { @@ -33,9 +36,14 @@ assert.throws( }); function testHmac(algo, key, data, expected) { - // FIPS does not support MD5. - if (crypto.getFips() && algo === 'md5') + if (crypto.getFips() === 1 && algo === 'md5') { + if (fips3) { + assert.throws(() => crypto.createHmac(algo, Buffer.alloc(32)), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + } return; + } if (!Array.isArray(data)) data = [data]; @@ -63,12 +71,13 @@ function testHmac(algo, key, data, expected) { { // Historically, dss1 and DSS1 are SHA-1 aliases. + const key = '0123456789abcdef'; const expected = - crypto.createHmac('sha1', 'key').update('data').digest('hex'); + crypto.createHmac('sha1', key).update('data').digest('hex'); for (const algo of ['dss1', 'DSS1']) { assert.strictEqual( - crypto.createHmac(algo, 'key').update('data').digest('hex'), + crypto.createHmac(algo, key).update('data').digest('hex'), expected); } } @@ -412,13 +421,16 @@ const rfc2202_sha1 = [ for (const { key, data, hmac } of rfc2202_sha1) testHmac('sha1', key, data, hmac); -assert.strictEqual( - crypto.createHmac('sha256', 'w00t').digest('ucs2'), - crypto.createHmac('sha256', 'w00t').digest().toString('ucs2')); +{ + const key = '0123456789abcdef'; + assert.strictEqual( + crypto.createHmac('sha256', key).digest('ucs2'), + crypto.createHmac('sha256', key).digest().toString('ucs2')); +} { assert.throws( - () => crypto.createHmac('sha7', 'key'), + () => crypto.createHmac('sha7', '0123456789abcdef'), /Invalid digest/); } diff --git a/test/parallel/test-crypto-job-error-parity.js b/test/parallel/test-crypto-job-error-parity.js index f10c910ff67b..ee9bd27bba6b 100644 --- a/test/parallel/test-crypto-job-error-parity.js +++ b/test/parallel/test-crypto-job-error-parity.js @@ -8,11 +8,13 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const assert = require('assert'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); + function getError(fn) { let err; assert.throws(fn, (e) => { err = e; return true; }); @@ -69,14 +71,26 @@ const data = Buffer.from('test data'); // Sign: RSA key too small for digest (OpenSSL error) { const { privateKey } = crypto.generateKeyPairSync('rsa', { - modulusLength: 512, + modulusLength: fips3 ? 2048 : 512, }); + const key = fips3 ? { + key: privateKey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: 256, + } : privateKey; + const digest = fips3 ? 'sha256' : 'sha512'; + + const syncErr = getError(() => crypto.sign(digest, data, key)); + if (fips3) { + assert.strictEqual(syncErr.code, + 'ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE'); + } else { + assert.match( + syncErr.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i); + } - const syncErr = getError(() => crypto.sign('sha512', data, privateKey)); - assert.match(syncErr.message, /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i); - - crypto.sign('sha512', data, privateKey, common.mustCall((asyncErr) => { - assertErrorMatch(syncErr, asyncErr, 'sign: RSA 512 + sha512'); + crypto.sign(digest, data, key, common.mustCall((asyncErr) => { + assertErrorMatch(syncErr, asyncErr, 'sign: RSA policy error'); })); } @@ -160,7 +174,9 @@ const data = Buffer.from('test data'); // DH: Mismatched DH group params (OpenSSL error) { - const alice = crypto.generateKeyPairSync('dh', { group: 'modp5' }); + const alice = crypto.generateKeyPairSync('dh', { + group: fips3 ? 'modp14' : 'modp5', + }); const bob = crypto.generateKeyPairSync('dh', { group: 'modp18' }); const syncErr = getError(() => diff --git a/test/parallel/test-crypto-key-objects-messageport.js b/test/parallel/test-crypto-key-objects-messageport.js index f0555b45f1ff..bd0856817660 100644 --- a/test/parallel/test-crypto-key-objects-messageport.js +++ b/test/parallel/test-crypto-key-objects-messageport.js @@ -9,6 +9,7 @@ const { generateKeyPairSync, KeyObject, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; const { createContext } = require('vm'); const { @@ -45,7 +46,7 @@ process.env.HAS_STARTED_WORKER = 1; // The main thread generates keys and passes them to worker threads. const secretKey = generateKeySync('aes', { length: 128 }); const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 1024 + modulusLength: hasFIPS(3) ? 2048 : 1024 }); const cryptoKey = await subtle.generateKey( { name: 'AES-CBC', length: 128 }, true, ['encrypt']); diff --git a/test/parallel/test-crypto-key-objects-to-crypto-key.js b/test/parallel/test-crypto-key-objects-to-crypto-key.js index 46029f5a4362..0fd3de845306 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -12,7 +12,9 @@ const { randomBytes, generateKeyPairSync, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { kSupportedAlgorithms } = require('internal/crypto/util'); +const rejectsXCurves = hasFIPS(3, 5); const hashes = Object.keys(kSupportedAlgorithms.digest).filter((name) => { return name.startsWith('SHA-') || name.startsWith('SHA3-'); @@ -239,6 +241,13 @@ function ecVectors(name, usagesByType) { } function cfrgVectors(name, usagesByType) { + if (rejectsXCurves && name.startsWith('X')) { + assert.throws(() => generateKeyPairSync(name.toLowerCase()), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + return []; + } + const keyPair = generateKeyPairSync(name.toLowerCase()); return asymmetricVectors(keyPair, name, usagesByType); } @@ -324,9 +333,11 @@ const invalid = { 'HMAC': () => macInvalid( { name: 'HMAC', hash: 'SHA-256' }, 'HmacImportParams.length cannot be 0'), - 'X25519': () => invalidAsymmetricKeyType('X25519', 'Ed25519'), }; +if (!rejectsXCurves) + invalid.X25519 = () => invalidAsymmetricKeyType('X25519', 'Ed25519'); + for (const name of ['AES-CBC', 'AES-CTR', 'AES-GCM', 'AES-OCB']) { if (name in kSupportedAlgorithms.importKey) tests[name] = symmetricVectors(name, ['encrypt', 'decrypt']); @@ -357,9 +368,11 @@ for (const [name, usages, invalidAlgorithm] of [ ]) { if (name in kSupportedAlgorithms.importKey) { tests[name] = cfrgVectors(name, usages); - invalid[name] = () => { - invalidAsymmetricKeyType(name, invalidAlgorithm); - }; + if (!rejectsXCurves) { + invalid[name] = () => { + invalidAsymmetricKeyType(name, invalidAlgorithm); + }; + } } } diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index dadeb780cf6c..ba9a05387f21 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -24,7 +24,17 @@ const { generateKeyPairSync, } = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { + hasOpenSSL, + hasFIPS, +} = require('../common/crypto'); + +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); +const fips30 = fips3 && !fips35; +const fips4 = hasFIPS(4); +const rejectsXCurves = fips35; +const fipsDigestErrorCode = 'ERR_OSSL_DIGEST_NOT_ALLOWED'; const fixtures = require('../common/fixtures'); @@ -201,15 +211,26 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', // It should also be possible to import an encrypted private key as a public // key. + const passphrase = 'password'; + if (fips4) { + assert.throws(() => privateKey.export({ + type: 'pkcs8', + format: 'pem', + passphrase: '123', + cipher: 'aes-128-cbc' + }), { + code: 'ERR_OSSL_PASSWORD_STRENGTH_TOO_WEAK', + }); + } const decryptedKey = createPublicKey({ key: privateKey.export({ type: 'pkcs8', format: 'pem', - passphrase: '123', + passphrase, cipher: 'aes-128-cbc' }), format: 'pem', - passphrase: '123' + passphrase }); assert.strictEqual(decryptedKey.type, 'public'); assert.strictEqual(decryptedKey.asymmetricKeyType, 'rsa'); @@ -328,7 +349,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', // This should not cause a crash: https://github.com/nodejs/node/issues/25247 assert.throws(() => { createPrivateKey({ key: '' }); - }, hasOpenSSL3 ? { + }, hasOpenSSL(3) ? { message: 'error:1E08010C:DECODER routines::unsupported', } : process.features.openssl_is_boringssl ? { message: 'error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE', @@ -360,7 +381,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', type: 'pkcs1' }); createPrivateKey({ key, format: 'der', type: 'pkcs1' }); - }, hasOpenSSL3 ? { + }, hasOpenSSL(3) ? { message: /error:1E08010C:DECODER routines::unsupported/, library: 'DECODER routines' } : process.features.openssl_is_boringssl ? { @@ -415,6 +436,8 @@ for (const info of [ } }, ]) { const keyType = info.keyType; + const fipsUnsupported = + rejectsXCurves && keyType.startsWith('x'); if (process.features.openssl_is_boringssl && keyType.endsWith('448')) { common.printSkipMessage(`Skipping unsupported ${keyType} test case`); @@ -433,19 +456,32 @@ for (const info of [ } { - const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); - assert.strictEqual(key.type, 'private'); - assert.strictEqual(key.asymmetricKeyType, keyType); - assert.strictEqual(key.symmetricKeySize, undefined); - assert.strictEqual( - key.export({ type: 'pkcs8', format: 'pem' }), info.private); - assert.deepStrictEqual( - key.export({ format: 'jwk' }), info.jwk); + if (fipsUnsupported) { + assert.throws( + () => createPrivateKey({ key: info.jwk, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' }); + } else { + const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } } { - for (const input of [ - info.private, info.public, { key: info.jwk, format: 'jwk' }]) { + const inputs = [info.private, info.public]; + if (fipsUnsupported) { + assert.throws( + () => createPublicKey({ key: info.jwk, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' }); + } else { + inputs.push({ key: info.jwk, format: 'jwk' }); + } + for (const input of inputs) { const key = createPublicKey(input); assert.strictEqual(key.type, 'public'); assert.strictEqual(key.asymmetricKeyType, keyType); @@ -469,21 +505,32 @@ for (const info of [ assert(Buffer.isBuffer(rawPriv)); assert(Buffer.isBuffer(rawPub)); - const importedPriv = createPrivateKey({ + const privateOptions = { key: rawPriv, format: 'raw-private', asymmetricKeyType: keyType, - }); - assert.strictEqual(importedPriv.type, 'private'); - assert.strictEqual(importedPriv.asymmetricKeyType, keyType); - assert.deepStrictEqual( - importedPriv.export({ format: 'raw-private' }), rawPriv); - - const importedPub = createPublicKey({ + }; + const publicOptions = { key: rawPub, format: 'raw-public', asymmetricKeyType: keyType, - }); - assert.strictEqual(importedPub.type, 'public'); - assert.strictEqual(importedPub.asymmetricKeyType, keyType); - assert.deepStrictEqual( - importedPub.export({ format: 'raw-public' }), rawPub); + }; + if (fipsUnsupported) { + assert.throws( + () => createPrivateKey(privateOptions), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws( + () => createPublicKey(publicOptions), + { code: 'ERR_INVALID_ARG_VALUE' }); + } else { + const importedPriv = createPrivateKey(privateOptions); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey(publicOptions); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } } } @@ -582,6 +629,7 @@ for (const info of [ } }, ]) { const { keyType, namedCurve } = info; + const fipsUnsupported = fips3 && namedCurve === 'secp256k1'; if (process.features.openssl_is_boringssl && !getCurves().includes(namedCurve)) { common.printSkipMessage(`Skipping unsupported ${keyType} test case`); @@ -601,20 +649,33 @@ for (const info of [ } { - const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); - assert.strictEqual(key.type, 'private'); - assert.strictEqual(key.asymmetricKeyType, keyType); - assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve }); - assert.strictEqual(key.symmetricKeySize, undefined); - assert.strictEqual( - key.export({ type: 'pkcs8', format: 'pem' }), info.private); - assert.deepStrictEqual( - key.export({ format: 'jwk' }), info.jwk); + if (fipsUnsupported) { + assert.throws( + () => createPrivateKey({ key: info.jwk, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' }); + } else { + const key = createPrivateKey({ key: info.jwk, format: 'jwk' }); + assert.strictEqual(key.type, 'private'); + assert.strictEqual(key.asymmetricKeyType, keyType); + assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve }); + assert.strictEqual(key.symmetricKeySize, undefined); + assert.strictEqual( + key.export({ type: 'pkcs8', format: 'pem' }), info.private); + assert.deepStrictEqual( + key.export({ format: 'jwk' }), info.jwk); + } } { - for (const input of [ - info.private, info.public, { key: info.jwk, format: 'jwk' }]) { + const inputs = [info.private, info.public]; + if (fipsUnsupported) { + assert.throws( + () => createPublicKey({ key: info.jwk, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' }); + } else { + inputs.push({ key: info.jwk, format: 'jwk' }); + } + for (const input of inputs) { const key = createPublicKey(input); assert.strictEqual(key.type, 'public'); assert.strictEqual(key.asymmetricKeyType, keyType); @@ -649,23 +710,35 @@ for (const info of [ assert(Buffer.isBuffer(rawPriv)); assert(Buffer.isBuffer(rawPub)); - const importedPriv = createPrivateKey({ + const privateOptions = { key: rawPriv, format: 'raw-private', asymmetricKeyType: keyType, namedCurve, - }); - assert.strictEqual(importedPriv.type, 'private'); - assert.strictEqual(importedPriv.asymmetricKeyType, keyType); - assert.deepStrictEqual( - importedPriv.export({ format: 'raw-private' }), rawPriv); + }; - const importedPub = createPublicKey({ + const publicOptions = { key: rawPub, format: 'raw-public', asymmetricKeyType: keyType, namedCurve, - }); - assert.strictEqual(importedPub.type, 'public'); - assert.strictEqual(importedPub.asymmetricKeyType, keyType); - assert.deepStrictEqual( - importedPub.export({ format: 'raw-public' }), rawPub); + }; + if (fipsUnsupported) { + assert.throws( + () => createPrivateKey(privateOptions), + { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws( + () => createPublicKey(publicOptions), + { code: 'ERR_INVALID_ARG_VALUE' }); + } else { + const importedPriv = createPrivateKey(privateOptions); + assert.strictEqual(importedPriv.type, 'private'); + assert.strictEqual(importedPriv.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPriv.export({ format: 'raw-private' }), rawPriv); + + const importedPub = createPublicKey(publicOptions); + assert.strictEqual(importedPub.type, 'public'); + assert.strictEqual(importedPub.asymmetricKeyType, keyType); + assert.deepStrictEqual( + importedPub.export({ format: 'raw-public' }), rawPub); + } } } @@ -717,7 +790,7 @@ for (const info of [ { // Reading an encrypted key without a passphrase should fail. - assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL3 ? { + assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL(3) ? { name: 'Error', message: 'error:07880109:common libcrypto routines::interrupted or ' + 'cancelled', @@ -733,7 +806,7 @@ for (const info of [ key: privateDsa, format: 'pem', passphrase: Buffer.alloc(1025, 'a') - }), hasOpenSSL3 ? { name: 'Error' } : { + }), hasOpenSSL(3) ? { name: 'Error' } : { code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ', name: 'Error' }); @@ -744,8 +817,10 @@ for (const info of [ key: privateDsa, format: 'pem', passphrase: Buffer.alloc(1024, 'a') - }), { - message: /bad decrypt|BAD_DECRYPT/ + }), fips4 ? { + code: 'ERR_OSSL_INVALID_SALT_LENGTH', + } : { + message: /bad decrypt|BAD_DECRYPT/, }); const publicKey = createPublicKey(publicDsa); @@ -756,11 +831,28 @@ for (const info of [ () => publicKey.export({ format: 'jwk' }), { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); - const privateKey = createPrivateKey({ - key: privateDsa, + const privateKeyData = + createPrivateKey(fixtures.readKey('dsa_private.pem')).export({ + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'password', + }); + const privateKeyOptions = { + key: privateKeyData, format: 'pem', - passphrase: 'secret' - }); + passphrase: 'password', + }; + if (fips4) { + assert.throws(() => createPrivateKey({ + key: privateDsa, + format: 'pem', + passphrase: 'secret', + }), { + code: 'ERR_OSSL_PASSWORD_STRENGTH_TOO_WEAK', + }); + } + const privateKey = createPrivateKey(privateKeyOptions); assert.strictEqual(privateKey.type, 'private'); assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); assert.strictEqual(privateKey.symmetricKeySize, undefined); @@ -804,20 +896,27 @@ if (!process.features.openssl_is_boringssl) { { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); for (const key of [privatePem, privateKey]) { - // Any algorithm should work. - for (const algo of ['sha1', 'sha256']) { - // Any salt length should work. - for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { - const signature = createSign(algo) - .update('foo') - .sign({ key, saltLength }); - - for (const pkey of [key, publicKey, publicPem]) { - const okay = createVerify(algo) - .update('foo') - .verify({ key: pkey, saltLength }, signature); - - assert.ok(okay); + if (fips30) { + // With no explicit parameters, this key defaults to SHA-1 for PSS. + assert.throws(() => createSign('sha256').update('foo').sign(key), { + code: fipsDigestErrorCode, + }); + } else { + // Any algorithm should work. + for (const algo of ['sha1', 'sha256']) { + // Any salt length should work. + for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { + const signature = createSign(algo) + .update('foo') + .sign({ key, saltLength }); + + for (const pkey of [key, publicKey, publicPem]) { + const okay = createVerify(algo) + .update('foo') + .verify({ key: pkey, saltLength }, signature); + + assert.ok(okay); + } } } } @@ -885,12 +984,15 @@ if (!process.features.openssl_is_boringssl) { // Signing with anything other than sha256 should fail. assert.throws(() => { createSign('sha1').sign(key); - }, /digest not allowed/); + }, fips30 ? { + code: fipsDigestErrorCode, + } : /digest not allowed/); // Signing with salt lengths less than 16 bytes should fail. for (const saltLength of [8, 10, 12]) { assert.throws(() => { - createSign('sha1').sign({ key, saltLength }); + createSign(fips3 ? 'sha256' : 'sha1') + .sign({ key, saltLength }); }, /pss saltlen too small/); } @@ -983,7 +1085,9 @@ if (!process.features.openssl_is_boringssl) { for (const algo of ['sha1', 'sha256']) { assert.throws(() => { createSign(algo).sign(key); - }, /digest not allowed/); + }, fips30 && algo === 'sha1' ? { + code: fipsDigestErrorCode, + } : /digest not allowed/); } // sha512 should produce a valid signature. @@ -1046,20 +1150,26 @@ if (!process.features.openssl_is_boringssl) { // provider is currently in use. const namedCurve = getCurves().find((curve) => !supported.includes(curve)); assert(namedCurve); - const keyPair = generateKeyPairSync('ec', { namedCurve }); - const { publicKey, privateKey } = keyPair; - assert.throws( - () => publicKey.export({ format: 'jwk' }), - { - code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', - message: `Unsupported JWK EC curve: ${namedCurve}.` - }); - assert.throws( - () => privateKey.export({ format: 'jwk' }), - { - code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', - message: `Unsupported JWK EC curve: ${namedCurve}.` + if (fips3) { + assert.throws(() => generateKeyPairSync('ec', { namedCurve }), { + code: 'ERR_OSSL_EC_UNKNOWN_GROUP', }); + } else { + const keyPair = generateKeyPairSync('ec', { namedCurve }); + const { publicKey, privateKey } = keyPair; + assert.throws( + () => publicKey.export({ format: 'jwk' }), + { + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: `Unsupported JWK EC curve: ${namedCurve}.` + }); + assert.throws( + () => privateKey.export({ format: 'jwk' }), + { + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', + message: `Unsupported JWK EC curve: ${namedCurve}.` + }); + } } { @@ -1102,12 +1212,18 @@ if (!process.features.openssl_is_boringssl) { { const first = generateKeyPairSync('ed25519'); - const second = generateKeyPairSync('x25519'); + if (rejectsXCurves) { + assert.throws(() => generateKeyPairSync('x25519'), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + } else { + const second = generateKeyPairSync('x25519'); - assert(!first.publicKey.equals(second.publicKey)); - assert(!first.publicKey.equals(second.privateKey)); - assert(!first.privateKey.equals(second.privateKey)); - assert(!first.privateKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.publicKey)); + assert(!first.publicKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.privateKey)); + assert(!first.privateKey.equals(second.publicKey)); + } } { diff --git a/test/parallel/test-crypto-keygen-async-dsa.js b/test/parallel/test-crypto-keygen-async-dsa.js index d7c857d35e21..6c7129c7efba 100644 --- a/test/parallel/test-crypto-keygen-async-dsa.js +++ b/test/parallel/test-crypto-keygen-async-dsa.js @@ -17,7 +17,7 @@ const { spkiExp, } = require('../common/crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); // Test async DSA key generation. { @@ -27,7 +27,7 @@ const { hasOpenSSL3 } = require('../common/crypto'); }; generateKeyPair('dsa', { - modulusLength: hasOpenSSL3 ? 2048 : 512, + modulusLength: hasOpenSSL(3) ? 2048 : 512, divisorLength: 256, publicKeyEncoding: { type: 'spki', @@ -35,7 +35,7 @@ const { hasOpenSSL3 } = require('../common/crypto'); }, privateKeyEncoding: { cipher: 'aes-128-cbc', - passphrase: 'secret', + passphrase: 'password', ...privateKeyEncoding } }, common.mustSucceed((publicKey, privateKeyDER) => { @@ -44,8 +44,8 @@ const { hasOpenSSL3 } = require('../common/crypto'); // The private key is DER-encoded. assert(Buffer.isBuffer(privateKeyDER)); - assertApproximateSize(publicKey, hasOpenSSL3 ? 1194 : 440); - assertApproximateSize(privateKeyDER, hasOpenSSL3 ? 721 : 336); + assertApproximateSize(publicKey, hasOpenSSL(3) ? 1194 : 440); + assertApproximateSize(privateKeyDER, hasOpenSSL(3) ? 721 : 336); // Since the private key is encrypted, signing shouldn't work anymore. assert.throws(() => { @@ -63,7 +63,7 @@ const { hasOpenSSL3 } = require('../common/crypto'); testSignVerify(publicKey, { key: privateKeyDER, ...privateKeyEncoding, - passphrase: 'secret' + passphrase: 'password' }); })); } diff --git a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js index b0945dcc83a2..bde6dc1d694b 100644 --- a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js +++ b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js @@ -8,6 +8,7 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // Test async elliptic curve key generation with 'jwk' encoding and named // curve. @@ -24,7 +25,12 @@ for (const curve of ['P-384', 'P-256', 'P-521', 'secp256k1']) { privateKeyEncoding: { format: 'jwk' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (hasFIPS(3) && curve === 'secp256k1') { + assert.strictEqual(err?.code, 'ERR_OSSL_EC_UNKNOWN_GROUP'); + return; + } + assert.ifError(err); assert.strictEqual(typeof publicKey, 'object'); assert.strictEqual(typeof privateKey, 'object'); assert.strictEqual(publicKey.x, privateKey.x); diff --git a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js index 449d1a97f9f6..f1a1bdf8322f 100644 --- a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js +++ b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-rsa.js @@ -8,11 +8,12 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // Test async elliptic curve key generation with 'jwk' encoding and RSA. { generateKeyPair('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, publicKeyEncoding: { format: 'jwk' }, diff --git a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js index 731960b0d56a..cfef18c889b6 100644 --- a/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js +++ b/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js @@ -8,6 +8,8 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); +const rejectsXCurves = hasFIPS(3, 5); // Test async elliptic curve key generation with 'jwk' encoding. { @@ -23,7 +25,12 @@ const { privateKeyEncoding: { format: 'jwk' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (rejectsXCurves && type.startsWith('x')) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); assert.strictEqual(typeof publicKey, 'object'); assert.strictEqual(typeof privateKey, 'object'); assert.strictEqual(publicKey.x, privateKey.x); diff --git a/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js b/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js index 3203dfe16eb6..c1bc6fc95ea0 100644 --- a/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js +++ b/test/parallel/test-crypto-keygen-async-encrypted-private-key-der.js @@ -10,15 +10,17 @@ const { } = require('crypto'); const { assertApproximateSize, + hasFIPS, testEncryptDecrypt, testSignVerify, } = require('../common/crypto'); // Test async RSA key generation with an encrypted private key, but encoded as DER. { + const isFips = hasFIPS(3); generateKeyPair('rsa', { publicExponent: 0x10001, - modulusLength: 512, + modulusLength: isFips ? 2048 : 512, publicKeyEncoding: { type: 'pkcs1', format: 'der' @@ -29,7 +31,7 @@ const { } }, common.mustSucceed((publicKeyDER, privateKeyDER) => { assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); + assertApproximateSize(publicKeyDER, isFips ? 270 : 74); assert(Buffer.isBuffer(privateKeyDER)); diff --git a/test/parallel/test-crypto-keygen-async-encrypted-private-key.js b/test/parallel/test-crypto-keygen-async-encrypted-private-key.js index 727cccc6f3ef..37a0121742df 100644 --- a/test/parallel/test-crypto-keygen-async-encrypted-private-key.js +++ b/test/parallel/test-crypto-keygen-async-encrypted-private-key.js @@ -10,15 +10,18 @@ const { } = require('crypto'); const { assertApproximateSize, + hasFIPS, testEncryptDecrypt, testSignVerify, } = require('../common/crypto'); // Test async RSA key generation with an encrypted private key, but encoded as DER. { + const isFips = hasFIPS(3); + const passphrase = 'password'; generateKeyPair('rsa', { publicExponent: 0x10001, - modulusLength: 512, + modulusLength: isFips ? 2048 : 512, publicKeyEncoding: { type: 'pkcs1', format: 'der' @@ -27,11 +30,11 @@ const { type: 'pkcs8', format: 'der', cipher: 'aes-256-cbc', - passphrase: 'secret' + passphrase } }, common.mustSucceed((publicKeyDER, privateKeyDER) => { assert(Buffer.isBuffer(publicKeyDER)); - assertApproximateSize(publicKeyDER, 74); + assertApproximateSize(publicKeyDER, isFips ? 270 : 74); assert(Buffer.isBuffer(privateKeyDER)); @@ -59,7 +62,7 @@ const { key: privateKeyDER, format: 'der', type: 'pkcs8', - passphrase: 'secret' + passphrase }; testEncryptDecrypt(publicKey, privateKey); testSignVerify(publicKey, privateKey); diff --git a/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js index c3b8ab6e8f50..081e709f46ec 100644 --- a/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js +++ b/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js @@ -12,10 +12,11 @@ const { generateKeyPair, } = require('crypto'); const { + hasFIPS, testSignVerify, spkiExp, sec1EncExp, - hasOpenSSL3, + hasOpenSSL, } = require('../common/crypto'); { @@ -34,7 +35,12 @@ const { cipher: 'aes-128-cbc', passphrase: 'secret' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (hasFIPS(3)) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, spkiExp); assert.strictEqual(typeof privateKey, 'string'); @@ -42,7 +48,7 @@ const { // Since the private key is encrypted, signing shouldn't work anymore. assert.throws(() => testSignVerify(publicKey, privateKey), - hasOpenSSL3 ? { + hasOpenSSL(3) ? { message: 'error:07880109:common libcrypto ' + 'routines::interrupted or cancelled' } : { diff --git a/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js index 0503ff74787f..84ea9d2f7a9a 100644 --- a/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js +++ b/test/parallel/test-crypto-keygen-async-named-elliptic-curve-encrypted.js @@ -9,10 +9,11 @@ const { generateKeyPair, } = require('crypto'); const { + hasFIPS, testSignVerify, spkiExp, sec1EncExp, - hasOpenSSL3, + hasOpenSSL, } = require('../common/crypto'); { @@ -31,7 +32,12 @@ const { cipher: 'aes-128-cbc', passphrase: 'secret' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (hasFIPS(3)) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, spkiExp); assert.strictEqual(typeof privateKey, 'string'); @@ -39,7 +45,7 @@ const { // Since the private key is encrypted, signing shouldn't work anymore. assert.throws(() => testSignVerify(publicKey, privateKey), - hasOpenSSL3 ? { + hasOpenSSL(3) ? { message: 'error:07880109:common libcrypto ' + 'routines::interrupted or cancelled' } : { diff --git a/test/parallel/test-crypto-keygen-async-rsa.js b/test/parallel/test-crypto-keygen-async-rsa.js index c80d7d334929..7a372ded9fc5 100644 --- a/test/parallel/test-crypto-keygen-async-rsa.js +++ b/test/parallel/test-crypto-keygen-async-rsa.js @@ -10,17 +10,19 @@ const { } = require('crypto'); const { assertApproximateSize, + hasFIPS, testEncryptDecrypt, testSignVerify, pkcs1EncExp, - hasOpenSSL3, + hasOpenSSL, } = require('../common/crypto'); // Test async RSA key generation with an encrypted private key. { + const isFips = hasFIPS(3); generateKeyPair('rsa', { publicExponent: 0x10001, - modulusLength: 512, + modulusLength: isFips ? 2048 : 512, publicKeyEncoding: { type: 'pkcs1', format: 'der' @@ -31,7 +33,12 @@ const { cipher: 'aes-256-cbc', passphrase: 'secret' } - }, common.mustSucceed((publicKeyDER, privateKey) => { + }, common.mustCall((err, publicKeyDER, privateKey) => { + if (isFips) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); assert(Buffer.isBuffer(publicKeyDER)); assertApproximateSize(publicKeyDER, 74); @@ -44,7 +51,7 @@ const { type: 'pkcs1', format: 'der', }; - const expectedError = hasOpenSSL3 ? { + const expectedError = hasOpenSSL(3) ? { name: 'Error', message: 'error:07880109:common libcrypto routines::interrupted or ' + 'cancelled' diff --git a/test/parallel/test-crypto-keygen-bit-length.js b/test/parallel/test-crypto-keygen-bit-length.js index 13234589a5d6..52765f3d7fe7 100644 --- a/test/parallel/test-crypto-keygen-bit-length.js +++ b/test/parallel/test-crypto-keygen-bit-length.js @@ -12,7 +12,9 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); + +const fips3 = hasFIPS(3); // This tests check that generateKeyPair returns correct bit length in // KeyObject's asymmetricKeyDetails. @@ -20,23 +22,38 @@ const { hasOpenSSL3 } = require('../common/crypto'); { generateKeyPair('rsa', { modulusLength: 513, - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (fips3) { + assert.strictEqual(err?.code, 'ERR_OSSL_RSA_INVALID_MODULUS'); + return; + } + assert.ifError(err); assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); })); generateKeyPair('rsa-pss', { modulusLength: 513, - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (fips3) { + assert.strictEqual(err?.code, 'ERR_OSSL_RSA_INVALID_MODULUS'); + return; + } + assert.ifError(err); assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); })); - if (hasOpenSSL3) { + if (hasOpenSSL(3)) { generateKeyPair('dsa', { modulusLength: 2049, divisorLength: 256, - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (fips3) { + assert.strictEqual(err?.code, 'ERR_OSSL_DSA_BAD_FFC_PARAMETERS'); + return; + } + assert.ifError(err); assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2049); assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2049); })); diff --git a/test/parallel/test-crypto-keygen-dh-classic.js b/test/parallel/test-crypto-keygen-dh-classic.js index 44af7730126a..13c5db2d0603 100644 --- a/test/parallel/test-crypto-keygen-dh-classic.js +++ b/test/parallel/test-crypto-keygen-dh-classic.js @@ -11,11 +11,12 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // Test classic Diffie-Hellman key generation. { generateKeyPair('dh', { - primeLength: 512 + primeLength: hasFIPS(3) ? 2048 : 512 }, common.mustSucceed((publicKey, privateKey) => { assert.strictEqual(publicKey.type, 'public'); assert.strictEqual(publicKey.asymmetricKeyType, 'dh'); diff --git a/test/parallel/test-crypto-keygen-eddsa.js b/test/parallel/test-crypto-keygen-eddsa.js index 0a132235ea28..5dbdf9a7aeea 100644 --- a/test/parallel/test-crypto-keygen-eddsa.js +++ b/test/parallel/test-crypto-keygen-eddsa.js @@ -8,6 +8,8 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); +const rejectsXCurves = hasFIPS(3, 5); // Test EdDSA key generation. { @@ -16,7 +18,12 @@ const { common.printSkipMessage(`Skipping unsupported ${keyType} test case`); continue; } - generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => { + generateKeyPair(keyType, common.mustCall((err, publicKey, privateKey) => { + if (rejectsXCurves && keyType.startsWith('x')) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); assert.strictEqual(publicKey.type, 'public'); assert.strictEqual(publicKey.asymmetricKeyType, keyType); assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {}); diff --git a/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js b/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js index 6c7938f99e1b..ae1ca23c29e8 100644 --- a/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js +++ b/test/parallel/test-crypto-keygen-empty-passphrase-no-error.js @@ -8,11 +8,14 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); + +const fips4 = hasFIPS(4); // Passing an empty passphrase string should not throw ERR_OSSL_CRYPTO_MALLOC_FAILURE even on OpenSSL 3. // Regression test for https://github.com/nodejs/node/issues/41428. generateKeyPair('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, publicKeyEncoding: { type: 'spki', format: 'pem' @@ -23,7 +26,12 @@ generateKeyPair('rsa', { cipher: 'aes-256-cbc', passphrase: '' } -}, common.mustSucceed((publicKey, privateKey) => { +}, common.mustCall((err, publicKey, privateKey) => { + if (fips4) { + assert.strictEqual(err?.code, 'ERR_OSSL_PASSWORD_STRENGTH_TOO_WEAK'); + return; + } + assert.ifError(err); assert.strictEqual(typeof publicKey, 'string'); assert.strictEqual(typeof privateKey, 'string'); })); diff --git a/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js b/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js index cb873ff04748..ccf98bcd3766 100644 --- a/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js +++ b/test/parallel/test-crypto-keygen-empty-passphrase-no-prompt.js @@ -10,23 +10,38 @@ const { generateKeyPair, } = require('crypto'); const { + hasFIPS, testSignVerify, - hasOpenSSL3, + hasOpenSSL, } = require('../common/crypto'); +const fips4 = hasFIPS(4); + // Passing an empty passphrase string should not cause OpenSSL's default // passphrase prompt in the terminal. // See https://github.com/nodejs/node/issues/35898. for (const type of ['pkcs1', 'pkcs8']) { generateKeyPair('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, privateKeyEncoding: { type, format: 'pem', cipher: 'aes-256-cbc', passphrase: '' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (hasFIPS(3) && type === 'pkcs1') { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + if (fips4) { + assert.strictEqual( + err?.code, + 'ERR_OSSL_PASSWORD_STRENGTH_TOO_WEAK', + ); + return; + } + assert.ifError(err); assert.strictEqual(publicKey.type, 'public'); for (const passphrase of ['', Buffer.alloc(0)]) { @@ -41,7 +56,7 @@ for (const type of ['pkcs1', 'pkcs8']) { // the key, and not specifying a passphrase should fail when decoding it. assert.throws(() => { return testSignVerify(publicKey, privateKey); - }, hasOpenSSL3 ? { + }, hasOpenSSL(3) ? { name: 'Error', code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED', message: 'error:07880109:common libcrypto routines::interrupted or cancelled' diff --git a/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js index 9086e2e8a5f1..9d1fdf23bc0b 100644 --- a/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js +++ b/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js @@ -12,6 +12,9 @@ const assert = require('assert'); const { generateKeyPairSync, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); + +const fips3 = hasFIPS(3); // Test invalid parameter encoding. { @@ -25,7 +28,8 @@ const { } }), { name: 'Error', - code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', - message: 'Unsupported JWK Key Type.' + code: fips3 ? 'ERR_OSSL_DSA_BAD_FFC_PARAMETERS' : + 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', + ...!fips3 && { message: 'Unsupported JWK Key Type.' }, }); } diff --git a/test/parallel/test-crypto-keygen-key-object-without-encoding.js b/test/parallel/test-crypto-keygen-key-object-without-encoding.js index abcd282871b6..3c44d174df6f 100644 --- a/test/parallel/test-crypto-keygen-key-object-without-encoding.js +++ b/test/parallel/test-crypto-keygen-key-object-without-encoding.js @@ -9,6 +9,7 @@ const { generateKeyPair, } = require('crypto'); const { + hasFIPS, testEncryptDecrypt, testSignVerify, } = require('../common/crypto'); @@ -17,7 +18,7 @@ const { { // If no publicKeyEncoding is specified, a key object should be returned. generateKeyPair('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, privateKeyEncoding: { type: 'pkcs1', format: 'pem' @@ -36,7 +37,7 @@ const { // If no privateKeyEncoding is specified, a key object should be returned. generateKeyPair('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, publicKeyEncoding: { type: 'pkcs1', format: 'pem' diff --git a/test/parallel/test-crypto-keygen-key-objects.js b/test/parallel/test-crypto-keygen-key-objects.js index a0f1bdf2bcb5..2d22e99360e3 100644 --- a/test/parallel/test-crypto-keygen-key-objects.js +++ b/test/parallel/test-crypto-keygen-key-objects.js @@ -8,18 +8,20 @@ const assert = require('assert'); const { generateKeyPairSync, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // Test sync key generation with key objects. { + const modulusLength = hasFIPS(3) ? 2048 : 512; const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 512 + modulusLength }); assert.strictEqual(typeof publicKey, 'object'); assert.strictEqual(publicKey.type, 'public'); assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, + modulusLength, publicExponent: 65537n }); @@ -27,7 +29,7 @@ const { assert.strictEqual(privateKey.type, 'private'); assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, + modulusLength, publicExponent: 65537n }); } diff --git a/test/parallel/test-crypto-keygen-missing-oid.js b/test/parallel/test-crypto-keygen-missing-oid.js index 1e4f309292eb..afe95dbee40f 100644 --- a/test/parallel/test-crypto-keygen-missing-oid.js +++ b/test/parallel/test-crypto-keygen-missing-oid.js @@ -11,7 +11,7 @@ const { getCurves, } = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); // This test creates EC key pairs on curves without associated OIDs. // Specifying a key encoding should not crash. @@ -22,7 +22,8 @@ const { hasOpenSSL3 } = require('../common/crypto'); continue; const expectedErrorCode = - hasOpenSSL3 ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID'; + hasFIPS(3) ? 'ERR_OSSL_EC_UNKNOWN_GROUP' : + hasOpenSSL(3) ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID'; const params = { namedCurve, publicKeyEncoding: { diff --git a/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js b/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js index 559c6f0af051..778899972863 100644 --- a/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js +++ b/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js @@ -11,15 +11,17 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // 'rsa-pss' should not add a RSASSA-PSS-params sequence by default. // Regression test for: https://github.com/nodejs/node/issues/39936 { + const modulusLength = hasFIPS(3) ? 2048 : 512; generateKeyPair('rsa-pss', { - modulusLength: 512 + modulusLength }, common.mustSucceed((publicKey, privateKey) => { const expectedKeyDetails = { - modulusLength: 512, + modulusLength, publicExponent: 65537n }; assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); @@ -30,6 +32,7 @@ const { // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected // length of 11 bytes (as opposed to > 11 bytes if node added params). const spki = publicKey.export({ format: 'der', type: 'spki' }); - assert.strictEqual(spki[3], 11, spki.toString('hex')); + assert.strictEqual( + spki[3], hasFIPS(3) ? 32 : 11, spki.toString('hex')); })); } diff --git a/test/parallel/test-crypto-keygen-non-standard-public-exponent.js b/test/parallel/test-crypto-keygen-non-standard-public-exponent.js index f54a9e8a6d9b..d6677683880e 100644 --- a/test/parallel/test-crypto-keygen-non-standard-public-exponent.js +++ b/test/parallel/test-crypto-keygen-non-standard-public-exponent.js @@ -8,28 +8,38 @@ const assert = require('assert'); const { generateKeyPairSync, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // Test sync key generation with key objects with a non-standard // publicExponent { - const { publicKey, privateKey } = generateKeyPairSync('rsa', { - publicExponent: 3, - modulusLength: 512 - }); + if (hasFIPS(3)) { + assert.throws(() => generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 2048, + }), { + code: 'ERR_OSSL_RSA_PUB_EXPONENT_OUT_OF_RANGE', + }); + } else { + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + publicExponent: 3, + modulusLength: 512 + }); - assert.strictEqual(typeof publicKey, 'object'); - assert.strictEqual(publicKey.type, 'public'); - assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 3n - }); + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); - assert.strictEqual(typeof privateKey, 'object'); - assert.strictEqual(privateKey.type, 'private'); - assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, - publicExponent: 3n - }); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 3n + }); + } } diff --git a/test/parallel/test-crypto-keygen-promisify.js b/test/parallel/test-crypto-keygen-promisify.js index cd6ca7d6e3e6..158367cef46b 100644 --- a/test/parallel/test-crypto-keygen-promisify.js +++ b/test/parallel/test-crypto-keygen-promisify.js @@ -10,6 +10,7 @@ const { } = require('crypto'); const { assertApproximateSize, + hasFIPS, testEncryptDecrypt, testSignVerify, pkcs1PubExp, @@ -19,9 +20,10 @@ const { promisify } = require('util'); // Test the util.promisified API with async RSA key generation. { + const isFips = hasFIPS(3); promisify(generateKeyPair)('rsa', { publicExponent: 0x10001, - modulusLength: 512, + modulusLength: isFips ? 2048 : 512, publicKeyEncoding: { type: 'pkcs1', format: 'pem' @@ -34,11 +36,11 @@ const { promisify } = require('util'); const { publicKey, privateKey } = keys; assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, pkcs1PubExp); - assertApproximateSize(publicKey, 180); + assertApproximateSize(publicKey, isFips ? 426 : 180); assert.strictEqual(typeof privateKey, 'string'); assert.match(privateKey, pkcs1PrivExp); - assertApproximateSize(privateKey, 512); + assertApproximateSize(privateKey, isFips ? 1675 : 512); testEncryptDecrypt(publicKey, privateKey); testSignVerify(publicKey, privateKey); diff --git a/test/parallel/test-crypto-keygen-raw.js b/test/parallel/test-crypto-keygen-raw.js index e55c3f10eed8..41ef9362dcac 100644 --- a/test/parallel/test-crypto-keygen-raw.js +++ b/test/parallel/test-crypto-keygen-raw.js @@ -11,7 +11,9 @@ const { createPublicKey, createPrivateKey, } = require('crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); + +const rejectsXCurves = hasFIPS(3, 5); // Test generateKeyPairSync with raw encoding for EdDSA/ECDH key types. { @@ -20,10 +22,17 @@ const { hasOpenSSL } = require('../common/crypto'); types.push('ed448', 'x448'); } for (const type of types) { - const { publicKey, privateKey } = generateKeyPairSync(type, { + const options = { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-private' }, - }); + }; + if (rejectsXCurves && type.startsWith('x')) { + assert.throws(() => generateKeyPairSync(type, options), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + continue; + } + const { publicKey, privateKey } = generateKeyPairSync(type, options); assert(Buffer.isBuffer(publicKey)); assert(Buffer.isBuffer(privateKey)); @@ -54,13 +63,20 @@ const { hasOpenSSL } = require('../common/crypto'); types.push('ed448', 'x448'); } for (const type of types) { - generateKeyPair(type, { + const options = { publicKeyEncoding: { format: 'raw-public' }, privateKeyEncoding: { format: 'raw-private' }, - }, common.mustSucceed((publicKey, privateKey) => { - assert(Buffer.isBuffer(publicKey)); - assert(Buffer.isBuffer(privateKey)); - })); + }; + generateKeyPair(type, options, + common.mustCall((err, publicKey, privateKey) => { + if (rejectsXCurves && type.startsWith('x')) { + assert.strictEqual(err?.code, 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + assert.ifError(err); + assert(Buffer.isBuffer(publicKey)); + assert(Buffer.isBuffer(privateKey)); + })); } } diff --git a/test/parallel/test-crypto-keygen-rfc8017-9-1.js b/test/parallel/test-crypto-keygen-rfc8017-9-1.js index fbefb1b4f642..3234084afe10 100644 --- a/test/parallel/test-crypto-keygen-rfc8017-9-1.js +++ b/test/parallel/test-crypto-keygen-rfc8017-9-1.js @@ -11,19 +11,20 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // RFC 8017, 9.1.: "Assuming that the mask generation function is based on a // hash function, it is RECOMMENDED that the hash function be the same as the // one that is applied to the message." { - + const modulusLength = hasFIPS(3) ? 2048 : 512; generateKeyPair('rsa-pss', { - modulusLength: 512, + modulusLength, hashAlgorithm: 'sha256', saltLength: 16 }, common.mustSucceed((publicKey, privateKey) => { const expectedKeyDetails = { - modulusLength: 512, + modulusLength, publicExponent: 65537n, hashAlgorithm: 'sha256', mgf1HashAlgorithm: 'sha256', diff --git a/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js b/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js index bc96d57ed0cd..f7cb70560e7e 100644 --- a/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js +++ b/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js @@ -11,16 +11,18 @@ const assert = require('assert'); const { generateKeyPair, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); // RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of // saltLength is the octet length of the hash value." { + const modulusLength = hasFIPS(3) ? 2048 : 512; generateKeyPair('rsa-pss', { - modulusLength: 512, + modulusLength, hashAlgorithm: 'sha512' }, common.mustSucceed((publicKey, privateKey) => { const expectedKeyDetails = { - modulusLength: 512, + modulusLength, publicExponent: 65537n, hashAlgorithm: 'sha512', mgf1HashAlgorithm: 'sha512', @@ -32,12 +34,12 @@ const { // It is still possible to explicitly set saltLength to 0. generateKeyPair('rsa-pss', { - modulusLength: 512, + modulusLength, hashAlgorithm: 'sha512', saltLength: 0 }, common.mustSucceed((publicKey, privateKey) => { const expectedKeyDetails = { - modulusLength: 512, + modulusLength, publicExponent: 65537n, hashAlgorithm: 'sha512', mgf1HashAlgorithm: 'sha512', diff --git a/test/parallel/test-crypto-keygen-rsa-pss.js b/test/parallel/test-crypto-keygen-rsa-pss.js index 3ce0d40e8d1b..88d522a223b9 100644 --- a/test/parallel/test-crypto-keygen-rsa-pss.js +++ b/test/parallel/test-crypto-keygen-rsa-pss.js @@ -13,14 +13,16 @@ const { generateKeyPair, } = require('crypto'); const { + hasFIPS, testEncryptDecrypt, testSignVerify, } = require('../common/crypto'); // Test RSA-PSS. { + const modulusLength = hasFIPS(3) ? 2048 : 512; generateKeyPair('rsa-pss', { - modulusLength: 512, + modulusLength, saltLength: 16, hashAlgorithm: 'sha256', mgf1HashAlgorithm: 'sha256' @@ -28,7 +30,7 @@ const { assert.strictEqual(publicKey.type, 'public'); assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - modulusLength: 512, + modulusLength, publicExponent: 65537n, hashAlgorithm: 'sha256', mgf1HashAlgorithm: 'sha256', @@ -38,7 +40,7 @@ const { assert.strictEqual(privateKey.type, 'private'); assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - modulusLength: 512, + modulusLength, publicExponent: 65537n, hashAlgorithm: 'sha256', mgf1HashAlgorithm: 'sha256', diff --git a/test/parallel/test-crypto-keygen-sync.js b/test/parallel/test-crypto-keygen-sync.js index a100379e21f1..54a91fcfbcae 100644 --- a/test/parallel/test-crypto-keygen-sync.js +++ b/test/parallel/test-crypto-keygen-sync.js @@ -10,6 +10,7 @@ const { } = require('crypto'); const { assertApproximateSize, + hasFIPS, testEncryptDecrypt, testSignVerify, pkcs1PubExp, @@ -19,9 +20,10 @@ const { // To make the test faster, we will only test sync key generation once and // with a relatively small key. { + const isFips = hasFIPS(3); const ret = generateKeyPairSync('rsa', { - publicExponent: 3, - modulusLength: 512, + publicExponent: isFips ? 0x10001 : 3, + modulusLength: isFips ? 2048 : 512, publicKeyEncoding: { type: 'pkcs1', format: 'pem' @@ -37,10 +39,10 @@ const { assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, pkcs1PubExp); - assertApproximateSize(publicKey, 162); + assertApproximateSize(publicKey, isFips ? 426 : 162); assert.strictEqual(typeof privateKey, 'string'); assert.match(privateKey, pkcs8Exp); - assertApproximateSize(privateKey, 512); + assertApproximateSize(privateKey, isFips ? 1704 : 512); testEncryptDecrypt(publicKey, privateKey); testSignVerify(publicKey, privateKey); diff --git a/test/parallel/test-crypto-keyobject-brand-check.js b/test/parallel/test-crypto-keyobject-brand-check.js index ac0cf1b65f70..ed8e5bdae76e 100644 --- a/test/parallel/test-crypto-keyobject-brand-check.js +++ b/test/parallel/test-crypto-keyobject-brand-check.js @@ -15,6 +15,7 @@ const { generateKeyPairSync, KeyObject, } = require('node:crypto'); +const { hasFIPS } = require('../common/crypto'); const { types: { isKeyObject } } = require('node:util'); const invalidThis = { code: 'ERR_INVALID_THIS', name: 'TypeError' }; @@ -25,7 +26,9 @@ function getter(proto, name) { { const secret = createSecretKey(Buffer.alloc(16)); - const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 1024 }); + const { publicKey } = generateKeyPairSync('rsa', { + modulusLength: hasFIPS(3) ? 2048 : 1024, + }); const type = getter(KeyObject.prototype, 'type'); const symmetricKeySize = diff --git a/test/parallel/test-crypto-keyobject-clone-transfer.js b/test/parallel/test-crypto-keyobject-clone-transfer.js index 1d68e4b9911a..6786df288589 100644 --- a/test/parallel/test-crypto-keyobject-clone-transfer.js +++ b/test/parallel/test-crypto-keyobject-clone-transfer.js @@ -16,6 +16,7 @@ const { sign, verify, } = require('node:crypto'); +const { hasFIPS } = require('../common/crypto'); const { MessageChannel, Worker } = require('node:worker_threads'); const { types: { isKeyObject } } = require('node:util'); @@ -85,7 +86,7 @@ function hmacDigest(key) { (async () => { const secret = createSecretKey(Buffer.alloc(16)); const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, }); for (const key of [secret, publicKey, privateKey]) { diff --git a/test/parallel/test-crypto-keyobject-hidden-slots.js b/test/parallel/test-crypto-keyobject-hidden-slots.js index 1ea243ba0ab8..b3db2068541f 100644 --- a/test/parallel/test-crypto-keyobject-hidden-slots.js +++ b/test/parallel/test-crypto-keyobject-hidden-slots.js @@ -29,6 +29,7 @@ const { X509Certificate, } = require('node:crypto'); const { readFileSync } = require('node:fs'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); function updateFinal(cipher, data = Buffer.alloc(16)) { @@ -110,12 +111,13 @@ function updateFinal(cipher, data = Buffer.alloc(16)) { } { + const modulusLength = hasFIPS(3) ? 2048 : 1024; const { publicKey } = generateKeyPairSync('rsa', { - modulusLength: 1024, + modulusLength, }); const details = publicKey.asymmetricKeyDetails; - assert.strictEqual(details.modulusLength, 1024); + assert.strictEqual(details.modulusLength, modulusLength); assert.strictEqual(details.publicExponent, 65537n); details.modulusLength = 1; @@ -124,7 +126,7 @@ function updateFinal(cipher, data = Buffer.alloc(16)) { const freshDetails = publicKey.asymmetricKeyDetails; assert.notStrictEqual(freshDetails, details); - assert.strictEqual(freshDetails.modulusLength, 1024); + assert.strictEqual(freshDetails.modulusLength, modulusLength); assert.strictEqual(freshDetails.publicExponent, 65537n); assert.strictEqual(freshDetails.extra, undefined); } @@ -150,7 +152,7 @@ function updateFinal(cipher, data = Buffer.alloc(16)) { { const { privateKey, publicKey } = generateKeyPairSync('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, }); const originalType = Object.getOwnPropertyDescriptor(KeyObject.prototype, 'type'); @@ -175,8 +177,10 @@ function updateFinal(cipher, data = Buffer.alloc(16)) { verifier.update(data); assert.strictEqual(verifier.verify(publicKey, streamSignature), true); - const ciphertext = publicEncrypt(publicKey, data); - assert.deepStrictEqual(privateDecrypt(privateKey, ciphertext), data); + const options = hasFIPS(3) ? { oaepHash: 'sha256' } : {}; + const ciphertext = publicEncrypt({ key: publicKey, ...options }, data); + assert.deepStrictEqual( + privateDecrypt({ key: privateKey, ...options }, ciphertext), data); assert.strictEqual(publicKey.equals(createPublicKey(privateKey)), true); diff --git a/test/parallel/test-crypto-keyobject-no-own-symbols.js b/test/parallel/test-crypto-keyobject-no-own-symbols.js index f1539c6a0f7a..945032446784 100644 --- a/test/parallel/test-crypto-keyobject-no-own-symbols.js +++ b/test/parallel/test-crypto-keyobject-no-own-symbols.js @@ -12,6 +12,7 @@ const { createSecretKey, generateKeyPairSync, } = require('node:crypto'); +const { hasFIPS } = require('../common/crypto'); function assertNoOwnKeys(key) { assert.deepStrictEqual(Object.getOwnPropertySymbols(key), []); @@ -22,7 +23,7 @@ function assertNoOwnKeys(key) { { const secret = createSecretKey(Buffer.alloc(16)); const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 1024, + modulusLength: hasFIPS(3) ? 2048 : 1024, }); for (const key of [secret, publicKey, privateKey]) { diff --git a/test/parallel/test-crypto-pbkdf2.js b/test/parallel/test-crypto-pbkdf2.js index 78b73ed6c4e0..7cd1206f4f02 100644 --- a/test/parallel/test-crypto-pbkdf2.js +++ b/test/parallel/test-crypto-pbkdf2.js @@ -5,7 +5,16 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { + hasOpenSSL, + hasFIPS, +} = require('../common/crypto'); + +const fips4 = hasFIPS(4); +const validPassword = fips4 ? 'password' : 'pass'; +const validSalt = fips4 ? '0123456789abcdef' : 'salt'; +const validIterations = fips4 ? 1000 : 8; +const validKeyLength = fips4 ? 16 : 8; function runPBKDF2(password, salt, iterations, keylen, hash) { const syncResult = @@ -19,6 +28,25 @@ function runPBKDF2(password, salt, iterations, keylen, hash) { return syncResult; } +function assertPBKDF2Fails(password, salt, iterations, keylen, hash) { + const expected = { message: 'PBKDF2 derivation failed' }; + assert.throws( + () => crypto.pbkdf2Sync(password, salt, iterations, keylen, hash), + expected, + ); + crypto.pbkdf2( + password, + salt, + iterations, + keylen, + hash, + common.mustCall((err, result) => { + assert.strictEqual(err?.message, expected.message); + assert.strictEqual(result, undefined); + }), + ); +} + function testPBKDF2(password, salt, iterations, keylen, expected, encoding) { const actual = runPBKDF2(password, salt, iterations, keylen, 'sha256'); assert.strictEqual(actual.toString(encoding || 'latin1'), expected); @@ -28,36 +56,55 @@ function testPBKDF2(password, salt, iterations, keylen, expected, encoding) { // Test PBKDF2 with RFC 6070 test vectors (except #4) // -testPBKDF2('password', 'salt', 1, 20, - '\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52' + - '\x56\xc4\xf8\x37\xa8\x65\x48\xc9'); - -testPBKDF2('password', 'salt', 2, 20, - '\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9' + - '\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e'); - -testPBKDF2('password', 'salt', 4096, 20, - '\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6' + - '\x84\x5c\x4c\x8d\x96\x28\x93\xa0'); - -testPBKDF2('passwordPASSWORDpassword', - 'saltSALTsaltSALTsaltSALTsaltSALTsalt', - 4096, - 25, - '\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11' + - '\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c'); - -testPBKDF2('pass\0word', 'sa\0lt', 4096, 16, - '\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26\x65' + - '\x0a\x86\x87'); - -testPBKDF2('password', 'salt', 32, 32, - '64c486c55d30d4c5a079b8823b7d7cb37ff0556f537da8410233bcec330ed956', - 'hex'); +if (fips4) { + testPBKDF2(validPassword, validSalt, validIterations, 32, + '8514638175a45bc45eb1f22f04ff7d27' + + 'f4f8be480498c455ff4b494ce8d1e7d2', + 'hex'); + + for (const args of [ + ['short', validSalt, validIterations], + [validPassword, 'short', validIterations], + [validPassword, validSalt, 999], + ]) { + assertPBKDF2Fails(...args, 32, 'sha256'); + } + assertPBKDF2Fails( + validPassword, validSalt, validIterations, 8, 'sha256'); +} else { + testPBKDF2('password', 'salt', 1, 20, + '\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52' + + '\x56\xc4\xf8\x37\xa8\x65\x48\xc9'); + + testPBKDF2('password', 'salt', 2, 20, + '\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9' + + '\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e'); + + testPBKDF2('password', 'salt', 4096, 20, + '\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6' + + '\x84\x5c\x4c\x8d\x96\x28\x93\xa0'); + + testPBKDF2('passwordPASSWORDpassword', + 'saltSALTsaltSALTsaltSALTsaltSALTsalt', + 4096, + 25, + '\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11' + + '\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c'); + + testPBKDF2('pass\0word', 'sa\0lt', 4096, 16, + '\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26\x65' + + '\x0a\x86\x87'); + + testPBKDF2('password', 'salt', 32, 32, + '64c486c55d30d4c5a079b8823b7d7cb3' + + '7ff0556f537da8410233bcec330ed956', + 'hex'); +} // Error path should not leak memory (check with valgrind). assert.throws( - () => crypto.pbkdf2('password', 'salt', 1, 20, 'sha1'), + () => crypto.pbkdf2( + validPassword, validSalt, validIterations, 20, 'sha1'), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' @@ -66,7 +113,8 @@ assert.throws( for (const iterations of [-1, 0, 2147483648]) { assert.throws( - () => crypto.pbkdf2Sync('password', 'salt', iterations, 20, 'sha1'), + () => crypto.pbkdf2Sync( + validPassword, validSalt, iterations, 20, 'sha1'), { code: 'ERR_OUT_OF_RANGE', name: 'RangeError', @@ -77,7 +125,8 @@ for (const iterations of [-1, 0, 2147483648]) { ['str', null, undefined, [], {}].forEach((notNumber) => { assert.throws( () => { - crypto.pbkdf2Sync('password', 'salt', 1, notNumber, 'sha256'); + crypto.pbkdf2Sync( + validPassword, validSalt, validIterations, notNumber, 'sha256'); }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -89,7 +138,7 @@ for (const iterations of [-1, 0, 2147483648]) { [Infinity, -Infinity, NaN].forEach((input) => { assert.throws( () => { - crypto.pbkdf2('password', 'salt', 1, input, 'sha256', + crypto.pbkdf2(validPassword, validSalt, validIterations, input, 'sha256', common.mustNotCall()); }, { code: 'ERR_OUT_OF_RANGE', @@ -102,7 +151,7 @@ for (const iterations of [-1, 0, 2147483648]) { [-1, 2147483648, 4294967296].forEach((input) => { assert.throws( () => { - crypto.pbkdf2('password', 'salt', 1, input, 'sha256', + crypto.pbkdf2(validPassword, validSalt, validIterations, input, 'sha256', common.mustNotCall()); }, { code: 'ERR_OUT_OF_RANGE', @@ -119,14 +168,16 @@ for (const iterations of [-1, 0, 2147483648]) { let posError; let posResult; try { - posResult = crypto.pbkdf2Sync('password', 'salt', 1, 0, 'sha256'); + posResult = crypto.pbkdf2Sync( + validPassword, validSalt, validIterations, 0, 'sha256'); } catch (err) { posError = err; } let negError; let negResult; try { - negResult = crypto.pbkdf2Sync('password', 'salt', 1, -0, 'sha256'); + negResult = crypto.pbkdf2Sync( + validPassword, validSalt, validIterations, -0, 'sha256'); } catch (err) { negError = err; } @@ -136,15 +187,22 @@ for (const iterations of [-1, 0, 2147483648]) { assert.deepStrictEqual(negResult, posResult); } - crypto.pbkdf2('password', 'salt', 1, -0, 'sha256', common.mustCall()); + crypto.pbkdf2( + validPassword, validSalt, validIterations, -0, 'sha256', + common.mustCall()); } // Should not get FATAL ERROR with empty password and salt // https://github.com/nodejs/node/issues/8571 -crypto.pbkdf2('', '', 1, 32, 'sha256', common.mustSucceed()); +if (fips4) { + assertPBKDF2Fails('', '', 1, 32, 'sha256'); +} else { + crypto.pbkdf2('', '', 1, 32, 'sha256', common.mustSucceed()); +} assert.throws( - () => crypto.pbkdf2('password', 'salt', 8, 8, common.mustNotCall()), + () => crypto.pbkdf2( + validPassword, validSalt, validIterations, 8, common.mustNotCall()), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -153,7 +211,7 @@ assert.throws( }); assert.throws( - () => crypto.pbkdf2Sync('password', 'salt', 8, 8), + () => crypto.pbkdf2Sync(validPassword, validSalt, validIterations, 8), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -162,7 +220,7 @@ assert.throws( }); assert.throws( - () => crypto.pbkdf2Sync('password', 'salt', 8, 8, null), + () => crypto.pbkdf2Sync(validPassword, validSalt, validIterations, 8, null), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -171,7 +229,8 @@ assert.throws( }); [1, {}, [], true, undefined, null].forEach((input) => { assert.throws( - () => crypto.pbkdf2(input, 'salt', 8, 8, 'sha256', common.mustNotCall()), + () => crypto.pbkdf2( + input, validSalt, validIterations, 8, 'sha256', common.mustNotCall()), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -179,7 +238,9 @@ assert.throws( ); assert.throws( - () => crypto.pbkdf2('pass', input, 8, 8, 'sha256', common.mustNotCall()), + () => crypto.pbkdf2( + validPassword, input, validIterations, 8, 'sha256', + common.mustNotCall()), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -187,7 +248,8 @@ assert.throws( ); assert.throws( - () => crypto.pbkdf2Sync(input, 'salt', 8, 8, 'sha256'), + () => crypto.pbkdf2Sync( + input, validSalt, validIterations, 8, 'sha256'), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -195,7 +257,8 @@ assert.throws( ); assert.throws( - () => crypto.pbkdf2Sync('pass', input, 8, 8, 'sha256'), + () => crypto.pbkdf2Sync( + validPassword, input, validIterations, 8, 'sha256'), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -206,7 +269,8 @@ assert.throws( ['test', {}, [], true, undefined, null].forEach((i) => { const received = common.invalidArgTypeHelper(i); assert.throws( - () => crypto.pbkdf2('pass', 'salt', i, 8, 'sha256', common.mustNotCall()), + () => crypto.pbkdf2( + validPassword, validSalt, i, 8, 'sha256', common.mustNotCall()), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -215,7 +279,7 @@ assert.throws( ); assert.throws( - () => crypto.pbkdf2Sync('pass', 'salt', i, 8, 'sha256'), + () => crypto.pbkdf2Sync(validPassword, validSalt, i, 8, 'sha256'), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', @@ -224,15 +288,32 @@ assert.throws( ); }); +if (fips4) { + assertPBKDF2Fails( + new Uint8Array(1), validSalt, validIterations, validKeyLength, 'sha256'); + assertPBKDF2Fails( + validPassword, new Uint8Array(1), validIterations, validKeyLength, + 'sha256'); +} + // Any TypedArray should work for password and salt. for (const SomeArray of [Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array, ArrayBuffer, SharedArrayBuffer]) { - runPBKDF2(new SomeArray(10), 'salt', 8, 8, 'sha256'); - runPBKDF2('pass', new SomeArray(10), 8, 8, 'sha256'); + const length = fips4 ? 16 : 10; + const input = new SomeArray(length); + const bytes = ArrayBuffer.isView(input) ? + new Uint8Array(input.buffer, input.byteOffset, input.byteLength) : + new Uint8Array(input); + for (let index = 0; index < bytes.length; index++) + bytes[index] = index + 1; + runPBKDF2(input, validSalt, validIterations, validKeyLength, 'sha256'); + runPBKDF2(validPassword, input, validIterations, validKeyLength, 'sha256'); } assert.throws( - () => crypto.pbkdf2('pass', 'salt', 8, 8, 'md55', common.mustNotCall()), + () => crypto.pbkdf2( + validPassword, validSalt, validIterations, 8, 'md55', + common.mustNotCall()), { code: 'ERR_CRYPTO_INVALID_DIGEST', name: 'TypeError', @@ -241,7 +322,8 @@ assert.throws( ); assert.throws( - () => crypto.pbkdf2Sync('pass', 'salt', 8, 8, 'md55'), + () => crypto.pbkdf2Sync( + validPassword, validSalt, validIterations, 8, 'md55'), { code: 'ERR_CRYPTO_INVALID_DIGEST', name: 'TypeError', @@ -249,7 +331,7 @@ assert.throws( } ); -if (!hasOpenSSL3) { +if (!hasOpenSSL(3)) { const kNotPBKDF2Supported = ['shake128', 'shake256']; crypto.getHashes() .filter((hash) => !kNotPBKDF2Supported.includes(hash)) @@ -261,7 +343,8 @@ if (!hasOpenSSL3) { { // This should not crash. assert.throws( - () => crypto.pbkdf2Sync('1', '2', 1, 1, '%'), + () => crypto.pbkdf2Sync( + validPassword, validSalt, validIterations, 1, '%'), { code: 'ERR_CRYPTO_INVALID_DIGEST', name: 'TypeError', diff --git a/test/parallel/test-crypto-pqc-encrypted-pkcs8.js b/test/parallel/test-crypto-pqc-encrypted-pkcs8.js index b4a1b586d21d..8f2a10725c94 100644 --- a/test/parallel/test-crypto-pqc-encrypted-pkcs8.js +++ b/test/parallel/test-crypto-pqc-encrypted-pkcs8.js @@ -4,7 +4,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); if (!hasOpenSSL(3, 5) && !process.features.openssl_is_boringssl) common.skip('requires OpenSSL >= 3.5 or BoringSSL'); @@ -30,19 +30,32 @@ if (process.features.openssl_is_boringssl) { // ciphers like RC2, the optional PBKDF2 keyLength INTEGER branch in // the EncryptedPrivateKeyInfo parser. const availableCiphers = new Set(getCiphers()); +const passphrase = 'top secret'; const ciphers = [ 'aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc', 'des-ede3-cbc', 'rc2-cbc', -].filter((c) => availableCiphers.has(c)); +].filter((cipher) => availableCiphers.has(cipher) && + (!hasFIPS(3) || cipher !== 'rc2-cbc')); + +if (hasFIPS(3)) { + const { privateKey } = generateKeyPairSync('ml-dsa-44'); + assert.throws(() => privateKey.export({ + type: 'pkcs8', + format: 'der', + cipher: 'rc2-cbc', + passphrase, + }), { code: 'ERR_OSSL_EVP_UNSUPPORTED' }); +} -const passphrase = 'top secret'; +const wrongPassphrase = 'wrong password'; const wrongPassphraseError = /bad decrypt|DECRYPTION_FAILED|BAD_DECRYPT|bad password|DECODE[ _]ERROR/i; // A wrong passphrase usually fails during cipher finalization, but CBC output // can have valid padding by chance. OpenSSL then parses the bad plaintext as // PKCS#8 and may report ASN.1 or decoder errors from the same failed import. function assertWrongPassphrase(fn) { - assert.throws(fn, (err) => wrongPassphraseError.test(err.message) || + assert.throws(fn, (err) => err.code === 'ERR_OSSL_BAD_DECRYPT' || + wrongPassphraseError.test(err.message) || err.code?.startsWith('ERR_OSSL_ASN1_') || err.code === 'ERR_OSSL_UNSUPPORTED'); } @@ -79,7 +92,7 @@ for (const asymmetricKeyType of algorithms) { key: encrypted, format, type: 'pkcs8', - passphrase: 'wrong', + passphrase: wrongPassphrase, })); } } @@ -128,7 +141,7 @@ for (const { alg, jwkFile, encBase } of fixtureCases) { key: encryptedFixture, format, type: 'pkcs8', - passphrase: 'wrong', + passphrase: wrongPassphrase, })); } } diff --git a/test/parallel/test-crypto-private-decrypt-gh32240.js b/test/parallel/test-crypto-private-decrypt-gh32240.js index 1ff5b565d6d5..a38fcba6775e 100644 --- a/test/parallel/test-crypto-private-decrypt-gh32240.js +++ b/test/parallel/test-crypto-private-decrypt-gh32240.js @@ -14,29 +14,57 @@ const { privateDecrypt, } = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { + hasOpenSSL, + hasFIPS, +} = require('../common/crypto'); -const pair = generateKeyPairSync('rsa', { modulusLength: 512 }); +const fips3 = hasFIPS(3); +const fips4 = hasFIPS(4); +const pair = generateKeyPairSync('rsa', { + modulusLength: fips3 ? 2048 : 512, +}); const expected = Buffer.from('shibboleth'); -const encrypted = publicEncrypt(pair.publicKey, expected); +const options = fips3 ? { oaepHash: 'sha256' } : {}; +const encrypted = publicEncrypt({ key: pair.publicKey, ...options }, expected); const pkey = pair.privateKey.export({ type: 'pkcs1', format: 'pem' }); -const pkeyEncrypted = - pair.privateKey.export({ +if (fips3) { + assert.throws(() => pair.privateKey.export({ type: 'pkcs1', format: 'pem', cipher: 'aes-128-cbc', passphrase: 'secret', + }), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); +} +if (fips4) { + assert.throws(() => pair.privateKey.export({ + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'secret', + }), { + code: 'ERR_OSSL_PASSWORD_STRENGTH_TOO_WEAK', + }); +} +const pkeyEncrypted = + pair.privateKey.export({ + type: fips3 ? 'pkcs8' : 'pkcs1', + format: 'pem', + cipher: fips3 ? 'aes-256-cbc' : 'aes-128-cbc', + passphrase: 'password', }); function decrypt(key) { - const decrypted = privateDecrypt(key, encrypted); + const decrypted = privateDecrypt({ key, ...options }, encrypted); assert.deepStrictEqual(decrypted, expected); } decrypt(pkey); -assert.throws(() => decrypt(pkeyEncrypted), hasOpenSSL3 ? +assert.throws(() => decrypt(pkeyEncrypted), hasOpenSSL(3) ? { message: 'error:07880109:common libcrypto routines::interrupted or ' + 'cancelled' } : { code: 'ERR_MISSING_PASSPHRASE' }); diff --git a/test/parallel/test-crypto-publicDecrypt-fails-first-time.js b/test/parallel/test-crypto-publicDecrypt-fails-first-time.js index 1d64e08920c6..21cc5f3ebce2 100644 --- a/test/parallel/test-crypto-publicDecrypt-fails-first-time.js +++ b/test/parallel/test-crypto-publicDecrypt-fails-first-time.js @@ -7,15 +7,17 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); -if (!hasOpenSSL3) { +if (!hasOpenSSL(3)) { common.skip('only openssl3'); // https://github.com/nodejs/node/pull/42793#issuecomment-1107491901 } const assert = require('assert'); const crypto = require('crypto'); +const passphrase = 'password'; + const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { @@ -26,7 +28,7 @@ const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { type: 'pkcs8', format: 'pem', cipher: 'aes-128-ecb', - passphrase: 'abcdef' + passphrase } }); assert.notStrictEqual(privateKey.toString(), ''); @@ -35,7 +37,7 @@ const msg = 'The quick brown fox jumps over the lazy dog'; const encryptedString = crypto.privateEncrypt({ key: privateKey, - passphrase: 'abcdef' + passphrase }, Buffer.from(msg)).toString('base64'); const decryptedString = crypto.publicDecrypt(publicKey, Buffer.from(encryptedString, 'base64')).toString(); console.log(`Encrypted: ${encryptedString}`); diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js index 35ad67302077..bda98652cf59 100644 --- a/test/parallel/test-crypto-rsa-dsa.js +++ b/test/parallel/test-crypto-rsa-dsa.js @@ -9,7 +9,16 @@ const crypto = require('crypto'); const constants = crypto.constants; const fixtures = require('../common/fixtures'); -const { hasOpenSSL, hasOpenSSL3 } = require('../common/crypto'); +const { + hasOpenSSL, + hasFIPS, +} = require('../common/crypto'); +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); +const fips30 = fips3 && !fips35; +const fips4 = hasFIPS(4); +const fipsDigestErrorCode = 'ERR_OSSL_DIGEST_NOT_ALLOWED'; +const wrongPassphrase = 'wrong-password'; // Test certificates const certPem = fixtures.readKey('rsa_cert.crt'); @@ -17,17 +26,42 @@ const keyPem = fixtures.readKey('rsa_private.pem'); const rsaKeySize = 2048; const rsaPubPem = fixtures.readKey('rsa_public.pem', 'ascii'); const rsaKeyPem = fixtures.readKey('rsa_private.pem', 'ascii'); -const rsaKeyPemEncrypted = fixtures.readKey('rsa_private_encrypted.pem', - 'ascii'); +const rsaKeyPemEncryptedLegacy = fixtures.readKey( + 'rsa_private_encrypted.pem', 'ascii'); +const rsaKeyPemEncrypted = fips3 ? + crypto.createPrivateKey(rsaKeyPem).export({ + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'password', + }) : rsaKeyPemEncryptedLegacy; const dsaPubPem = fixtures.readKey('dsa_public.pem', 'ascii'); const dsaKeyPem = fixtures.readKey('dsa_private.pem', 'ascii'); -const dsaKeyPemEncrypted = fixtures.readKey('dsa_private_encrypted.pem', - 'ascii'); +const dsaKeyPemEncryptedLegacy = fixtures.readKey( + 'dsa_private_encrypted.pem', 'ascii'); +const dsaKeyPemEncrypted = fips3 ? + crypto.createPrivateKey(dsaKeyPem).export({ + type: 'pkcs8', + format: 'pem', + cipher: 'aes-256-cbc', + passphrase: 'password', + }) : dsaKeyPemEncryptedLegacy; const rsaPkcs8KeyPem = fixtures.readKey('rsa_private_pkcs8.pem'); const dsaPkcs8KeyPem = fixtures.readKey('dsa_private_pkcs8.pem'); const ec = new TextEncoder(); +if (fips3) { + for (const key of [rsaKeyPemEncryptedLegacy, dsaKeyPemEncryptedLegacy]) { + assert.throws(() => crypto.createPrivateKey({ + key, + passphrase: 'password', + }), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + } +} + const openssl1DecryptError = { message: 'error:06065064:digital envelope routines:EVP_DecryptFinal_ex:' + 'bad decrypt', @@ -37,18 +71,21 @@ const openssl1DecryptError = { library: 'digital envelope routines', }; -const decryptError = hasOpenSSL3 ? - { message: 'error:1C800064:Provider routines::bad decrypt' } : - process.features.openssl_is_boringssl ? { - message: 'error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT', - code: 'ERR_OSSL_BAD_DECRYPT', - reason: 'BAD_DECRYPT', - function: 'OPENSSL_internal', - library: 'Cipher functions', - } : - openssl1DecryptError; - -const decryptPrivateKeyError = hasOpenSSL3 ? { +const decryptError = fips4 ? + { code: 'ERR_OSSL_BAD_DECRYPT' } : hasOpenSSL(3) ? + { message: 'error:1C800064:Provider routines::bad decrypt' } : + process.features.openssl_is_boringssl ? { + message: 'error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT', + code: 'ERR_OSSL_BAD_DECRYPT', + reason: 'BAD_DECRYPT', + function: 'OPENSSL_internal', + library: 'Cipher functions', + } : + openssl1DecryptError; + +const decryptPrivateKeyError = fips4 ? { + code: 'ERR_OSSL_BAD_DECRYPT', +} : hasOpenSSL(3) ? { message: 'error:1C800064:Provider routines::bad decrypt', } : process.features.openssl_is_boringssl ? { message: 'error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT', @@ -156,7 +193,7 @@ function getBufferCopy(buf) { // Now with RSA_NO_PADDING. Plaintext needs to match key size. // OpenSSL 3.x has a rsa_check_padding that will cause an error if // RSA_NO_PADDING is used. - if (!hasOpenSSL3) { + if (!hasOpenSSL(3)) { { const plaintext = 'x'.repeat(rsaKeySize / 8); encryptedBuffer = crypto.privateEncrypt({ @@ -192,14 +229,14 @@ function getBufferCopy(buf) { assert.throws(() => { crypto.privateDecrypt({ key: rsaKeyPemEncrypted, - passphrase: 'wrong' + passphrase: wrongPassphrase }, bufferToEncrypt); }, decryptError); assert.throws(() => { crypto.publicEncrypt({ key: rsaKeyPemEncrypted, - passphrase: 'wrong' + passphrase: wrongPassphrase }, encryptedBuffer); }, decryptError); @@ -211,7 +248,7 @@ function getBufferCopy(buf) { assert.throws(() => { crypto.publicDecrypt({ key: rsaKeyPemEncrypted, - passphrase: Buffer.from('wrong') + passphrase: Buffer.from(wrongPassphrase) }, encryptedBuffer); }, decryptError); } @@ -349,8 +386,12 @@ test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha256'); test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha512', 'sha512'); assert.throws(() => { test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha512'); -}, { - code: 'ERR_OSSL_RSA_OAEP_DECODING_ERROR' +}, fips35 ? { + code: 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE', +} : fips3 ? { + message: 'error:00000000:lib(0)::reason(0)', +} : { + code: 'ERR_OSSL_RSA_OAEP_DECODING_ERROR', }); // The following RSA-OAEP test cases were created using the WebCrypto API to @@ -416,8 +457,9 @@ for (const fn of [crypto.publicEncrypt, crypto.privateDecrypt]) { } // Test RSA key signing/verification -let rsaSign = crypto.createSign('SHA1'); -let rsaVerify = crypto.createVerify('SHA1'); +const rsaDigest = fips3 ? 'SHA256' : 'SHA1'; +let rsaSign = crypto.createSign(rsaDigest); +let rsaVerify = crypto.createVerify(rsaDigest); assert.ok(rsaSign); assert.ok(rsaVerify); @@ -428,36 +470,39 @@ const expectedSignature = fixtures.readKey( rsaSign.update(rsaPubPem); let rsaSignature = rsaSign.sign(rsaKeyPem, 'hex'); -assert.strictEqual(rsaSignature, expectedSignature); +if (!fips3) + assert.strictEqual(rsaSignature, expectedSignature); rsaVerify.update(rsaPubPem); assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); // Test RSA PKCS#8 key signing/verification -rsaSign = crypto.createSign('SHA1'); +rsaSign = crypto.createSign(rsaDigest); rsaSign.update(rsaPubPem); rsaSignature = rsaSign.sign(rsaPkcs8KeyPem, 'hex'); -assert.strictEqual(rsaSignature, expectedSignature); +if (!fips3) + assert.strictEqual(rsaSignature, expectedSignature); -rsaVerify = crypto.createVerify('SHA1'); +rsaVerify = crypto.createVerify(rsaDigest); rsaVerify.update(rsaPubPem); assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); // Test RSA key signing/verification with encrypted key -rsaSign = crypto.createSign('SHA1'); +rsaSign = crypto.createSign(rsaDigest); rsaSign.update(rsaPubPem); const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'password' }; rsaSignature = rsaSign.sign(signOptions, 'hex'); -assert.strictEqual(rsaSignature, expectedSignature); +if (!fips3) + assert.strictEqual(rsaSignature, expectedSignature); -rsaVerify = crypto.createVerify('SHA1'); +rsaVerify = crypto.createVerify(rsaDigest); rsaVerify.update(rsaPubPem); assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); -rsaSign = crypto.createSign('SHA1'); +rsaSign = crypto.createSign(rsaDigest); rsaSign.update(rsaPubPem); assert.throws(() => { - const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'wrong' }; + const signOptions = { key: rsaKeyPemEncrypted, passphrase: wrongPassphrase }; rsaSign.sign(signOptions, 'hex'); }, decryptPrivateKeyError); @@ -508,11 +553,12 @@ if (!process.features.openssl_is_boringssl) { // DSA signatures vary across runs so there is no static string to verify // against. - const sign = crypto.createSign('SHA1'); + const dsaDigest = fips3 ? 'SHA256' : 'SHA1'; + const sign = crypto.createSign(dsaDigest); sign.update(input); const signature = sign.sign(dsaKeyPem, 'hex'); - const verify = crypto.createVerify('SHA1'); + const verify = crypto.createVerify(dsaDigest); verify.update(input); assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); @@ -520,12 +566,18 @@ if (!process.features.openssl_is_boringssl) { // Test the legacy 'DSS1' name. const sign2 = crypto.createSign('DSS1'); sign2.update(input); - const signature2 = sign2.sign(dsaKeyPem, 'hex'); + if (fips30) { + assert.throws(() => sign2.sign(dsaKeyPem, 'hex'), { + code: fipsDigestErrorCode, + }); + } else { + const signature2 = sign2.sign(dsaKeyPem, 'hex'); - const verify2 = crypto.createVerify('DSS1'); - verify2.update(input); + const verify2 = crypto.createVerify('DSS1'); + verify2.update(input); - assert.strictEqual(verify2.verify(dsaPubPem, signature2, 'hex'), true); + assert.strictEqual(verify2.verify(dsaPubPem, signature2, 'hex'), true); + } } else { common.printSkipMessage('Skipping unsupported DSA test case'); } @@ -539,11 +591,12 @@ if (!process.features.openssl_is_boringssl) { // DSA signatures vary across runs so there is no static string to verify // against. - const sign = crypto.createSign('SHA1'); + const dsaDigest = fips3 ? 'SHA256' : 'SHA1'; + const sign = crypto.createSign(dsaDigest); sign.update(input); const signature = sign.sign(dsaPkcs8KeyPem, 'hex'); - const verify = crypto.createVerify('SHA1'); + const verify = crypto.createVerify(dsaDigest); verify.update(input); assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); @@ -558,22 +611,23 @@ if (!process.features.openssl_is_boringssl) { const input = 'I AM THE WALRUS'; { - const sign = crypto.createSign('SHA1'); + const sign = crypto.createSign(fips3 ? 'SHA256' : 'SHA1'); sign.update(input); assert.throws(() => { - sign.sign({ key: dsaKeyPemEncrypted, passphrase: 'wrong' }, 'hex'); + sign.sign({ key: dsaKeyPemEncrypted, passphrase: wrongPassphrase }, 'hex'); }, decryptPrivateKeyError); } if (!process.features.openssl_is_boringssl) { // DSA signatures vary across runs so there is no static string to verify // against. - const sign = crypto.createSign('SHA1'); + const dsaDigest = fips3 ? 'SHA256' : 'SHA1'; + const sign = crypto.createSign(dsaDigest); sign.update(input); const signOptions = { key: dsaKeyPemEncrypted, passphrase: 'password' }; const signature = sign.sign(signOptions, 'hex'); - const verify = crypto.createVerify('SHA1'); + const verify = crypto.createVerify(dsaDigest); verify.update(input); assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); diff --git a/test/parallel/test-crypto-scrypt.js b/test/parallel/test-crypto-scrypt.js index 421ee4ce8f34..3012a8fa55d5 100644 --- a/test/parallel/test-crypto-scrypt.js +++ b/test/parallel/test-crypto-scrypt.js @@ -5,6 +5,8 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); +const isFips = hasFIPS(3); if (typeof crypto.scrypt !== 'function' || typeof crypto.scryptSync !== 'function') common.skip('no scrypt support'); @@ -159,13 +161,20 @@ const badargs = [ }, ]; -for (const options of good) { - const { pass, salt, keylen, expected } = options; - const actual = crypto.scryptSync(pass, salt, keylen, options); - assert.strictEqual(actual.toString('hex'), expected); - crypto.scrypt(pass, salt, keylen, options, common.mustSucceed((actual) => { +if (isFips) { + const expected = { code: 'ERR_CRYPTO_INVALID_SCRYPT_PARAMS' }; + assert.throws(() => crypto.scryptSync('pass', 'salt', 1), expected); + assert.throws( + () => crypto.scrypt('pass', 'salt', 1, () => {}), expected); +} else { + for (const options of good) { + const { pass, salt, keylen, expected } = options; + const actual = crypto.scryptSync(pass, salt, keylen, options); assert.strictEqual(actual.toString('hex'), expected); - })); + crypto.scrypt(pass, salt, keylen, options, common.mustSucceed((actual) => { + assert.strictEqual(actual.toString('hex'), expected); + })); + } } for (const options of bad) { @@ -191,7 +200,9 @@ for (const options of incompatibleOptions) { } for (const options of toobig) { - const expected = { + const expected = isFips ? { + code: 'ERR_CRYPTO_INVALID_SCRYPT_PARAMS', + } : { message: process.features.openssl_is_boringssl ? /Invalid scrypt params:.*(INVALID_PARAMETERS|MEMORY_LIMIT_EXCEEDED)/ : /Invalid scrypt params:.*memory limit exceeded/, @@ -203,7 +214,7 @@ for (const options of toobig) { expected); } -{ +if (!isFips) { const defaults = { N: 16384, p: 1, r: 8 }; const expected = crypto.scryptSync('pass', 'salt', 1, defaults); const actual = crypto.scryptSync('pass', 'salt', 1); @@ -229,10 +240,12 @@ for (const { args, expected } of badargs) { { // Values for maxmem that do not fit in 32 bits but that are still safe // integers should be allowed. - crypto.scrypt('', '', 4, { maxmem: 2 ** 52 }, - common.mustSucceed((actual) => { - assert.strictEqual(actual.toString('hex'), 'd72c87d0'); - })); + if (!isFips) { + crypto.scrypt('', '', 4, { maxmem: 2 ** 52 }, + common.mustSucceed((actual) => { + assert.strictEqual(actual.toString('hex'), 'd72c87d0'); + })); + } // Values that exceed Number.isSafeInteger should not be allowed. assert.throws(() => crypto.scryptSync('', '', 0, { maxmem: 2 ** 53 }), { @@ -240,7 +253,7 @@ for (const { args, expected } of badargs) { }); } -{ +if (!isFips) { // Regression test for https://github.com/nodejs/node/issues/28836. function testParameter(name, value) { @@ -299,5 +312,11 @@ for (const { args, expected } of badargs) { assert.deepStrictEqual(negResult, posResult); } - crypto.scrypt('', '', -0, common.mustCall()); + if (isFips) { + assert.throws( + () => crypto.scrypt('', '', -0, () => {}), + { code: 'ERR_CRYPTO_INVALID_SCRYPT_PARAMS' }); + } else { + crypto.scrypt('', '', -0, common.mustCall()); + } } diff --git a/test/parallel/test-crypto-secure-heap.js b/test/parallel/test-crypto-secure-heap.js index 3845f49a4748..8bd93c5281da 100644 --- a/test/parallel/test-crypto-secure-heap.js +++ b/test/parallel/test-crypto-secure-heap.js @@ -20,7 +20,7 @@ if (process.features.openssl_is_boringssl) { const assert = require('assert'); const { fork } = require('child_process'); const fixtures = require('../common/fixtures'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const { secureHeapUsed, createDiffieHellman, @@ -38,7 +38,8 @@ if (process.argv[2] === 'child') { assert.strictEqual(a.used, 0); { - const size = getFips() || hasOpenSSL3 ? 1024 : 256; + const size = hasFIPS(3) ? + 2048 : (getFips() === 1 || hasOpenSSL(3) ? 1024 : 256); const dh1 = createDiffieHellman(size); const p1 = dh1.getPrime('buffer'); const dh2 = createDiffieHellman(p1, 'buffer'); diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index 527c39b30786..e8398c21bafd 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -10,6 +10,7 @@ const crypto = require('crypto'); const fixtures = require('../common/fixtures'); const { hasOpenSSL, + hasFIPS, opensslCli, } = require('../common/crypto'); @@ -17,6 +18,17 @@ const { const certPem = fixtures.readKey('rsa_cert.crt'); const keyPem = fixtures.readKey('rsa_private.pem'); const keySize = 2048; +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); +const fips30 = fips3 && !fips35; +const fipsDigestErrorCode = 'ERR_OSSL_DIGEST_NOT_ALLOWED'; +const signingHash = fips3 ? 'SHA256' : 'SHA1'; + +if (fips30) { + assert.throws( + () => crypto.createSign('SHA1').update('Test123').sign(keyPem), + { code: fipsDigestErrorCode }); +} { const Sign = crypto.Sign; @@ -60,7 +72,7 @@ const keySize = 2048; Object.defineProperty(Object.prototype, 'opensslErrorStack', errorStack); assert.throws(() => { - crypto.createSign('SHA1') + crypto.createSign('SHA256') .update('Test123') .sign({ key: keyPem, @@ -99,15 +111,15 @@ assert.throws( // Test signing and verifying { - const s1 = crypto.createSign('SHA1') + const s1 = crypto.createSign(signingHash) .update('Test123') .sign(keyPem, 'base64'); - let s1stream = crypto.createSign('SHA1'); + let s1stream = crypto.createSign(signingHash); s1stream.end('Test123'); s1stream = s1stream.sign(keyPem, 'base64'); assert.strictEqual(s1, s1stream, `${s1} should equal ${s1stream}`); - const verified = crypto.createVerify('SHA1') + const verified = crypto.createVerify(signingHash) .update('Test') .update('123') .verify(certPem, s1, 'base64'); @@ -138,16 +150,16 @@ assert.throws( } { - const s3 = crypto.createSign('SHA1') + const s3 = crypto.createSign(signingHash) .update('Test123') .sign(keyPem, 'buffer'); - let verified = crypto.createVerify('SHA1') + let verified = crypto.createVerify(signingHash) .update('Test') .update('123') .verify(certPem, s3); assert.strictEqual(verified, true); - const verStream = crypto.createVerify('SHA1'); + const verStream = crypto.createVerify(signingHash); verStream.write('Tes'); verStream.write('t12'); verStream.end('3'); @@ -190,6 +202,17 @@ assert.throws( const data = Buffer.from('Test123'); + if (fips30) { + const streamOptions = { + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST, + }; + assert.throws( + () => crypto.createSign(algo).update(data).sign(streamOptions), + { code: fipsDigestErrorCode }); + } + signSaltLengths.forEach((signSaltLength) => { if (signSaltLength > max) { // If the salt length is too big, an Error should be thrown @@ -211,20 +234,23 @@ assert.throws( }, errMessage); } else { // Otherwise, a valid signature should be generated - const s4 = crypto.createSign(algo) - .update(data) - .sign({ - key: keyPem, - padding: crypto.constants.RSA_PKCS1_PSS_PADDING, - saltLength: signSaltLength - }); const s4_2 = crypto.sign(algo, data, { key: keyPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: signSaltLength }); + const signatures = [s4_2]; + if (!fips30) { + signatures.unshift(crypto.createSign(algo) + .update(data) + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + })); + } - [s4, s4_2].forEach((sig) => { + signatures.forEach((sig) => { let verified; verifySaltLengths.forEach((verifySaltLength) => { // Verification should succeed if and only if the salt length is @@ -281,7 +307,8 @@ assert.throws( }); } - testPSS('SHA1', 20); + if (!fips30) + testPSS('SHA1', 20); testPSS('SHA256', 32); } @@ -340,7 +367,7 @@ assert.throws( }); assert.throws(() => { - crypto.createSign('SHA1') + crypto.createSign('SHA256') .update('Test123') .sign({ key: keyPem, @@ -365,7 +392,7 @@ assert.throws( // Test throws exception when key options is null { assert.throws(() => { - crypto.createSign('SHA1').update('Test123').sign(null, 'base64'); + crypto.createSign('SHA256').update('Test123').sign(null, 'base64'); }, { code: 'ERR_CRYPTO_SIGN_KEY_REQUIRED', name: 'Error' @@ -373,8 +400,8 @@ assert.throws( } { - const sign = crypto.createSign('SHA1'); - const verify = crypto.createVerify('SHA1'); + const sign = crypto.createSign('SHA256'); + const verify = crypto.createVerify('SHA256'); [1, [], {}, undefined, null, true, Infinity].forEach((input) => { const errObj = { @@ -441,7 +468,7 @@ for (const pair of [ { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'), public: fixtures.readKey('rsa_public_2048.pem', 'ascii'), skip: false, - algo: 'sha1', + algo: signingHash, sigLen: 256, raw: false }, ]) { @@ -450,6 +477,7 @@ for (const pair of [ continue; } const algo = pair.algo; + const keyType = crypto.createPrivateKey(pair.private).asymmetricKeyType; { const data = Buffer.from('Hello world'); @@ -521,15 +549,34 @@ for (const pair of [ const sig = crypto.sign(algo, data, { key: pair.private, context }); assert.strictEqual(crypto.verify(algo, data, { key: pair.public }, sig), true); assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context }, sig), true); - assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context: crypto.randomBytes(30) }, sig), false); + const mismatchedContext = { key: pair.public, context: crypto.randomBytes(30) }; + if (fips35 && keyType === 'ed25519') { + assert.throws(() => crypto.verify(algo, data, mismatchedContext, sig), { + code: 'ERR_OSSL_INVALID_EDDSA_INSTANCE_FOR_ATTEMPTED_OPERATION', + }); + } else { + assert.strictEqual( + crypto.verify(algo, data, mismatchedContext, sig), false); + } } { const context = new Uint8Array(32); - const sig = crypto.sign(algo, data, { key: pair.private, context }); - assert.strictEqual(crypto.verify(algo, data, { key: pair.public }, sig), false); - assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context }, sig), true); - assert.strictEqual(crypto.verify(algo, data, { key: pair.public, context: crypto.randomBytes(30) }, sig), false); + if (fips35 && keyType === 'ed25519') { + assert.throws( + () => crypto.sign(algo, data, { key: pair.private, context }), + { code: 'ERR_OSSL_INVALID_EDDSA_INSTANCE_FOR_ATTEMPTED_OPERATION' }); + } else { + const sig = crypto.sign(algo, data, { key: pair.private, context }); + assert.strictEqual( + crypto.verify(algo, data, { key: pair.public }, sig), false); + assert.strictEqual( + crypto.verify(algo, data, { key: pair.public, context }, sig), true); + assert.strictEqual(crypto.verify(algo, data, { + key: pair.public, + context: crypto.randomBytes(30), + }, sig), false); + } } assert.throws(() => crypto.sign(algo, data, { key: pair.private, context: new Uint8Array(256) }), { @@ -572,20 +619,27 @@ if (hasOpenSSL(3, 2)) { { const context = Buffer.from('my context'); - const sig = crypto.sign(null, data, { key: privKey, context }); - assert.strictEqual(sig.length, 64); + if (fips35) { + assert.throws(() => crypto.sign(null, data, { key: privKey, context }), { + code: 'ERR_OSSL_INVALID_EDDSA_INSTANCE_FOR_ATTEMPTED_OPERATION', + }); + } else { + const sig = crypto.sign(null, data, { key: privKey, context }); + assert.strictEqual(sig.length, 64); - // Verify with matching context succeeds - assert.strictEqual(crypto.verify(null, data, { key: pubKey, context }, sig), true); + // Verify with matching context succeeds + assert.strictEqual( + crypto.verify(null, data, { key: pubKey, context }, sig), true); - // Verify without context fails (Ed25519ctx !== Ed25519 pure) - assert.strictEqual(crypto.verify(null, data, { key: pubKey }, sig), false); + // Verify without context fails (Ed25519ctx !== Ed25519 pure) + assert.strictEqual(crypto.verify(null, data, { key: pubKey }, sig), false); - // Verify with wrong context fails - assert.strictEqual(crypto.verify(null, data, { - key: pubKey, - context: Buffer.from('wrong'), - }, sig), false); + // Verify with wrong context fails + assert.strictEqual(crypto.verify(null, data, { + key: pubKey, + context: Buffer.from('wrong'), + }, sig), false); + } } { @@ -662,7 +716,9 @@ MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1fiOx1BhdoAvpolZdyX46aGWlNoa { const data = Buffer.from('Hello world'); - const keys = [['ec-key.pem', 64], ['dsa_private_1025.pem', 40]]; + const dsaKey = fips3 ? + ['dsa_private.pem', 64] : ['dsa_private_1025.pem', 40]; + const keys = [['ec-key.pem', 64], dsaKey]; for (const [file, length] of keys) { if (process.features.openssl_is_boringssl && file.startsWith('dsa_')) { @@ -670,28 +726,29 @@ MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1fiOx1BhdoAvpolZdyX46aGWlNoa continue; } const privKey = fixtures.readKey(file); + const digest = fips3 ? 'sha256' : 'sha1'; [ - crypto.createSign('sha1').update(data).sign(privKey), - crypto.sign('sha1', data, privKey), - crypto.sign('sha1', data, { key: privKey, dsaEncoding: 'der' }), + crypto.createSign(digest).update(data).sign(privKey), + crypto.sign(digest, data, privKey), + crypto.sign(digest, data, { key: privKey, dsaEncoding: 'der' }), ].forEach((sig) => { // Signature length variability due to DER encoding assert(sig.length >= length + 4 && sig.length <= length + 8); assert.strictEqual( - crypto.createVerify('sha1').update(data).verify(privKey, sig), + crypto.createVerify(digest).update(data).verify(privKey, sig), true ); - assert.strictEqual(crypto.verify('sha1', data, privKey, sig), true); + assert.strictEqual(crypto.verify(digest, data, privKey, sig), true); }); // Test (EC)DSA signature conversion. const opts = { key: privKey, dsaEncoding: 'ieee-p1363' }; - let sig = crypto.sign('sha1', data, opts); + let sig = crypto.sign(digest, data, opts); // Unlike DER signatures, IEEE P1363 signatures have a predictable length. assert.strictEqual(sig.length, length); - assert.strictEqual(crypto.verify('sha1', data, opts, sig), true); - assert.strictEqual(crypto.createVerify('sha1') + assert.strictEqual(crypto.verify(digest, data, opts, sig), true); + assert.strictEqual(crypto.createVerify(digest) .update(data) .verify(opts, sig), true); @@ -700,7 +757,7 @@ MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1fiOx1BhdoAvpolZdyX46aGWlNoa sig = crypto.randomBytes(length + i); let result; try { - result = crypto.verify('sha1', data, opts, sig); + result = crypto.verify(digest, data, opts, sig); } catch (err) { assert.match(err.message, /asn1 encoding/); assert.strictEqual(err.library, 'asn1 encoding routines'); @@ -735,20 +792,20 @@ MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1fiOx1BhdoAvpolZdyX46aGWlNoa } // Non-(EC)DSA keys should ignore the option. - const sig = crypto.sign('sha1', data, { + const sig = crypto.sign(signingHash, data, { key: keyPem, dsaEncoding: 'ieee-p1363' }); - assert.strictEqual(crypto.verify('sha1', data, certPem, sig), true); + assert.strictEqual(crypto.verify(signingHash, data, certPem, sig), true); assert.strictEqual( - crypto.verify('sha1', data, { + crypto.verify(signingHash, data, { key: certPem, dsaEncoding: 'ieee-p1363' }, sig), true ); assert.strictEqual( - crypto.verify('sha1', data, { + crypto.verify(signingHash, data, { key: certPem, dsaEncoding: 'der' }, sig), @@ -757,7 +814,7 @@ MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1fiOx1BhdoAvpolZdyX46aGWlNoa for (const dsaEncoding of ['foo', null, {}, 5, true, NaN]) { assert.throws(() => { - crypto.sign('sha1', data, { + crypto.sign(signingHash, data, { key: certPem, dsaEncoding }); @@ -776,12 +833,13 @@ if (!opensslCli) { const privkey = fixtures.readKey('rsa_private_2048.pem'); const msg = 'Test123'; - const s5 = crypto.createSign('SHA256') - .update(msg) - .sign({ - key: privkey, - padding: crypto.constants.RSA_PKCS1_PSS_PADDING - }); + const options = { + key: privkey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + }; + const s5 = fips30 ? + crypto.sign('SHA256', Buffer.from(msg), options) : + crypto.createSign('SHA256').update(msg).sign(options); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); @@ -811,8 +869,14 @@ if (!process.features.openssl_is_boringssl) { const privateKey = crypto.createPrivateKey(privatePem); for (const key of [privatePem, privateKey]) { - // Any algorithm should work. - for (const algo of ['sha1', 'sha256']) { + if (fips30) { + assert.throws(() => crypto.sign('sha1', 'foo', key), { + code: fipsDigestErrorCode, + }); + } + // Any algorithm should work unless SHA-1 signing is unavailable. + const algorithms = fips30 ? ['sha256'] : ['sha1', 'sha256']; + for (const algo of algorithms) { // Any salt length should work. for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { const signature = crypto.sign(algo, 'foo', { key, saltLength }); @@ -847,7 +911,9 @@ if (!process.features.openssl_is_boringssl) { // Signing with anything other than sha256 should fail. assert.throws(() => { crypto.sign('sha1', 'foo', key); - }, /digest not allowed/); + }, fips30 ? { + code: fipsDigestErrorCode, + } : /digest not allowed/); // Signing with salt lengths less than 16 bytes should fail. for (const saltLength of [8, 10, 12]) { @@ -895,7 +961,9 @@ if (!process.features.openssl_is_boringssl) { for (const algo of ['sha1', 'sha256']) { assert.throws(() => { crypto.sign(algo, 'foo', key); - }, /digest not allowed/); + }, fips30 && algo === 'sha1' ? { + code: fipsDigestErrorCode, + } : /digest not allowed/); } // sha512 should produce a valid signature. @@ -915,15 +983,21 @@ if (!process.features.openssl_is_boringssl) { // The sign function should not swallow OpenSSL errors. // Regression test for https://github.com/nodejs/node/issues/40794. { - assert.throws(() => { - const { privateKey } = crypto.generateKeyPairSync('rsa', { - modulusLength: 512 + if (fips3) { + assert.throws(() => crypto.generateKeyPairSync('rsa', { + modulusLength: 512, + }), { code: 'ERR_OSSL_RSA_INVALID_MODULUS' }); + } else { + assert.throws(() => { + const { privateKey } = crypto.generateKeyPairSync('rsa', { + modulusLength: 512 + }); + crypto.sign('sha512', 'message', privateKey); + }, { + code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY', + message: /digest too big for rsa key|DIGEST_TOO_BIG_FOR_RSA_KEY/ }); - crypto.sign('sha512', 'message', privateKey); - }, { - code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY', - message: /digest too big for rsa key|DIGEST_TOO_BIG_FOR_RSA_KEY/ - }); + } } { diff --git a/test/parallel/test-crypto-worker-thread.js b/test/parallel/test-crypto-worker-thread.js index d9030d5cfc11..42027f3f2b44 100644 --- a/test/parallel/test-crypto-worker-thread.js +++ b/test/parallel/test-crypto-worker-thread.js @@ -9,6 +9,7 @@ const { generateKeySync, generateKeyPairSync, } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; const assert = require('assert'); @@ -19,7 +20,7 @@ if (isMainThread) { (async () => { const secretKey = generateKeySync('aes', { length: 128 }); const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 1024 + modulusLength: hasFIPS(3) ? 2048 : 1024 }); const cryptoKey = await subtle.generateKey( { name: 'AES-CBC', length: 128 }, false, ['encrypt']); diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index 1217ca37b03a..047f051d1e94 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -29,7 +29,8 @@ const assert = require('assert'); const crypto = require('crypto'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); +const isFips = hasFIPS(3); // Test Certificates const certPfx = fixtures.readKey('rsa_cert.pfx'); @@ -54,27 +55,35 @@ assert.throws(() => { }); // PFX tests -tls.createSecureContext({ pfx: certPfx, passphrase: 'sample' }); - -assert.throws(() => { - tls.createSecureContext({ pfx: certPfx }); -}, (err) => { - // Throws general Error, so there is no opensslErrorStack property. - return err instanceof Error && - err.name === 'Error' && - /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && - !('opensslErrorStack' in err); -}); +if (isFips) { + for (const passphrase of ['sample', undefined, 'test']) { + assert.throws( + () => tls.createSecureContext({ pfx: certPfx, passphrase }), + { code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION' }); + } +} else { + tls.createSecureContext({ pfx: certPfx, passphrase: 'sample' }); + + assert.throws(() => { + tls.createSecureContext({ pfx: certPfx }); + }, (err) => { + // Throws general Error, so there is no opensslErrorStack property. + return err instanceof Error && + err.name === 'Error' && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && + !('opensslErrorStack' in err); + }); -assert.throws(() => { - tls.createSecureContext({ pfx: certPfx, passphrase: 'test' }); -}, (err) => { - // Throws general Error, so there is no opensslErrorStack property. - return err instanceof Error && - err.name === 'Error' && - /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && - !('opensslErrorStack' in err); -}); + assert.throws(() => { + tls.createSecureContext({ pfx: certPfx, passphrase: 'test' }); + }, (err) => { + // Throws general Error, so there is no opensslErrorStack property. + return err instanceof Error && + err.name === 'Error' && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && + !('opensslErrorStack' in err); + }); +} assert.throws(() => { tls.createSecureContext({ pfx: 'sample', passphrase: 'test' }); @@ -197,7 +206,7 @@ assert.throws( ); assert.throws( - () => crypto.createHmac('sha256', 'a secret').update('0', 'hex'), + () => crypto.createHmac('sha256', '0123456789abcdef').update('0', 'hex'), (error) => { assert.ok(!('opensslErrorStack' in error)); assert.throws(() => { throw error; }, encodingError); @@ -217,7 +226,11 @@ assert.throws(() => { ].join('\n'); crypto.createSign('SHA256').update('test').sign(priv); }, (err) => { - if (process.features.openssl_is_boringssl) { + if (isFips) { + assert.throws(() => { throw err; }, { + code: 'ERR_OSSL_INVALID_KEY_LENGTH', + }); + } else if (process.features.openssl_is_boringssl) { // BoringSSL rejects the tiny RSA key while decoding it, before signing. assert.throws(() => { throw err; }, { name: 'Error', @@ -231,9 +244,9 @@ assert.throws(() => { assert(Array.isArray(err.opensslErrorStack)); assert(err.opensslErrorStack.length > 0); } else { - if (!hasOpenSSL3) + if (!hasOpenSSL(3)) assert.ok(!('opensslErrorStack' in err)); - assert.throws(() => { throw err; }, hasOpenSSL3 ? { + assert.throws(() => { throw err; }, hasOpenSSL(3) ? { name: 'Error', message: 'error:02000070:rsa routines::digest too big for rsa key', library: 'rsa routines', @@ -249,7 +262,7 @@ assert.throws(() => { return true; }); -if (!hasOpenSSL3) { +if (!hasOpenSSL(3)) { // The correct header inside `rsa_private_pkcs8_bad.pem` should have been // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- // instead of diff --git a/test/parallel/test-https-agent-additional-options.js b/test/parallel/test-https-agent-additional-options.js index 000cb9d3d0c2..3707855ed5c8 100644 --- a/test/parallel/test-https-agent-additional-options.js +++ b/test/parallel/test-https-agent-additional-options.js @@ -6,17 +6,20 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); const https = require('https'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); const options = { key: fixtures.readKey('agent1-key.pem'), cert: fixtures.readKey('agent1-cert.pem'), ca: fixtures.readKey('ca1-cert.pem'), - minVersion: 'TLSv1.1', + minVersion: fips3 ? 'TLSv1.2' : 'TLSv1.1', }; if (!process.features.openssl_is_boringssl) { - options.ciphers = 'ALL@SECLEVEL=0'; + options.ciphers = fips3 ? + 'ECDHE-RSA-AES256-GCM-SHA384' : 'ALL@SECLEVEL=0'; } const server = https.Server(options, (req, res) => { @@ -34,7 +37,8 @@ function getBaseOptions(port) { }; if (!process.features.openssl_is_boringssl) { - baseOptions.ciphers = 'ALL@SECLEVEL=0'; + baseOptions.ciphers = fips3 ? + 'ECDHE-RSA-AES256-GCM-SHA384' : 'ALL@SECLEVEL=0'; } return baseOptions; @@ -44,10 +48,11 @@ const updatedValues = new Map([ ['dhparam', fixtures.readKey('dh2048.pem')], ['ecdhCurve', 'secp384r1'], ['honorCipherOrder', true], - ['minVersion', 'TLSv1.1'], + ['minVersion', fips3 ? 'TLSv1.2' : 'TLSv1.1'], ['maxVersion', 'TLSv1.3'], ['secureOptions', crypto.constants.SSL_OP_CIPHER_SERVER_PREFERENCE], - ['secureProtocol', 'TLSv1_1_method'], + ['secureProtocol', fips3 ? + 'TLSv1_2_method' : 'TLSv1_1_method'], ['sessionIdContext', 'sessionIdContext'], ]); diff --git a/test/parallel/test-https-agent-pfx-object-array-reuse.js b/test/parallel/test-https-agent-pfx-object-array-reuse.js index 95134855e971..758c32e54008 100644 --- a/test/parallel/test-https-agent-pfx-object-array-reuse.js +++ b/test/parallel/test-https-agent-pfx-object-array-reuse.js @@ -6,21 +6,57 @@ if (!common.hasCrypto) const assert = require('assert'); const https = require('https'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); + +const onRequest = (req, res) => { + res.end(req.socket.getPeerCertificate().subject.CN); +}; +const requestHandler = fips3 && !fips35 ? + common.mustNotCall() : + common.mustCall(onRequest, fips3 ? 1 : 2); const server = https.createServer({ key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem'), requestCert: true, rejectUnauthorized: false, -}, common.mustCall((req, res) => { - res.end(req.socket.getPeerCertificate().subject.CN); -}, 2)); +}, requestHandler); server.listen(0, common.mustCall(async () => { const agent = new https.Agent({ keepAlive: true, maxSockets: 1 }); const port = server.address().port; + if (fips3) { + await assert.rejects(request({ + agent, + port, + pfx: [{ buf: fixtures.readKey('agent1.pfx'), passphrase: 'sample' }], + }, false), { code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION' }); + + if (!fips35) { + agent.destroy(); + server.close(); + return; + } + + const result = await request({ + agent, + port, + pfx: [{ + buf: fixtures.readKey('agent1-fips.pfx'), + passphrase: 'password', + }], + }); + assert.strictEqual(result.body, 'agent1'); + assert.strictEqual(result.reusedSocket, false); + agent.destroy(); + server.close(); + return; + } + const first = await request({ agent, port, @@ -41,19 +77,20 @@ server.listen(0, common.mustCall(async () => { server.close(); })); -function request(options) { +function request(options, expectResponse = true) { return new Promise((resolve, reject) => { - const req = https.get({ - ...options, - rejectUnauthorized: false, - }, common.mustCall((res) => { + const onResponse = expectResponse ? common.mustCall((res) => { let body = ''; res.setEncoding('utf8'); res.on('data', (chunk) => body += chunk); res.on('end', common.mustCall(() => { resolve({ body, reusedSocket: req.reusedSocket }); })); - })); + }) : common.mustNotCall(); + const req = https.get({ + ...options, + rejectUnauthorized: false, + }, onResponse); req.on('error', reject); }); } diff --git a/test/parallel/test-https-agent-session-eviction.js b/test/parallel/test-https-agent-session-eviction.js index de2790e0d0a3..971a8f359a6f 100644 --- a/test/parallel/test-https-agent-session-eviction.js +++ b/test/parallel/test-https-agent-session-eviction.js @@ -8,10 +8,11 @@ if (!common.hasCrypto) { } const fixtures = require('../common/fixtures'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const https = require('https'); -const { SSL_OP_NO_TICKET } = require('crypto').constants; +const { constants: { SSL_OP_NO_TICKET } } = require('crypto'); +const fips3 = hasFIPS(3); const options = { key: fixtures.readKey('agent1-key.pem'), @@ -19,11 +20,17 @@ const options = { secureOptions: SSL_OP_NO_TICKET, }; +if (fips3) { + options.minVersion = 'TLSv1.3'; + options.maxVersion = 'TLSv1.3'; +} + if (!process.features.openssl_is_boringssl) { - options.ciphers = 'RSA@SECLEVEL=0'; + options.ciphers = fips3 ? + 'ECDHE-RSA-AES256-GCM-SHA384' : 'RSA@SECLEVEL=0'; } -// Create TLS1.2 server +// Create the initial server and cache a session from it. https.createServer(options, function(req, res) { res.writeHead(200, { 'Connection': 'close' }); res.end('ohai'); @@ -47,9 +54,14 @@ function first(server) { req.end(); } -// Create TLS1 server +// Create a server constrained to a different TLS version. function faultyServer(port) { - options.secureProtocol = 'TLSv1_method'; + if (fips3) { + options.minVersion = 'TLSv1.2'; + options.maxVersion = 'TLSv1.2'; + } else { + options.secureProtocol = 'TLSv1_method'; + } https.createServer(options, function(req, res) { res.writeHead(200, { 'Connection': 'close' }); res.end('hello faulty'); @@ -62,14 +74,15 @@ function faultyServer(port) { function second(server, session) { const req = https.request({ port: server.address().port, - ciphers: (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), + ciphers: fips3 ? 'ECDHE-RSA-AES256-GCM-SHA384' : + (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), rejectUnauthorized: false }, function(res) { res.resume(); }); - // Although we have a TLS 1.2 session to offer to the TLS 1.0 server, - // connection to the TLS 1.0 server should work. + // Offering the cached session to a server using another TLS version should + // not prevent a fresh connection. req.on('response', common.mustCall(function(res) { // The test is now complete for OpenSSL 1.1.0. server.close(); diff --git a/test/parallel/test-https-pfx.js b/test/parallel/test-https-pfx.js index 3c0aa82b3dbe..2be91887207d 100644 --- a/test/parallel/test-https-pfx.js +++ b/test/parallel/test-https-pfx.js @@ -29,6 +29,8 @@ const fixtures = require('../common/fixtures'); const assert = require('assert'); const https = require('https'); +const { hasFIPS } = require('../common/crypto'); +const fips3 = hasFIPS(3); const pfx = fixtures.readKey('rsa_cert.pfx'); @@ -42,10 +44,23 @@ const options = { rejectUnauthorized: false }; +if (fips3) { + assert.throws(() => https.createServer(options), { + code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', + }); + + if (!hasFIPS(3, 5)) { + return; + } + + options.pfx = fixtures.readKey('agent1-fips.pfx'); + options.passphrase = 'password'; +} + const server = https.createServer(options, common.mustCallAtLeast((req, res) => { - assert.strictEqual(req.socket.authorized, false); // not a client cert - assert.strictEqual(req.socket.authorizationError, - 'DEPTH_ZERO_SELF_SIGNED_CERT'); + assert.strictEqual(req.socket.authorized, fips3); + assert.strictEqual(req.socket.authorizationError, fips3 ? + null : 'DEPTH_ZERO_SELF_SIGNED_CERT'); res.writeHead(200); res.end('OK'); })); diff --git a/test/parallel/test-https-selfsigned-no-keycertsign-no-crash.js b/test/parallel/test-https-selfsigned-no-keycertsign-no-crash.js index 667be03d58ce..ec1b8dda8ca1 100644 --- a/test/parallel/test-https-selfsigned-no-keycertsign-no-crash.js +++ b/test/parallel/test-https-selfsigned-no-keycertsign-no-crash.js @@ -13,9 +13,10 @@ if (!common.hasCrypto) common.skip('missing crypto'); const crypto = require('crypto'); +const { hasOpenSSL } = require('../common/crypto'); // See #37990 for details on why this is problematic with FIPS. -if (process.config.variables.openssl_is_fips) +if (crypto.getFips() === 1 && !hasOpenSSL(3)) common.skip('Skipping as test uses non-fips compliant EC curve'); // This test will fail for OpenSSL < 1.1.1h diff --git a/test/parallel/test-tls-alert.js b/test/parallel/test-tls-alert.js index 64b7080e39ba..f20bf42a2dcd 100644 --- a/test/parallel/test-tls-alert.js +++ b/test/parallel/test-tls-alert.js @@ -27,6 +27,7 @@ if (!common.hasCrypto) { const { hasOpenSSL, + hasFIPS, opensslCli, } = require('../common/crypto'); @@ -43,11 +44,18 @@ function loadPEM(n) { return fixtures.readKey(`${n}.pem`); } -const server = tls.Server({ - secureProtocol: 'TLSv1_2_server_method', +const serverOptions = { key: loadPEM('agent2-key'), - cert: loadPEM('agent2-cert') -}, null).listen(0, common.mustCall(() => { + cert: loadPEM('agent2-cert'), +}; +if (hasFIPS(3)) { + serverOptions.minVersion = 'TLSv1.3'; + serverOptions.maxVersion = 'TLSv1.3'; +} else { + serverOptions.secureProtocol = 'TLSv1_2_server_method'; +} + +const server = tls.Server(serverOptions, null).listen(0, common.mustCall(() => { if (process.features.openssl_is_boringssl) { let gotClientError = false; let gotServerError = false; @@ -75,8 +83,9 @@ const server = tls.Server({ return; } - const args = ['s_client', '-quiet', '-tls1_1', - '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), + const args = ['s_client', '-quiet', hasFIPS(3) ? '-tls1_2' : '-tls1_1', + '-cipher', hasFIPS(3) ? 'DEFAULT' : + (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `127.0.0.1:${server.address().port}`]; execFile(opensslCli, args, common.mustCall((err, _, stderr) => { diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js index ea6dec7bdc46..db41fdf6a098 100644 --- a/test/parallel/test-tls-client-getephemeralkeyinfo.js +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -9,7 +9,7 @@ if (process.features.openssl_is_boringssl) { } const fixtures = require('../common/fixtures'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const assert = require('assert'); const { X509Certificate } = require('crypto'); @@ -17,12 +17,14 @@ const tls = require('tls'); const key = fixtures.readKey('agent2-key.pem'); const cert = fixtures.readKey('agent2-cert.pem'); +const fips3 = hasFIPS(3); +const rejectsXCurves = hasFIPS(3, 5); function loadDHParam(n) { return fixtures.readKey(`dh${n}.pem`); } -function test(size, type, name, cipher) { +function test(size, type, name, cipher, expectError = false) { assert(cipher); const options = { @@ -49,39 +51,62 @@ function test(size, type, name, cipher) { } } - const server = tls.createServer(options, common.mustCall((conn) => { - assert.strictEqual(conn.getEphemeralKeyInfo(), null); - conn.end(); - })); + if (rejectsXCurves && (name === 'X25519' || name === 'X448')) { + assert.throws(() => tls.createServer(options), { + code: 'ERR_CRYPTO_OPERATION_FAILED', + }); + return; + } + + const onConnection = expectError ? common.mustNotCall() : + common.mustCall((conn) => { + assert.strictEqual(conn.getEphemeralKeyInfo(), null); + conn.end(); + }); + const server = tls.createServer(options, onConnection); server.on('close', common.mustSucceed()); server.listen(0, common.mustCall(() => { + const onSecureConnect = expectError ? common.mustNotCall() : + common.mustCall(function() { + const ekeyinfo = client.getEphemeralKeyInfo(); + assert.strictEqual(ekeyinfo.type, type); + assert.strictEqual(ekeyinfo.size, size); + assert.strictEqual(ekeyinfo.name, name); + server.close(); + }); const client = tls.connect({ port: server.address().port, rejectUnauthorized: false - }, common.mustCall(function() { - const ekeyinfo = client.getEphemeralKeyInfo(); - assert.strictEqual(ekeyinfo.type, type); - assert.strictEqual(ekeyinfo.size, size); - assert.strictEqual(ekeyinfo.name, name); - server.close(); - })); - client.on('secureConnect', common.mustCall()); + }, onSecureConnect); + if (expectError) { + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_BAD_DH_VALUE'); + server.close(); + })); + } else { + client.on('secureConnect', common.mustCall()); + } })); } -test(undefined, undefined, undefined, 'AES256-SHA256'); +if (!fips3) + test(undefined, undefined, undefined, 'AES256-SHA256'); test('auto', 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); -if (hasOpenSSL(4, 0)) { - // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and - // always selects FFDHE-2048 regardless of the server-supplied dhparam. -} else if (!hasOpenSSL(3, 2)) { - test(1024, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); +if (fips3 && !hasOpenSSL(4)) { + test(2048, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384', true); } else { - test(3072, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); + if (hasOpenSSL(4, 0)) { + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // always selects FFDHE-2048 regardless of the server-supplied dhparam. + } else if (!hasOpenSSL(3, 2)) { + test(1024, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); + } else { + test(3072, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); + } + test(2048, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); } -test(2048, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); test(256, 'ECDH', 'prime256v1', 'ECDHE-RSA-AES256-GCM-SHA384'); test(521, 'ECDH', 'secp521r1', 'ECDHE-RSA-AES256-GCM-SHA384'); test(253, 'ECDH', 'X25519', 'ECDHE-RSA-AES256-GCM-SHA384'); @@ -96,6 +121,17 @@ function testTLS13Group(size, type, name) { maxVersion: 'TLSv1.3', }; + const unsupportedFipsGroup = + (rejectsXCurves && name === 'X25519') || + (hasFIPS(4) && + (name === 'curveSM2' || name === 'curveSM2MLKEM768')); + if (unsupportedFipsGroup) { + assert.throws(() => tls.createServer(options), { + code: 'ERR_CRYPTO_OPERATION_FAILED', + }); + return; + } + const server = tls.createServer(options, common.mustCall((conn) => { assert.strictEqual(conn.getEphemeralKeyInfo(), null); conn.end(); @@ -121,6 +157,8 @@ function testTLS13Group(size, type, name) { })); } +if (fips3) + testTLS13Group(256, 'ECDH', 'prime256v1'); testTLS13Group(253, 'ECDH', 'X25519'); if (hasOpenSSL(3, 5)) { diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index d777a9bfa97f..8f3b2eafbb8a 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -13,7 +13,9 @@ const secLevel = require('internal/crypto/util').getOpenSSLSecLevel(); const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); +const fips3 = hasFIPS(3); +const fips4 = hasFIPS(4); const key = fixtures.readKey('agent2-key.pem'); const cert = fixtures.readKey('agent2-cert.pem'); @@ -29,7 +31,7 @@ function test(size, err, next, minDHSizeOverride) { const options = { key: key, cert: cert, - dhparam: loadDHParam(size), + dhparam: size === 'auto' ? 'auto' : loadDHParam(size), ciphers: 'DHE-RSA-AES128-GCM-SHA256' }; @@ -60,7 +62,8 @@ function test(size, err, next, minDHSizeOverride) { if (err) { client.on('error', common.mustCall((e) => { nerror++; - assert.strictEqual(e.code, 'ERR_TLS_DH_PARAM_SIZE'); + assert.strictEqual(e.code, fips3 && !fips4 ? + 'ERR_SSL_BAD_DH_VALUE' : 'ERR_TLS_DH_PARAM_SIZE'); server.close(); })); } @@ -86,7 +89,11 @@ function testDHE3072() { } if (!process.features.openssl_is_boringssl) { - if (hasOpenSSL(4, 0)) { + if (fips3 && !fips4) { + // The FIPS provider rejects explicit DH parameters without a validated + // subgroup, while OpenSSL's built-in FFDHE group remains available. + testDHE2048(true, () => test('auto', false, null, 2048)); + } else if (hasOpenSSL(4, 0)) { // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 // success case is therefore replaced by a 2048 success case. diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 83af6daccbd0..65f3dc6867c4 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -34,6 +34,7 @@ if (process.features.openssl_is_boringssl) { const { opensslCli, hasOpenSSL, + hasFIPS, } = require('../common/crypto'); // OpenSSL has a set of security levels which affect what algorithms @@ -62,7 +63,7 @@ const dheCipher = 'DHE-RSA-AES128-SHA256'; const ecdheCipher = 'ECDHE-RSA-AES128-SHA256'; const ciphers = `${dheCipher}:${ecdheCipher}`; -if (secLevel < 2) { +if (secLevel < 2 && !hasFIPS(3)) { // Test will emit a warning because the DH parameter size is < 2048 bits // when the test is run on versions lower than OpenSSL32 common.expectWarning('SecurityWarning', @@ -74,7 +75,7 @@ function loadDHParam(n) { return fixtures.readKey(keyname); } -function test(dhparam, keylen, expectedCipher) { +function test(dhparam, keylen, expectedCipher, expectedError) { const options = { key, cert, @@ -84,12 +85,29 @@ function test(dhparam, keylen, expectedCipher) { }; const server = tls.createServer(options, (conn) => conn.end()); + if (typeof expectedError === 'string' || Array.isArray(expectedError)) { + server.once('tlsClientError', common.mustCall((err) => { + if (Array.isArray(expectedError)) { + assert.ok(expectedError.includes(err.code), err); + } else { + assert.strictEqual(err.code, expectedError); + } + })); + } server.listen(0, '127.0.0.1', common.mustCall(() => { const args = ['s_client', '-connect', `127.0.0.1:${server.address().port}`, '-cipher', `${ciphers}:@SECLEVEL=1`]; - execFile(opensslCli, args, common.mustSucceed((stdout) => { + execFile(opensslCli, args, common.mustCall((err, stdout, stderr) => { + if (expectedError) { + assert.strictEqual(err?.code, 1); + if (expectedError instanceof RegExp) assert.match(stderr, expectedError); + server.close(); + return; + } + + assert.ifError(err); assert(keylen === null || // s_client < OpenSSL 3.5 stdout.includes(`Server Temp Key: DH, ${keylen} bits`) || @@ -103,10 +121,10 @@ function test(dhparam, keylen, expectedCipher) { return once(server, 'close'); } -function testCustomParam(keylen, expectedCipher) { +function testCustomParam(keylen, expectedCipher, expectedError) { const dhparam = loadDHParam(keylen); if (keylen === 'error') keylen = null; - return test(dhparam, keylen, expectedCipher); + return test(dhparam, keylen, expectedCipher, expectedError); } (async () => { @@ -140,14 +158,29 @@ function testCustomParam(keylen, expectedCipher) { // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and // ignores the server-supplied dhparam in favor of FFDHE-2048, so the // negotiated key length is always 2048. - if (secLevel < 2) { - await testCustomParam(1024, dheCipher); - } else if (hasOpenSSL(4, 0)) { - await test(loadDHParam(3072), 2048, dheCipher); + if (hasFIPS(3)) { + if (hasFIPS(4)) { + await test(loadDHParam(3072), 2048, dheCipher); + await testCustomParam(2048, dheCipher); + } else { + const errorCode = hasFIPS(3, 5) ? + [ + 'ERR_SSL_INVALID_KEY_LENGTH', + 'ERR_SSL_SSL/TLS_ALERT_ILLEGAL_PARAMETER', + ] : 'ERR_SSL_INTERNAL_ERROR'; + await testCustomParam(3072, null, errorCode); + await testCustomParam(2048, null, errorCode); + } } else { - await testCustomParam(3072, dheCipher); + if (secLevel < 2) { + await testCustomParam(1024, dheCipher); + } else if (hasOpenSSL(4, 0)) { + await test(loadDHParam(3072), 2048, dheCipher); + } else { + await testCustomParam(3072, dheCipher); + } + await testCustomParam(2048, dheCipher); } - await testCustomParam(2048, dheCipher); // Invalid DHE parameters are discarded. Prior to OpenSSL 4.0 this // disabled DHE and ECDHE was negotiated; since 4.0, FFDHE-2048 is used. diff --git a/test/parallel/test-tls-ecdh-multiple.js b/test/parallel/test-tls-ecdh-multiple.js index ed60044197d7..b01051d5b457 100644 --- a/test/parallel/test-tls-ecdh-multiple.js +++ b/test/parallel/test-tls-ecdh-multiple.js @@ -8,7 +8,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } -const { opensslCli, hasOpenSSL } = require('../common/crypto'); +const { + opensslCli, + hasOpenSSL, + hasFIPS, +} = require('../common/crypto'); const crypto = require('crypto'); if (!opensslCli) { @@ -19,14 +23,16 @@ const assert = require('assert'); const tls = require('tls'); const { execFile } = require('child_process'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); function loadPEM(n) { return fixtures.readKey(`${n}.pem`); } -// OpenSSL 4.0 disables support for deprecated elliptic curves from RFC 8422 -// (including secp256k1) by default. -const ecdhCurve = process.features.openssl_is_boringssl || hasOpenSSL(4, 0) ? +// The FIPS provider and OpenSSL 4.0 disable support for deprecated elliptic +// curves from RFC 8422 (including secp256k1) by default. +const ecdhCurve = process.features.openssl_is_boringssl || + hasOpenSSL(4, 0) || hasFIPS(3) ? 'prime256v1:secp521r1' : 'secp256k1:prime256v1:secp521r1'; @@ -61,8 +67,17 @@ const server = tls.createServer(options, (conn) => { 'prime192v3', ]; - // Brainpool is not supported in FIPS mode. - if (crypto.getFips()) { + // Setting a Brainpool group on a TLS context is deferred by OpenSSL, so + // exercise the prohibited key operation directly under FIPS properties. + if (fips3) { + if (hasFIPS(3, 5)) { + assert.throws( + () => crypto.createECDH('brainpoolP256r1').generateKeys(), + { code: 'ERR_CRYPTO_OPERATION_FAILED' }); + } else { + unsupportedCurves.push('brainpoolP256r1'); + } + } else if (crypto.getFips() === 1) { unsupportedCurves.push('brainpoolP256r1'); } diff --git a/test/parallel/test-tls-env-extra-ca-with-options.js b/test/parallel/test-tls-env-extra-ca-with-options.js index 8f04decf670c..5775284eea44 100644 --- a/test/parallel/test-tls-env-extra-ca-with-options.js +++ b/test/parallel/test-tls-env-extra-ca-with-options.js @@ -8,7 +8,9 @@ if (!common.hasCrypto) const assert = require('node:assert'); const tls = require('node:tls'); const { fork } = require('node:child_process'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); const tests = [ { @@ -28,13 +30,26 @@ const tests = [ crl: fixtures.readKey('ca2-crl.pem') } }, - { +]; + +if (fips3) { + assert.throws(() => tls.createSecureContext({ + pfx: fixtures.readKey('agent1.pfx'), + passphrase: 'sample', + }), { + code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', + }); +} + +if (!fips3 || hasFIPS(3, 5)) { + tests.push({ clientOptions: { - pfx: fixtures.readKey('agent1.pfx'), - passphrase: 'sample' + pfx: fixtures.readKey(fips3 ? + 'agent1-fips.pfx' : 'agent1.pfx'), + passphrase: fips3 ? 'password' : 'sample' } - }, -]; + }); +} if (process.argv[2]) { const testNumber = parseInt(process.argv[2], 10); diff --git a/test/parallel/test-tls-getprotocol.js b/test/parallel/test-tls-getprotocol.js index 2945ff99b5a2..2fe971444271 100644 --- a/test/parallel/test-tls-getprotocol.js +++ b/test/parallel/test-tls-getprotocol.js @@ -3,7 +3,7 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); // This test ensures that `getProtocol` returns the right protocol // from a TLS connection @@ -11,6 +11,7 @@ const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); let clientConfigs = [ { @@ -42,27 +43,50 @@ const serverConfig = { }; if (!process.features.openssl_is_boringssl) { - serverConfig.ciphers = 'RSA@SECLEVEL=0'; + serverConfig.ciphers = fips3 ? + 'ECDHE-RSA-AES256-GCM-SHA384' : 'RSA@SECLEVEL=0'; } -const server = tls.createServer(serverConfig, common.mustCall(clientConfigs.length)) +const expectedConnections = fips3 ? 1 : clientConfigs.length; +const server = tls.createServer(serverConfig, common.mustCall(expectedConnections)); + +if (fips3) { + server.on('tlsClientError', common.mustCall((err) => { + assert.ok([ + 'ERR_SSL_NO_SUITABLE_DIGEST_ALGORITHM', + 'ERR_SSL_UNEXPECTED_MESSAGE', + ].includes(err.code), err); + }, 2)); +} + +server .listen(0, common.localhostIPv4, common.mustCall(function() { - let connected = 0; + let completed = 0; + function done() { + if (++completed === clientConfigs.length) + server.close(); + } + for (const v of clientConfigs) { - tls.connect({ + const shouldConnect = !fips3 || v.version === 'TLSv1.2'; + const client = tls.connect({ host: common.localhostIPv4, port: server.address().port, ciphers: v.ciphers, rejectUnauthorized: false, secureProtocol: v.secureProtocol - }, common.mustCall(function() { + }, shouldConnect ? common.mustCall(function() { assert.strictEqual(this.getProtocol(), v.version); this.on('end', common.mustCall()); this.on('close', common.mustCall(function() { assert.strictEqual(this.getProtocol(), null); + done(); })).end(); - if (++connected === clientConfigs.length) - server.close(); - })); + }) : common.mustNotCall()); + + if (!shouldConnect) { + client.on('error', common.mustCall((err) => assert(err.code))); + client.on('close', common.mustCall(done)); + } } })); diff --git a/test/parallel/test-tls-honorcipherorder.js b/test/parallel/test-tls-honorcipherorder.js index d86a59aa4cdc..fbee483d83b9 100644 --- a/test/parallel/test-tls-honorcipherorder.js +++ b/test/parallel/test-tls-honorcipherorder.js @@ -8,6 +8,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const mustCall = common.mustCall; const tls = require('tls'); const util = require('util'); @@ -16,7 +17,7 @@ const util = require('util'); // default method is updated in the future const SSL_Method = 'TLSv1_2_method'; const localhost = '127.0.0.1'; -const config = process.features.openssl_is_boringssl ? { +const config = process.features.openssl_is_boringssl || hasFIPS(3) ? { serverCiphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256', clientPreferenceCiphers: diff --git a/test/parallel/test-tls-invalid-pfx.js b/test/parallel/test-tls-invalid-pfx.js index c16858f0f788..749d9e8b9f76 100644 --- a/test/parallel/test-tls-invalid-pfx.js +++ b/test/parallel/test-tls-invalid-pfx.js @@ -7,6 +7,7 @@ const fixtures = require('../common/fixtures'); const { assert, connect, keys } = require(fixtures.path('tls-connect')); +const { hasFIPS } = require('../common/crypto'); const invalidPfx = fixtures.readKey('cert-without-key.pfx'); @@ -18,6 +19,10 @@ connect({ }, server: keys.agent1 }, common.mustCall((e, pair, cleanup) => { - assert.strictEqual(e.message, 'Unable to load private key from PFX data'); + if (hasFIPS(3)) { + assert.strictEqual(e.code, 'ERR_CRYPTO_UNSUPPORTED_OPERATION'); + } else { + assert.strictEqual(e.message, 'Unable to load private key from PFX data'); + } cleanup(); })); diff --git a/test/parallel/test-tls-min-max-version.js b/test/parallel/test-tls-min-max-version.js index 88dce9f4b8e6..83797238cf4b 100644 --- a/test/parallel/test-tls-min-max-version.js +++ b/test/parallel/test-tls-min-max-version.js @@ -12,7 +12,7 @@ if (process.features.openssl_is_boringssl) { const { hasOpenSSL, - hasOpenSSL3, + hasFIPS, } = require('../common/crypto'); const fixtures = require('../common/fixtures'); const { inspect } = require('util'); @@ -29,8 +29,24 @@ const DEFAULT_MAX_VERSION = tls.DEFAULT_MAX_VERSION; function test(cmin, cmax, cprot, smin, smax, sprot, proto, cerr, serr) { assert(proto || cerr || serr, 'test missing any expectations'); + const legacyProtocols = new Set([ + 'TLSv1', + 'TLSv1.1', + 'TLSv1_method', + 'TLSv1_1_method', + ]); + const expectedLegacyProtocol = proto === 'TLSv1' || proto === 'TLSv1.1'; + const legacyOnlyConfiguration = [cprot, sprot, cmax, smax] + .some((value) => legacyProtocols.has(value)); + const fipsLegacyFailure = hasFIPS(3) && + (expectedLegacyProtocol || (!proto && legacyOnlyConfiguration)); + + if (hasFIPS(3) && expectedLegacyProtocol) { + proto = undefined; + } + let ciphers; - if (hasOpenSSL3 && (proto === 'TLSv1' || proto === 'TLSv1.1' || + if (hasOpenSSL(3) && (proto === 'TLSv1' || proto === 'TLSv1.1' || proto === 'TLSv1_1_method' || proto === 'TLSv1_method' || sprot === 'TLSv1_1_method' || sprot === 'TLSv1_method')) { if (serr !== 'ERR_SSL_UNSUPPORTED_PROTOCOL') @@ -65,6 +81,27 @@ function test(cmin, cmax, cprot, smin, smax, sprot, proto, cerr, serr) { console.log('test:', u(cmin), u(cmax), u(cprot), u(smin), u(smax), u(sprot), u(ciphers), 'expect', u(proto), u(cerr), u(serr)); console.log(' ', where); + if (fipsLegacyFailure) { + const errors = [pair.client.err, pair.server.err].filter(Boolean); + assert(errors.length > 0); + const expectedCodes = new Set([ + 'ERR_SSL_NO_PROTOCOLS_AVAILABLE', + 'ERR_SSL_NO_SUITABLE_DIGEST_ALGORITHM', + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE', + 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE', + 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION', + 'ERR_SSL_UNEXPECTED_MESSAGE', + 'ERR_SSL_UNSUPPORTED_PROTOCOL', + 'ERR_SSL_VERSION_TOO_LOW', + 'ERR_SSL_WRONG_VERSION_NUMBER', + ]); + if (hasFIPS(4)) + expectedCodes.add('ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE'); + for (const error of errors) + assert(expectedCodes.has(error.code), error); + return cleanup(); + } + if (!proto) { console.log('client', pair.client.err ? pair.client.err.code : undefined); console.log('server', pair.server.err ? pair.server.err.code : undefined); @@ -139,9 +176,9 @@ test(U, U, 'TLS_method', U, U, 'TLSv1_method', 'TLSv1'); // OpenSSL 1.1.1 and 3.0 use a different error code and alert (sent to the // client) when no protocols are enabled on the server. -const NO_PROTOCOLS_AVAILABLE_SERVER = hasOpenSSL3 ? +const NO_PROTOCOLS_AVAILABLE_SERVER = hasOpenSSL(3) ? 'ERR_SSL_NO_PROTOCOLS_AVAILABLE' : 'ERR_SSL_INTERNAL_ERROR'; -const NO_PROTOCOLS_AVAILABLE_SERVER_ALERT = hasOpenSSL3 ? +const NO_PROTOCOLS_AVAILABLE_SERVER_ALERT = hasOpenSSL(3) ? 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION' : 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR'; // SSLv23 also means "any supported protocol" greater than the default diff --git a/test/parallel/test-tls-multi-key.js b/test/parallel/test-tls-multi-key.js index 0a9c6f108bf6..239cb8aec725 100644 --- a/test/parallel/test-tls-multi-key.js +++ b/test/parallel/test-tls-multi-key.js @@ -35,6 +35,7 @@ if (process.features.openssl_is_boringssl) { const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); +const { hasFIPS } = require('../common/crypto'); // Key is ordered as ec, rsa, cert is ordered as rsa, ec. test({ @@ -143,6 +144,17 @@ test({ }); function test(options) { + if (hasFIPS(3) && options.pfx) { + const serverOptions = { ...options }; + delete serverOptions.rsaCN; + delete serverOptions.eccCN; + delete serverOptions.client; + assert.throws(() => tls.createServer(serverOptions), { + code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', + }); + return; + } + const rsaCN = options.rsaCN || 'agent1'; const eccCN = options.eccCN || 'agent2'; const clientTrustRoots = options.client.ca; diff --git a/test/parallel/test-tls-multi-pfx.js b/test/parallel/test-tls-multi-pfx.js index fec697cd3b70..b5bbb6decc12 100644 --- a/test/parallel/test-tls-multi-pfx.js +++ b/test/parallel/test-tls-multi-pfx.js @@ -10,9 +10,12 @@ if (process.features.openssl_is_boringssl) { const assert = require('assert'); const tls = require('tls'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); +const fips3 = hasFIPS(3); +const fips4 = hasFIPS(4); -const options = { +const legacyOptions = { pfx: [ { buf: fixtures.readKey('agent1.pfx'), @@ -22,6 +25,40 @@ const options = { ] }; +if (fips3) { + assert.throws(() => tls.createServer(legacyOptions), { + code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', + }); + + if (!hasFIPS(3, 5)) { + return; + } +} + +const fipsPfx = [ + { + buf: fixtures.readKey('agent1-fips.pfx'), + passphrase: 'password', + }, + { + buf: fixtures.readKey('ec-fips.pfx'), + passphrase: 'password', + }, +]; + +if (fips4) { + for (const { buf } of fipsPfx) { + assert.throws(() => tls.createServer({ + pfx: buf, + passphrase: 'sample', + }), { + message: 'password strength too weak', + }); + } +} + +const options = fips3 ? { pfx: fipsPfx } : legacyOptions; + const ciphers = []; const server = tls.createServer(options, function(conn) { diff --git a/test/parallel/test-tls-passphrase.js b/test/parallel/test-tls-passphrase.js index 4372da249bb5..1fe2c1ec11cf 100644 --- a/test/parallel/test-tls-passphrase.js +++ b/test/parallel/test-tls-passphrase.js @@ -26,6 +26,7 @@ if (!common.hasCrypto) const assert = require('assert'); const tls = require('tls'); +const { hasFIPS } = require('../common/crypto'); const fixtures = require('../common/fixtures'); const passKey = fixtures.readKey('rsa_private_encrypted.pem'); @@ -37,6 +38,33 @@ assert(Buffer.isBuffer(cert)); assert.strictEqual(typeof passKey.toString(), 'string'); assert.strictEqual(typeof cert.toString(), 'string'); +if (hasFIPS(3)) { + const encryptedKeyOptions = { + key: passKey, + passphrase: 'password', + cert, + }; + assert.throws(() => tls.Server(encryptedKeyOptions), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + assert.throws(() => tls.connect(encryptedKeyOptions), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + + const server = tls.Server({ key: rawKey, passphrase: 'ignored', cert }); + server.listen(0, common.mustCall(function() { + const client = tls.connect({ + port: this.address().port, + key: rawKey, + passphrase: 'ignored', + cert, + rejectUnauthorized: false, + }, common.mustCall(() => client.end())); + client.on('close', common.mustCall(() => server.close())); + })); + return; +} + function onSecureConnect() { return common.mustCall(function() { this.end(); }); } diff --git a/test/parallel/test-tls-pfx-authorizationerror.js b/test/parallel/test-tls-pfx-authorizationerror.js index 53fcc0b16b5b..e115eea80fea 100644 --- a/test/parallel/test-tls-pfx-authorizationerror.js +++ b/test/parallel/test-tls-pfx-authorizationerror.js @@ -10,14 +10,38 @@ const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); +const { hasFIPS } = require('../common/crypto'); -const pfx = fixtures.readKey('agent1.pfx'); +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); +const fips4 = hasFIPS(4); +const pfx = fixtures.readKey(fips35 ? 'agent1-fips.pfx' : 'agent1.pfx'); +const passphrase = fips35 ? 'password' : 'sample'; + +if (fips3) { + assert.throws(() => tls.createServer({ + pfx: fixtures.readKey('agent1.pfx'), + passphrase: 'sample', + }), { + code: 'ERR_CRYPTO_UNSUPPORTED_OPERATION', + }); + + if (!fips35) { + return; + } + + if (fips4) { + assert.throws(() => tls.createServer({ pfx, passphrase: 'sample' }), { + message: 'password strength too weak', + }); + } +} const server = tls .createServer( { pfx: pfx, - passphrase: 'sample', + passphrase, requestCert: true, rejectUnauthorized: false }, @@ -33,7 +57,7 @@ const server = tls { port: this.address().port, pfx: pfx, - passphrase: 'sample', + passphrase, rejectUnauthorized: false }, common.mustCall(() => { diff --git a/test/parallel/test-tls-session-cache.js b/test/parallel/test-tls-session-cache.js index ae560e567980..0789879e1dce 100644 --- a/test/parallel/test-tls-session-cache.js +++ b/test/parallel/test-tls-session-cache.js @@ -26,6 +26,7 @@ if (!common.hasCrypto) { } const { hasOpenSSL, + hasFIPS, opensslCli, } = require('../common/crypto'); @@ -59,7 +60,8 @@ function doTest(testOptions, callback) { secureProtocol: 'TLS_method', // BoringSSL supports the RSA cipher selector, but not OpenSSL's // cipher-string policy command syntax. - ciphers: isBoringSSL ? 'RSA' : 'RSA@SECLEVEL=0' + ciphers: hasFIPS(3) ? 'ECDHE-RSA-AES256-GCM-SHA384' : + (isBoringSSL ? 'RSA' : 'RSA@SECLEVEL=0') }; let requestCount = 0; let resumeCount = 0; @@ -108,8 +110,9 @@ function doTest(testOptions, callback) { server.listen(0, common.mustCall(function() { const args = [ 's_client', - isBoringSSL ? '-tls1_2' : '-tls1', - '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), + isBoringSSL || hasFIPS(3) ? '-tls1_2' : '-tls1', + '-cipher', hasFIPS(3) ? 'ECDHE-RSA-AES256-GCM-SHA384' : + (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `localhost:${this.address().port}`, '-servername', 'ohgod', '-key', fixtures.path('keys/rsa_private.pem'), diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 82a19bb9e90f..57fb35991121 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -6,10 +6,10 @@ if (!common.hasCrypto) { const { hasOpenSSL, - hasOpenSSL3, + hasFIPS, } = require('../common/crypto'); -if (!hasOpenSSL3) { +if (!hasOpenSSL(3)) { common.skip('missing crypto, or OpenSSL version lower than 3'); } @@ -96,57 +96,83 @@ if (hasOpenSSL(4, 0)) { expectedTLSAlertError = 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE'; } -// Have shared ciphers. -test(U, 'AES256-SHA', 'AES256-SHA'); -test('AES256-SHA', U, 'AES256-SHA'); - -test(U, 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); -test('TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); -test('TLS_AES_256_GCM_SHA384:!TLS_CHACHA20_POLY1305_SHA256', U, 'TLS_AES_256_GCM_SHA384'); - -// Do not have shared ciphers. -test('TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256', - U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); - -test('AES256-SHA', 'AES256-SHA256', U, expectedTLSAlertError, - 'ERR_SSL_NO_SHARED_CIPHER'); -test('AES256-SHA:TLS_AES_256_GCM_SHA384', - 'TLS_CHACHA20_POLY1305_SHA256:AES256-SHA256', - U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); - -// Cipher order ignored, TLS1.3 chosen before TLS1.2. -test('AES256-SHA:TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); -test(U, 'AES256-SHA:TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); - -// Cipher order ignored, TLS1.3 before TLS1.2 and -// cipher suites are not disabled if TLS ciphers are set only -// TODO: maybe these tests should be reworked so maxVersion clamping -// is done explicitly and not implicitly in the test() function -test('AES256-SHA', U, 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' }); -test(U, 'AES256-SHA', 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' }); - -// TLS_AES_128_CCM_8_SHA256 & TLS_AES_128_CCM_SHA256 are not enabled by -// default, but work. -// However, for OpenSSL32 AES_128 is not enabled due to the -// default security level -if (!hasOpenSSL(3, 2)) { - test('TLS_AES_128_CCM_8_SHA256', U, - U, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE', 'ERR_SSL_NO_SHARED_CIPHER'); - - test('TLS_AES_128_CCM_8_SHA256', 'TLS_AES_128_CCM_8_SHA256', - 'TLS_AES_128_CCM_8_SHA256'); +if (hasFIPS(3)) { + const tls12Cipher = 'ECDHE-RSA-AES256-GCM-SHA384'; + + // FIPS-approved TLS 1.2 and TLS 1.3 cipher suites work. + test(U, tls12Cipher, tls12Cipher); + test(tls12Cipher, U, tls12Cipher); + test(U, 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + test('TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); + + // The FIPS provider rejects ChaCha20-Poly1305. + test('TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256', + U, expectedTLSAlertError, 'ERR_SSL_NO_CIPHERS_AVAILABLE'); + + // Invalid cipher values are still validated before provider selection. + test(9, tls12Cipher, U, 'ERR_INVALID_ARG_TYPE', U); + test(tls12Cipher, 9, U, U, 'ERR_INVALID_ARG_TYPE'); + test(':', tls12Cipher, U, 'ERR_INVALID_ARG_VALUE', U); + test(tls12Cipher, ':', U, U, 'ERR_INVALID_ARG_VALUE'); + + // Empty and null values continue to select the defaults. + test('TLS_AES_256_GCM_SHA384', '', 'TLS_AES_256_GCM_SHA384'); + test('', 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + test(null, 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + test('TLS_AES_256_GCM_SHA384', null, 'TLS_AES_256_GCM_SHA384'); +} else { + // Have shared ciphers. + test(U, 'AES256-SHA', 'AES256-SHA'); + test('AES256-SHA', U, 'AES256-SHA'); + + test(U, 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + test('TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); + test('TLS_AES_256_GCM_SHA384:!TLS_CHACHA20_POLY1305_SHA256', U, 'TLS_AES_256_GCM_SHA384'); + + // Do not have shared ciphers. + test('TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256', + U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); + + test('AES256-SHA', 'AES256-SHA256', U, expectedTLSAlertError, + 'ERR_SSL_NO_SHARED_CIPHER'); + test('AES256-SHA:TLS_AES_256_GCM_SHA384', + 'TLS_CHACHA20_POLY1305_SHA256:AES256-SHA256', + U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); + + // Cipher order ignored, TLS1.3 chosen before TLS1.2. + test('AES256-SHA:TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384'); + test(U, 'AES256-SHA:TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + + // Cipher order ignored, TLS1.3 before TLS1.2 and + // cipher suites are not disabled if TLS ciphers are set only + // TODO: maybe these tests should be reworked so maxVersion clamping + // is done explicitly and not implicitly in the test() function + test('AES256-SHA', U, 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' }); + test(U, 'AES256-SHA', 'TLS_AES_256_GCM_SHA384', U, U, { maxVersion: 'TLSv1.3' }); + + // TLS_AES_128_CCM_8_SHA256 & TLS_AES_128_CCM_SHA256 are not enabled by + // default, but work. + // However, for OpenSSL32 AES_128 is not enabled due to the + // default security level + if (!hasOpenSSL(3, 2)) { + test('TLS_AES_128_CCM_8_SHA256', U, + U, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE', 'ERR_SSL_NO_SHARED_CIPHER'); + + test('TLS_AES_128_CCM_8_SHA256', 'TLS_AES_128_CCM_8_SHA256', + 'TLS_AES_128_CCM_8_SHA256'); + } + + // Invalid cipher values + test(9, 'AES256-SHA', U, 'ERR_INVALID_ARG_TYPE', U); + test('AES256-SHA', 9, U, U, 'ERR_INVALID_ARG_TYPE'); + test(':', 'AES256-SHA', U, 'ERR_INVALID_ARG_VALUE', U); + test('AES256-SHA', ':', U, U, 'ERR_INVALID_ARG_VALUE'); + + // Using '' is synonymous for "use default ciphers" + test('TLS_AES_256_GCM_SHA384', '', 'TLS_AES_256_GCM_SHA384'); + test('', 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); + + // Using null should be treated the same as undefined. + test(null, 'AES256-SHA', 'AES256-SHA'); + test('AES256-SHA', null, 'AES256-SHA'); } - -// Invalid cipher values -test(9, 'AES256-SHA', U, 'ERR_INVALID_ARG_TYPE', U); -test('AES256-SHA', 9, U, U, 'ERR_INVALID_ARG_TYPE'); -test(':', 'AES256-SHA', U, 'ERR_INVALID_ARG_VALUE', U); -test('AES256-SHA', ':', U, U, 'ERR_INVALID_ARG_VALUE'); - -// Using '' is synonymous for "use default ciphers" -test('TLS_AES_256_GCM_SHA384', '', 'TLS_AES_256_GCM_SHA384'); -test('', 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384'); - -// Using null should be treated the same as undefined. -test(null, 'AES256-SHA', 'AES256-SHA'); -test('AES256-SHA', null, 'AES256-SHA'); diff --git a/test/parallel/test-tls-write-error.js b/test/parallel/test-tls-write-error.js index 8a8d820a09cc..f6ec7b9bd245 100644 --- a/test/parallel/test-tls-write-error.js +++ b/test/parallel/test-tls-write-error.js @@ -5,7 +5,9 @@ if (!common.hasCrypto) const { TestTLSSocket, ccs } = require('../common/tls'); const fixtures = require('../common/fixtures'); +const assert = require('assert'); const https = require('https'); +const { hasFIPS } = require('../common/crypto'); // Regression test for an use-after-free bug in the TLS implementation that // would occur when `SSL_write()` failed. @@ -18,6 +20,7 @@ const opts = { key: server_key, cert: server_cert, }; +const rejectsClientHello = hasFIPS(3) && !hasFIPS(3, 5); if (!process.features.openssl_is_boringssl) { opts.ciphers = 'ALL@SECLEVEL=0'; @@ -25,7 +28,15 @@ if (!process.features.openssl_is_boringssl) { const server = https.createServer(opts, (req, res) => { res.write('hello'); -}).listen(0, common.mustCall(() => { +}); + +if (rejectsClientHello) { + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_WRONG_SIGNATURE_TYPE'); + })); +} + +server.listen(0, common.mustCall(() => { const client = new TestTLSSocket(server_cert); client.connect({ @@ -37,6 +48,12 @@ const server = https.createServer(opts, (req, res) => { })); client.once('data', common.mustCall((buf) => { + if (rejectsClientHello) { + client.end(); + server.close(); + return; + } + let remaining = buf; do { remaining = client.parseTLSFrame(remaining); diff --git a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js index 316d706e7b79..5960c46f8aa8 100644 --- a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js +++ b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js @@ -6,10 +6,17 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const fips3 = hasFIPS(3); -async function test(algorithmName, keyLength, ivLength, format = 'raw') { +async function test( + algorithmName, + keyLength, + ivLength, + format = 'raw', + causeCode, +) { const key = await subtle.importKey( format, new Uint8Array(keyLength), @@ -21,19 +28,40 @@ async function test(algorithmName, keyLength, ivLength, format = 'raw') { const data = new Uint8Array(32); data.buffer.transfer(); + const expected = causeCode === undefined ? + { name: 'OperationError' } : + (err) => err.name === 'OperationError' && + err.cause?.code === causeCode; await assert.rejects( subtle.decrypt({ name: algorithmName, iv: new Uint8Array(ivLength) }, key, data), - { name: 'OperationError' }, + expected, ); } const tests = [ test('AES-GCM', 32, 12), - test('ChaCha20-Poly1305', 32, 12, 'raw-secret'), ]; +if (fips3) { + tests.push(assert.rejects( + subtle.importKey( + 'raw-secret', + new Uint8Array(32), + 'ChaCha20-Poly1305', + false, + ['encrypt', 'decrypt']), + { name: 'NotSupportedError' })); +} else { + tests.push(test('ChaCha20-Poly1305', 32, 12, 'raw-secret')); +} + if (hasOpenSSL(3)) { - tests.push(test('AES-OCB', 32, 12, 'raw-secret')); + tests.push(test( + 'AES-OCB', + 32, + 12, + 'raw-secret', + fips3 ? 'ERR_OSSL_EVP_UNSUPPORTED' : undefined)); } Promise.all(tests).then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js index 782265edc294..3d13b6c92bba 100644 --- a/test/parallel/test-webcrypto-constructors.js +++ b/test/parallel/test-webcrypto-constructors.js @@ -6,7 +6,9 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const fips4 = hasFIPS(4); // Test CryptoKey constructor { @@ -152,19 +154,41 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto); } { - subtle.importKey( - 'raw', - globalThis.crypto.getRandomValues(new Uint8Array(4)), - 'PBKDF2', - false, - ['deriveKey'], - ).then((key) => { + const keyData = globalThis.crypto.getRandomValues( + new Uint8Array(fips4 ? 8 : 4)); + const importedKeys = [ + subtle.importKey('raw', keyData, 'PBKDF2', false, ['deriveKey']), + ]; + if (fips4) { + importedKeys.push( + subtle.importKey( + 'raw', + globalThis.crypto.getRandomValues(new Uint8Array(4)), + 'PBKDF2', + false, + ['deriveKey'])); + } + + Promise.all(importedKeys).then(async ([key, weakKey]) => { subtle.importKey = common.mustNotCall(); - return subtle.deriveKey({ + if (fips4) { + await assert.rejects(subtle.deriveKey({ + name: 'PBKDF2', + hash: 'SHA-512', + salt: new Uint8Array(), + iterations: 5, + }, weakKey, { + name: 'AES-GCM', + length: 256, + }, true, ['encrypt', 'decrypt']), { name: 'OperationError' }); + } + + await subtle.deriveKey({ name: 'PBKDF2', hash: 'SHA-512', - salt: globalThis.crypto.getRandomValues(new Uint8Array()), - iterations: 5, + salt: globalThis.crypto.getRandomValues( + new Uint8Array(fips4 ? 16 : 0)), + iterations: fips4 ? 1000 : 5, }, key, { name: 'AES-GCM', length: 256 diff --git a/test/parallel/test-webcrypto-cryptokey-hidden-slots.js b/test/parallel/test-webcrypto-cryptokey-hidden-slots.js index 7728ba026651..2200088af387 100644 --- a/test/parallel/test-webcrypto-cryptokey-hidden-slots.js +++ b/test/parallel/test-webcrypto-cryptokey-hidden-slots.js @@ -21,6 +21,7 @@ if (!common.hasCrypto) const assert = require('node:assert'); const { + getFips, KeyObject, } = require('node:crypto'); const { inspect } = require('node:util'); @@ -35,7 +36,7 @@ const { subtle } = globalThis.crypto; const { publicKey: rsaPublicKey } = await subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, diff --git a/test/parallel/test-webcrypto-deduplicate-usages.js b/test/parallel/test-webcrypto-deduplicate-usages.js index 70b35f6cfa38..d27675d873ce 100644 --- a/test/parallel/test-webcrypto-deduplicate-usages.js +++ b/test/parallel/test-webcrypto-deduplicate-usages.js @@ -13,7 +13,7 @@ if (!common.hasCrypto) const assert = require('assert'); const { createSecretKey } = require('crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; function assertSameSet(actual, expected, msg) { @@ -50,6 +50,10 @@ function assertSameSet(actual, expected, msg) { expected: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'] }, ]; + if (hasFIPS(3)) + symmetric.splice(symmetric.findIndex(({ algorithm }) => + algorithm.name === 'ChaCha20-Poly1305'), 1); + if (hasOpenSSL(3)) { symmetric.push({ algorithm: { name: 'AES-OCB', length: 128 }, @@ -107,6 +111,10 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['deriveKey', 'deriveBits'] }, ]; + if (hasFIPS(3)) + asymmetric.splice(asymmetric.findIndex(({ algorithm }) => + algorithm.name === 'X25519'), 1); + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { asymmetric.push({ algorithm: { name: 'ML-DSA-65' }, @@ -336,12 +344,17 @@ function assertSameSet(actual, expected, msg) { // ChaCha20-Poly1305 raw-secret import. tests.push((async () => { - const key = await subtle.importKey( + const imported = subtle.importKey( 'raw-secret', new Uint8Array(32), { name: 'ChaCha20-Poly1305' }, true, ['decrypt', 'encrypt', 'decrypt', 'encrypt']); + if (hasFIPS(3)) { + await assert.rejects(imported, { name: 'NotSupportedError' }); + return; + } + const key = await imported; assertSameSet(key.usages, ['encrypt', 'decrypt']); assert.strictEqual(key.usages.length, 2); })()); @@ -491,6 +504,10 @@ function assertSameSet(actual, expected, msg) { privateExpected: ['deriveKey', 'deriveBits'] }, ]; + if (hasFIPS(3)) + jwkPairVectors.splice(jwkPairVectors.findIndex(({ algorithm }) => + algorithm.name === 'X25519'), 1); + if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { jwkPairVectors.push({ algorithm: { name: 'ML-DSA-65' }, diff --git a/test/parallel/test-webcrypto-derivebits-cfrg.js b/test/parallel/test-webcrypto-derivebits-cfrg.js index 757c81272536..fae39ac89c5e 100644 --- a/test/parallel/test-webcrypto-derivebits-cfrg.js +++ b/test/parallel/test-webcrypto-derivebits-cfrg.js @@ -6,7 +6,9 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const rejectsXCurves = hasFIPS(3, 5); const kTests = [ { @@ -77,6 +79,15 @@ async function prepareKeys() { Object.keys(keys).map(async (name) => { const { size, result, privateKey, publicKey } = keys[name]; + if (rejectsXCurves) { + await assert.rejects( + subtle.deriveBits({ name, public: publicKey }, privateKey, 8 * size), + (err) => err.name === 'OperationError' && + err.cause?.code === + 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE'); + return; + } + { // Good parameters const bits = await subtle.deriveBits({ diff --git a/test/parallel/test-webcrypto-derivebits.js b/test/parallel/test-webcrypto-derivebits.js index 50892be7400e..6ef2227ab2d2 100644 --- a/test/parallel/test-webcrypto-derivebits.js +++ b/test/parallel/test-webcrypto-derivebits.js @@ -7,7 +7,11 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const requiresLongHkdfInputs = hasFIPS(3) && !hasFIPS(3, 5); +const rejectsWeakPbkdf2Inputs = hasFIPS(4); +const rejectsXCurves = hasFIPS(3, 5); // This is only a partial test. The WebCrypto Web Platform Tests // will provide much greater coverage. @@ -56,12 +60,20 @@ const { subtle } = globalThis.crypto; } const kTests = [ - ['hello', 'there', 'my friend', 'SHA-256', 512, - '14d93b0ccd99d4f2cbd9fbfe9c830b5b8a43e3e45e329' + - '41ef21bdeb0fa87b6b6bfa5c54466aa5bf76cdc2685fb' + - 'a4408ea5b94c049fe035649b46f92fdc519374'], - ['hello', 'there', 'my friend', 'SHA-384', 128, - 'e36cf2cf943d8f3a88adb80f478745c3'], + [requiresLongHkdfInputs ? 'hello hello hello' : 'hello', + 'there', requiresLongHkdfInputs ? 'my friend indeed' : 'my friend', + 'SHA-256', 512, + requiresLongHkdfInputs ? + 'bc2b7841512a6f4563f723c317909ac305ddbfbdec1daf0055d0587b5db8d635' + + 'a22f97b0dfbcc12dcd2d096123385227b16e95e5bccc0d6751491f38c5e48428' : + '14d93b0ccd99d4f2cbd9fbfe9c830b5b8a43e3e45e329' + + '41ef21bdeb0fa87b6b6bfa5c54466aa5bf76cdc2685fb' + + 'a4408ea5b94c049fe035649b46f92fdc519374'], + [requiresLongHkdfInputs ? 'hello hello hello' : 'hello', + 'there', requiresLongHkdfInputs ? 'my friend indeed' : 'my friend', + 'SHA-384', 128, + requiresLongHkdfInputs ? 'ee2d1d7dc759c26f2ab8ee6d7cfa0c23' : + 'e36cf2cf943d8f3a88adb80f478745c3'], ]; const tests = Promise.all(kTests.map((args) => test(...args))); @@ -88,17 +100,24 @@ const { subtle } = globalThis.crypto; } const kTests = [ - ['hello', 'there', 10, 'SHA-256', 512, - 'f72d1cf4853fffbd16a42751765d11f8dc7939498ee7b7' + - 'ce7678b4cb16fad88098110a83e71f4483ce73203f7a64' + - '719d293280f780f9fafdcf46925c5c0588b3'], - ['hello', 'there', 5, 'SHA-384', 128, - '201509b012c9cd2fbe7ea938f0c509b3'], + ['password', 'there there here', 1000, 'SHA-256', 512, + '8802c34ee684a523f9304a6335394c0a5f02350d51383d' + + '17d3cf89fa0808591ddede3c832fe4691c7f361ade53b9' + + '36bf94347055bcf86fd662abe038fb945d17'], + ['password', 'there there here', 2000, 'SHA-384', 128, + '7c650b88798cea1a390802a6f97e05b0'], ]; const tests = Promise.all(kTests.map((args) => test(...args))); tests.then(common.mustCall()); + + if (rejectsWeakPbkdf2Inputs) { + assert.rejects( + test('hello', 'there', 10, 'SHA-256', 512), + { name: 'OperationError' }) + .then(common.mustCall()); + } } // Test X25519 and X448 bit derivation @@ -123,10 +142,20 @@ const { subtle } = globalThis.crypto; assert.deepStrictEqual(secret1, secret2); } - test('X25519').then(common.mustCall()); - if (!process.features.openssl_is_boringssl) { - test('X448').then(common.mustCall()); + if (rejectsXCurves) { + for (const name of ['X25519', 'X448']) { + assert.rejects( + test(name), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED') + .then(common.mustCall()); + } } else { - common.printSkipMessage('Skipping unsupported X448 test case'); + test('X25519').then(common.mustCall()); + if (!process.features.openssl_is_boringssl) { + test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); + } } } diff --git a/test/parallel/test-webcrypto-derivekey-cfrg.js b/test/parallel/test-webcrypto-derivekey-cfrg.js index c5a5b1f3518f..13c4e56ce5b0 100644 --- a/test/parallel/test-webcrypto-derivekey-cfrg.js +++ b/test/parallel/test-webcrypto-derivekey-cfrg.js @@ -6,7 +6,9 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const rejectsXCurves = hasFIPS(3, 5); const kTests = [ { @@ -80,6 +82,15 @@ async function prepareKeys() { Object.keys(keys).map(async (name) => { const { result, privateKey, publicKey } = keys[name]; + if (rejectsXCurves) { + await assert.rejects( + subtle.deriveKey({ name, public: publicKey }, privateKey, ...otherArgs), + (err) => err.name === 'OperationError' && + err.cause?.code === + 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE'); + return; + } + { // Good parameters const key = await subtle.deriveKey({ diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index 3ac17b2ff01c..392e3e998afe 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -6,11 +6,13 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const assert = require('assert'); const { subtle } = globalThis.crypto; const { getCryptoKeyHandle } = require('internal/crypto/keys'); +const rejectsXCurves = hasFIPS(3, 5); +const fips4 = hasFIPS(4); // This is only a partial test. The WebCrypto Web Platform Tests // will provide much greater coverage. @@ -75,24 +77,24 @@ const { getCryptoKeyHandle } = require('internal/crypto/keys'); } const kTests = [ - ['hello', 'there', 'my friend', 'SHA-1', - '365ca5d3f42d050c74302e420c83975327950f1913a151eecd00526bf52614a0'], - ['hello', 'there', 'my friend', 'SHA-256', - '14d93b0ccd99d4f2cbd9fbfe9c830b5b8a43e3e45e32941ef21bdeb0fa87b6b6'], - ['hello', 'there', 'my friend', 'SHA-384', - 'e36cf2cf943d8f3a88adb80f478745c336ac811b1a86d03a7d10eb0b6b52295c'], - ['hello', 'there', 'my friend', 'SHA-512', - '1e42d43fcacba361716f65853bd5f3c479f679612f0180eab3c51ed6c9d2b47d'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA-1', + 'aac1ecdc73147af6a418393da6875bff5f566c0a473e25d54b4dfc3cb7cb2ace'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA-256', + 'bc2b7841512a6f4563f723c317909ac305ddbfbdec1daf0055d0587b5db8d635'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA-384', + 'ee2d1d7dc759c26f2ab8ee6d7cfa0c2313e82650a4514673c867063dc1849040'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA-512', + 'a7abd704d0be364c6d4a530b6f93fcaff95474a2eee5a127ff86c5d095a2a812'], ]; if (!process.features.openssl_is_boringssl) { kTests.push( - ['hello', 'there', 'my friend', 'SHA3-256', - '2a49a3b6fb219117af9e251c6c65f16600cbca13bd0be6e70d96b0b9fa4cf3fd'], - ['hello', 'there', 'my friend', 'SHA3-384', - '0437bb59b95f2db2c7684c0b439028cb0fdd6f0f5d03b9f489066a87ae147221'], - ['hello', 'there', 'my friend', 'SHA3-512', - '3bbc469d38214371921e52c6f147e96cb7eb370421a81f53dea8b4851dfb8bce'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA3-256', + '89b3751df2ada85322a57ec82f7d0a5c233c6def91c92e681bc5118bd5768dca'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA3-384', + 'b4fa7b9929a595bbaa370eb959b194c1232d5a329abd02a5fa166a1424962fcf'], + ['hello hello hello', 'there', 'my friend indeed', 'SHA3-512', + 'ac5d90a6bc848961e78a491887539b29c532a9c0d0b39cec464df071a63e0061'], ); } else { common.printSkipMessage('Skipping unsupported SHA-3 test cases'); @@ -128,30 +130,36 @@ const { getCryptoKeyHandle } = require('internal/crypto/keys'); } const kTests = [ - ['hello', 'there', 5, 'SHA-1', - 'f8f65a5fd92c9b74916083a7e9b0001c46bc89e2a14c48014cf1e0e1dbabf635'], - ['hello', 'there', 5, 'SHA-256', - '2e575eae24267db32106c7dba01615e5417557e8c5cf33ba15a311cb0c2907ee'], - ['hello', 'there', 5, 'SHA-384', - '201509b012c9cd2fbe7ea938f0c509b36ecb140f38bf9130e96923f55f46756d'], - ['hello', 'there', 5, 'SHA-512', - '2e8d981741f98193e0af9c79870af0e985089341221edad9a130d297eae1984b'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA-1', + 'b747604ca226287ccae90d8d8c119645a80d1154625a56b2debb3f9b172eb134'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA-256', + '3cc64f6cfcbdb9c42b63b471016f17d1966b70934b4719a12ce95382940252f2'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA-384', + '5ce64241beef3a3931dbfac6eef7303b5bdbea13449d4eeb4f89c3e9f9357c65'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA-512', + '12790ce09027db067d680670f4dc704715b5120d139e8fde810afc34fb66f9f1'], ]; if (!process.features.openssl_is_boringssl) { kTests.push( - ['hello', 'there', 5, 'SHA3-256', - '0aed29b61b3ca3978aea34a9793276574ea997b69e8d03727438199f90571649'], - ['hello', 'there', 5, 'SHA3-384', - '7aa4a274aa19b4623c5d3091c4b06355de85ff6f25e53a83e3126cbb86ae68df'], - ['hello', 'there', 5, 'SHA3-512', - '4d909c47a81c625f866d1f9406248e6bc3c7ea89225fbccf1f08820254c9ef56'] + ['hello hello hello', 'my friend indeed', 1000, 'SHA3-256', + '0f69b46660cba27b95215d5676492c64ed6abf6d426669a4a02b0ca3a1c36c11'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA3-384', + 'a2e86a2d4cdf9844d70ae37f71302356ce2b9a899f5d778fc9af64d32e351d70'], + ['hello hello hello', 'my friend indeed', 1000, 'SHA3-512', + '03431052c37d626ae3fc1df582ff2a4d610642fc27e1b8130ca5980c0b0756ac'] ); } else { common.printSkipMessage('Skipping unsupported SHA-3 test cases'); } - const tests = Promise.all(kTests.map((args) => test(...args))); + const promises = kTests.map((args) => test(...args)); + if (fips4) { + promises.push(assert.rejects( + test('hello', 'there', 5, 'SHA-256', ''), + { name: 'OperationError' })); + } + const tests = Promise.all(promises); tests.then(common.mustCall()); } @@ -255,8 +263,18 @@ const { getCryptoKeyHandle } = require('internal/crypto/keys'); (async () => { for (const [derivedKeyAlgorithm, usage, expected] of vectors) { const derived = await subtle.deriveKey( - { name: 'PBKDF2', salt: new Uint8Array([]), hash: 'SHA-256', iterations: 20 }, - await subtle.importKey('raw', new Uint8Array([]), { name: 'PBKDF2' }, false, ['deriveKey']), + { + name: 'PBKDF2', + salt: new Uint8Array(16), + hash: 'SHA-256', + iterations: 1000, + }, + await subtle.importKey( + 'raw', + new Uint8Array(8), + { name: 'PBKDF2' }, + false, + ['deriveKey']), derivedKeyAlgorithm, false, [usage]); @@ -272,17 +290,27 @@ if (hasOpenSSL(3)) { const usages = ['sign']; for (const [algorithm, baseKeyAlgorithm] of [ [ - { name: 'HKDF', salt: new Uint8Array(), info: new Uint8Array(), hash: 'SHA-256' }, + { + name: 'HKDF', + salt: new Uint8Array(16), + info: new Uint8Array(), + hash: 'SHA-256', + }, { name: 'HKDF' }, ], [ - { name: 'PBKDF2', salt: new Uint8Array(), hash: 'SHA-256', iterations: 20 }, + { + name: 'PBKDF2', + salt: new Uint8Array(16), + hash: 'SHA-256', + iterations: 1000, + }, { name: 'PBKDF2' }, ], ]) { const baseKey = await subtle.importKey( 'raw', - new Uint8Array(), + new Uint8Array(baseKeyAlgorithm.name === 'HKDF' ? 16 : 8), baseKeyAlgorithm, false, ['deriveKey']); @@ -294,11 +322,15 @@ if (hasOpenSSL(3)) { usages); assert.strictEqual(derived.algorithm.length, 0); - const signature = await subtle.sign({ + const signature = subtle.sign({ name: 'KMAC128', outputLength: 256, }, derived, new Uint8Array()); - assert.strictEqual(signature.byteLength, 32); + if (fips4) { + await assert.rejects(signature, { name: 'OperationError' }); + } else { + assert.strictEqual((await signature).byteLength, 32); + } } })().then(common.mustCall()); } @@ -334,10 +366,20 @@ if (hasOpenSSL(3)) { assert.deepStrictEqual(raw1, raw2); } - test('X25519').then(common.mustCall()); - if (!process.features.openssl_is_boringssl) { - test('X448').then(common.mustCall()); + if (rejectsXCurves) { + for (const name of ['X25519', 'X448']) { + assert.rejects( + test(name), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED') + .then(common.mustCall()); + } } else { - common.printSkipMessage('Skipping unsupported X448 test case'); + test('X25519').then(common.mustCall()); + if (!process.features.openssl_is_boringssl) { + test('X448').then(common.mustCall()); + } else { + common.printSkipMessage('Skipping unsupported X448 test case'); + } } } diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index 8e1b6797ee86..447948212bc1 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -9,7 +9,8 @@ const assert = require('assert'); const { Buffer } = require('buffer'); const { subtle } = globalThis.crypto; const { createHash, getHashes } = require('crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); +const fips4 = hasFIPS(4); const kTests = [ ['SHA-1', ['sha1'], 160], @@ -397,18 +398,35 @@ if (getHashes().includes('shake128')) { 'ca6f88db415829', }, ]) { - assert.strictEqual( - Buffer.from(await subtle.digest(algorithm, data)).toString('hex'), - expected); + 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); + } } - const truncated = Buffer.from(await subtle.digest( + const truncatedDigest = subtle.digest( { ...nistCShakeSample1.algorithm, outputLength: 255 }, - 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]); + 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]); + } })().then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js index d7a7dca6584c..00c294839bfe 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js @@ -8,6 +8,7 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); +const { getFips } = require('crypto'); const { subtle } = globalThis.crypto; async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { @@ -237,6 +238,14 @@ if (hasOpenSSL(3)) { } = require('../fixtures/crypto/aes_ocb')(); (async function() { + if (getFips() === 1) { + await assert.rejects( + testEncrypt(passing[0]), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + const variations = []; passing.forEach((vector) => { diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js index 723fd26ea570..45225115fd52 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js @@ -6,8 +6,19 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +if (hasFIPS(3)) { + assert.rejects( + subtle.generateKey( + { name: 'ChaCha20-Poly1305' }, + false, + ['encrypt', 'decrypt']), + { name: 'NotSupportedError' }).then(common.mustCall()); + return; +} + async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) { // Using a copy of plaintext to prevent tampering of the original plaintext = Buffer.from(plaintext); diff --git a/test/parallel/test-webcrypto-encrypt-decrypt.js b/test/parallel/test-webcrypto-encrypt-decrypt.js index c4ca52862fe0..1015752a8b0a 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt.js @@ -7,6 +7,7 @@ if (!common.hasCrypto) const assert = require('assert'); const { hasOpenSSL } = require('../common/crypto'); +const { getFips } = require('crypto'); const { subtle } = globalThis.crypto; // This is only a partial test. The WebCrypto Web Platform Tests @@ -207,7 +208,15 @@ if (hasOpenSSL(3)) { Buffer.from(buf).toString('hex')); } - test().then(common.mustCall()); + if (getFips() === 1) { + assert.rejects( + test(), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED') + .then(common.mustCall()); + } else { + test().then(common.mustCall()); + } } else { common.printSkipMessage('Skipping unsupported AES-OCB test cases'); } diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index 14f475fc1779..0ff7c61bdc68 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -8,7 +8,9 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const rejectsXCurves = hasFIPS(3, 5); const keyData = { 'Ed25519': { @@ -413,9 +415,19 @@ async function testImportRaw({ name, publicUsages }) { for (const extractable of [true, false]) { tests.push(testImportSpki(vector, extractable)); tests.push(testImportPkcs8(vector, extractable)); - tests.push(testImportJwk(vector, extractable)); + if (rejectsXCurves && vector.name.startsWith('X')) { + tests.push(assert.rejects( + testImportJwk(vector, extractable), + { name: 'DataError' })); + } else { + tests.push(testImportJwk(vector, extractable)); + } + } + if (rejectsXCurves && vector.name.startsWith('X')) { + tests.push(assert.rejects(testImportRaw(vector), { name: 'DataError' })); + } else { + tests.push(testImportRaw(vector)); } - tests.push(testImportRaw(vector)); } await Promise.all(tests); })().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index c7399c69d9c9..9f6b2125d8ed 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -10,7 +10,12 @@ const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); const { subtle } = globalThis.crypto; -const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto'); +const { + createPrivateKey, + createPublicKey, + createSecretKey, + getFips, +} = require('crypto'); { async function test() { @@ -397,7 +402,7 @@ if (hasOpenSSL(3)) { async function test() { const { publicKey, privateKey } = await subtle.generateKey({ name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-384' }, true, ['sign', 'verify']); diff --git a/test/parallel/test-webcrypto-get-public-key.mjs b/test/parallel/test-webcrypto-get-public-key.mjs index 622ec4adca68..65e3cb5334d9 100644 --- a/test/parallel/test-webcrypto-get-public-key.mjs +++ b/test/parallel/test-webcrypto-get-public-key.mjs @@ -9,8 +9,10 @@ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const { kSupportedAlgorithms } = require('internal/crypto/util'); +const { hasFIPS } = require('../common/crypto'); const { SubtleCrypto } = globalThis; const { subtle } = globalThis.crypto; +const rejectsXCurves = hasFIPS(3, 5); const RSA_KEY_GEN = { modulusLength: 2048, @@ -80,6 +82,15 @@ for (const name of Object.keys(kSupportedAlgorithms.exportKey)) { assert.strictEqual(SubtleCrypto.supports('getPublicKey', name), true); + if (rejectsXCurves && + (name === 'X25519' || name === 'X448')) { + await assert.rejects( + subtle.generateKey(test.algorithm, false, test.privateUsages), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + continue; + } + const { privateKey } = await subtle.generateKey( test.algorithm, false, test.privateUsages); const usages = test.publicUsages; diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index 989fdbb47616..6ea2579ae041 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -6,15 +6,18 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const assert = require('assert'); const { types: { isCryptoKey } } = require('util'); const { createSecretKey, + getFips, KeyObject, } = require('crypto'); const { subtle } = globalThis.crypto; +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); const { bigIntArrayToUnsignedBigInt } = require('internal/crypto/util'); @@ -69,7 +72,7 @@ const vectors = { }, 'RSASSA-PKCS1-v1_5': { algorithm: { - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -81,7 +84,7 @@ const vectors = { }, 'RSA-PSS': { algorithm: { - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -93,7 +96,7 @@ const vectors = { }, 'RSA-OAEP': { algorithm: { - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -247,6 +250,21 @@ if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { // Test bad usages { async function test(name) { + if (fips3 && name === 'ChaCha20-Poly1305') { + await assert.rejects( + subtle.generateKey({ name }, true, []), + { name: 'NotSupportedError' }); + return; + } + + if (fips35 && (name === 'X25519' || name === 'X448')) { + await assert.rejects( + subtle.generateKey({ name }, true, ['deriveBits']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + return; + } + await assert.rejects( subtle.generateKey( { @@ -453,7 +471,7 @@ if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { const kTests = [ [ 'RSASSA-PKCS1-v1_5', - 1024, + getFips() === 1 ? 2048 : 1024, Buffer.from([1, 0, 1]), 'SHA-1', ['sign'], @@ -461,7 +479,7 @@ if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { ], [ 'RSA-PSS', - 1024, + getFips() === 1 ? 2048 : 1024, Buffer.from([1, 0, 1]), 'SHA-256', ['sign'], @@ -470,22 +488,37 @@ if (hasOpenSSL(3, 5) || process.features.openssl_is_boringssl) { ]; + let fipsExponentTest; if (!process.features.openssl_is_boringssl) { - kTests.push( - [ - 'RSA-OAEP', - 1024, - Buffer.from([3]), - 'SHA3-256', - ['decrypt', 'unwrapKey'], - ['encrypt', 'wrapKey'], - ], - ); + if (fips3) { + fipsExponentTest = assert.rejects( + subtle.generateKey({ + name: 'RSA-OAEP', + modulusLength: 2048, + publicExponent: Buffer.from([3]), + hash: 'SHA3-256', + }, true, ['decrypt', 'unwrapKey', 'encrypt', 'wrapKey']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_RSA_PUB_EXPONENT_OUT_OF_RANGE'); + } else { + kTests.push( + [ + 'RSA-OAEP', + 1024, + Buffer.from([3]), + 'SHA3-256', + ['decrypt', 'unwrapKey'], + ['encrypt', 'wrapKey'], + ], + ); + } } else { common.printSkipMessage('Skipping unsupported SHA-3 test case'); } const tests = kTests.map((args) => test(...args)); + if (fipsExponentTest !== undefined) + tests.push(fipsExponentTest); Promise.all(tests).then(common.mustCall()); } @@ -706,6 +739,13 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); // Test OKP Key Generation { + async function testFipsUnsupported(name) { + await assert.rejects( + subtle.generateKey({ name }, true, ['deriveKey', 'deriveBits']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + } + async function test( name, privateUsages, @@ -770,7 +810,12 @@ assert.throws(() => new CryptoKey(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); common.printSkipMessage('Skipping unsupported Curve448 test cases'); } - const tests = kTests.map((args) => test(...args)); + const tests = kTests.map((args) => { + const [name] = args; + if (fips35 && (name === 'X25519' || name === 'X448')) + return testFipsUnsupported(name); + return test(...args); + }); Promise.all(tests).then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs index 5c13561dc260..a92dbcdd8ac7 100644 --- a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs +++ b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs @@ -24,7 +24,16 @@ if (!common.hasCrypto) common.skip('missing crypto'); const require = createRequire(import.meta.url); const { kSupportedAlgorithms } = require('internal/crypto/util'); +const { getFips } = require('node:crypto'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); +const fips4 = hasFIPS(4); +const fips35UnavailableKeyGeneration = new Set([ + 'X25519', + 'X448', +]); Promise.prototype.then = common.mustNotCall('Promise.prototype.then'); @@ -333,7 +342,7 @@ function algorithm(name, params = {}) { function rsaAlgorithm(name) { return algorithm(name, { - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }); @@ -877,8 +886,8 @@ for (const name of ['HKDF', 'PBKDF2']) { }) : algorithm(name, { hash: 'SHA-256', - salt: new Uint8Array(8), - iterations: 1, + salt: new Uint8Array(fips4 ? 16 : 8), + iterations: fips4 ? 1000 : 1, }), })); } @@ -1029,6 +1038,16 @@ for (const [name, operations] of supportedAlgorithms) { assert(fixture, `missing prototype pollution fixture for ${name}`); const ctx = { __proto__: null }; + if ((fips3 && name === 'ChaCha20-Poly1305') || + (fips35 && fips35UnavailableKeyGeneration.has(name))) { + const expected = name === 'ChaCha20-Poly1305' ? + { name: 'NotSupportedError' } : + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'; + await assert.rejects(fixture.generateKey(ctx), expected); + continue; + } + for (const operation of operationOrder) { if (!operations.has(operation)) continue; @@ -1036,7 +1055,17 @@ for (const [name, operations] of supportedAlgorithms) { typeof fixture[operation], 'function', `missing prototype pollution coverage for ${name} ${operation}`); - await fixture[operation](ctx); + if (fips3 && name === 'AES-OCB' && + (operation === 'encrypt' || operation === 'decrypt')) { + if (operation === 'decrypt') + ctx.ciphertext = new Uint8Array(); + await assert.rejects( + fixture[operation](ctx), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + } else { + await fixture[operation](ctx); + } } if (typeof fixture.getPublicKey === 'function' && @@ -1074,17 +1103,22 @@ for (const name of getKeyLengthAlgorithms) { continue; } - await assertCryptoKeyResult(`get key length ${name}`, () => + const deriveKey = () => assertCryptoKeyResult(`get key length ${name}`, () => subtle.deriveKey( algorithm('PBKDF2', { hash: 'SHA-256', - salt: new Uint8Array(8), - iterations: 1, + salt: new Uint8Array(fips4 ? 16 : 8), + iterations: fips4 ? 1000 : 1, }), pbkdf2Key, target.algorithm, true, target.usages)); + if (fips3 && name === 'ChaCha20-Poly1305') { + await assert.rejects(deriveKey(), { name: 'NotSupportedError' }); + } else { + await deriveKey(); + } } // Keep one explicit unwrapKey('jwk') negative case: the parsed object must not diff --git a/test/parallel/test-webcrypto-raw-format-aliases.js b/test/parallel/test-webcrypto-raw-format-aliases.js index 94e9474fde13..c9a0e8a4cf48 100644 --- a/test/parallel/test-webcrypto-raw-format-aliases.js +++ b/test/parallel/test-webcrypto-raw-format-aliases.js @@ -6,7 +6,9 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const rejectsXCurves = hasFIPS(3, 5); function getAlgorithmName(algorithm) { return typeof algorithm === 'string' ? algorithm : algorithm.name; @@ -50,7 +52,7 @@ async function assertPublicKeyDoesNotAcceptRawSecret( importUsages); } -Promise.all([ +const tests = [ assertSecretKeyDoesNotAcceptRawPublic('HKDF'), assertSecretKeyDoesNotAcceptRawPublic('PBKDF2'), assertPublicKeyDoesNotAcceptRawSecret( @@ -65,8 +67,18 @@ Promise.all([ 'Ed25519', ['sign', 'verify'], ['verify']), - assertPublicKeyDoesNotAcceptRawSecret( +]; + +if (rejectsXCurves) { + tests.push(assert.rejects( + subtle.generateKey('X25519', true, ['deriveBits']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED')); +} else { + tests.push(assertPublicKeyDoesNotAcceptRawSecret( 'X25519', ['deriveBits'], - []), -]).then(common.mustCall()); + [])); +} + +Promise.all(tests).then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-sign-verify-ecdsa.js b/test/parallel/test-webcrypto-sign-verify-ecdsa.js index eb7814efa556..94e3eff02eb9 100644 --- a/test/parallel/test-webcrypto-sign-verify-ecdsa.js +++ b/test/parallel/test-webcrypto-sign-verify-ecdsa.js @@ -6,7 +6,10 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { getFips } = require('crypto'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const rejectsSha1Signing = hasFIPS(3) && !hasFIPS(3, 5); const vectors = require('../fixtures/crypto/ecdsa')(); @@ -50,7 +53,7 @@ async function testVerify({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, @@ -173,7 +176,7 @@ async function testSign({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, @@ -229,6 +232,32 @@ async function testSign({ name, for (let i = 0; i < vectors.length; ++i) { const vector = vectors[i]; + if (rejectsSha1Signing && vector.hash === 'SHA-1') { + const publicKey = await subtle.importKey( + 'spki', + vector.publicKeyBuffer, + { name: vector.name, namedCurve: vector.namedCurve }, + false, + ['verify']); + const privateKey = await subtle.importKey( + 'pkcs8', + vector.privateKeyBuffer, + { name: vector.name, namedCurve: vector.namedCurve }, + false, + ['sign']); + assert(await subtle.verify( + { name: vector.name, hash: vector.hash }, + publicKey, + vector.signature, + vector.plaintext)); + await assert.rejects( + subtle.sign( + { name: vector.name, hash: vector.hash }, + privateKey, + vector.plaintext), + { name: 'OperationError' }); + continue; + } variations.push(testVerify(vector)); variations.push(testSign(vector)); } diff --git a/test/parallel/test-webcrypto-sign-verify-eddsa.js b/test/parallel/test-webcrypto-sign-verify-eddsa.js index 3c40139754be..ad587a1220e0 100644 --- a/test/parallel/test-webcrypto-sign-verify-eddsa.js +++ b/test/parallel/test-webcrypto-sign-verify-eddsa.js @@ -92,7 +92,7 @@ async function testVerify({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: crypto.getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, @@ -219,7 +219,7 @@ async function testSign({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: crypto.getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, diff --git a/test/parallel/test-webcrypto-sign-verify-hmac.js b/test/parallel/test-webcrypto-sign-verify-hmac.js index ac3841fad79a..bbe5dd498109 100644 --- a/test/parallel/test-webcrypto-sign-verify-hmac.js +++ b/test/parallel/test-webcrypto-sign-verify-hmac.js @@ -6,6 +6,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { getFips } = require('crypto'); const { subtle } = globalThis.crypto; const vectors = require('../fixtures/crypto/hmac')(); @@ -35,7 +36,7 @@ async function testVerify({ hash, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, @@ -126,7 +127,7 @@ async function testSign({ hash, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, diff --git a/test/parallel/test-webcrypto-sign-verify-kmac.js b/test/parallel/test-webcrypto-sign-verify-kmac.js index f93fc293b2a4..160067b9b760 100644 --- a/test/parallel/test-webcrypto-sign-verify-kmac.js +++ b/test/parallel/test-webcrypto-sign-verify-kmac.js @@ -5,16 +5,29 @@ 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)) common.skip('requires OpenSSL >= 3'); const assert = require('assert'); const { subtle } = globalThis.crypto; +const fips4 = hasFIPS(4); const vectors = require('../fixtures/crypto/kmac')(); +function isFipsUnsupported(err) { + return err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'; +} + +function isFips4Incompatible({ key, keyLength, outputLength }) { + const keyLengthInBits = keyLength ?? key.byteLength * 8; + return keyLengthInBits < 128 || + keyLengthInBits % 8 !== 0 || + outputLength % 8 !== 0; +} + async function testVerify({ algorithm, key, keyLength, @@ -193,8 +206,13 @@ async function testSign({ algorithm, const variations = []; for (const vector of vectors) { - variations.push(testVerify(vector)); - variations.push(testSign(vector)); + if (fips4 && isFips4Incompatible(vector)) { + variations.push(assert.rejects(testVerify(vector), isFipsUnsupported)); + variations.push(assert.rejects(testSign(vector), isFipsUnsupported)); + } else { + variations.push(testVerify(vector)); + variations.push(testSign(vector)); + } } await Promise.all(variations); @@ -209,25 +227,47 @@ async function testSign({ algorithm, ['sign', 'verify']); const algorithm = { name: 'KMAC128', - outputLength: 9, + outputLength: fips4 ? 16 : 9, customization: new Uint8Array(), }; const data = new Uint8Array([1, 2, 3]); + if (fips4) { + await assert.rejects( + subtle.sign({ ...algorithm, outputLength: 9 }, key, data), + isFipsUnsupported); + } + const signature = await subtle.sign(algorithm, key, data); assert.strictEqual(signature.byteLength, 2); - assert.strictEqual(new Uint8Array(signature)[1] & 0b01111111, 0); + if (!fips4) + assert.strictEqual(new Uint8Array(signature)[1] & 0b01111111, 0); assert(await subtle.verify(algorithm, key, signature, data)); - const signature16 = new Uint8Array(await subtle.sign({ - ...algorithm, - outputLength: 16, - }, key, data)); - signature16[1] &= 0b10000000; - assert.notDeepStrictEqual(new Uint8Array(signature), signature16); + if (fips4) { + const signature128 = await subtle.sign({ + ...algorithm, + outputLength: 128, + }, key, data); + assert.strictEqual(signature128.byteLength, 16); + assert(await subtle.verify({ + ...algorithm, + outputLength: 128, + }, key, signature128, data)); + } else { + const signature16 = new Uint8Array(await subtle.sign({ + ...algorithm, + outputLength: 16, + }, key, data)); + signature16[1] &= 0b10000000; + assert.notDeepStrictEqual(new Uint8Array(signature), signature16); + } const invalidSignature = new Uint8Array(signature); - invalidSignature[1] |= 0b00000001; + if (fips4) + invalidSignature[0] ^= 0b00000001; + else + invalidSignature[1] |= 0b00000001; assert(!(await subtle.verify(algorithm, key, invalidSignature, data))); const nonByteKey = await subtle.importKey( @@ -236,15 +276,20 @@ async function testSign({ algorithm, { name: 'KMAC128', length: 25 }, false, ['sign', 'verify']); - const nonByteKeySignature = await subtle.sign({ + const nonByteKeySignature = subtle.sign({ ...algorithm, outputLength: 16, }, nonByteKey, data); - assert.strictEqual(nonByteKeySignature.byteLength, 2); - assert(await subtle.verify({ - ...algorithm, - outputLength: 16, - }, nonByteKey, nonByteKeySignature, data)); + if (fips4) { + await assert.rejects(nonByteKeySignature, isFipsUnsupported); + } else { + const result = await nonByteKeySignature; + assert.strictEqual(result.byteLength, 2); + assert(await subtle.verify({ + ...algorithm, + outputLength: 16, + }, nonByteKey, result, data)); + } })().then(common.mustCall()); (async function() { @@ -265,9 +310,14 @@ async function testSign({ algorithm, assert.strictEqual(key.algorithm.length, keyData.byteLength * 8); const algorithm = { name, outputLength: 256 }; - const signature = await subtle.sign(algorithm, key, data); - assert.strictEqual(signature.byteLength, 32); - assert(await subtle.verify(algorithm, key, signature, data)); + const signature = subtle.sign(algorithm, key, data); + if (fips4) { + await assert.rejects(signature, isFipsUnsupported); + } else { + const result = await signature; + assert.strictEqual(result.byteLength, 32); + assert(await subtle.verify(algorithm, key, result, data)); + } } } })().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js index b11e65ade791..67c85f92e4e2 100644 --- a/test/parallel/test-webcrypto-sign-verify-ml-dsa.js +++ b/test/parallel/test-webcrypto-sign-verify-ml-dsa.js @@ -43,7 +43,7 @@ async function testVerify({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: crypto.getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, @@ -156,7 +156,7 @@ async function testSign({ name, subtle.generateKey( { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: crypto.getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, diff --git a/test/parallel/test-webcrypto-sign-verify-rsa.js b/test/parallel/test-webcrypto-sign-verify-rsa.js index 0ccbf431f147..3e706941595d 100644 --- a/test/parallel/test-webcrypto-sign-verify-rsa.js +++ b/test/parallel/test-webcrypto-sign-verify-rsa.js @@ -6,7 +6,10 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const fips3 = hasFIPS(3); +const rejectsSha1Signing = hasFIPS(3) && !hasFIPS(3, 5); const rsa_pkcs = require('../fixtures/crypto/rsa_pkcs'); const rsa_pss = require('../fixtures/crypto/rsa_pss'); @@ -194,6 +197,23 @@ async function testSign({ }); } +async function testFipsSignRejected({ + algorithm, + hash, + privateKeyBuffer, + plaintext, +}) { + const privateKey = await subtle.importKey( + 'pkcs8', + privateKeyBuffer, + { name: algorithm.name, hash }, + false, + ['sign']); + await assert.rejects( + subtle.sign(algorithm, privateKey, plaintext), + { name: 'OperationError' }); +} + async function testSaltLength(keyLength, hash, hLen) { const { publicKey, privateKey } = await subtle.generateKey({ name: 'RSA-PSS', @@ -205,7 +225,8 @@ async function testSaltLength(keyLength, hash, hLen) { const data = Buffer.from('Hello, world!'); const max = keyLength / 8 - hLen - 2; - const signature = await subtle.sign({ name: 'RSA-PSS', saltLength: max }, privateKey, data); + const signature = await subtle.sign( + { name: 'RSA-PSS', saltLength: max }, privateKey, data); await assert.rejects( subtle.sign({ name: 'RSA-PSS', saltLength: max + 1 }, privateKey, data), (err) => { assert.strictEqual(err.name, 'OperationError'); @@ -213,7 +234,8 @@ async function testSaltLength(keyLength, hash, hLen) { assert.strictEqual(err.cause?.message, `The value of "algorithm.saltLength" is out of range. It must be >= 0 && <= ${max}. Received ${max + 1}`); return true; }); - await subtle.verify({ name: 'RSA-PSS', saltLength: max }, publicKey, signature, data); + await subtle.verify( + { name: 'RSA-PSS', saltLength: max }, publicKey, signature, data); await assert.rejects( subtle.verify({ name: 'RSA-PSS', saltLength: max + 1 }, publicKey, signature, data), (err) => { assert.strictEqual(err.name, 'OperationError'); @@ -228,14 +250,28 @@ async function testSaltLength(keyLength, hash, hLen) { rsa_pkcs().forEach((vector) => { variations.push(testVerify(vector)); - variations.push(testSign(vector)); + variations.push(rejectsSha1Signing && vector.hash === 'SHA-1' ? + testFipsSignRejected(vector) : testSign(vector)); }); rsa_pss().forEach((vector) => { variations.push(testVerify(vector)); - variations.push(testSign(vector)); + variations.push(rejectsSha1Signing && vector.hash === 'SHA-1' ? + testFipsSignRejected(vector) : testSign(vector)); }); - for (const keyLength of [1024, 2048]) { + if (fips3) { + variations.push(assert.rejects( + subtle.generateKey({ + name: 'RSA-PSS', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, false, ['sign', 'verify']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_RSA_INVALID_MODULUS')); + } + + for (const keyLength of fips3 ? [2048] : [1024, 2048]) { for (const [hash, hLen] of [ ['SHA-1', 20], ['SHA-256', 32], @@ -247,6 +283,8 @@ async function testSaltLength(keyLength, hash, hLen) { ['SHA3-512', 64], ] : []), ]) { + if (rejectsSha1Signing && hash === 'SHA-1') + continue; variations.push(testSaltLength(keyLength, hash, hLen)); } } diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 0a6f5cffe7b9..db6c9e093872 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -8,6 +8,7 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); +const { getFips } = require('crypto'); const { subtle } = globalThis.crypto; // This is only a partial test. The WebCrypto Web Platform Tests @@ -19,7 +20,7 @@ const { subtle } = globalThis.crypto; const ec = new TextEncoder(); const { publicKey, privateKey } = await subtle.generateKey({ name: 'RSASSA-PKCS1-v1_5', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, true, ['sign', 'verify']); diff --git a/test/parallel/test-webcrypto-supports.mjs b/test/parallel/test-webcrypto-supports.mjs index 43d3ee03c8bc..2bc9d589b478 100644 --- a/test/parallel/test-webcrypto-supports.mjs +++ b/test/parallel/test-webcrypto-supports.mjs @@ -4,6 +4,11 @@ if (!common.hasCrypto) common.skip('missing crypto'); import * as assert from 'node:assert'; +import { hasFIPS } from '../common/crypto.js'; + +if (hasFIPS(3)) + common.skip('SubtleCrypto.supports() does not reflect FIPS provider availability'); + const { SubtleCrypto } = globalThis; const sources = [ diff --git a/test/parallel/test-webcrypto-wrap-unwrap.js b/test/parallel/test-webcrypto-wrap-unwrap.js index 18ba6dc37963..342eae0859e4 100644 --- a/test/parallel/test-webcrypto-wrap-unwrap.js +++ b/test/parallel/test-webcrypto-wrap-unwrap.js @@ -5,10 +5,13 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { hasOpenSSL } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); const assert = require('assert'); +const { getFips } = require('crypto'); const { subtle } = globalThis.crypto; +const fips3 = hasFIPS(3); +const fips35 = hasFIPS(3, 5); const kWrappingData = { 'RSA-OAEP': { @@ -35,7 +38,7 @@ const kWrappingData = { wrap: { iv: new Uint8Array(16), additionalData: new Uint8Array(16), - tagLength: 64 + tagLength: fips3 ? 128 : 64 }, pair: false }, @@ -54,7 +57,10 @@ const kWrappingData = { } }; -if (hasOpenSSL(3)) { +if (fips3) + delete kWrappingData['ChaCha20-Poly1305']; + +if (hasOpenSSL(3) && !fips3) { kWrappingData['AES-OCB'] = { generate: { length: 128 }, wrap: { @@ -87,7 +93,7 @@ async function generateKeysToWrap() { { algorithm: { name: 'RSASSA-PKCS1-v1_5', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -98,7 +104,7 @@ async function generateKeysToWrap() { { algorithm: { name: 'RSA-PSS', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -109,7 +115,7 @@ async function generateKeysToWrap() { { algorithm: { name: 'RSA-OAEP', - modulusLength: 1024, + modulusLength: getFips() === 1 ? 2048 : 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, @@ -234,6 +240,20 @@ async function generateKeysToWrap() { common.printSkipMessage('Skipping unsupported Curve test cases'); } + if (fips3) { + const unsupported = new Set([ + 'ChaCha20-Poly1305', + ]); + if (fips35) { + unsupported.add('X25519'); + unsupported.add('X448'); + } + for (let i = parameters.length - 1; i >= 0; --i) { + if (unsupported.has(parameters[i].algorithm.name)) + parameters.splice(i, 1); + } + } + const allkeys = await Promise.all(parameters.map(async (params) => { const usages = 'usages' in params ? params.usages : @@ -360,6 +380,37 @@ function testWrapping(name, keys) { } (async function() { + if (fips3) { + await assert.rejects( + subtle.generateKey( + { name: 'ChaCha20-Poly1305' }, true, ['wrapKey']), + { name: 'NotSupportedError' }); + + if (fips35) { + for (const name of ['X25519', 'X448']) { + await assert.rejects( + subtle.generateKey({ name }, true, ['deriveBits']), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + } + } + + const wrappingKey = await subtle.generateKey( + { name: 'AES-OCB', length: 128 }, true, ['wrapKey']); + const key = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256', length: 256 }, + true, + ['sign']); + await assert.rejects( + subtle.wrapKey( + 'raw', + key, + wrappingKey, + { name: 'AES-OCB', iv: new Uint8Array(15), tagLength: 128 }), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + } + await generateWrappingKeys(); const keys = await generateKeysToWrap(); const variations = []; diff --git a/test/pummel/test-crypto-dh-keys.js b/test/pummel/test-crypto-dh-keys.js index 8aa1e30e354f..824628732a55 100644 --- a/test/pummel/test-crypto-dh-keys.js +++ b/test/pummel/test-crypto-dh-keys.js @@ -32,11 +32,21 @@ if (common.isPi()) { const assert = require('assert'); const crypto = require('crypto'); +const { hasFIPS } = require('../common/crypto'); for (const name of ['modp1', 'modp2', 'modp5', 'modp14', 'modp15', 'modp16', 'modp17']) { // modp1 is 768 bits, FIPS requires >= 1024. // BoringSSL does not support modp1 or modp2. - if ((name === 'modp1' && crypto.getFips()) || + if (hasFIPS(3) && ['modp1', 'modp2', 'modp5'].includes(name)) { + const parameters = crypto.getDiffieHellman(name); + const group = crypto.createDiffieHellman( + parameters.getPrime(), parameters.getGenerator()); + assert.throws(() => group.generateKeys(), { + code: 'ERR_CRYPTO_OPERATION_FAILED', + }); + continue; + } + if ((name === 'modp1' && crypto.getFips() === 1) || (process.features.openssl_is_boringssl && (name === 'modp1' || name === 'modp2'))) { common.printSkipMessage(`Skipping unsupported ${name} test case`); diff --git a/test/pummel/test-dh-regr.js b/test/pummel/test-dh-regr.js index 961bea3246fb..c442fbc3a809 100644 --- a/test/pummel/test-dh-regr.js +++ b/test/pummel/test-dh-regr.js @@ -32,15 +32,30 @@ if (common.isPi()) { const assert = require('assert'); const crypto = require('crypto'); -const { hasOpenSSL3 } = require('../common/crypto'); +const { hasOpenSSL, hasFIPS } = require('../common/crypto'); -// FIPS requires length >= 1024 but we use 512/256 in this test to keep it from -// taking too long and timing out in CI. -const length = crypto.getFips() ? 1024 : hasOpenSSL3 ? 512 : 256; +let p; +let iterations = 2000; +if (hasFIPS(3)) { + assert.throws(() => crypto.createDiffieHellman(1024), { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + }); -const p = crypto.createDiffieHellman(length).getPrime(); + // Use a precomputed approved group instead of generating a 2048-bit prime + // for every test run. Its larger keys also make each pummel iteration more + // expensive, so use enough iterations to exercise the regression without + // making the FIPS job excessively slow. + p = crypto.getDiffieHellman('modp14').getPrime(); + iterations = 100; +} else { + // FIPS requires length >= 1024, but small parameters keep this pummel test + // from timing out in ordinary CI. + const length = crypto.getFips() === 1 ? 1024 : (hasOpenSSL(3) ? 512 : 256); + p = crypto.createDiffieHellman(length).getPrime(); +} -for (let i = 0; i < 2000; i++) { +for (let i = 0; i < iterations; i++) { const a = crypto.createDiffieHellman(p); const b = crypto.createDiffieHellman(p); diff --git a/test/pummel/test-webcrypto-derivebits-pbkdf2.js b/test/pummel/test-webcrypto-derivebits-pbkdf2.js index bfb01ac0c94f..9e6cdcad8ab0 100644 --- a/test/pummel/test-webcrypto-derivebits-pbkdf2.js +++ b/test/pummel/test-webcrypto-derivebits-pbkdf2.js @@ -11,7 +11,9 @@ if (common.isPi()) { } const assert = require('assert'); +const { hasFIPS } = require('../common/crypto'); const { subtle } = globalThis.crypto; +const fips4 = hasFIPS(4); function getDeriveKeyInfo(name, length, hash, ...usages) { return [{ name, length, hash }, usages]; @@ -632,6 +634,19 @@ async function testWrongKeyType( Object.keys(kDerivations[size][saltSize][hash]) .forEach((iterations) => { const args = [baseKeys, size, saltSize, hash, iterations | 0]; + if (fips4 && + (size === 'empty' || saltSize !== 'long' || iterations < 1000)) { + variations.push(assert.rejects( + testDeriveBits(...args), { name: 'OperationError' })); + kDerivedKeyTypes.forEach((keyType) => { + const keyArgs = getDeriveKeyInfo(...keyType); + variations.push(assert.rejects( + testDeriveKey(...args, ...keyArgs), + { name: 'OperationError' })); + }); + return; + } + variations.push(testDeriveBits(...args)); variations.push(testDeriveBitsBadLengths(...args)); variations.push(testDeriveBitsBadHash(...args)); @@ -674,12 +689,17 @@ async function testWrongKeyType( // https://github.com/w3c/webcrypto/pull/380 { - crypto.subtle.importKey('raw', new Uint8Array(0), 'PBKDF2', false, ['deriveBits']).then((key) => { + crypto.subtle.importKey( + 'raw', + new Uint8Array(fips4 ? 8 : 0), + 'PBKDF2', + false, + ['deriveBits']).then((key) => { return crypto.subtle.deriveBits({ name: 'PBKDF2', hash: { name: 'SHA-256' }, - iterations: 10, - salt: new Uint8Array(0), + iterations: fips4 ? 1000 : 10, + salt: new Uint8Array(fips4 ? 16 : 0), }, key, 0); }).then((bits) => { assert.deepStrictEqual(bits, new ArrayBuffer(0)); diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js index 9a976b7b8d86..0842f8fdfa98 100644 --- a/test/sequential/test-async-wrap-getasyncid.js +++ b/test/sequential/test-async-wrap-getasyncid.js @@ -141,6 +141,7 @@ function testInitialized(req, ctor_name) { if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check const crypto = require('crypto'); + const { hasFIPS } = require('../common/crypto'); // The handle for PBKDF2 and RandomBytes isn't returned by the function call, // so need to check it from the callback. @@ -154,7 +155,8 @@ if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check testInitialized(this, 'RandomBytesJob'); })); - if (typeof internalBinding('crypto').ScryptJob === 'function') { + if (typeof internalBinding('crypto').ScryptJob === 'function' && + !hasFIPS(3)) { crypto.scrypt('password', 'salt', 8, common.mustCall(function() { testInitialized(this, 'ScryptJob'); })); diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 8ec27109eeca..ff5f258db904 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -1,14 +1,20 @@ 'use strict'; -const { hasOpenSSL } = require('../../common/crypto.js'); +const { + hasOpenSSL, + hasFIPS, +} = require('../../common/crypto.js'); const conditionalFileSkips = {}; const conditionalSubtestSkips = {}; function skip(...files) { for (const file of files) { + const provider = process.features.openssl_is_boringssl ? + 'BoringSSL' : + `OpenSSL ${process.versions.openssl}${hasFIPS(3) ? ' FIPS mode' : ''}`; conditionalFileSkips[file] = { - 'skip': 'Unsupported in ' + (process.features.openssl_is_boringssl ? 'BoringSSL' : `OpenSSL ${process.versions.openssl}`), + 'skip': `Unsupported in ${provider}`, }; } } @@ -91,6 +97,80 @@ if (process.features.openssl_is_boringssl) { ['supports-modern.tentative.https.any.js', /ml-kem-512/i]); } +if (hasFIPS(3)) { + skip( + 'encrypt_decrypt/aes_ocb.tentative.https.any.js', + 'encrypt_decrypt/chacha20_poly1305.tentative.https.any.js', + 'generateKey/failures_chacha20_poly1305.tentative.https.any.js', + 'generateKey/successes_chacha20_poly1305.tentative.https.any.js', + 'import_export/ChaCha20-Poly1305_importKey.tentative.https.any.js', + 'serialization/chacha20-poly1305.tentative.https.any.js'); + + skipSubtests( + [ + 'supports-modern.tentative.https.any.js', + /(?:ChaCha20-Poly1305|^supports returns (?:true|false) for algorithm objects with (?:valid|invalid) parameters$)/, + ], + [ + 'wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js', + /(?=.*(?:RSASSA-PKCS1-v1_5|RSA-PSS|RSA-OAEP) private key)(?=.*non-extractable)/, + ]); +} + +// OpenSSL 3.0 through 3.3 reject SHA-1 signature generation in FIPS mode. +// OpenSSL 3.4 permits it for legacy use cases while marking the operation as +// non-approved through a per-operation FIPS indicator. Node does not expose +// that indicator, so the round-trip tests succeed. +if (hasFIPS(3) && !hasOpenSSL(3, 4)) { + skipSubtests( + ['sign_verify/ecdsa.https.any.js', /with SHA-1.*round trip$/], + ['sign_verify/rsa_pkcs.https.any.js', /with SHA-1.*round trip$/], + ['sign_verify/rsa_pss.https.any.js', /with SHA-1.*round trip$/]); +} + +if (hasFIPS(3, 5)) { + skip( + 'derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js', + 'derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js', + 'derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js', + 'derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js', + 'generateKey/successes_X25519.https.any.js', + 'generateKey/successes_X448.tentative.https.any.js', + 'import_export/okp_importKey_X25519.https.any.js', + 'import_export/okp_importKey_X448.tentative.https.any.js', + 'import_export/okp_importKey_failures_X25519.https.any.js', + 'import_export/okp_importKey_failures_X448.tentative.https.any.js', + 'serialization/x25519.https.any.js', + 'serialization/x448.tentative.https.any.js'); + + skipSubtests( + [ + 'derive_bits_keys/derived_bits_length.https.any.js', + /^X25519 derivation/, + ], + ['getPublicKey.tentative.https.any.js', /(?:X25519|X448)/], + [ + 'import_export/raw_format_aliases.tentative.https.any.js', + /(?:X25519|X448)/, + ], + [ + 'supports.tentative.https.any.js', + /(?:X25519|^deriveKey promise tests$)/, + ], + [ + 'wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js', + /(?=.*(?:X25519|X448))(?=.*(?:jwk|as non-extractable using pkcs8))/, + ]); +} + +if (hasFIPS(4)) { + skipSubtests( + [ + 'derive_bits_keys/pbkdf2.https.any.js', + /(?:empty password|(?:short|empty) salt|with 1 iterations)/, + ]); +} + skipSubtests( ['digest/kangarootwelve.tentative.https.any.js', /C=(?:\d{4,}|5(?:1[3-9]|[2-9]\d)|[6-9]\d{2}) bytes/]); diff --git a/test/wpt/test-webcrypto.js b/test/wpt/test-webcrypto.js index 0d53a51901bb..0828047b3799 100644 --- a/test/wpt/test-webcrypto.js +++ b/test/wpt/test-webcrypto.js @@ -4,10 +4,38 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +const { join } = require('node:path'); +const { hasFIPS } = require('../common/crypto'); const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('WebCryptoAPI'); runner.pretendGlobalThisAs('Window'); +if (hasFIPS(3, 5)) { + const supportsFile = join( + 'WebCryptoAPI', + 'supports.tentative.https.any.js'); + const eagerX25519Key = ` deriveBitsParams: { + name: 'X25519', + public: crypto.subtle.generateKey('X25519', false, ['deriveBits']), + },`; + const unavailableX25519Key = ` deriveBitsParams: { + name: 'X25519', + public: undefined, + },`; + runner.setScriptModifier((script) => { + if (!script.filename.endsWith(supportsFile)) + return; + + const fragments = script.code.split(eagerX25519Key); + if (fragments.length !== 2) { + throw new Error( + `Expected exactly one eager X25519 key in ${script.filename}; ` + + `found ${fragments.length - 1}`); + } + script.code = fragments.join(unavailableX25519Key); + }); +} + runner.runJsTests(); From 4b890a60531a3dedff2280f3e21cf0805ef98761 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 2 Aug 2026 15:01:19 +0200 Subject: [PATCH 2/3] tools: add an OpenSSL FIPS build to test-shared.yml Signed-off-by: Filip Skokan --- tools/dep_updaters/update-nixpkgs-pin.sh | 6 ++++ tools/nix/openssl-fips.nix | 38 ++++++++++++++++++++++++ tools/nix/openssl-matrix.nix | 5 ++++ 3 files changed, 49 insertions(+) create mode 100644 tools/nix/openssl-fips.nix diff --git a/tools/dep_updaters/update-nixpkgs-pin.sh b/tools/dep_updaters/update-nixpkgs-pin.sh index a025676e20a7..cf7cedab2c5a 100755 --- a/tools/dep_updaters/update-nixpkgs-pin.sh +++ b/tools/dep_updaters/update-nixpkgs-pin.sh @@ -72,6 +72,12 @@ nix-instantiate -I "nixpkgs=$NIXPKGS_PIN_FILE" --eval --strict --json -E " inherit (pkgs) \(.attrs | sort | join("\n ")) ; + + # Keep this aligned with the bundled OpenSSL major/minor. + openssl_3_5_fips = import ./openssl-fips.nix { + inherit pkgs; + openssl = pkgs.openssl_3_5; + }; }"' > "$OPENSSL_MATRIX_FILE" cat -</dev/null + if "$bin/bin/openssl" md5 /dev/null 2>&1; then + echo "MD5 unexpectedly available with FIPS properties enabled" >&2 + exit 1 + fi + ''; +}) diff --git a/tools/nix/openssl-matrix.nix b/tools/nix/openssl-matrix.nix index 36978c5d4efc..c8f55850e756 100644 --- a/tools/nix/openssl-matrix.nix +++ b/tools/nix/openssl-matrix.nix @@ -13,4 +13,9 @@ openssl_3_6 openssl_4_0 ; + + # Keep this aligned with the bundled OpenSSL major/minor. + openssl_3_5_fips = import ./openssl-fips.nix { + openssl = pkgs.openssl_3_5; + }; } From 5699e8d72c8f2be9db72ed5c627b4a61b2bd0fe5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 3 Aug 2026 14:38:54 +0200 Subject: [PATCH 3/3] fixup! test: update tests to run with OpenSSL >= 3.0 FIPS mode --- test/parallel/test-crypto-argon2-job.js | 4 +- test/parallel/test-crypto-argon2.js | 13 +++++- test/parallel/test-crypto-key-store.js | 44 +++++++++++-------- .../test-webcrypto-derivebits-argon2.js | 4 +- ...-webcrypto-promise-prototype-pollution.mjs | 13 ++++++ ...t-crypto-argon2-nonblocking-constructor.js | 4 +- test/wpt/status/WebCryptoAPI.cjs | 2 +- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/test/parallel/test-crypto-argon2-job.js b/test/parallel/test-crypto-argon2-job.js index 37a0127958cf..b5bf48e197eb 100644 --- a/test/parallel/test-crypto-argon2-job.js +++ b/test/parallel/test-crypto-argon2-job.js @@ -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 diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js index 1f238e61a61d..447812877e9c 100644 --- a/test/parallel/test-crypto-argon2.js +++ b/test/parallel/test-crypto-argon2.js @@ -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'); @@ -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 diff --git a/test/parallel/test-crypto-key-store.js b/test/parallel/test-crypto-key-store.js index 72826afaaeff..fdc82cc9d579 100644 --- a/test/parallel/test-crypto-key-store.js +++ b/test/parallel/test-crypto-key-store.js @@ -2,7 +2,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)) common.skip('requires OpenSSL 3.x'); @@ -84,26 +84,32 @@ const data = Buffer.from('hello store'); } { - const alice = generateKeyPairSync('x25519'); - const bob = generateKeyPairSync('x25519'); - const file = path.join(tmpdir.path, 'x25519.pem'); - fs.writeFileSync(file, alice.privateKey.export({ - format: 'pem', - type: 'pkcs8', - })); - const url = pathToFileURL(file); + if (hasFIPS(3, 5)) { + assert.throws(() => generateKeyPairSync('x25519'), { + code: 'ERR_OSSL_EVP_UNSUPPORTED', + }); + } else { + const alice = generateKeyPairSync('x25519'); + const bob = generateKeyPairSync('x25519'); + const file = path.join(tmpdir.path, 'x25519.pem'); + fs.writeFileSync(file, alice.privateKey.export({ + format: 'pem', + type: 'pkcs8', + })); + const url = pathToFileURL(file); - const expected = diffieHellman({ - privateKey: alice.privateKey, - publicKey: bob.publicKey, - }); - assert.deepStrictEqual( - diffieHellman({ privateKey: url, publicKey: bob.publicKey }), - expected); + const expected = diffieHellman({ + privateKey: alice.privateKey, + publicKey: bob.publicKey, + }); + assert.deepStrictEqual( + diffieHellman({ privateKey: url, publicKey: bob.publicKey }), + expected); - if (hasOpenSSL(3, 2)) { - const { sharedKey, ciphertext } = encapsulate(alice.publicKey); - assert.deepStrictEqual(decapsulate(url, ciphertext), sharedKey); + if (hasOpenSSL(3, 2)) { + const { sharedKey, ciphertext } = encapsulate(alice.publicKey); + assert.deepStrictEqual(decapsulate(url, ciphertext), sharedKey); + } } } diff --git a/test/parallel/test-webcrypto-derivebits-argon2.js b/test/parallel/test-webcrypto-derivebits-argon2.js index e2b465ab206b..129b716c5919 100644 --- a/test/parallel/test-webcrypto-derivebits-argon2.js +++ b/test/parallel/test-webcrypto-derivebits-argon2.js @@ -5,10 +5,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'); const assert = require('assert'); const { createSecretKey } = require('crypto'); diff --git a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs index a92dbcdd8ac7..6d8a3fa3df9f 100644 --- a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs +++ b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs @@ -34,6 +34,11 @@ const fips35UnavailableKeyGeneration = new Set([ 'X25519', 'X448', ]); +const fips3UnavailableDerivation = new Set([ + 'Argon2d', + 'Argon2i', + 'Argon2id', +]); Promise.prototype.then = common.mustNotCall('Promise.prototype.then'); @@ -1038,6 +1043,14 @@ for (const [name, operations] of supportedAlgorithms) { assert(fixture, `missing prototype pollution fixture for ${name}`); const ctx = { __proto__: null }; + if (fips3 && fips3UnavailableDerivation.has(name)) { + await fixture.importKey(ctx); + await assert.rejects( + fixture.deriveBits(ctx), + (err) => err.name === 'OperationError' && + err.cause?.code === 'ERR_OSSL_EVP_UNSUPPORTED'); + continue; + } if ((fips3 && name === 'ChaCha20-Poly1305') || (fips35 && fips35UnavailableKeyGeneration.has(name))) { const expected = name === 'ChaCha20-Poly1305' ? diff --git a/test/pummel/test-crypto-argon2-nonblocking-constructor.js b/test/pummel/test-crypto-argon2-nonblocking-constructor.js index 37cb5363d43c..4e0ff50c3fa3 100644 --- a/test/pummel/test-crypto-argon2-nonblocking-constructor.js +++ b/test/pummel/test-crypto-argon2-nonblocking-constructor.js @@ -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'); // Regression test for https://github.com/nodejs/node/issues/62861. // `AdditionalConfig` used to invoke the full Argon2 KDF synchronously inside diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index ff5f258db904..8dfd37b33b34 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -43,7 +43,7 @@ if (!hasOpenSSL(3, 0)) { 'sign_verify/kmac.tentative.https.any.js'); } -if (!hasOpenSSL(3, 2)) { +if (!hasOpenSSL(3, 2) || hasFIPS(3)) { skip( 'derive_bits_keys/argon2.tentative.https.any.js', 'import_export/Argon2_importKey.tentative.https.any.js');