diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 81e6634a76e..025f47cd920 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2804,6 +2804,13 @@ const EVP_MD* getDigestByName(const char* name) { if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] { return EVP_sha1(); } +#if NCRYPTO_USE_BORINGSSL + // BoringSSL exposes SHA-3 through EVP_MD descriptors without registering + // their names or assigning them NIDs. + if (CaseInsensitiveNameEqual()(name, "sha3-256")) return EVP_sha3_256(); + if (CaseInsensitiveNameEqual()(name, "sha3-384")) return EVP_sha3_384(); + if (CaseInsensitiveNameEqual()(name, "sha3-512")) return EVP_sha3_512(); +#endif return EVP_get_digestbyname(name); } diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 7602e375bc3..1a49c6f4083 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -53,6 +53,8 @@ const { isKeyAlgorithmAvailable, } = internalBinding('crypto'); +const { openssl_is_boringssl: isBoringSSL } = process.features; + let fips; let fipsGeneration; function isFips() { @@ -533,7 +535,7 @@ function getConditionalAlgorithms() { 'Argon2d': !!Argon2Job && !fips, 'Argon2i': !!Argon2Job && !fips, 'Argon2id': !!Argon2Job && !fips, - 'ChaCha20-Poly1305': process.features.openssl_is_boringssl || + 'ChaCha20-Poly1305': isBoringSSL || has(ciphers, 'chacha20-poly1305'), 'cSHAKE128': has(hashes, 'shake128'), 'cSHAKE256': has(hashes, 'shake256'), @@ -550,15 +552,15 @@ function getConditionalAlgorithms() { 'ML-KEM-768': mlKem768, 'ML-KEM-1024': mlKem1024, 'MLKEM768-P256': !fips && mlKem768 && - (!process.features.openssl_is_boringssl || + (!isBoringSSL || (has(hashes, 'sha3-256') && has(hashes, 'shake256'))), 'MLKEM768-X25519': !fips && mlKem768 && - (!process.features.openssl_is_boringssl || + (!isBoringSSL || (has(hashes, 'sha3-256') && has(hashes, 'shake256'))), 'MLKEM1024-P384': !fips && mlKem1024 && - (!process.features.openssl_is_boringssl || + (!isBoringSSL || (has(hashes, 'sha3-256') && has(hashes, 'shake256'))), 'SHA-1': has(hashes, 'sha1'), @@ -847,6 +849,11 @@ function normalizeAlgorithm(algorithm, op) { throw lazyDOMException( `Only SHA hashes are supported in ${desiredType}`, 'NotSupportedError'); } + if (isBoringSSL && StringPrototypeStartsWith(hash.name, 'SHA3-')) { + throw lazyDOMException( + 'SHA-3 is only supported for digest operations', + 'NotSupportedError'); + } normalizedAlgorithm[member] = hash; } else if (idlType === 'AlgorithmIdentifier') { // This extension point is not used by any supported algorithm (yet?) diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 33705dbb9c3..63d086a7698 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -14,6 +14,8 @@ const { TypedArrayPrototypeIncludes, } = primordials; +const { openssl_is_boringssl: isBoringSSL } = process.features; + const { lazyDOMException, } = require('internal/util'); @@ -670,7 +672,7 @@ converters.ContextParams = createAlgorithmDictionaryConverter( const validateLength = (V) => validateMaxBufferLength(V, 'ContextParams.context', 255); - if (process.features.openssl_is_boringssl) { + if (isBoringSSL) { this.validator = validateLength; } else { let { 0: major, 1: minor } = diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index c58c3ac6477..fabf6654430 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -255,6 +255,12 @@ const std::vector& GetSupportedHashAlgorithms(Environment* env) { EVP_MD_do_all_provided(nullptr, SaveSupportedProviderHashAlgorithms, env); #else EVP_MD_do_all_sorted(SaveSupportedHashAlgorithms, env); +#endif +#if NCRYPTO_USE_BORINGSSL + // SHA-3 descriptors are not included in BoringSSL's name registry. + for (const char* name : {"sha3-256", "sha3-384", "sha3-512"}) { + env->supported_hash_algorithms.emplace_back(name); + } #endif } const uint64_t current_generation = ncrypto::getFipsStateGeneration(); diff --git a/test/fixtures/crypto/ecdsa.js b/test/fixtures/crypto/ecdsa.js index 6da56a0601f..677e553d6e0 100644 --- a/test/fixtures/crypto/ecdsa.js +++ b/test/fixtures/crypto/ecdsa.js @@ -2,7 +2,8 @@ const { isBoringSSL } = require('../../common/crypto'); -module.exports = function() { +module.exports = function({ includeUnsupported = false } = {}) { + const includeSha3 = !isBoringSSL || includeUnsupported; const pkcs8 = { 'P-256': Buffer.from( '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b020101' + @@ -74,7 +75,7 @@ module.exports = function() { 'b6a0a14d7e4bc6dd2eda82c9234f174b670b60c8f7d101f68fdf5889e02373b025' + 'dcbc4c82f2929b8e06c68535da98e38fe399c53a814b097935581ef21535eb', 'hex'), - ...(!isBoringSSL ? { + ...(includeSha3 ? { 'SHA3-256': Buffer.from( 'f6a48eb5557f484ed0c3e4b5c78a3cf497cbd346db06a4165d429248aa2cc51a69' + '747d09f57af145469a8b607a9b8b9709629d74e8f5ca337c6ddc581b6f6103', @@ -106,7 +107,7 @@ module.exports = function() { '72fbdb369fd34c1c54264d07f4facd69b02e4206f8a8bb259b882a305c56fde2d3' + '5107e493c53cd6b4af0b31306f4d03fd43cfc762a1030e17a3d775453a1212b142' + '9f7b3d93066a5f42a10b138cd177dc09616e827d598822d78d4627b754e6', 'hex'), - ...(!isBoringSSL ? { + ...(includeSha3 ? { 'SHA3-256': Buffer.from( '0b07c078be30fa5925a307d6fc559c5f398e63fb5d007d6b24a834847f2d3d18d5' + 'b5e840711c52a7bc6626c3ced93301e873c013a706f6b297c12cc6d47a71e0529e' + @@ -146,7 +147,7 @@ module.exports = function() { '01f0071e6a32867fa70f695cd39c4e87e142b9e4134d38740bd6fee354a575167e' + '13524e94832637910fe11e53a85fb21b91adb81bb1779c4e2b8bc87c717dc35084', 'hex'), - ...(!isBoringSSL ? { + ...(includeSha3 ? { 'SHA3-256': Buffer.from( '00463679f47a4c705e03447360dcf34d1743e0d4b2591cc66832a6bc80d92e538c' + '169a1fd330f98e7235ca7fec7e16ac44fb13095b8edf2c76b75c4845177d59e425' + @@ -172,7 +173,7 @@ module.exports = function() { const curves = ['P-256', 'P-384', 'P-521']; const hashes = ['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512']; - if (!isBoringSSL) { + if (includeSha3) { hashes.push('SHA3-256', 'SHA3-384', 'SHA3-512'); } diff --git a/test/fixtures/crypto/hmac.js b/test/fixtures/crypto/hmac.js index 55376a1299d..6505c6e2ae5 100644 --- a/test/fixtures/crypto/hmac.js +++ b/test/fixtures/crypto/hmac.js @@ -1,7 +1,5 @@ 'use strict'; -const { isBoringSSL } = require('../../common/crypto'); - module.exports = function () { const plaintext = Buffer.from( '5f4dba4f320c0ce876725afce5fbd25bf83e5a7125a08cafe73c3ebac421779df9d55d' + @@ -24,18 +22,16 @@ module.exports = function () { '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', 'hex'), - ...(!isBoringSSL ? { - 'SHA3-256': Buffer.from( - 'e588ec0811463d767241df1074b47ae4071b51f2ce36537ba69ccdc3fdc2b7a8', - 'hex'), - 'SHA3-384': Buffer.from( - '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + - 'd2354cd0bdc5e147d2e9009373494488', 'hex'), - 'SHA3-512': Buffer.from( - '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + - '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', - 'hex') - } : {}) + 'SHA3-256': Buffer.from( + 'e588ec0811463d767241df1074b47ae4071b51f2ce36537ba69ccdc3fdc2b7a8', + 'hex'), + 'SHA3-384': Buffer.from( + '6b1da28eab1f582ad9718effe05e23d5fd2c9877a2d9443f90bec093bece2ea7' + + 'd2354cd0bdc5e147d2e9009373494488', 'hex'), + 'SHA3-512': Buffer.from( + '5dcc359443aaf652fa1375d6b3e61fdcf29bb4a28bd5d3dcfa40f82f906bb280' + + '0455db03b5d31fb972a15a6d0103a24e56d156a119c0e5a1e92a44c3c5657cf9', + 'hex') } const signatures = { @@ -50,18 +46,16 @@ module.exports = function () { '61fb278c3ffb0cce2bf1cf723ddfd8ef1f931c0c618c25907324605939e3f9a2' + 'c6f4af690bda3407dc2f5770f6a0a44b954d64a332e3ee0821abf82b7f3e99c1', 'hex'), - ...(!isBoringSSL ? { - 'SHA3-256': Buffer.from( - 'c1ac5e11fcd50c48bf567f6e296632f5801c4eb07a8a47579b41dee971a3099b', - 'hex'), - 'SHA3-384': Buffer.from( - 'ac8c97f6dd8d9e16101063077c16b23fe291a5e6d149653e9ac7002365159317' + - 'adcfad511996578b0053a5c14b75f16c', 'hex'), - 'SHA3-512': Buffer.from( - '2162c2a8907e6b2f68599a69e81a464d8f076b5eeb555d98b4d20330034df3c7' + - 'cf35b1fa958a074ca12f0d242df39f0da3d4f1dbfb3629057798fe1f883974ee', - 'hex') - } : {}) + 'SHA3-256': Buffer.from( + 'c1ac5e11fcd50c48bf567f6e296632f5801c4eb07a8a47579b41dee971a3099b', + 'hex'), + 'SHA3-384': Buffer.from( + 'ac8c97f6dd8d9e16101063077c16b23fe291a5e6d149653e9ac7002365159317' + + 'adcfad511996578b0053a5c14b75f16c', 'hex'), + 'SHA3-512': Buffer.from( + '2162c2a8907e6b2f68599a69e81a464d8f076b5eeb555d98b4d20330034df3c7' + + 'cf35b1fa958a074ca12f0d242df39f0da3d4f1dbfb3629057798fe1f883974ee', + 'hex') } const vectors = []; diff --git a/test/fixtures/crypto/rsa_pkcs.js b/test/fixtures/crypto/rsa_pkcs.js index 74a8c0512ef..598097362e7 100644 --- a/test/fixtures/crypto/rsa_pkcs.js +++ b/test/fixtures/crypto/rsa_pkcs.js @@ -2,7 +2,8 @@ const { isBoringSSL } = require('../../common/crypto'); -module.exports = function () { +module.exports = function ({ includeUnsupported = false } = {}) { + const includeSha3 = !isBoringSSL || includeUnsupported; const pkcs8 = Buffer.from( '308204bf020100300d06092a864886f70d0101010500048204a9308204a50201000282' + '010100d3576092e62957364544e7e4233b7bdb293db2085122c479328546f9f0f712f6' + @@ -99,7 +100,7 @@ module.exports = function () { '7a6335c70e193235dcda48add6858626bd96311e60f7e5ea4491b6c1e6248afe12b' + 'bbd54f8869b043a5b0444562813f0a98b300356f306e6b783a29f3bec97ca40ea20' + '062cab8926ec5d96aa387cc84821a6d72b8ea126e7d', 'hex'), - ...(!isBoringSSL ? { + ...(includeSha3 ? { 'sha3-256': Buffer.from( 'be1b476c1911a01d71710fd8a2f3158d6f7839e91443b01bed30dfdd04336d80c6b' + 'f692c06fad254877901c10a73853e8fb202a29cddefdf16c3adcda1fc123625897d' + @@ -163,7 +164,7 @@ module.exports = function () { plaintext, signature: signatures['sha-512'] }, - ...(!isBoringSSL ? [ + ...(includeSha3 ? [ { publicKeyBuffer: spki, privateKeyBuffer: pkcs8, diff --git a/test/fixtures/crypto/rsa_pss.js b/test/fixtures/crypto/rsa_pss.js index 4aeaf3330e7..101122b2ffe 100644 --- a/test/fixtures/crypto/rsa_pss.js +++ b/test/fixtures/crypto/rsa_pss.js @@ -1,7 +1,5 @@ 'use strict'; -const { isBoringSSL } = require('../../common/crypto'); - module.exports = function() { const pkcs8 = Buffer.from( '308204bf020100300d06092a864886f70d0101010500048204a9308204a5020100028' + @@ -152,44 +150,42 @@ module.exports = function() { 'b68c04bfe452c3adc6c10066a915231b7b404727eb6201b4921eb96d9407de2b963' + '3879ceb71d759d9828d7b4d062f6ef100757d8328187caf57dfb859d1555345207c' + '1cce7905c3564c08fec78867a53d5a2cf84810e1ffa', 'hex'), - ...(!isBoringSSL ? { - 'sha3-512, no salt': Buffer.from( - 'd2430dc87abeaa7d13f7cec8510f1a296e1c608f44b1696829c59a99e8eefe9b2ee' + - '6ee8ad6fdc93c24fcba2f04d1da195924b6209717e1992c10ed9f4783478765fe34' + - '3e761203bff9d326bb6dc2061b0a7554c8ce0814b29249136c20c8e30054df0c6bc' + - '656509a82845149368896690e32ff5dd32ef01543686f01d6a69bb438b049e66a8b' + - 'df485a13edcd7dc482da4cc57d0b740aca3e56f0da247794e600afd27b22b6da13b' + - 'cc15dd2059b525f8cb6bcd07540aa843f0ae51d4b0eea27045485914b908bdd01d0' + - 'a9d42379f9f7180f4ad162ff73df5fed0200eb02ad01473975d54a77c15a9c61a3c' + - 'b5e27de5d1eecc363d45506f7123a5ddd115c5e4c9e', 'hex'), - 'sha3-256, salted': Buffer.from( - '59cb9cce6ae838eb20d38d6af4acb9b866b0753bb7df9e441037d788512c03279e8' + - '3d9a9cf5c0921fe1c0b6e8e895a8c0ad24a18b123f809b34ef2a3a1f05974030320' + - '435692ef5d378cef4368c3658c098a25371dfaf1c0b6910f653a4ec15f2c08956c1' + - '405136c2aba7f25a808fa7dbf57a4cb2978bd91af710b27ee239d955c8cac7a76ae' + - '9085cefeda2a585a99cc948f064b5da66a9c4aa4f3f767ac905a9f314b47038e05c' + - '3608fbb7e67a278e4f009a62c3cd3fdf43692e759d9361be1217999a76a69d4d119' + - 'f8791a90e207e46b3f6125721f56fd819292d06a3cdae2c62c9a1dc0d964a06036c' + - '8c18661cc6c873532a3536ab51e1ce210926db299e2', 'hex'), - 'sha3-384, salted': Buffer.from( - '8d1f9297c8169f27f0c58827dba991d862de58c1155f612ad2995d2bf862d051c4a' + - '91b48571849b0412384382e5b77990de6a3c84010046b35c4a504f175a3479483d9' + - '5c58f86bb96d53a27e59d6f67fddaae295ce90610f5086acc711557c2c85aac32d3' + - '24199cff2367ae44e1d91307a98c8cbfb085a8bce6b1c20714711bc15b0eddb7881' + - '823227d4be477ffdad8093663a6a1fc62eb39c49c2c3a821c2b202cf7904b49ca92' + - '3c83819602bb13931577354a80f99309030044935b1cd41f0513160e661db1959fb' + - '1ec15f087f3d288e875d54cbf070ec860b0aeecc951ea65e97cd5460750d4b7de52' + - '22cb9e7466b1f506ecf6a81fc399dfd8334160f9084', 'hex'), - 'sha3-512, salted': Buffer.from( - '7b6d7be418c5d37cc8070698b8b03d818ecd8b673d047d34921913f6d59c69cb496' + - '172d6118207d9ff92b8e1246acf0e03a845d935a70f8a82c3d5d6db6a1a0e337269' + - '4b904372413dcbaa7ac5486bc8ccaf70d7e9470be82b928a90017e272cf9761ed26' + - 'c160fe874a2675a4fb2acad72c50fbfffdd70b5a6f2919553d7ea1829934670f8de' + - 'f2a5c2816404b1aa153323c92c58400622f184b9b0463fa48d6b27091f68c287e3f' + - '6d9ab9eb451711a5d984c547f3d56f14a686a89ddf36c47ce25092b8c6530904de9' + - '5df7fc602fe9394315f1b1847aae304cb5ad71e2cb78acfbc997a87a9d62a6898bb' + - '6d84a81bb89b50186265f4be171a93d837a4bf777c8', 'hex') - } : {}) + 'sha3-512, no salt': Buffer.from( + 'd2430dc87abeaa7d13f7cec8510f1a296e1c608f44b1696829c59a99e8eefe9b2ee' + + '6ee8ad6fdc93c24fcba2f04d1da195924b6209717e1992c10ed9f4783478765fe34' + + '3e761203bff9d326bb6dc2061b0a7554c8ce0814b29249136c20c8e30054df0c6bc' + + '656509a82845149368896690e32ff5dd32ef01543686f01d6a69bb438b049e66a8b' + + 'df485a13edcd7dc482da4cc57d0b740aca3e56f0da247794e600afd27b22b6da13b' + + 'cc15dd2059b525f8cb6bcd07540aa843f0ae51d4b0eea27045485914b908bdd01d0' + + 'a9d42379f9f7180f4ad162ff73df5fed0200eb02ad01473975d54a77c15a9c61a3c' + + 'b5e27de5d1eecc363d45506f7123a5ddd115c5e4c9e', 'hex'), + 'sha3-256, salted': Buffer.from( + '59cb9cce6ae838eb20d38d6af4acb9b866b0753bb7df9e441037d788512c03279e8' + + '3d9a9cf5c0921fe1c0b6e8e895a8c0ad24a18b123f809b34ef2a3a1f05974030320' + + '435692ef5d378cef4368c3658c098a25371dfaf1c0b6910f653a4ec15f2c08956c1' + + '405136c2aba7f25a808fa7dbf57a4cb2978bd91af710b27ee239d955c8cac7a76ae' + + '9085cefeda2a585a99cc948f064b5da66a9c4aa4f3f767ac905a9f314b47038e05c' + + '3608fbb7e67a278e4f009a62c3cd3fdf43692e759d9361be1217999a76a69d4d119' + + 'f8791a90e207e46b3f6125721f56fd819292d06a3cdae2c62c9a1dc0d964a06036c' + + '8c18661cc6c873532a3536ab51e1ce210926db299e2', 'hex'), + 'sha3-384, salted': Buffer.from( + '8d1f9297c8169f27f0c58827dba991d862de58c1155f612ad2995d2bf862d051c4a' + + '91b48571849b0412384382e5b77990de6a3c84010046b35c4a504f175a3479483d9' + + '5c58f86bb96d53a27e59d6f67fddaae295ce90610f5086acc711557c2c85aac32d3' + + '24199cff2367ae44e1d91307a98c8cbfb085a8bce6b1c20714711bc15b0eddb7881' + + '823227d4be477ffdad8093663a6a1fc62eb39c49c2c3a821c2b202cf7904b49ca92' + + '3c83819602bb13931577354a80f99309030044935b1cd41f0513160e661db1959fb' + + '1ec15f087f3d288e875d54cbf070ec860b0aeecc951ea65e97cd5460750d4b7de52' + + '22cb9e7466b1f506ecf6a81fc399dfd8334160f9084', 'hex'), + 'sha3-512, salted': Buffer.from( + '7b6d7be418c5d37cc8070698b8b03d818ecd8b673d047d34921913f6d59c69cb496' + + '172d6118207d9ff92b8e1246acf0e03a845d935a70f8a82c3d5d6db6a1a0e337269' + + '4b904372413dcbaa7ac5486bc8ccaf70d7e9470be82b928a90017e272cf9761ed26' + + 'c160fe874a2675a4fb2acad72c50fbfffdd70b5a6f2919553d7ea1829934670f8de' + + 'f2a5c2816404b1aa153323c92c58400622f184b9b0463fa48d6b27091f68c287e3f' + + '6d9ab9eb451711a5d984c547f3d56f14a686a89ddf36c47ce25092b8c6530904de9' + + '5df7fc602fe9394315f1b1847aae304cb5ad71e2cb78acfbc997a87a9d62a6898bb' + + '6d84a81bb89b50186265f4be171a93d837a4bf777c8', 'hex') } const vectors = [ @@ -257,56 +253,54 @@ module.exports = function() { plaintext, signature: signatures['sha-512, salted'] }, - ...(!isBoringSSL ? [ - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 0 }, - hash: 'SHA3-256', - plaintext, - signature: signatures['sha3-256, no salt'] - }, - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 0 }, - hash: 'SHA3-384', - plaintext, - signature: signatures['sha3-384, no salt'] - }, - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 0 }, - hash: 'SHA3-512', - plaintext, - signature: signatures['sha3-512, no salt'] - }, - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 32 }, - hash: 'SHA3-256', - plaintext, - signature: signatures['sha3-256, salted'] - }, - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 48 }, - hash: 'SHA3-384', - plaintext, - signature: signatures['sha3-384, salted'] - }, - { - publicKeyBuffer: spki, - privateKeyBuffer: pkcs8, - algorithm: { name: 'RSA-PSS', saltLength: 64 }, - hash: 'SHA3-512', - plaintext, - signature: signatures['sha3-512, salted'] - } - ] : []), + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 0 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, no salt'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 32 }, + hash: 'SHA3-256', + plaintext, + signature: signatures['sha3-256, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 48 }, + hash: 'SHA3-384', + plaintext, + signature: signatures['sha3-384, salted'] + }, + { + publicKeyBuffer: spki, + privateKeyBuffer: pkcs8, + algorithm: { name: 'RSA-PSS', saltLength: 64 }, + hash: 'SHA3-512', + plaintext, + signature: signatures['sha3-512, salted'] + } ]; return vectors; diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index 8d58397783e..250646125fa 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -13,10 +13,9 @@ const shake128 = crypto.getHashes().includes('shake128'); const shake256 = crypto.getHashes().includes('shake256'); const cshake128 = crypto.getHashes().includes('cshake128'); const cshake256 = crypto.getHashes().includes('cshake256'); -const sha3 = crypto.getHashes().includes('sha3-256'); const ocb = hasOpenSSL(3) && crypto.getCiphers().includes('aes-128-ocb'); const kmac = hasOpenSSL(3) && crypto.getMacs().includes('kmac128'); -const hybridKems = !fips && pqc && (!boringSSL || (sha3 && shake256)); +const hybridKems = !fips && pqc && (!boringSSL || shake256); export const vectors = { diff --git a/test/fixtures/webcrypto/supports-sha3.mjs b/test/fixtures/webcrypto/supports-sha3.mjs index 699b72bb11b..a3221b1a2ec 100644 --- a/test/fixtures/webcrypto/supports-sha3.mjs +++ b/test/fixtures/webcrypto/supports-sha3.mjs @@ -1,28 +1,30 @@ -import { getHashes } from 'node:crypto'; +import { isBoringSSL } from '../../common/crypto.js'; import { ECDH, X25519 } from './supports-level-2.mjs'; -const hashes = getHashes(); -const hasSha3 = hashes.includes('sha3-256'); - const RSA_KEY_GEN = { modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]) }; export const vectors = { + 'sign': [ + [!isBoringSSL, { name: 'ECDSA', hash: 'SHA3-256' }], + [!isBoringSSL, { name: 'ECDSA', hash: 'SHA3-384' }], + [!isBoringSSL, { name: 'ECDSA', hash: 'SHA3-512' }], + ], 'digest': [ - [hasSha3, 'SHA3-256'], - [hashes.includes('sha3-384'), 'SHA3-384'], - [hashes.includes('sha3-512'), 'SHA3-512'], + [true, 'SHA3-256'], + [true, 'SHA3-384'], + [true, 'SHA3-512'], ], 'generateKey': [ - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], - [hasSha3, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!isBoringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], // This interaction is not defined for now. @@ -30,28 +32,28 @@ export const vectors = { [false, { name: 'HMAC', hash: 'SHA3-256' }], ], 'deriveKey': [ - [hasSha3, + [!isBoringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, { name: 'AES-CBC', length: 128 }], - [hasSha3, + [!isBoringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 'HKDF'], - [hasSha3, + [!isBoringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, { name: 'AES-CBC', length: 128 }], - [hasSha3, + [!isBoringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 'HKDF'], - [hasSha3 && X25519 !== undefined, + [!isBoringSSL && X25519 !== undefined, { name: 'X25519', public: X25519?.publicKey }, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], - [hasSha3, + [!isBoringSSL, { name: 'ECDH', public: ECDH.publicKey }, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], @@ -71,27 +73,27 @@ export const vectors = { { name: 'HMAC', hash: 'SHA3-256' }], ], 'deriveBits': [ - [hasSha3, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], - [hasSha3, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], + [!isBoringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 8], + [!isBoringSSL, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 0], [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, null], [false, { name: 'HKDF', hash: 'SHA3-256', salt: Buffer.alloc(0), info: Buffer.alloc(0) }, 7], - [hasSha3, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8], - [hasSha3, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0], + [!isBoringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 8], + [!isBoringSSL, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 0], [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 0 }, 8], [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, null], [false, { name: 'PBKDF2', hash: 'SHA3-256', salt: Buffer.alloc(0), iterations: 1 }, 7], ], 'importKey': [ - [hasSha3, { name: 'HMAC', hash: 'SHA3-256' }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], - [hasSha3, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256' }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], - [hasSha3, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], + [!isBoringSSL, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'RSA-PSS', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'RSA-OAEP', hash: 'SHA3-256', ...RSA_KEY_GEN }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256' }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 256 }], + [!isBoringSSL, { name: 'HMAC', hash: 'SHA3-256', length: 25 }], [false, { name: 'HMAC', hash: 'SHA3-256', length: 0 }], ], 'get key length': [ diff --git a/test/parallel/test-crypto-boringssl-evp-list.js b/test/parallel/test-crypto-boringssl-evp-list.js index f2d8fd4796c..1589837eee8 100644 --- a/test/parallel/test-crypto-boringssl-evp-list.js +++ b/test/parallel/test-crypto-boringssl-evp-list.js @@ -30,4 +30,7 @@ const hashes = getHashes(); 'sha1', 'sha256', 'sha512-256', + 'sha3-256', + 'sha3-384', + 'sha3-512', ].forEach((hash) => assert(hashes.includes(hash), hash)); diff --git a/test/parallel/test-crypto-boringssl-sha3-signatures.js b/test/parallel/test-crypto-boringssl-sha3-signatures.js new file mode 100644 index 00000000000..f5549d73931 --- /dev/null +++ b/test/parallel/test-crypto-boringssl-sha3-signatures.js @@ -0,0 +1,104 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { isBoringSSL } = require('../common/crypto'); +if (!isBoringSSL) + common.skip('BoringSSL-only test'); + +const assert = require('assert'); +const { + constants, + createPrivateKey, + createPublicKey, + createSign, + createVerify, + sign, + subtle, + verify, +} = require('crypto'); +const { promisify } = require('util'); +const signAsync = promisify(sign); +const verifyAsync = promisify(verify); +// BoringSSL exposes SHA-3 digests, but its PKCS1v1.5 DigestInfo table and ECDSA +// digest restrictions still reject them. Use valid independent signatures so +// these assertions fail when support lands and the fixture skips can be removed. +const vectors = [ + ...require('../fixtures/crypto/rsa_pkcs')({ includeUnsupported: true }), + ...require('../fixtures/crypto/ecdsa')({ includeUnsupported: true }), +].filter(({ hash }) => hash.startsWith('SHA3-')); +assert.strictEqual(vectors.length, 12); + +async function test(vector) { + const { + hash, + namedCurve, + plaintext, + privateKeyBuffer, + publicKeyBuffer, + signature, + } = vector; + const isECDSA = vector.name === 'ECDSA'; + const name = isECDSA ? vector.name : vector.algorithm.name; + const context = `${name} ${namedCurve ?? ''} ${hash}`; + const error = { + code: isECDSA ? 'ERR_OSSL_EVP_INVALID_DIGEST_TYPE' : + 'ERR_OSSL_RSA_UNKNOWN_ALGORITHM_TYPE', + }; + const privateKey = createPrivateKey({ + key: privateKeyBuffer, format: 'der', type: 'pkcs8', + }); + const publicKey = createPublicKey({ + key: publicKeyBuffer, format: 'der', type: 'spki', + }); + const options = isECDSA ? { dsaEncoding: 'ieee-p1363' } : + { padding: constants.RSA_PKCS1_PADDING }; + const signingKey = { key: privateKey, ...options }; + const verificationKey = { key: publicKey, ...options }; + + assert.throws(() => sign(hash, plaintext, signingKey), error, context); + await assert.rejects(signAsync(hash, plaintext, signingKey), error, context); + assert.throws(() => createSign(hash).update(plaintext).sign(signingKey), + error, context); + + if (isECDSA) { + assert.throws(() => verify(hash, plaintext, verificationKey, signature), + error, context); + await assert.rejects(verifyAsync(hash, plaintext, verificationKey, signature), + error, context); + } else { + assert.strictEqual(verify(hash, plaintext, verificationKey, signature), false, + context); + assert.strictEqual( + await verifyAsync(hash, plaintext, verificationKey, signature), false, + context); + } + assert.strictEqual(createVerify(hash).update(plaintext) + .verify(verificationKey, signature), false, context); + + const unsupportedHash = { name: 'NotSupportedError' }; + if (!isECDSA) { + const algorithm = { name, hash }; + await assert.rejects(subtle.importKey( + 'pkcs8', privateKeyBuffer, algorithm, false, ['sign']), + unsupportedHash, context); + await assert.rejects(subtle.importKey( + 'spki', publicKeyBuffer, algorithm, false, ['verify']), + unsupportedHash, context); + return; + } + + const [webPrivateKey, webPublicKey] = await Promise.all([ + subtle.importKey('pkcs8', privateKeyBuffer, { name, namedCurve }, false, ['sign']), + subtle.importKey('spki', publicKeyBuffer, { name, namedCurve }, false, ['verify']), + ]); + await assert.rejects(subtle.sign({ name, hash }, webPrivateKey, plaintext), + unsupportedHash, context); + await assert.rejects(subtle.verify({ name, hash }, webPublicKey, signature, + plaintext), unsupportedHash, context); +} + +for (const vector of vectors) + test(vector).then(common.mustCall()); diff --git a/test/parallel/test-crypto-hash-stream-pipe.js b/test/parallel/test-crypto-hash-stream-pipe.js index c6b9433456e..d94af277f63 100644 --- a/test/parallel/test-crypto-hash-stream-pipe.js +++ b/test/parallel/test-crypto-hash-stream-pipe.js @@ -22,7 +22,6 @@ 'use strict'; const common = require('../common'); -const { isBoringSSL } = require('../common/crypto'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -32,14 +31,8 @@ const crypto = require('crypto'); const stream = require('stream'); const s = new stream.PassThrough(); -const h = isBoringSSL ? - crypto.createHash('sha512') : - crypto.createHash('sha3-512'); -const expect = isBoringSSL ? - 'fba055c6fd0c5b6645407749ed7a8b41' + - 'b8f629f2163c3ca3701d864adabda1f8' + - '93c37bf82b22fdd151ba8e357f611da4' + - '88a74b6a5525dd9b69554c6ce5138ad7' : +const h = crypto.createHash('sha3-512'); +const expect = '36a38a2a35e698974d4e5791a3f05b05' + '198235381e864f91a0e8cd6a26b677ec' + 'dcde8e2b069bd7355fabd68abd6fc801' + 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 a5430df9e0f..88d90715041 100644 --- a/test/parallel/test-crypto-key-objects-to-crypto-key.js +++ b/test/parallel/test-crypto-key-objects-to-crypto-key.js @@ -12,13 +12,15 @@ const { randomBytes, generateKeyPairSync, } = require('crypto'); -const { hasFIPS } = require('../common/crypto'); +const { hasFIPS, isBoringSSL } = 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-'); }); +const unsupportedKeyHashes = isBoringSSL ? ['SHA3-256', 'SHA3-384', 'SHA3-512'] : []; +const keyHashes = hashes.filter((hash) => !unsupportedKeyHashes.includes(hash)); const namedCurves = ['P-256', 'P-384', 'P-521']; const signingUsages = { @@ -105,6 +107,17 @@ function runVector(vector) { assertVector(vector, false); } +function assertUnsupportedKeyHashes(keyObject, name, usages) { + for (const hash of unsupportedKeyHashes) { + for (const extractable of [true, false]) { + assert.throws(() => keyObject.toCryptoKey({ name, hash }, extractable, usages), { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', + }); + } + } +} + function symmetricVectors(name, usages) { const vectors = []; for (const length of [128, 192, 256]) { @@ -181,7 +194,8 @@ function hmacVectors() { const vectors = []; for (const length of [128, 192, 256]) { const key = secretKey(length); - for (const hash of hashes) { + assertUnsupportedKeyHashes(key, 'HMAC', ['sign', 'verify']); + for (const hash of keyHashes) { vectors.push(vector( key, { name: 'HMAC', hash }, @@ -219,7 +233,8 @@ function rsaVectors(name, usagesByType) { const keyPair = generateKeyPairSync('rsa', { modulusLength: 2048 }); const vectors = []; for (const keyObject of [keyPair.publicKey, keyPair.privateKey]) { - for (const hash of hashes) { + assertUnsupportedKeyHashes(keyObject, name, getUsages(keyObject, usagesByType)); + for (const hash of keyHashes) { vectors.push(vector( keyObject, { name, hash }, diff --git a/test/parallel/test-crypto-sha3.js b/test/parallel/test-crypto-sha3.js new file mode 100644 index 00000000000..6a7ed2e13b3 --- /dev/null +++ b/test/parallel/test-crypto-sha3.js @@ -0,0 +1,117 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + constants, + createHash, + createHmac, + createPrivateKey, + createPublicKey, + createSign, + createVerify, + getHashes, + hash, + hkdf, + hkdfSync, + pbkdf2, + pbkdf2Sync, + sign, + verify, +} = require('crypto'); +const rsaPss = require('../fixtures/crypto/rsa_pss'); + +const vectors = [ + { + name: 'sha3-256', + digest: '3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532', + hmac: '961fcf59ed455732e405e74f5dc78beb7aa41ad315af5e2b2a0dcf8cef9887e0', + hkdf: '38c2d1e7b3933f7df51e35f3eeaa0a0138b1b812e2bf57810ad4e2300e2c4a81', + pbkdf2: 'db7d83f5e7ebc6b0ea83e9b6c57a27e254e96cfef315278e9ed96b10483a7ad0', + }, + { + name: 'sha3-384', + digest: 'ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b29' + + '8d88cea927ac7f539f1edf228376d25', + hmac: 'f1d3898bb4cd7b7904fe4e7cecb84d3225b3add0265842ec7386867d662ee3364' + + '8a90e98a5c673b2266eb1077fb91ed6', + hkdf: '3ca21ccc4e742ec6a365ee5857db1ab5a0a8843196467d7a0b26c724e542f6f5', + pbkdf2: '7f049e44bfddb1b591d1ae2beaa1f3be54d683063bd25759b412a86012186c16', + }, + { + name: 'sha3-512', + digest: 'b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e1' + + '0e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0', + hmac: '5576c01b021fc928ae1bc83a6ddb8db5f9a201681e940ecf1f7d5c8a6ec80ac34' + + '614d4a68c9581957f69dc8a8c0fbf413984b0f938db3e8212c0744a1a9c5915', + hkdf: 'cd54f848a5f6ffb9e730d4bfbdff30d69a4b86316afdf5ba61b4fe95bd022037', + pbkdf2: '2b5150c50e0bf48dcb5b32205ad81e8c42d97f8f8dff57dcc365a38e857a05c7', + }, +]; + +const hashes = getHashes(); +for (const vector of vectors) { + assert(hashes.includes(vector.name)); + for (const name of [vector.name, vector.name.toUpperCase(), + vector.name.replace('sha', 'sHa')]) { + assert.strictEqual(hash(name, 'abc'), vector.digest); + + const original = createHash(name).update('a'); + const copy = original.copy(); + assert.strictEqual(original.update('bc').digest('hex'), vector.digest); + assert.strictEqual(copy.update('b').copy().update('c').digest('hex'), vector.digest); + + // Exceed every SHA-3 block size so HMAC also hashes the key. + assert.strictEqual(createHmac(name, Buffer.alloc(200, 0xaa)) + .update('abc').digest('hex'), vector.hmac); + + assert.strictEqual(Buffer.from(hkdfSync(name, 'passwordpassword', 'saltsaltsaltsalt', 'info', 32)) + .toString('hex'), vector.hkdf); + hkdf(name, 'passwordpassword', 'saltsaltsaltsalt', 'info', 32, common.mustSucceed((result) => { + assert.strictEqual(Buffer.from(result).toString('hex'), vector.hkdf); + })); + + assert.strictEqual(pbkdf2Sync('passwordpassword', 'saltsaltsaltsalt', 1000, 32, name) + .toString('hex'), vector.pbkdf2); + pbkdf2('passwordpassword', 'saltsaltsaltsalt', 1000, 32, name, common.mustSucceed((result) => { + assert.strictEqual(result.toString('hex'), vector.pbkdf2); + })); + } +} + +// Reuse independent RSA-PSS signature vectors to check SHA-3 in both the +// one-shot and streaming APIs, including zero-length and digest-length salts. +for (const vector of rsaPss()) { + if (!vector.hash.startsWith('SHA3-')) continue; + const privateKey = createPrivateKey({ + key: vector.privateKeyBuffer, format: 'der', type: 'pkcs8', + }); + const publicKey = createPublicKey({ + key: vector.publicKeyBuffer, format: 'der', type: 'spki', + }); + const options = { + padding: constants.RSA_PKCS1_PSS_PADDING, + saltLength: vector.algorithm.saltLength, + }; + const signingKey = { key: privateKey, ...options }; + const verificationKey = { key: publicKey, ...options }; + + assert(verify(vector.hash, vector.plaintext, verificationKey, + vector.signature)); + assert(createVerify(vector.hash).update(vector.plaintext) + .verify(verificationKey, vector.signature)); + + for (const signature of [ + sign(vector.hash, vector.plaintext, signingKey), + createSign(vector.hash).update(vector.plaintext).sign(signingKey), + ]) { + assert(verify(vector.hash, vector.plaintext, verificationKey, signature)); + assert(createVerify(vector.hash).update(vector.plaintext) + .verify(verificationKey, signature)); + if (options.saltLength === 0) + assert.deepStrictEqual(signature, vector.signature); + } +} diff --git a/test/parallel/test-webcrypto-boringssl-sha3.js b/test/parallel/test-webcrypto-boringssl-sha3.js new file mode 100644 index 00000000000..f4e2267fe0e --- /dev/null +++ b/test/parallel/test-webcrypto-boringssl-sha3.js @@ -0,0 +1,80 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { isBoringSSL } = require('../common/crypto'); +if (!isBoringSSL) + common.skip('BoringSSL-only test'); + +const assert = require('assert'); +const { subtle } = require('crypto'); +const { SubtleCrypto } = globalThis; + +const unsupportedHash = { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', +}; + +async function test() { + const data = Buffer.from('abc'); + const secret = Buffer.alloc(32); + const wrappingKey = await subtle.importKey( + 'raw', Buffer.alloc(16), 'AES-KW', false, ['wrapKey', 'unwrapKey']); + const keyToWrap = await subtle.importKey( + 'raw', secret, { name: 'HMAC', hash: 'SHA-256' }, true, ['sign']); + const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW'); + const bases = await Promise.all(['HKDF', 'PBKDF2'].map((name) => + subtle.importKey('raw', secret, name, false, ['deriveBits', 'deriveKey']))); + + for (const [hash, length] of [ + ['SHA3-256', 32], + ['SHA3-384', 48], + ['SHA3-512', 64], + ]) { + assert.strictEqual(SubtleCrypto.supports('digest', hash), true); + assert.strictEqual((await subtle.digest(hash, data)).byteLength, length); + + for (const name of ['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP']) { + const algorithm = { + name, hash, modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + }; + const usages = name === 'RSA-OAEP' ? ['encrypt', 'decrypt'] : ['sign', 'verify']; + assert.strictEqual(SubtleCrypto.supports('generateKey', algorithm), false); + assert.strictEqual(SubtleCrypto.supports('importKey', algorithm), false); + await assert.rejects(subtle.generateKey(algorithm, true, usages), unsupportedHash); + } + + const hmac = { name: 'HMAC', hash, length: 256 }; + assert.strictEqual(SubtleCrypto.supports('generateKey', hmac), false); + assert.strictEqual(SubtleCrypto.supports('importKey', hmac), false); + await assert.rejects(subtle.generateKey(hmac, true, ['sign']), unsupportedHash); + await assert.rejects(subtle.importKey('raw', secret, hmac, false, ['sign']), + unsupportedHash); + assert.strictEqual(SubtleCrypto.supports('unwrapKey', 'AES-KW', hmac), false); + await assert.rejects(subtle.unwrapKey( + 'raw', wrapped, wrappingKey, 'AES-KW', hmac, false, ['sign']), unsupportedHash); + + for (const [index, algorithm] of [ + { name: 'HKDF', hash, salt: secret, info: data }, + { name: 'PBKDF2', hash, salt: secret, iterations: 1 }, + ].entries()) { + const base = bases[index]; + const derivedKey = { name: 'AES-GCM', length: 128 }; + assert.strictEqual(SubtleCrypto.supports('deriveBits', algorithm, 128), false); + assert.strictEqual(SubtleCrypto.supports('deriveKey', algorithm, derivedKey), false); + await assert.rejects(subtle.deriveBits(algorithm, base, 128), unsupportedHash); + await assert.rejects(subtle.deriveKey( + algorithm, base, derivedKey, false, ['encrypt']), unsupportedHash); + + const sha256 = { ...algorithm, hash: 'SHA-256' }; + assert.strictEqual(SubtleCrypto.supports('deriveKey', sha256, hmac), false); + await assert.rejects(subtle.deriveKey( + sha256, base, hmac, false, ['sign']), unsupportedHash); + } + } +} + +test().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-derivebits-hkdf.js b/test/parallel/test-webcrypto-derivebits-hkdf.js index 4fb372fd3a8..4fbaa1d7c64 100644 --- a/test/parallel/test-webcrypto-derivebits-hkdf.js +++ b/test/parallel/test-webcrypto-derivebits-hkdf.js @@ -35,7 +35,7 @@ if (!isBoringSSL) { ['HMAC', 256, 'SHA3-512', 'sign', 'verify'], ); } else { - common.printSkipMessage('Skipping unsupported test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } if (hasOpenSSL(3)) { diff --git a/test/parallel/test-webcrypto-derivekey.js b/test/parallel/test-webcrypto-derivekey.js index 318ca8d2500..a4acb112c6b 100644 --- a/test/parallel/test-webcrypto-derivekey.js +++ b/test/parallel/test-webcrypto-derivekey.js @@ -97,7 +97,7 @@ const fips4 = hasFIPS(4); 'ac5d90a6bc848961e78a491887539b29c532a9c0d0b39cec464df071a63e0061'], ); } else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } const tests = Promise.all(kTests.map((args) => test(...args))); @@ -150,7 +150,7 @@ const fips4 = hasFIPS(4); '03431052c37d626ae3fc1df582ff2a4d610642fc27e1b8130ca5980c0b0756ac'] ); } else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } const promises = kTests.map((args) => test(...args)); @@ -188,7 +188,7 @@ const fips4 = hasFIPS(4); // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], ); } else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } if (hasOpenSSL(3)) { @@ -248,7 +248,7 @@ const fips4 = hasFIPS(4); // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], ); } else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } if (hasOpenSSL(3)) { diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index 89bd86bd928..f9e31192602 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -16,15 +16,15 @@ const kTests = [ ['SHA-256', ['sha256'], 256], ['SHA-384', ['sha384'], 384], ['SHA-512', ['sha512'], 512], + ['SHA3-256', ['sha3-256'], 256], + ['SHA3-384', ['sha3-384'], 384], + ['SHA3-512', ['sha3-512'], 512], ]; if (!isBoringSSL) { kTests.push( [{ name: 'cSHAKE128', outputLength: 256 }, ['shake128', { outputLength: 256 >> 3 }], 256], [{ name: 'cSHAKE256', outputLength: 512 }, ['shake256', { outputLength: 512 >> 3 }], 512], - ['SHA3-256', ['sha3-256'], 256], - ['SHA3-384', ['sha3-384'], 384], - ['SHA3-512', ['sha3-512'], 512], ); } else { common.printSkipMessage('Skipping unsupported test cases'); @@ -175,41 +175,41 @@ const kDigestedData = { 'd5290933492f9d17411926a613dd0611668c2ac999e8' + 'c011aabaa9004323425fbad75b0f58ee6e777a94' }, - 'sha3-256': { - empty: 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a', - short: '3059af7aa33b517084e8ad7bbc4fb208a44c28ef32b4698d103dd540e4f91aa1', - medium: '1fa7cd1da74cd8046417508c8314e74a9a4a9d38f9f18e6cb215b8c891a0a80e', - long: 'b2cfc61e0386cdaef5e10a2be189891f5ef52a7624bfcd8edc893acc64fec600' - }, - 'sha3-384': { - empty: '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa9' + - '4fc61995e71bbee983a2ac3713831264adb47fb6bd1' + - 'e058d5f004', - short: '54b8f0e4cf4974de740098f66b3024479b01631315a' + - '6773606c33eadc32556a6e778e08f0225ae79265aec' + - '666cb2390b', - medium: '437b7d8b68b250b5c1739ea4cc86db2033879dfb18' + - 'de292c9c50d9c193a4c79a08a6cae3f4e483c2795e' + - 'a5d1ef7e69d2', - long: '3b39c4c97ad87613305d0ccc987181713e2d5e84b1f9' + - '760011bcce0c297499005bdce8a3d2409b5ad0164f32' + - 'bb8778d0' - }, - 'sha3-512': { - empty: 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe' + - '25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9' + - '402c3ac558f500199d95b6d3e301758586281dcd26', - short: '2dd2e07a62e6ad0498ba84f313c4d4024cb46001f78' + - 'f75db336b0d4d8bd2a9ec152c4ad20878735d82ba08' + - '72ecf59608ef3ced2b2a8669427e7da31e362333d8', - medium: 'e640a21909536640369e9b0a48931c5cb2efcbc91f' + - 'ecf247306bc96a0e4ca33307cb8e1b9af367946dd01' + - 'c243f3907508d04f1692a3161df1f898de8ee25febe', - long: 'bd262cecf565c338032de5ba0138f0aacfe7dde83d27' + - '2d0d37d952829ed25de1a1342d98659ef7d2fa4aca7c' + - 'e2b1aa0784d8fc1dcbf81bcec7a7431a3da36bf7' - } } : {}), + 'sha3-256': { + empty: 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a', + short: '3059af7aa33b517084e8ad7bbc4fb208a44c28ef32b4698d103dd540e4f91aa1', + medium: '1fa7cd1da74cd8046417508c8314e74a9a4a9d38f9f18e6cb215b8c891a0a80e', + long: 'b2cfc61e0386cdaef5e10a2be189891f5ef52a7624bfcd8edc893acc64fec600' + }, + 'sha3-384': { + empty: '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa9' + + '4fc61995e71bbee983a2ac3713831264adb47fb6bd1' + + 'e058d5f004', + short: '54b8f0e4cf4974de740098f66b3024479b01631315a' + + '6773606c33eadc32556a6e778e08f0225ae79265aec' + + '666cb2390b', + medium: '437b7d8b68b250b5c1739ea4cc86db2033879dfb18' + + 'de292c9c50d9c193a4c79a08a6cae3f4e483c2795e' + + 'a5d1ef7e69d2', + long: '3b39c4c97ad87613305d0ccc987181713e2d5e84b1f9' + + '760011bcce0c297499005bdce8a3d2409b5ad0164f32' + + 'bb8778d0' + }, + 'sha3-512': { + empty: 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe' + + '25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9' + + '402c3ac558f500199d95b6d3e301758586281dcd26', + short: '2dd2e07a62e6ad0498ba84f313c4d4024cb46001f78' + + 'f75db336b0d4d8bd2a9ec152c4ad20878735d82ba08' + + '72ecf59608ef3ced2b2a8669427e7da31e362333d8', + medium: 'e640a21909536640369e9b0a48931c5cb2efcbc91f' + + 'ecf247306bc96a0e4ca33307cb8e1b9af367946dd01' + + 'c243f3907508d04f1692a3161df1f898de8ee25febe', + long: 'bd262cecf565c338032de5ba0138f0aacfe7dde83d27' + + '2d0d37d952829ed25de1a1342d98659ef7d2fa4aca7c' + + 'e2b1aa0784d8fc1dcbf81bcec7a7431a3da36bf7' + } }; async function testDigest(size, alg) { diff --git a/test/parallel/test-webcrypto-encrypt-decrypt.js b/test/parallel/test-webcrypto-encrypt-decrypt.js index 8c851682432..45d0aa64f72 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt.js @@ -48,18 +48,21 @@ async function testRSAOaep(publicKey, privateKey) { await testRSAOaep(publicKey, privateKey); + // SHA-3 is only supported for digest operations in BoringSSL. if (!isBoringSSL) { // Import the same key material with SHA-3 to avoid another RSA keygen. const [spki, pkcs8] = await Promise.all([ subtle.exportKey('spki', publicKey), subtle.exportKey('pkcs8', privateKey), ]); - const algorithm = { name: 'RSA-OAEP', hash: 'SHA3-384' }; - const [sha3PublicKey, sha3PrivateKey] = await Promise.all([ - subtle.importKey('spki', spki, algorithm, false, ['encrypt']), - subtle.importKey('pkcs8', pkcs8, algorithm, false, ['decrypt']), - ]); - await testRSAOaep(sha3PublicKey, sha3PrivateKey); + for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) { + const algorithm = { name: 'RSA-OAEP', hash }; + const [sha3PublicKey, sha3PrivateKey] = await Promise.all([ + subtle.importKey('spki', spki, algorithm, false, ['encrypt']), + subtle.importKey('pkcs8', pkcs8, algorithm, false, ['decrypt']), + ]); + await testRSAOaep(sha3PublicKey, sha3PrivateKey); + } } })().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-rsa.js b/test/parallel/test-webcrypto-export-import-rsa.js index 385b8e0bc52..9f184084e7c 100644 --- a/test/parallel/test-webcrypto-export-import-rsa.js +++ b/test/parallel/test-webcrypto-export-import-rsa.js @@ -2,7 +2,6 @@ const common = require('../common'); -const { isBoringSSL } = require('../common/crypto'); const fixtures = require('../common/fixtures'); if (!common.hasCrypto) @@ -10,6 +9,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { isBoringSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; const sizes = [1024, 2048, 4096]; @@ -19,18 +19,11 @@ const hashes = [ 'SHA-256', 'SHA-384', 'SHA-512', + 'SHA3-256', + 'SHA3-384', + 'SHA3-512', ]; -if (!isBoringSSL) { - hashes.push( - 'SHA3-256', - 'SHA3-384', - 'SHA3-512', - ); -} else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); -} - const keyData = { 1024: { spki: Buffer.from( @@ -643,6 +636,22 @@ const testVectors = [ hashes.forEach((hash) => { [true, false].forEach((extractable) => { testVectors.forEach((vector) => { + if (isBoringSSL && hash.startsWith('SHA3-')) { + const { spki, pkcs8, jwk } = keyData[size]; + for (const [format, data, usages] of [ + ['spki', spki, vector.publicUsages], + ['pkcs8', pkcs8, vector.privateUsages], + ['jwk', { kty: jwk.kty, n: jwk.n, e: jwk.e }, vector.publicUsages], + ['jwk', jwk, vector.privateUsages], + ]) { + variations.push(assert.rejects(subtle.importKey( + format, data, { name: vector.name, hash }, extractable, usages), { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', + })); + } + return; + } variations.push(testImportSpki(vector, size, hash, extractable)); variations.push(testImportPkcs8(vector, size, hash, extractable)); variations.push(testImportJwk(vector, size, hash, extractable)); diff --git a/test/parallel/test-webcrypto-export-import.js b/test/parallel/test-webcrypto-export-import.js index 63fdde05f1e..4578f7d587d 100644 --- a/test/parallel/test-webcrypto-export-import.js +++ b/test/parallel/test-webcrypto-export-import.js @@ -484,12 +484,19 @@ if (hasOpenSSL(3)) { } // SHA-3 hashes and JWK "alg" -if (!isBoringSSL) { +{ const rsa = fixtures.readKey('rsa_private_2048.pem'); const privateKey = createPrivateKey(rsa); const publicKey = createPublicKey(privateKey); async function test(keyObject, algorithm, usages) { + if (isBoringSSL) { + assert.throws(() => keyObject.toCryptoKey(algorithm, true, usages), { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', + }); + return; + } const key = keyObject.toCryptoKey(algorithm, true, usages); const jwk = await subtle.exportKey('jwk', key); assert.strictEqual(jwk.alg, undefined); @@ -501,7 +508,8 @@ if (!isBoringSSL) { test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall()); } - test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']); + test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign']) + .then(common.mustCall()); } { @@ -517,7 +525,16 @@ if (!isBoringSSL) { message: 'JWK "alg" does not match the requested algorithm', }).then(common.mustCall()); - subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']).then(common.mustCall()); + const imported = subtle.importKey( + 'jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']); + if (isBoringSSL) { + assert.rejects(imported, { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', + }).then(common.mustCall()); + } else { + imported.then(common.mustCall()); + } } } diff --git a/test/parallel/test-webcrypto-keygen.js b/test/parallel/test-webcrypto-keygen.js index b1fc8f41b2a..6618a43db74 100644 --- a/test/parallel/test-webcrypto-keygen.js +++ b/test/parallel/test-webcrypto-keygen.js @@ -527,7 +527,7 @@ if (hasOpenSSL(3, 5) || isBoringSSL) { ); } } else { - common.printSkipMessage('Skipping unsupported SHA-3 test case'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } const tests = kTests.map((args) => test(...args)); @@ -733,7 +733,7 @@ if (hasOpenSSL(3, 5) || isBoringSSL) { // [undefined, 'SHA3-512', ['sign', 'verify']], ); } else { - common.printSkipMessage('Skipping unsupported SHA-3 test cases'); + common.printSkipMessage('SHA-3 is only supported for digest operations'); } const tests = Promise.all(kTests.map((args) => test(...args))); diff --git a/test/parallel/test-webcrypto-normalization-boundary.js b/test/parallel/test-webcrypto-normalization-boundary.js index 20102c32e6a..8365a7d4059 100644 --- a/test/parallel/test-webcrypto-normalization-boundary.js +++ b/test/parallel/test-webcrypto-normalization-boundary.js @@ -39,7 +39,7 @@ const { subtle } = globalThis.crypto; { name: 'OperationError' }); await assert.rejects(subtle.generateKey({ name: 'HMAC', hash: 'SHA-256', length: 0 }, false, []), { name: 'OperationError' }); - if (SubtleCrypto.supports('digest', 'SHA3-256')) { + if (SubtleCrypto.supports('generateKey', { name: 'HMAC', hash: 'SHA3-256', length: 256 })) { const hmac = { name: 'HMAC', hash: 'SHA3-256' }; await assert.rejects(subtle.generateKey(hmac, false, ['encrypt']), { name: 'SyntaxError' }); await assert.rejects(subtle.generateKey(hmac, false, []), { name: 'NotSupportedError' }); diff --git a/test/parallel/test-webcrypto-rsa-jwk-sha3-alg.js b/test/parallel/test-webcrypto-rsa-jwk-sha3-alg.js index 88bc64194e6..55e45119103 100644 --- a/test/parallel/test-webcrypto-rsa-jwk-sha3-alg.js +++ b/test/parallel/test-webcrypto-rsa-jwk-sha3-alg.js @@ -4,13 +4,10 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { isBoringSSL } = require('../common/crypto'); -if (isBoringSSL) - common.skip('missing SHA-3'); - const assert = require('assert'); const { createPrivateKey, createPublicKey } = require('crypto'); const fixtures = require('../common/fixtures'); +const { isBoringSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; (async () => { @@ -24,6 +21,14 @@ const { subtle } = globalThis.crypto; const usages = name === 'RSA-OAEP' ? [jwk.d ? 'decrypt' : 'encrypt'] : [jwk.d ? 'sign' : 'verify']; const algorithm = { name, hash }; + if (isBoringSSL) { + await assert.rejects( + subtle.importKey('jwk', jwk, algorithm, true, usages), { + name: 'NotSupportedError', + message: 'SHA-3 is only supported for digest operations', + }); + continue; + } // There is no JWK alg identifier for RSA with SHA-3. Omitting alg is // valid, but an identifier for SHA-2 or an unknown identifier is not. const key = await subtle.importKey('jwk', jwk, algorithm, true, usages); diff --git a/test/parallel/test-webcrypto-sign-verify-hmac.js b/test/parallel/test-webcrypto-sign-verify-hmac.js index bbe5dd49810..93d8a43c7ae 100644 --- a/test/parallel/test-webcrypto-sign-verify-hmac.js +++ b/test/parallel/test-webcrypto-sign-verify-hmac.js @@ -7,6 +7,7 @@ if (!common.hasCrypto) const assert = require('assert'); const { getFips } = require('crypto'); +const { isBoringSSL } = require('../common/crypto'); const { subtle } = globalThis.crypto; const vectors = require('../fixtures/crypto/hmac')(); @@ -174,6 +175,10 @@ async function testSign({ hash, const variations = []; for (const vector of vectors) { + // The shared fixtures also cover node:crypto; WebCrypto only supports + // SHA-3 for digest operations in BoringSSL. + if (isBoringSSL && vector.hash.startsWith('SHA3-')) + continue; variations.push(testVerify(vector)); variations.push(testSign(vector)); } diff --git a/test/parallel/test-webcrypto-sign-verify-rsa.js b/test/parallel/test-webcrypto-sign-verify-rsa.js index 201d28fbf1f..8551ad3afa3 100644 --- a/test/parallel/test-webcrypto-sign-verify-rsa.js +++ b/test/parallel/test-webcrypto-sign-verify-rsa.js @@ -267,6 +267,7 @@ async function testSaltLengths(keyLength) { ['SHA-256', 32], ['SHA-384', 48], ['SHA-512', 64], + // SHA-3 is only supported for digest operations in BoringSSL. ...(!isBoringSSL ? [ ['SHA3-256', 32], ['SHA3-384', 48], @@ -283,12 +284,18 @@ async function testSaltLengths(keyLength) { (async function() { const variations = []; + // The shared fixtures also cover node:crypto; WebCrypto only supports + // SHA-3 for digest operations in BoringSSL. rsa_pkcs().forEach((vector) => { + if (isBoringSSL && vector.hash.startsWith('SHA3-')) + return; variations.push(testVerify(vector)); variations.push(rejectsSha1Signing && vector.hash === 'SHA-1' ? testFipsSignRejected(vector) : testSign(vector)); }); rsa_pss().forEach((vector) => { + if (isBoringSSL && vector.hash.startsWith('SHA3-')) + return; variations.push(testVerify(vector)); variations.push(rejectsSha1Signing && vector.hash === 'SHA-1' ? testFipsSignRejected(vector) : testSign(vector)); diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index a73db3793a9..4e8e4efd882 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -81,7 +81,6 @@ if (isBoringSSL) { 'secure-curves/derive_bits_keys/cfrg_curves_bits_curve448.https.any.js', 'secure-curves/derive_bits_keys/cfrg_curves_keys_curve448.https.any.js', 'modern-algos/digest/cshake.https.any.js', - 'modern-algos/digest/sha3.https.any.js', 'secure-curves/generateKey/failures_Ed448.https.any.js', 'modern-algos/generateKey/failures_Hybrid-KEM.https.any.js', 'secure-curves/generateKey/failures_X448.https.any.js',