diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index d35d601abc70..a6d1a99559b4 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2797,6 +2797,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); } @@ -4998,11 +5005,7 @@ bool SSLPointer::isServer() const { EVPKeyPointer SSLPointer::getPeerTempKey() const { if (!ssl_) return {}; EVP_PKEY* raw_key = nullptr; -#ifndef OPENSSL_IS_BORINGSSL if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; -#else - if (!SSL_get_server_tmp_key(get(), &raw_key)) return {}; -#endif return EVPKeyPointer(raw_key); } @@ -5100,13 +5103,57 @@ bool SSLCtxPointer::setGroups(const char* groups) { } bool SSLCtxPointer::setCipherSuites(const char* ciphers) { -#ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; +#ifndef OPENSSL_IS_BORINGSSL return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers); #else - // BoringSSL does not allow API config of TLS 1.3 cipher suites. - // We treat this as a non-op. - return true; + constexpr std::array supported = { + SSL_CIPHER_AES_128_GCM_SHA256, + SSL_CIPHER_AES_256_GCM_SHA384, + SSL_CIPHER_CHACHA20_POLY1305_SHA256, + }; + std::vector cipher_ids; + std::string_view remaining(ciphers); + while (!remaining.empty()) { + const size_t end = remaining.find(':'); + std::string_view name = remaining.substr(0, end); + const size_t first = name.find_first_not_of(" \t\n\r\f\v"); + if (first != std::string_view::npos) { + name = name.substr(first); + name = name.substr(0, name.find_last_not_of(" \t\n\r\f\v") + 1); + for (const auto id : supported) { + const SSL_CIPHER* cipher = SSL_get_cipher_by_value(id); + if (name == SSL_CIPHER_standard_name(cipher)) { + // OpenSSL ignores unknown names and accepts duplicates, while + // BoringSSL requires unique protocol IDs. + if (std::find(cipher_ids.begin(), cipher_ids.end(), id) == + cipher_ids.end()) { + cipher_ids.push_back(id); + } + break; + } + } + } + if (end == std::string_view::npos) break; + remaining.remove_prefix(end + 1); + } + if (*ciphers != '\0' && cipher_ids.empty()) { + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH); + return false; + } + std::vector flags(cipher_ids.size()); + if (!flags.empty() && + (SSL_CTX_get_options(get()) & SSL_OP_CIPHER_SERVER_PREFERENCE) == 0) { + // TLS 1.3 otherwise always uses server preference in BoringSSL. An + // equal-preference group lets the client choose from the configured list. + std::fill(flags.begin(), + flags.end() - 1, + SSL_CIPHER_FLAG_EQUAL_PREFERENCE_WITH_NEXT); + } + // An empty input resets BoringSSL's built-in preference order. The public + // TLS API expands empty cipher strings to Node's defaults before this call. + return SSL_CTX_set1_tls13_ciphers( + get(), cipher_ids.data(), flags.data(), cipher_ids.size()) == 1; #endif } diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 7602e375bc3f..1a49c6f40837 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 33705dbb9c33..63d086a7698a 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/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js index 65611de32d8f..d339c4d774cf 100644 --- a/lib/internal/tls/wrap.js +++ b/lib/internal/tls/wrap.js @@ -39,6 +39,8 @@ const { assertCrypto(); +const { openssl_is_boringssl: isBoringSSL } = process.features; + const { setImmediate } = require('timers'); const assert = require('internal/assert'); const crypto = require('crypto'); @@ -1126,7 +1128,7 @@ TLSSocket.prototype.renegotiate = function(options, callback) { this._handle.renegotiate(); } catch (err) { const isBoringSSLRenegotiationUnsupported = - process.features.openssl_is_boringssl && + isBoringSSL && err?.code === 'ERR_SSL_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED'; const error = isBoringSSLRenegotiationUnsupported ? new ERR_TLS_RENEGOTIATION_UNSUPPORTED() : err; diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index ad056b5610bc..759353f08f03 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -59,6 +59,9 @@ MaybeLocal GetValidationErrorReason(Environment* env, int err) { // Suggest --use-system-ca if the error indicates a certificate issue bool suggest_system_ca = (err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) || +#ifdef OPENSSL_IS_BORINGSSL + (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || +#endif (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || ((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) && !env->options()->use_system_ca); diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index c58c3ac6477e..fabf66544306 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/client-proxy/test-https-proxy-request-handshake-failure.mjs b/test/client-proxy/test-https-proxy-request-handshake-failure.mjs index 251d136afaa6..dcae2b024ff2 100644 --- a/test/client-proxy/test-https-proxy-request-handshake-failure.mjs +++ b/test/client-proxy/test-https-proxy-request-handshake-failure.mjs @@ -13,6 +13,7 @@ if (!common.hasCrypto) // https must be dynamically imported so that builds without crypto support // can skip it. const { default: https } = await import('node:https'); +const { untrustedLeafErrorCode } = await import('../common/tls.js'); const server = https.createServer({ cert: fixtures.readKey('agent8-cert.pem'), @@ -36,8 +37,8 @@ const { code, signal, stderr, stdout } = await runProxiedRequest({ HTTPS_PROXY: `http://localhost:${proxy.address().port}`, }); -// The proxy client should get a UNABLE_TO_VERIFY_LEAF_SIGNATURE during TLS handshake. -assert.match(stderr, /UNABLE_TO_VERIFY_LEAF_SIGNATURE/); +// The proxy client should report the untrusted leaf during the TLS handshake. +assert.match(stderr, new RegExp(untrustedLeafErrorCode)); assert.strictEqual(stdout, ''); assert.strictEqual(code, 0); assert.strictEqual(signal, null); diff --git a/test/common/boringssl.js b/test/common/boringssl.js index ab7f0505b0ce..53d5044e545b 100644 --- a/test/common/boringssl.js +++ b/test/common/boringssl.js @@ -167,33 +167,6 @@ function testEphemeralKeyInfo() { })); } -/** - * The protocol matrix tests cover OpenSSL behavior for legacy TLS protocols. - * For BoringSSL we only need to exhibit that a TLSv1-only client cannot connect - * to a server whose minimum protocol is TLS 1.2; the client receives the - * protocol-version alert instead of the OpenSSL version-specific matrix. - */ -function testLegacyProtocolUnsupported() { - const server = tls.createServer({ - key: fixtures.readKey('agent2-key.pem'), - cert: fixtures.readKey('agent2-cert.pem'), - minVersion: 'TLSv1.2', - }, common.mustNotCall()); - - server.on('tlsClientError', common.mustCall()); - server.listen(0, common.mustCall(() => { - const client = tls.connect({ - port: server.address().port, - rejectUnauthorized: false, - secureProtocol: 'TLSv1_method', - }, common.mustNotCall()); - client.on('error', common.mustCall((err) => { - assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION'); - server.close(); - })); - })); -} - /** * BoringSSL can load a multi-PFX option well enough to serve the ECDSA * identity, but it does not provide the same OpenSSL multi-identity selection @@ -335,7 +308,6 @@ module.exports = { assertNoCipherMatch, assertOpenSSLSecurityLevelsUnsupported, testEphemeralKeyInfo, - testLegacyProtocolUnsupported, testMultiPfxSelectionDifference, testPskTls13Unsupported, testRenegotiationUnsupported, diff --git a/test/common/tls.js b/test/common/tls.js index 8568659d13cc..a841f96b6147 100644 --- a/test/common/tls.js +++ b/test/common/tls.js @@ -4,6 +4,12 @@ const crypto = require('crypto'); const net = require('net'); const assert = require('assert'); +const { isBoringSSL } = require('./crypto'); + +// BoringSSL reports the missing issuer directly for a lone, non-self-signed +// leaf. OpenSSL replaces it with a leaf signature verification error. +exports.untrustedLeafErrorCode = isBoringSSL ? + 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' : 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'; exports.ccs = Buffer.from('140303000101', 'hex'); diff --git a/test/fixtures/crypto/ecdsa.js b/test/fixtures/crypto/ecdsa.js index 6da56a0601ff..677e553d6e08 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 55376a1299d8..6505c6e2ae55 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 74a8c0512ef4..598097362e70 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 4aeaf3330e78..101122b2ffe3 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 8d58397783ed..250646125fa6 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 699b72bb11b4..a3221b1a2ecd 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/internet/test-tls-add-ca-cert.js b/test/internet/test-tls-add-ca-cert.js index 3f76f2b95e7b..2ee1bbcab59f 100644 --- a/test/internet/test-tls-add-ca-cert.js +++ b/test/internet/test-tls-add-ca-cert.js @@ -34,7 +34,7 @@ tls.connect(opts, common.mustCall(end)); // The .ca option replaces the well-known roots, so connection fails. opts.ca = caCert; tls.connect(opts, fail).on('error', common.mustCall((err) => { - assert.strictEqual(err.message, 'unable to get local issuer certificate'); + assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'); })); function fail() { diff --git a/test/parallel/test-crypto-boringssl-evp-list.js b/test/parallel/test-crypto-boringssl-evp-list.js index f2d8fd4796c0..1589837eee8f 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 000000000000..f5549d739310 --- /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-dh-curves.js b/test/parallel/test-crypto-dh-curves.js index 22b05fb89609..ef5d0323d33f 100644 --- a/test/parallel/test-crypto-dh-curves.js +++ b/test/parallel/test-crypto-dh-curves.js @@ -21,12 +21,14 @@ const p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; crypto.createDiffieHellman(p, 'hex'); -if (!isBoringSSL) { +{ + // BoringSSL also flags these primes as unsuitable for generator 2. + const generatorError = isBoringSSL ? DH_NOT_SUITABLE_GENERATOR : 0; const notPrime = Buffer.from(p, 'hex'); notPrime[notPrime.length - 1] = 0xfd; assert.strictEqual( crypto.createDiffieHellman(notPrime, Buffer.from([2])).verifyError, - DH_CHECK_P_NOT_PRIME); + DH_CHECK_P_NOT_PRIME | generatorError); const notSafePrime = Buffer.from( 'd2d6d13e1c1e0bbb63c742199dee010411f089ac74f0f7213348388280700fd6' + @@ -34,7 +36,7 @@ if (!isBoringSSL) { 'hex'); assert.strictEqual( crypto.createDiffieHellman(notSafePrime, Buffer.from([2])).verifyError, - DH_CHECK_P_NOT_SAFE_PRIME); + DH_CHECK_P_NOT_SAFE_PRIME | generatorError); } const group = crypto.getDiffieHellman('modp14'); @@ -95,7 +97,7 @@ if (availableCurves.has('Oakley-EC2N-3')) { } // Test ECDH -if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { +if (availableCurves.has('prime256v1')) { const ecdh1 = crypto.createECDH('prime256v1'); const ecdh2 = crypto.createECDH('prime256v1'); const key1 = ecdh1.generateKeys(); @@ -109,8 +111,15 @@ if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { assert.strictEqual(ecdh1.getPublicKey('buffer', 'uncompressed')[0], 4); let firstByte = ecdh1.getPublicKey('buffer', 'compressed')[0]; assert(firstByte === 2 || firstByte === 3); - firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0]; - assert(firstByte === 6 || firstByte === 7); + if (isBoringSSL) { + assert.throws(() => ecdh1.getPublicKey('buffer', 'hybrid'), { + code: 'ERR_CRYPTO_OPERATION_FAILED', + message: 'Failed to get public key length', + }); + } else { + firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0]; + assert(firstByte === 6 || firstByte === 7); + } // Format value should be string assert.throws( @@ -121,127 +130,128 @@ if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) { message: 'Invalid ECDH format: 10' }); - // ECDH should check that point is on curve - const ecdh3 = crypto.createECDH('secp256k1'); - if (hasFIPS(3)) { - assert.throws(() => ecdh3.generateKeys(), { - code: 'ERR_CRYPTO_OPERATION_FAILED', - }); - } else { - 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' + // ECDH should allow .setPrivateKey()/.setPublicKey() + const ecdh4 = crypto.createECDH('prime256v1'); + + ecdh4.setPrivateKey(ecdh1.getPrivateKey()); + ecdh4.setPublicKey(ecdh1.getPublicKey()); + + const ecdh4Secret = ecdh4.computeSecret(ecdh2.getPublicKey()); + assert.deepStrictEqual(ecdh4.computeSecret(ecdh2.getPublicKey()), ecdh4Secret); + + if (availableCurves.has('secp256k1')) { + // ECDH should check that point is on curve + const ecdh3 = crypto.createECDH('secp256k1'); + if (hasFIPS(3)) { + assert.throws(() => ecdh3.generateKeys(), { + code: 'ERR_CRYPTO_OPERATION_FAILED', }); + } else { + 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' + }); + + assert.throws(() => { + ecdh4.setPublicKey(ecdh3.getPublicKey()); + }, { message: 'Failed to convert Buffer to EC_POINT' }); + assert.deepStrictEqual(ecdh4.computeSecret(ecdh2.getPublicKey()), + ecdh4Secret); + + // Verify that we can use ECDH without having to use newly generated keys. + const ecdh5 = crypto.createECDH('secp256k1'); + + // Verify errors are thrown when retrieving keys from an uninitialized object. + assert.throws(() => { + ecdh5.getPublicKey(); + }, /^Error: Failed to get ECDH public key$/); - // ECDH should allow .setPrivateKey()/.setPublicKey() - const ecdh4 = crypto.createECDH('prime256v1'); - - ecdh4.setPrivateKey(ecdh1.getPrivateKey()); - ecdh4.setPublicKey(ecdh1.getPublicKey()); - - const ecdh4Secret = ecdh4.computeSecret(ecdh2.getPublicKey()); - assert.deepStrictEqual(ecdh4.computeSecret(ecdh2.getPublicKey()), - ecdh4Secret); - - assert.throws(() => { - ecdh4.setPublicKey(ecdh3.getPublicKey()); - }, { message: 'Failed to convert Buffer to EC_POINT' }); - assert.deepStrictEqual(ecdh4.computeSecret(ecdh2.getPublicKey()), - ecdh4Secret); - - // Verify that we can use ECDH without having to use newly generated keys. - const ecdh5 = crypto.createECDH('secp256k1'); - - // 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 = - '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3'; - 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 = - '02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae'; - const peerPubPtUnComp = - '04c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae' + - 'b651944a574a362082a77e3f2b5d9223eb54d7f2f76846522bf75f3bedb8178e'; - - const sharedSecret = - '1da220b5329bbe8bfd19ceef5a5898593f411a6f12ea40f2a8eead9a5cf59970'; - - assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'), - sharedSecret); - assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'), - sharedSecret); - assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, '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); - - // 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. + 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 = + '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3'; + 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 = + '02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae'; + const peerPubPtUnComp = + '04c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae' + + 'b651944a574a362082a77e3f2b5d9223eb54d7f2f76846522bf75f3bedb8178e'; + + const sharedSecret = + '1da220b5329bbe8bfd19ceef5a5898593f411a6f12ea40f2a8eead9a5cf59970'; + + assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'), + sharedSecret); + assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'), + sharedSecret); + assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, '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); + + // 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); + }); + } } } diff --git a/test/parallel/test-crypto-dh-group-setters.js b/test/parallel/test-crypto-dh-group-setters.js index ccc370e2352d..f759d29be0ff 100644 --- a/test/parallel/test-crypto-dh-group-setters.js +++ b/test/parallel/test-crypto-dh-group-setters.js @@ -1,19 +1,14 @@ 'use strict'; const common = require('../common'); -const { isBoringSSL } = require('../common/crypto'); if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); const crypto = require('crypto'); -if (isBoringSSL) { - common.skip('Skipping unsupported Diffie-Hellman tests'); -} - // Unlike DiffieHellman, DiffieHellmanGroup does not have any setters. -const dhg = crypto.getDiffieHellman('modp1'); +const dhg = crypto.getDiffieHellman('modp5'); assert.strictEqual(dhg.constructor, crypto.DiffieHellmanGroup); assert.strictEqual(dhg.setPrivateKey, undefined); assert.strictEqual(dhg.setPublicKey, undefined); diff --git a/test/parallel/test-crypto-dh-modp2-views.js b/test/parallel/test-crypto-dh-modp2-views.js index ce2d8823beec..0ce2f3d8af63 100644 --- a/test/parallel/test-crypto-dh-modp2-views.js +++ b/test/parallel/test-crypto-dh-modp2-views.js @@ -8,7 +8,11 @@ const crypto = require('crypto'); const { hasFIPS, modp2buf, isBoringSSL } = require('../common/crypto'); if (isBoringSSL) { - common.skip('Skipping unsupported Diffie-Hellman tests'); + // BoringSSL omits this named group but accepts its raw parameters. + assert.throws(() => crypto.createDiffieHellmanGroup('modp2'), { + code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP', + message: 'Unknown DH group', + }); } if (hasFIPS(3)) { @@ -16,7 +20,8 @@ if (hasFIPS(3)) { code: 'ERR_INVALID_ARG_VALUE', }); } else { - const modp2 = crypto.createDiffieHellmanGroup('modp2'); + const modp2 = isBoringSSL ? crypto.createDiffieHellman(modp2buf, 2) : + crypto.createDiffieHellmanGroup('modp2'); const views = common.getArrayBufferViews(modp2buf); for (const buf of [modp2buf, ...views]) { diff --git a/test/parallel/test-crypto-dh-modp2.js b/test/parallel/test-crypto-dh-modp2.js index a0ec31a1eb4f..45e6eee319bf 100644 --- a/test/parallel/test-crypto-dh-modp2.js +++ b/test/parallel/test-crypto-dh-modp2.js @@ -8,7 +8,11 @@ const crypto = require('crypto'); const { hasFIPS, modp2buf, isBoringSSL } = require('../common/crypto'); if (isBoringSSL) { - common.skip('Skipping unsupported Diffie-Hellman tests'); + // BoringSSL omits this named group but accepts its raw parameters. + assert.throws(() => crypto.createDiffieHellmanGroup('modp2'), { + code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP', + message: 'Unknown DH group', + }); } if (hasFIPS(3)) { @@ -16,7 +20,8 @@ if (hasFIPS(3)) { code: 'ERR_INVALID_ARG_VALUE', }); } else { - const modp2 = crypto.createDiffieHellmanGroup('modp2'); + const modp2 = isBoringSSL ? crypto.createDiffieHellman(modp2buf, 2) : + crypto.createDiffieHellmanGroup('modp2'); { // Ensure specific generator (buffer) works as expected. diff --git a/test/parallel/test-crypto-getcipherinfo.js b/test/parallel/test-crypto-getcipherinfo.js index faf45c766c5c..4c8e0c01c085 100644 --- a/test/parallel/test-crypto-getcipherinfo.js +++ b/test/parallel/test-crypto-getcipherinfo.js @@ -54,21 +54,13 @@ if (ciphers.includes('aes-128-wrap-inv')) { } assert(!ciphers.some((cipher) => /^\d+(?:\.\d+)+$/.test(cipher))); -if (!isBoringSSL) { - // A failed provider fetch must not contaminate the OpenSSL error queue. - assert.throws(() => createHash('sha256', { outputLength: 28 }), { - code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH', - }); -} +// A failed provider fetch must not contaminate the OpenSSL error queue. +assert.throws(() => createHash('sha256', { outputLength: 28 }), { + code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH', +}); for (const cipher of ciphers) { const info = getCipherInfo(cipher); - if (isBoringSSL && !info) { - // BoringSSL reports some legacy ciphers in getCiphers() but returns no - // info for them (e.g. des-ede3, des-ede3-ecb, rc2-40-cbc). - common.printSkipMessage(`Skipping unsupported ${cipher} test case`); - continue; - } assert(info); if (info.nid !== undefined) { const info2 = getCipherInfo(info.nid); diff --git a/test/parallel/test-crypto-hash-stream-pipe.js b/test/parallel/test-crypto-hash-stream-pipe.js index c6b9433456ea..d94af277f638 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-raw.js b/test/parallel/test-crypto-key-objects-raw.js index 4a129d1a6185..7cf4e7bb1693 100644 --- a/test/parallel/test-crypto-key-objects-raw.js +++ b/test/parallel/test-crypto-key-objects-raw.js @@ -121,12 +121,8 @@ if (hasOpenSSL(3, 5)) { { const unsupportedKeyTypes = [ ['rsa', 'rsa_public_2048.pem', 'rsa_private_2048.pem'], + ['dsa', 'dsa_public.pem', 'dsa_private.pem'], ]; - if (!isBoringSSL) { - unsupportedKeyTypes.push(['dsa', 'dsa_public.pem', 'dsa_private.pem']); - } else { - common.printSkipMessage('Skipping unsupported dsa test case'); - } for (const [type, pub, priv] of unsupportedKeyTypes) { const pubKeyObj = crypto.createPublicKey( fixtures.readKey(pub, 'ascii')); 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 a5430df9e0fc..88d907150414 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 000000000000..6a7ed2e13b3b --- /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-crypto-x509.js b/test/parallel/test-crypto-x509.js index 3f58a6cf0ccd..1467fd6104db 100644 --- a/test/parallel/test-crypto-x509.js +++ b/test/parallel/test-crypto-x509.js @@ -177,10 +177,8 @@ for (const [commonName, property] of [ assert.deepStrictEqual(x509.raw, der); - if (!isBoringSSL) { - assert.deepStrictEqual(x509.validFromDate, new Date('2022-09-03T21:40:37Z')); - assert.deepStrictEqual(x509.validToDate, new Date('2296-06-17T21:40:37Z')); - } + assert.deepStrictEqual(x509.validFromDate, new Date('2022-09-03T21:40:37Z')); + assert.deepStrictEqual(x509.validToDate, new Date('2296-06-17T21:40:37Z')); assert(x509.publicKey); assert.strictEqual(x509.publicKey.type, 'public'); @@ -565,10 +563,8 @@ UidvpWWipVLZgK+oDks+bKTobcoXGW9oXobiIYqslXPy -----END CERTIFICATE-----`.trim(); const c1 = new X509Certificate(certPemUTCTime); - if (!isBoringSSL) { - assert.deepStrictEqual(c1.validFromDate, new Date('1949-12-25T23:59:58Z')); - assert.deepStrictEqual(c1.validToDate, new Date('1950-01-01T23:59:58Z')); - } + assert.deepStrictEqual(c1.validFromDate, new Date('1949-12-25T23:59:58Z')); + assert.deepStrictEqual(c1.validToDate, new Date('1950-01-01T23:59:58Z')); // The GeneralizedTime format is used for dates in 2050 or later. const certPemGeneralizedTime = `-----BEGIN CERTIFICATE----- @@ -602,10 +598,8 @@ CWwQO8JZjJqFtqtuzy2n+gLCvqePgG/gmSqHOPm2ZbLW -----END CERTIFICATE-----`.trim(); const c2 = new X509Certificate(certPemGeneralizedTime); - if (!isBoringSSL) { - assert.deepStrictEqual(c2.validFromDate, new Date('2049-12-26T00:00:01Z')); - assert.deepStrictEqual(c2.validToDate, new Date('2050-01-02T00:00:01Z')); - } + assert.deepStrictEqual(c2.validFromDate, new Date('2049-12-26T00:00:01Z')); + assert.deepStrictEqual(c2.validToDate, new Date('2050-01-02T00:00:01Z')); } { diff --git a/test/parallel/test-http2-session-reuse-different-authority.js b/test/parallel/test-http2-session-reuse-different-authority.js index d76f17a728f3..7ff47e65d33f 100644 --- a/test/parallel/test-http2-session-reuse-different-authority.js +++ b/test/parallel/test-http2-session-reuse-different-authority.js @@ -5,6 +5,7 @@ if (!common.hasCrypto) const assert = require('assert'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); const h2 = require('http2'); const tls = require('tls'); const { once } = require('events'); @@ -125,7 +126,7 @@ server.on('stream', (stream) => { session: first.session, }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); @@ -136,7 +137,7 @@ server.on('stream', (stream) => { maxVersion: 'TLSv1.2', }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); } finally { diff --git a/test/parallel/test-https-agent-create-connection.js b/test/parallel/test-https-agent-create-connection.js index 56e967476c8b..5c4c23cdbe8a 100644 --- a/test/parallel/test-https-agent-create-connection.js +++ b/test/parallel/test-https-agent-create-connection.js @@ -5,6 +5,8 @@ const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); +const { untrustedLeafErrorCode } = require('../common/tls'); + const assert = require('assert'); const https = require('https'); @@ -17,7 +19,6 @@ const options = { const expectedHeader = /^HTTP\/1\.1 200 OK/; const expectedBody = /hello world\n/; -const expectCertError = /^UNABLE_TO_VERIFY_LEAF_SIGNATURE$/; function checkRequest(socket, server) { let result = ''; @@ -112,7 +113,7 @@ function createServer() { const options = null; const socket = agent.createConnection(port, host, options); socket.on('error', common.mustCall((e) => { - assert.match(e.code, expectCertError); + assert.strictEqual(e.code, untrustedLeafErrorCode); server.close(); })); })); @@ -127,7 +128,7 @@ function createServer() { const options = undefined; const socket = agent.createConnection(port, host, options); socket.on('error', common.mustCall((e) => { - assert.match(e.code, expectCertError); + assert.strictEqual(e.code, untrustedLeafErrorCode); server.close(); })); })); diff --git a/test/parallel/test-https-options-boolean-check.js b/test/parallel/test-https-options-boolean-check.js index 776e966211c7..f4193fdf737d 100644 --- a/test/parallel/test-https-options-boolean-check.js +++ b/test/parallel/test-https-options-boolean-check.js @@ -42,20 +42,6 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); -function filterBoringSSLKeyCertArrayCases(options, setName) { - if (!isBoringSSL) - return options; - - // The array-valued cases exercise multi-identity key/cert handling. - // BoringSSL may reject those cases with backend key/cert mismatch errors - // before the boolean/type validation this test is targeting. Keep the scalar - // cases so https.createServer() option type validation is still covered. - common.printSkipMessage( - `BoringSSL: skipping ${setName} key/cert array cases`); - return options.filter(([key, cert]) => !Array.isArray(key) && - !Array.isArray(cert)); -} - // Checks to ensure https.createServer doesn't throw an error // Format ['key', 'cert'] const validOptions = [ @@ -72,18 +58,24 @@ const validOptions = [ [keyDataView, certDataView], [keyDataView, false], [false, certDataView], - [[keyBuff, keyBuff2], [certBuff, certBuff2]], - [[keyStr, keyStr2], [certStr, certStr2]], [[keyStr, keyStr2], false], [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], ]; -filterBoringSSLKeyCertArrayCases(validOptions, 'valid') - .forEach(([key, cert]) => { - https.createServer({ key, cert }); - }); +// Node's BoringSSL setup keeps one identity, so mixed RSA/EC key and +// certificate arrays fail with a key-type mismatch before option validation. +if (!isBoringSSL) { + validOptions.push( + [[keyBuff, keyBuff2], [certBuff, certBuff2]], + [[keyStr, keyStr2], [certStr, certStr2]], + ); +} + +validOptions.forEach(([key, cert]) => { + https.createServer({ key, cert }); +}); // Checks to ensure https.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] @@ -96,14 +88,18 @@ const invalidKeyOptions = [ [true, false], [{ pem: keyBuff }, false], [1, false], - [[keyBuff, true], [certBuff, certBuff2], 1], [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], ]; -for (const [key, cert, index] of - filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { +// With both certificates loaded, BoringSSL rejects the RSA key against the +// last (EC) certificate before reaching the invalid second key. +if (!isBoringSSL) { + invalidKeyOptions.push([[keyBuff, true], [certBuff, certBuff2], 1]); +} + +for (const [key, cert, index] of invalidKeyOptions) { const val = index === undefined ? key : key[index]; assert.throws(() => { https.createServer({ key, cert }); @@ -131,8 +127,7 @@ const invalidCertOptions = [ [[keyStr, keyStr2], true], ]; -for (const [key, cert, index] of - filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { +for (const [key, cert, index] of invalidCertOptions) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { https.createServer({ key, cert }); diff --git a/test/parallel/test-https-session-reuse-different-servername.js b/test/parallel/test-https-session-reuse-different-servername.js index 138acd693501..3f2c0c62ae98 100644 --- a/test/parallel/test-https-session-reuse-different-servername.js +++ b/test/parallel/test-https-session-reuse-different-servername.js @@ -5,6 +5,7 @@ if (!common.hasCrypto) const assert = require('assert'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); const https = require('https'); const tls = require('tls'); const { once } = require('events'); @@ -104,7 +105,7 @@ const server = https.createServer({ ca: [load('ca1-cert.pem')], }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); @@ -116,7 +117,7 @@ const server = https.createServer({ ca: [load('ca1-cert.pem')], }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); } finally { diff --git a/test/parallel/test-https-simple.js b/test/parallel/test-https-simple.js index b0562a1cd981..042bc4aa66c6 100644 --- a/test/parallel/test-https-simple.js +++ b/test/parallel/test-https-simple.js @@ -26,6 +26,8 @@ const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); +const { untrustedLeafErrorCode } = require('../common/tls'); + const assert = require('assert'); const https = require('https'); @@ -93,7 +95,7 @@ server.listen(0, common.mustCall(() => { const checkCertReq = https.request(options, common.mustNotCall()).end(); checkCertReq.on('error', common.mustCall((e) => { - assert.strictEqual(e.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(e.code, untrustedLeafErrorCode); done(); })); })); diff --git a/test/parallel/test-https-strict.js b/test/parallel/test-https-strict.js index a72168289d54..3b27753209f9 100644 --- a/test/parallel/test-https-strict.js +++ b/test/parallel/test-https-strict.js @@ -25,6 +25,8 @@ const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); +const { untrustedLeafErrorCode } = require('../common/tls'); + // Disable strict server certificate validation by the client process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; @@ -167,7 +169,7 @@ function allListening() { const port3 = server3.address().port; // server1: host 'agent1', signed by ca1 - makeReq('/inv1', port1, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + makeReq('/inv1', port1, untrustedLeafErrorCode); makeReq('/inv1-ca1', port1, 'ERR_TLS_CERT_ALTNAME_INVALID', null, ca1); makeReq('/inv1-ca1ca2', port1, 'ERR_TLS_CERT_ALTNAME_INVALID', @@ -175,7 +177,7 @@ function allListening() { makeReq('/val1-ca1', port1, null, 'agent1', ca1); makeReq('/val1-ca1ca2', port1, null, 'agent1', [ca1, ca2]); makeReq('/inv1-ca2', port1, - 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'agent1', ca2); + untrustedLeafErrorCode, 'agent1', ca2); // server2: self-signed, host = 'agent2' // doesn't matter that thename matches, all of these will error. @@ -186,14 +188,14 @@ function allListening() { 'agent2', [ca1, ca2]); // server3: host 'agent3', signed by ca2 - makeReq('/inv3', port3, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + makeReq('/inv3', port3, untrustedLeafErrorCode); makeReq('/inv3-ca2', port3, 'ERR_TLS_CERT_ALTNAME_INVALID', null, ca2); makeReq('/inv3-ca1ca2', port3, 'ERR_TLS_CERT_ALTNAME_INVALID', null, [ca1, ca2]); makeReq('/val3-ca2', port3, null, 'agent3', ca2); makeReq('/val3-ca1ca2', port3, null, 'agent3', [ca1, ca2]); makeReq('/inv3-ca1', port3, - 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'agent1', ca1); + untrustedLeafErrorCode, 'agent1', ca1); } diff --git a/test/parallel/test-tls-addca.js b/test/parallel/test-tls-addca.js index a99d53f55d3f..4935350b8574 100644 --- a/test/parallel/test-tls-addca.js +++ b/test/parallel/test-tls-addca.js @@ -9,6 +9,7 @@ const fixtures = require('../common/fixtures'); const { assert, connect, keys, tls } = require(fixtures.path('tls-connect')); +const { untrustedLeafErrorCode } = require('../common/tls'); const contextWithoutCert = tls.createSecureContext({}); const contextWithCert = tls.createSecureContext({}); @@ -34,7 +35,7 @@ connect({ server: serverOptions, }, common.mustCall((err, pair, cleanup) => { assert(err); - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); cleanup(); // This time it should connect because contextWithCert includes the needed CA diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index da06156e2e07..509dc3f4bd6f 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -11,6 +11,7 @@ const { hasOpenSSL, isBoringSSL } = require('../common/crypto'); const { assert, connect, keys, tls } = require(fixtures.path('tls-connect')); +const { untrustedLeafErrorCode } = require('../common/tls'); // Use ec10 and agent10, they are the only identities with intermediate CAs. const client = keys.ec10; @@ -185,7 +186,7 @@ connect({ cert: server.single, }, }, common.mustCall((err, pair, cleanup) => { - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); return cleanup(); })); diff --git a/test/parallel/test-tls-client-cert-resumption.js b/test/parallel/test-tls-client-cert-resumption.js index 790809678d66..3fb90e0080c7 100644 --- a/test/parallel/test-tls-client-cert-resumption.js +++ b/test/parallel/test-tls-client-cert-resumption.js @@ -22,6 +22,7 @@ const assert = require('assert'); const crypto = require('crypto'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); const { once } = require('events'); const ca = fixtures.readKey('ca1-cert.pem'); @@ -47,7 +48,7 @@ const CLIENTS = { cert: fixtures.readKey('agent3-cert.pem'), }, authorized: false, - authorizationError: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + authorizationError: untrustedLeafErrorCode, peerCN: 'agent3', }, missing: { // No client certificate diff --git a/test/parallel/test-tls-friendly-error-message.js b/test/parallel/test-tls-friendly-error-message.js index 84471da487b2..2d734789e3d1 100644 --- a/test/parallel/test-tls-friendly-error-message.js +++ b/test/parallel/test-tls-friendly-error-message.js @@ -25,6 +25,7 @@ if (!common.hasCrypto) common.skip('missing crypto'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); const assert = require('assert'); const tls = require('tls'); @@ -39,6 +40,7 @@ tls.createServer({ key, cert }).on('connection', common.mustCall(function() { })).listen(0, common.mustCall(function() { const options = { port: this.address().port, rejectUnauthorized: true }; tls.connect(options).on('error', common.mustCall(function(err) { - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); + assert.match(err.message, /try running Node\.js with --use-system-ca/); })); })); diff --git a/test/parallel/test-tls-getcipher.js b/test/parallel/test-tls-getcipher.js index cb8f1ba826b5..560a1b2a6e25 100644 --- a/test/parallel/test-tls-getcipher.js +++ b/test/parallel/test-tls-getcipher.js @@ -109,10 +109,7 @@ tls.createServer({ rejectUnauthorized: false }, common.mustCall(() => { const cipher = client.getCipher(); - const expectedCipher = isBoringSSL ? - 'TLS_AES_128_GCM_SHA256' : - 'TLS_AES_256_GCM_SHA384'; - assert.strictEqual(cipher.name, expectedCipher); + assert.strictEqual(cipher.name, 'TLS_AES_256_GCM_SHA384'); assert.strictEqual(cipher.standardName, cipher.name); assert.strictEqual(cipher.version, isBoringSSL ? 'TLSv1/SSLv3' : diff --git a/test/parallel/test-tls-getprotocol.js b/test/parallel/test-tls-getprotocol.js index e3e0a1fa2418..6695d7a37bb5 100644 --- a/test/parallel/test-tls-getprotocol.js +++ b/test/parallel/test-tls-getprotocol.js @@ -13,7 +13,7 @@ const tls = require('tls'); const fixtures = require('../common/fixtures'); const fips3 = hasFIPS(3); -let clientConfigs = [ +const clientConfigs = [ { secureProtocol: 'TLSv1_method', version: 'TLSv1', @@ -28,24 +28,16 @@ let clientConfigs = [ }, ]; -if (isBoringSSL) { - // Remove the TLSv1 and TLSv1.1 cases. BoringSSL does not negotiate those - // legacy protocols in this configuration; keep TLSv1.2 to cover getProtocol() - // on a successful BoringSSL TLS handshake. - common.printSkipMessage('BoringSSL: skipping TLSv1/TLSv1.1 getProtocol cases'); - clientConfigs = clientConfigs.filter(({ version }) => version === 'TLSv1.2'); -} - const serverConfig = { - secureProtocol: 'TLS_method', + // BoringSSL's library default minimum is TLSv1.2, so enable legacy versions + // explicitly instead of relying on TLS_method's default minimum. + ...(isBoringSSL ? { minVersion: 'TLSv1' } : { secureProtocol: 'TLS_method' }), key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem') }; -if (!isBoringSSL) { - serverConfig.ciphers = fips3 ? - 'ECDHE-RSA-AES256-GCM-SHA384' : 'RSA@SECLEVEL=0'; -} +serverConfig.ciphers = fips3 ? + 'ECDHE-RSA-AES256-GCM-SHA384' : isBoringSSL ? 'RSA' : 'RSA@SECLEVEL=0'; const expectedConnections = fips3 ? 1 : clientConfigs.length; const server = tls.createServer(serverConfig, common.mustCall(expectedConnections)); diff --git a/test/parallel/test-tls-min-max-version.js b/test/parallel/test-tls-min-max-version.js index 932ad7f0f219..3f91d946c232 100644 --- a/test/parallel/test-tls-min-max-version.js +++ b/test/parallel/test-tls-min-max-version.js @@ -11,11 +11,6 @@ const { hasFIPS, } = require('../common/crypto'); -if (isBoringSSL) { - require('../common/boringssl').testLegacyProtocolUnsupported(); - return; -} - const fixtures = require('../common/fixtures'); const { inspect } = require('util'); @@ -168,20 +163,39 @@ test(U, 'TLSv1.2', 'TLS1_2_method', U, U, U, test('TLSv1.2', U, 'TLS1_2_method', U, U, U, U, 'ERR_TLS_PROTOCOL_VERSION_CONFLICT'); -// TLS_method means "any supported protocol". +// TLS_method uses the library's default minimum protocol version. BoringSSL's +// default is TLSv1.2, even when older versions are explicitly enabled by the +// other peer. test(U, U, 'TLSv1_2_method', U, U, 'TLS_method', 'TLSv1.2'); -test(U, U, 'TLSv1_1_method', U, U, 'TLS_method', 'TLSv1.1'); -test(U, U, 'TLSv1_method', U, U, 'TLS_method', 'TLSv1'); test(U, U, 'TLS_method', U, U, 'TLSv1_2_method', 'TLSv1.2'); -test(U, U, 'TLS_method', U, U, 'TLSv1_1_method', 'TLSv1.1'); -test(U, U, 'TLS_method', U, U, 'TLSv1_method', 'TLSv1'); +for (const [method, version] of [ + ['TLSv1_1_method', 'TLSv1.1'], + ['TLSv1_method', 'TLSv1'], +]) { + const expected = isBoringSSL ? + [U, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION', 'ERR_SSL_UNSUPPORTED_PROTOCOL'] : + [version]; + test(U, U, method, U, U, 'TLS_method', ...expected); + test(U, U, 'TLS_method', U, U, method, ...expected); +} // 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 = hasOpenSSL(3) ? - 'ERR_SSL_NO_PROTOCOLS_AVAILABLE' : 'ERR_SSL_INTERNAL_ERROR'; -const NO_PROTOCOLS_AVAILABLE_SERVER_ALERT = hasOpenSSL(3) ? - 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION' : 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR'; +// BoringSSL closes the connection without sending an alert in this case. +const NO_PROTOCOLS_AVAILABLE_SERVER = isBoringSSL ? + 'ERR_SSL_NO_SUPPORTED_VERSIONS_ENABLED' : hasOpenSSL(3) ? + 'ERR_SSL_NO_PROTOCOLS_AVAILABLE' : 'ERR_SSL_INTERNAL_ERROR'; +const NO_PROTOCOLS_AVAILABLE_SERVER_ALERT = isBoringSSL ? + 'ECONNRESET' : hasOpenSSL(3) ? + 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION' : 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR'; +const NO_PROTOCOLS_AVAILABLE_CLIENT = isBoringSSL ? + 'ERR_SSL_NO_SUPPORTED_VERSIONS_ENABLED' : 'ERR_SSL_NO_PROTOCOLS_AVAILABLE'; +const NO_PROTOCOLS_AVAILABLE_CLIENT_ALERT = isBoringSSL ? + 'ECONNRESET' : 'ERR_SSL_UNEXPECTED_MESSAGE'; +// When a client rejects the server's legacy version, BoringSSL surfaces the +// protocol-version alert while OpenSSL reports a record version mismatch. +const UNSUPPORTED_PROTOCOL_SERVER_ERROR = isBoringSSL ? + 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION' : 'ERR_SSL_WRONG_VERSION_NUMBER'; // SSLv23 also means "any supported protocol" greater than the default // minimum (which is configurable via command line). @@ -198,9 +212,9 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.3') { test(U, U, 'TLSv1_method', U, U, 'SSLv23_method', U, NO_PROTOCOLS_AVAILABLE_SERVER_ALERT, NO_PROTOCOLS_AVAILABLE_SERVER); test(U, U, 'SSLv23_method', U, U, 'TLSv1_1_method', - U, 'ERR_SSL_NO_PROTOCOLS_AVAILABLE', 'ERR_SSL_UNEXPECTED_MESSAGE'); + U, NO_PROTOCOLS_AVAILABLE_CLIENT, NO_PROTOCOLS_AVAILABLE_CLIENT_ALERT); test(U, U, 'SSLv23_method', U, U, 'TLSv1_method', - U, 'ERR_SSL_NO_PROTOCOLS_AVAILABLE', 'ERR_SSL_UNEXPECTED_MESSAGE'); + U, NO_PROTOCOLS_AVAILABLE_CLIENT, NO_PROTOCOLS_AVAILABLE_CLIENT_ALERT); } if (DEFAULT_MIN_VERSION === 'TLSv1.2') { @@ -211,9 +225,9 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.2') { U, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION', 'ERR_SSL_UNSUPPORTED_PROTOCOL'); test(U, U, 'SSLv23_method', U, U, 'TLSv1_1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); test(U, U, 'SSLv23_method', U, U, 'TLSv1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); } if (DEFAULT_MIN_VERSION === 'TLSv1.1') { @@ -223,7 +237,7 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.1') { 'ERR_SSL_UNSUPPORTED_PROTOCOL'); test(U, U, 'SSLv23_method', U, U, 'TLSv1_1_method', 'TLSv1.1'); test(U, U, 'SSLv23_method', U, U, 'TLSv1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); } if (DEFAULT_MIN_VERSION === 'TLSv1') { @@ -250,9 +264,9 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.2') { if (DEFAULT_MAX_VERSION === 'TLSv1.2') { test(U, U, U, U, U, 'TLSv1_1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); test(U, U, U, U, U, 'TLSv1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); } else { // TLS1.3 client hellos are not understood by TLS1.1 or below. test(U, U, U, U, U, 'TLSv1_1_method', @@ -274,7 +288,7 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.1') { if (DEFAULT_MAX_VERSION === 'TLSv1.2') { test(U, U, U, U, U, 'TLSv1_method', - U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER'); + U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', UNSUPPORTED_PROTOCOL_SERVER_ERROR); } else { // TLS1.3 client hellos are not understood by TLS1.1 or below. test(U, U, U, U, U, 'TLSv1_method', diff --git a/test/parallel/test-tls-name-constraints-cn.js b/test/parallel/test-tls-name-constraints-cn.js index 0aae9502eeb0..22b87e4e0192 100644 --- a/test/parallel/test-tls-name-constraints-cn.js +++ b/test/parallel/test-tls-name-constraints-cn.js @@ -3,9 +3,6 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); const { isBoringSSL } = require('../common/crypto'); -// BoringSSL rejects DNS-like CNs without SANs under name constraints, -// even when the CN is permitted by those constraints. -if (isBoringSSL) common.skip('requires OpenSSL CN name constraints'); const assert = require('assert'); const tls = require('tls'); @@ -14,14 +11,19 @@ const fixtures = require('../common/fixtures'); const ca = fixtures.readKey('name-constraints-ca-cert.pem'); const key = fixtures.readKey('agent1-key.pem'); -// A DNS SAN prevents CN fallback; an email SAN does not. Name constraints -// must cover the CN whenever hostname verification can use it. +// A DNS SAN prevents CN fallback; an email SAN does not. OpenSSL constrains +// the CN whenever hostname verification can use it. BoringSSL instead rejects +// DNS-like CNs when the SAN extension is absent, but does not constrain the CN +// when an email SAN is present, so that case remains authorized. +const constraintError = isBoringSSL ? + /Issuer has name constraints but leaf has no SANs/ : + /permitted subtree violation/; for (const version of ['TLSv1.2', 'TLSv1.3']) { for (const [name, servername, valid] of [ - ['permitted', 'www.example.com', true], + ['permitted', 'www.example.com', !isBoringSSL], ['excluded', 'outside.invalid', false], ['dns-san', 'www.example.com', true], - ['email-san', 'outside.invalid', false], + ['email-san', 'outside.invalid', isBoringSSL], ]) { for (const rejectUnauthorized of [true, false]) { const rejected = !valid && rejectUnauthorized; @@ -46,7 +48,7 @@ for (const version of ['TLSv1.2', 'TLSv1.3']) { })); client.on('error', rejected ? common.mustCall((err) => { assert.strictEqual(err.code, 'UNSPECIFIED'); - assert.match(err.message, /permitted subtree violation/); + assert.match(err.message, constraintError); }) : common.mustNotCall()); client.on('close', common.mustCall(() => server.close())); })); diff --git a/test/parallel/test-tls-options-boolean-check.js b/test/parallel/test-tls-options-boolean-check.js index 4366502aacb3..776548c838f3 100644 --- a/test/parallel/test-tls-options-boolean-check.js +++ b/test/parallel/test-tls-options-boolean-check.js @@ -42,20 +42,6 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); -function filterBoringSSLKeyCertArrayCases(options, setName) { - if (!isBoringSSL) - return options; - - // The array-valued cases exercise multi-identity key/cert handling. - // BoringSSL may reject those cases with backend key/cert mismatch errors - // before the boolean/type validation this test is targeting. Keep the scalar - // cases so tls.createServer() option type validation is still covered. - common.printSkipMessage( - `BoringSSL: skipping ${setName} key/cert array cases`); - return options.filter(([key, cert]) => !Array.isArray(key) && - !Array.isArray(cert)); -} - // Checks to ensure tls.createServer doesn't throw an error // Format ['key', 'cert'] const validOptions = [ @@ -72,18 +58,24 @@ const validOptions = [ [keyDataView, certDataView], [keyDataView, false], [false, certDataView], - [[keyBuff, keyBuff2], [certBuff, certBuff2]], - [[keyStr, keyStr2], [certStr, certStr2]], [[keyStr, keyStr2], false], [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], ]; -filterBoringSSLKeyCertArrayCases(validOptions, 'valid') - .forEach(([key, cert]) => { - tls.createServer({ key, cert }); - }); +// Node's BoringSSL setup keeps one identity, so mixed RSA/EC key and +// certificate arrays fail with a key-type mismatch before option validation. +if (!isBoringSSL) { + validOptions.push( + [[keyBuff, keyBuff2], [certBuff, certBuff2]], + [[keyStr, keyStr2], [certStr, certStr2]], + ); +} + +validOptions.forEach(([key, cert]) => { + tls.createServer({ key, cert }); +}); // Checks to ensure tls.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] @@ -95,14 +87,18 @@ const invalidKeyOptions = [ [true, false], [true, false], [{ pem: keyBuff }, false], - [[keyBuff, true], [certBuff, certBuff2], 1], [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], ]; -for (const [key, cert, index] of - filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { +// With both certificates loaded, BoringSSL rejects the RSA key against the +// last (EC) certificate before reaching the invalid second key. +if (!isBoringSSL) { + invalidKeyOptions.push([[keyBuff, true], [certBuff, certBuff2], 1]); +} + +for (const [key, cert, index] of invalidKeyOptions) { const val = index === undefined ? key : key[index]; assert.throws(() => { tls.createServer({ key, cert }); @@ -130,8 +126,7 @@ const invalidCertOptions = [ [[keyStr, keyStr2], true], ]; -for (const [key, cert, index] of - filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { +for (const [key, cert, index] of invalidCertOptions) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { tls.createServer({ key, cert }); diff --git a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js index 0045517b0224..50701b633e94 100644 --- a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js +++ b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js @@ -15,11 +15,6 @@ const { isBoringSSL } = require('../common/crypto'); if (!common.hasCrypto) common.skip('missing crypto'); -if (isBoringSSL) { - require('../common/boringssl').testPskTls13Unsupported(); - return; -} - const assert = require('assert'); const { describe, it } = require('node:test'); const https = require('node:https'); @@ -27,6 +22,8 @@ const tls = require('tls'); const fixtures = require('../common/fixtures'); const CIPHERS = 'PSK+HIGH'; +// BoringSSL's PSK callbacks support the certificate-less setup under TLS 1.2. +const pskOptions = isBoringSSL ? { maxVersion: 'TLSv1.2' } : {}; const TEST_TIMEOUT = 5000; // Helper to create a promise that rejects on uncaughtException or timeout @@ -75,6 +72,7 @@ describe('TLS callback exception handling', () => { it('pskCallback returning invalid type emits tlsClientError', async (t) => { const server = tls.createServer({ ciphers: CIPHERS, + ...pskOptions, pskCallback: () => { // Return invalid type (string instead of object/Buffer) return 'invalid-should-be-object-or-buffer'; @@ -116,7 +114,10 @@ describe('TLS callback exception handling', () => { await promise; }); - it('newSession synchronous error emits error', async (t) => { + it('newSession synchronous error emits error', { + skip: isBoringSSL && + 'BoringSSL does not trigger the TLS 1.3 newSession callback in this test', + }, async (t) => { const server = tls.createServer({ key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem'), @@ -284,6 +285,7 @@ describe('TLS callback exception handling', () => { it('pskCallback throwing emits tlsClientError', async (t) => { const server = tls.createServer({ ciphers: CIPHERS, + ...pskOptions, pskCallback: () => { throw new Error('Intentional callback error'); }, @@ -411,6 +413,7 @@ describe('TLS callback exception handling', () => { const server = tls.createServer({ ciphers: CIPHERS, + ...pskOptions, pskCallback: () => PSK, pskIdentityHint: 'test-hint', }); @@ -453,6 +456,7 @@ describe('TLS callback exception handling', () => { const server = tls.createServer({ ciphers: CIPHERS, + ...pskOptions, pskCallback: () => PSK, pskIdentityHint: 'test-hint', }); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index 9cd4a68676ec..0e4bf3a09f29 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -7,11 +7,6 @@ if (!common.hasCrypto) { const { isBoringSSL, hasOpenSSL } = require('../common/crypto'); -if (isBoringSSL) { - require('../common/boringssl').testPskTls13Unsupported(); - return; -} - const assert = require('assert'); const tls = require('tls'); @@ -64,8 +59,16 @@ function test(secret, opts, error) { })); } -test({ psk: USERS.UserA, identity: 'UserA' }); test({ psk: USERS.UserA, identity: 'UserA' }, { maxVersion: 'TLSv1.2' }); + +if (isBoringSSL) { + // Keep the TLS 1.2 callback and data checks above. The remaining cases use + // TLS 1.3, which Node's BoringSSL PSK integration does not support yet. + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + +test({ psk: USERS.UserA, identity: 'UserA' }); test({ psk: USERS.UserA, identity: 'UserA' }, { minVersion: 'TLSv1.3' }); test({ psk: USERS.UserB, identity: 'UserB' }); test({ psk: USERS.UserB, identity: 'UserB' }, { minVersion: 'TLSv1.3' }); diff --git a/test/parallel/test-tls-psk-server.js b/test/parallel/test-tls-psk-server.js index 29d164f019da..527c06d553a2 100644 --- a/test/parallel/test-tls-psk-server.js +++ b/test/parallel/test-tls-psk-server.js @@ -5,14 +5,7 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } -const { isBoringSSL } = require('../common/crypto'); - -if (isBoringSSL) { - require('../common/boringssl').testPskTls13Unsupported(); - return; -} - -const { opensslCli } = require('../common/crypto'); +const { isBoringSSL, opensslCli } = require('../common/crypto'); if (!opensslCli) { common.skip('missing openssl cli'); @@ -29,6 +22,8 @@ const IDENTITY = 'TestUser'; const server = tls.createServer({ ciphers: CIPHERS, + // BoringSSL requires TLS 1.2 for this certificate-less PSK handshake. + ...(isBoringSSL ? { maxVersion: 'TLSv1.2' } : {}), pskIdentityHint: IDENTITY, pskCallback: common.mustCall((socket, identity) => { assert.ok(socket instanceof tls.TLSSocket); diff --git a/test/parallel/test-tls-session-reuse-different-servername.js b/test/parallel/test-tls-session-reuse-different-servername.js index c3ae4be15785..1692f6bab051 100644 --- a/test/parallel/test-tls-session-reuse-different-servername.js +++ b/test/parallel/test-tls-session-reuse-different-servername.js @@ -5,6 +5,7 @@ if (!common.hasCrypto) const assert = require('assert'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); const tls = require('tls'); const { once } = require('events'); @@ -90,7 +91,7 @@ async function testVersion(minVersion, maxVersion) { return socket; }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); @@ -107,7 +108,7 @@ async function testVersion(minVersion, maxVersion) { return socket; }), { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', + code: untrustedLeafErrorCode, }, ); } finally { diff --git a/test/parallel/test-tls-set-ciphers-error.js b/test/parallel/test-tls-set-ciphers-error.js index 102186f9e399..3cfc8c391bf7 100644 --- a/test/parallel/test-tls-set-ciphers-error.js +++ b/test/parallel/test-tls-set-ciphers-error.js @@ -7,7 +7,7 @@ if (!common.hasCrypto) const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const { hasOpenSSL, isBoringSSL } = require('../common/crypto'); +const { hasOpenSSL } = require('../common/crypto'); { const options = { @@ -21,12 +21,8 @@ const { hasOpenSSL, isBoringSSL } = require('../common/crypto'); assert.throws(() => tls.createServer(options, common.mustNotCall()), /no[_ ]cipher[_ ]match/i); options.ciphers = 'TLS_not_a_cipher'; - if (isBoringSSL) { - tls.createServer(options).close(); - } else { - assert.throws(() => tls.createServer(options, common.mustNotCall()), - /no[_ ]cipher[_ ]match/i); - } + assert.throws(() => tls.createServer(options, common.mustNotCall()), + /no[_ ]cipher[_ ]match/i); } // Cipher name matching is case-sensitive prior to OpenSSL 4.0, and diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 57fb35991121..0562c808b8d0 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -7,9 +7,10 @@ if (!common.hasCrypto) { const { hasOpenSSL, hasFIPS, + isBoringSSL, } = require('../common/crypto'); -if (!hasOpenSSL(3)) { +if (!hasOpenSSL(3) && !isBoringSSL) { common.skip('missing crypto, or OpenSSL version lower than 3'); } @@ -133,10 +134,11 @@ if (hasFIPS(3)) { test('TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256', U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); - test('AES256-SHA', 'AES256-SHA256', U, expectedTLSAlertError, + // Use TLS 1.2 suites available on both crypto backends. + test('AES256-SHA', 'ECDHE-RSA-AES256-GCM-SHA384', U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); test('AES256-SHA:TLS_AES_256_GCM_SHA384', - 'TLS_CHACHA20_POLY1305_SHA256:AES256-SHA256', + 'TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES256-GCM-SHA384', U, expectedTLSAlertError, 'ERR_SSL_NO_SHARED_CIPHER'); // Cipher order ignored, TLS1.3 chosen before TLS1.2. @@ -154,7 +156,10 @@ if (hasFIPS(3)) { // default, but work. // However, for OpenSSL32 AES_128 is not enabled due to the // default security level - if (!hasOpenSSL(3, 2)) { + if (isBoringSSL) { + // BoringSSL does not implement the TLS 1.3 CCM cipher suites. + test('TLS_AES_128_CCM_8_SHA256', U, U, 'ERR_SSL_NO_CIPHER_MATCH', U); + } else if (!hasOpenSSL(3, 2)) { test('TLS_AES_128_CCM_8_SHA256', U, U, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE', 'ERR_SSL_NO_SHARED_CIPHER'); diff --git a/test/parallel/test-tls-set-default-ca-certificates-append-fetch.mjs b/test/parallel/test-tls-set-default-ca-certificates-append-fetch.mjs index ca8436b22b0a..af27acb7bf40 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-append-fetch.mjs +++ b/test/parallel/test-tls-set-default-ca-certificates-append-fetch.mjs @@ -11,7 +11,7 @@ import assert from 'node:assert'; if (!common.hasCrypto) common.skip('missing crypto'); -const { includesCert } = await import('../common/tls.js'); +const { includesCert, untrustedLeafErrorCode } = await import('../common/tls.js'); const { default: https } = await import('node:https'); const { default: tls } = await import('node:tls'); @@ -37,7 +37,7 @@ const url = `https://localhost:${server.address().port}/hello-world`; await assert.rejects( fetch(url), (err) => { - assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.cause.code, untrustedLeafErrorCode); return true; }, ); diff --git a/test/parallel/test-tls-set-default-ca-certificates-append-https-request.js b/test/parallel/test-tls-set-default-ca-certificates-append-https-request.js index d7a3baded420..f20cb010ca8d 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-append-https-request.js +++ b/test/parallel/test-tls-set-default-ca-certificates-append-https-request.js @@ -10,7 +10,7 @@ const assert = require('assert'); const https = require('https'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const { includesCert } = require('../common/tls'); +const { includesCert, untrustedLeafErrorCode } = require('../common/tls'); const bundledCerts = tls.getCACertificates('bundled'); const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem'); @@ -43,7 +43,7 @@ server.listen(0, common.mustCall(() => { req1.on('error', common.mustCall((err) => { console.log(err); // Should fail with certificate verification error - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); // Now add the fake-startcom-root-cert to bundled certs - connection should succeed tls.setDefaultCACertificates([...bundledCerts, fixtureCert]); diff --git a/test/parallel/test-tls-set-default-ca-certificates-reset-fetch.mjs b/test/parallel/test-tls-set-default-ca-certificates-reset-fetch.mjs index 479b415d4c51..363497cbe828 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-reset-fetch.mjs +++ b/test/parallel/test-tls-set-default-ca-certificates-reset-fetch.mjs @@ -11,6 +11,7 @@ import assert from 'node:assert'; if (!common.hasCrypto) common.skip('missing crypto'); +const { untrustedLeafErrorCode } = await import('../common/tls.js'); const { default: https } = await import('node:https'); const { default: tls } = await import('node:tls'); @@ -39,7 +40,7 @@ tls.setDefaultCACertificates([]); await assert.rejects( fetch(`https://127.0.0.1:${server.address().port}/empty-ca-test`), (err) => { - assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.cause.code, untrustedLeafErrorCode); return true; }, ); diff --git a/test/parallel/test-tls-set-default-ca-certificates-reset-https-request.js b/test/parallel/test-tls-set-default-ca-certificates-reset-https-request.js index 7389cacf6d66..b4fe577ab46c 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-reset-https-request.js +++ b/test/parallel/test-tls-set-default-ca-certificates-reset-https-request.js @@ -9,6 +9,7 @@ const assert = require('assert'); const https = require('https'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const { untrustedLeafErrorCode } = require('../common/tls'); // Test HTTPS connection succeeds with proper CA, fails after removing it const server = https.createServer({ @@ -49,7 +50,7 @@ server.listen(0, common.mustCall(() => { req2.on('error', common.mustCall((err) => { // Should fail with certificate verification error. - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); server.close(); })); diff --git a/test/parallel/test-tls-socket-default-options.js b/test/parallel/test-tls-socket-default-options.js index 8dfd912285dc..e7ad1be3d423 100644 --- a/test/parallel/test-tls-socket-default-options.js +++ b/test/parallel/test-tls-socket-default-options.js @@ -8,13 +8,14 @@ const assert = require('assert'); const { connect, keys, tls } = require(fixtures.path('tls-connect')); +const { untrustedLeafErrorCode } = require('../common/tls'); test(undefined, (err) => { - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); }); test({}, (err) => { - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); }); test( @@ -30,8 +31,7 @@ test( test( { secureContext: tls.createSecureContext(), ca: keys.agent1.ca }, (err) => { - assert.strictEqual(err.code, - 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); }); function test(client, callback) { @@ -42,7 +42,7 @@ function test(client, callback) { cert: keys.agent1.cert, }, }, common.mustCall(function(err, pair, cleanup) { - assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.code, untrustedLeafErrorCode); let recv = ''; pair.server.server.once('secureConnection', common.mustCall((conn) => { conn.on('data', (data) => recv += data); diff --git a/test/parallel/test-tls13-ciphers.js b/test/parallel/test-tls13-ciphers.js new file mode 100644 index 000000000000..7cf87557e8dc --- /dev/null +++ b/test/parallel/test-tls13-ciphers.js @@ -0,0 +1,151 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { constants } = require('crypto'); +const tls = require('tls'); +const fixtures = require('../common/fixtures'); +const { hasOpenSSL, isBoringSSL } = require('../common/crypto'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { connect, keys } = require(fixtures.path('tls-connect')); + +const aes128 = 'TLS_AES_128_GCM_SHA256'; +const aes256 = 'TLS_AES_256_GCM_SHA384'; +const tls13Ciphers = [aes128, aes256, 'TLS_CHACHA20_POLY1305_SHA256']; +const legacy = 'ECDHE-RSA-AES256-GCM-SHA384'; + +function test(clientCiphers, serverCiphers, expectedCipher, options = {}) { + connect({ + client: { + rejectUnauthorized: false, + minVersion: 'TLSv1.2', + maxVersion: 'TLSv1.3', + ciphers: clientCiphers, + ...options.client, + }, + server: { + key: keys.agent2.key, + cert: keys.agent2.cert, + minVersion: 'TLSv1.2', + maxVersion: 'TLSv1.3', + ciphers: serverCiphers, + ...options.server, + }, + }, common.mustCall((err, pair, cleanup) => { + if (expectedCipher === undefined) { + assert(err); + assert.strictEqual(pair.server.err.code, 'ERR_SSL_NO_SHARED_CIPHER'); + assert(pair.client.err); + } else { + assert.ifError(err); + const expected = Array.isArray(expectedCipher) ? expectedCipher : [expectedCipher]; + const cipher = pair.client.conn.getCipher().name; + assert(expected.includes(cipher)); + for (const { conn } of [pair.client, pair.server]) { + assert.strictEqual(conn.getCipher().name, cipher); + assert.strictEqual(conn.getProtocol(), + expectedCipher === legacy ? 'TLSv1.2' : 'TLSv1.3'); + } + } + cleanup(); + })); +} + +function testLegacyOnlyCipherLists() { + for (const honorCipherOrder of [true, false]) { + const options = { server: { honorCipherOrder } }; + // A legacy-only list retains the backend's TLS 1.3 defaults. Check that + // each peer can negotiate either AES suite without assuming their order. + test(aes128, legacy, aes128, options); + test(aes256, legacy, aes256, options); + test(legacy, aes128, aes128, options); + test(legacy, aes256, aes256, options); + test(legacy, legacy, tls13Ciphers, options); + } +} + +if (process.argv[2] !== undefined) { + const mode = process.argv[2]; + if (mode === 'runtime-defaults') { + tls.DEFAULT_CIPHERS = aes128; + } else { + assert.strictEqual(tls.DEFAULT_CIPHERS, + mode === 'cli-defaults' ? aes128 : legacy); + } + // Explicit TLS 1.3 defaults are still applied when ciphers is omitted, but + // must not replace the native defaults for an explicit legacy-only list. + test(undefined, undefined, mode === 'cli-legacy-defaults' ? tls13Ciphers : aes128); + testLegacyOnlyCipherLists(); + return; +} + +// Both peers must apply the configured TLS 1.3 cipher list. +test(aes256, undefined, aes256); +test(undefined, aes256, aes256); +test(aes128, aes256, undefined); + +// Duplicate names and trailing whitespace must not cause rejection. +const duplicates = `${aes256} \t:${aes256}`; +test(duplicates, duplicates, aes256); + +// OpenSSL 1.1.1 rejects unknown TLS 1.3 cipher names. OpenSSL 3 and BoringSSL +// ignore them and do not interpret exclusion rules in the TLS 1.3 list. +const list = `TLS_UNKNOWN:!${aes128}:${duplicates}`; +if (hasOpenSSL(3) || isBoringSSL) { + test(list, list, aes256); +} else { + assert.throws(() => tls.createSecureContext({ ciphers: list }), { + code: 'ERR_SSL_NO_CIPHER_MATCH', + }); +} +for (const ciphers of ['TLS_UNKNOWN', `!${aes128}`]) { + assert.throws(() => tls.createSecureContext({ ciphers }), { + code: 'ERR_SSL_NO_CIPHER_MATCH', + }); +} +if (isBoringSSL) { + assert.throws(() => tls.createSecureContext({ + ciphers: 'TLS_AES_128_CCM_SHA256', + }), { + code: 'ERR_SSL_NO_CIPHER_MATCH', + }); +} + +// Honor the server's cipher-order option in TLS 1.3 as well as TLS 1.2. +test(`${aes256}:${aes128}`, `${aes128}:${aes256}`, aes128, { + server: { honorCipherOrder: true }, +}); +test(`${aes256}:${aes128}`, `${aes128}:${aes256}`, aes256, { + server: { honorCipherOrder: false }, +}); +testLegacyOnlyCipherLists(); + +// TLS 1.2 configuration and explicit protocol restrictions remain effective. +const mixed = `${legacy}:${aes256}`; +test(mixed, mixed, aes256); +test(mixed, mixed, legacy, { + client: { maxVersion: 'TLSv1.2' }, + server: { maxVersion: 'TLSv1.2' }, +}); +test(mixed, mixed, legacy, { + server: { secureOptions: constants.SSL_OP_NO_TLSv1_3 }, +}); + +// Empty public cipher strings continue to select Node's defaults. +test('', aes256, aes256); +test(aes256, '', aes256); + +for (const [mode, ciphers] of [ + ['runtime-defaults', undefined], + ['cli-defaults', aes128], + ['cli-legacy-defaults', legacy], +]) { + spawnSyncAndAssert(process.execPath, [ + ...process.execArgv, + ...(ciphers === undefined ? [] : [`--tls-cipher-list=${ciphers}`]), + __filename, + mode, + ], {}); +} diff --git a/test/parallel/test-webcrypto-boringssl-sha3.js b/test/parallel/test-webcrypto-boringssl-sha3.js new file mode 100644 index 000000000000..f4e2267fe0e0 --- /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 4fb372fd3a8b..4fbaa1d7c646 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 318ca8d2500d..a4acb112c6b3 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 89bd86bd9284..f9e31192602b 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 8c851682432a..45d0aa64f721 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 385b8e0bc520..9f184084e7c2 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 63fdde05f1ef..4578f7d587d8 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 b1fc8f41b2a9..6618a43db749 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 20102c32e6a5..8365a7d40598 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 88bc64194e6b..55e45119103c 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 bbe5dd498109..93d8a43c7ae4 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 201d28fbf1f0..8551ad3afa36 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/sequential/test-tls-psk-client.js b/test/sequential/test-tls-psk-client.js index 5cb429f47dec..37aa43a841f2 100644 --- a/test/sequential/test-tls-psk-client.js +++ b/test/sequential/test-tls-psk-client.js @@ -5,14 +5,7 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } -const { isBoringSSL } = require('../common/crypto'); - -if (isBoringSSL) { - require('../common/boringssl').testPskTls13Unsupported(); - return; -} - -const { opensslCli } = require('../common/crypto'); +const { isBoringSSL, opensslCli } = require('../common/crypto'); if (!opensslCli) { common.skip('missing openssl cli'); @@ -95,6 +88,8 @@ waitForPort(common.PORT, common.mustCall((err) => { function runClient(message, cb) { const s = tls.connect(common.PORT, { ciphers: CIPHERS, + // BoringSSL requires TLS 1.2 for this certificate-less PSK handshake. + ...(isBoringSSL ? { maxVersion: 'TLSv1.2' } : {}), checkServerIdentity: () => {}, pskCallback(hint) { // 'hint' will be null in TLS1.3. diff --git a/test/system-ca/test-native-intermediate-certs.mjs b/test/system-ca/test-native-intermediate-certs.mjs index edf1315422f8..9638176b9be0 100644 --- a/test/system-ca/test-native-intermediate-certs.mjs +++ b/test/system-ca/test-native-intermediate-certs.mjs @@ -11,6 +11,8 @@ if (!common.hasCrypto) { common.skip('requires crypto'); } +const { untrustedLeafErrorCode } = await import('../common/tls.js'); + // To run this test, the system needs to be configured to trust // the CA certificate first (which needs an interactive GUI approval, e.g. TouchID): // see the README.md in this folder for instructions on how to do this. @@ -72,7 +74,7 @@ describe('use-system-ca', function() { if (common.isWindows) { assert.strictEqual(err.cause.code, 'UNABLE_TO_GET_ISSUER_CERT'); } else { - assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.cause.code, untrustedLeafErrorCode); } } }); diff --git a/test/system-ca/test-set-default-ca-certificates-append-system-ca.mjs b/test/system-ca/test-set-default-ca-certificates-append-system-ca.mjs index 0cbd9804c365..c9a4cc25a047 100644 --- a/test/system-ca/test-set-default-ca-certificates-append-system-ca.mjs +++ b/test/system-ca/test-set-default-ca-certificates-append-system-ca.mjs @@ -13,6 +13,7 @@ if (!common.hasCrypto) { common.skip('requires crypto'); } +const { untrustedLeafErrorCode } = await import('../common/tls.js'); const { default: https } = await import('node:https'); const { default: tls } = await import('node:tls'); @@ -32,7 +33,7 @@ const url = `https://localhost:${server.address().port}/hello-world`; await assert.rejects( fetch(url), (err) => { - assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.cause.code, untrustedLeafErrorCode); return true; }, ); diff --git a/test/system-ca/test-use-system-ca-worker-disable.mjs b/test/system-ca/test-use-system-ca-worker-disable.mjs index 43f3f2937602..be8497bb54c6 100644 --- a/test/system-ca/test-use-system-ca-worker-disable.mjs +++ b/test/system-ca/test-use-system-ca-worker-disable.mjs @@ -14,6 +14,8 @@ if (!common.hasCrypto) { common.skip('requires crypto'); } +const { untrustedLeafErrorCode } = await import('../common/tls.js'); + // To run this test, the system needs to be configured to trust // the CA certificate first (which needs an interactive GUI approval, e.g. TouchID): // see the README.md in this folder for instructions on how to do this. @@ -61,7 +63,7 @@ describe('use-system-ca', function() { const [message] = await once(worker, 'message'); assert.strictEqual(message.ok, false); assert( - message.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || + message.code === untrustedLeafErrorCode || message.code === 'SELF_SIGNED_CERT_IN_CHAIN', `Expected certificate verification error but got: ${JSON.stringify( message, diff --git a/test/system-ca/test-use-system-ca-worker-enable.mjs b/test/system-ca/test-use-system-ca-worker-enable.mjs index f5f3faab4814..3b8d7d71a713 100644 --- a/test/system-ca/test-use-system-ca-worker-enable.mjs +++ b/test/system-ca/test-use-system-ca-worker-enable.mjs @@ -14,6 +14,8 @@ if (!common.hasCrypto) { common.skip('requires crypto'); } +const { untrustedLeafErrorCode } = await import('../common/tls.js'); + // To run this test, the system needs to be configured to trust // the CA certificate first (which needs an interactive GUI approval, e.g. TouchID): // see the README.md in this folder for instructions on how to do this. @@ -57,7 +59,7 @@ describe('use-system-ca', function() { workerData: { url }, }); await assert.rejects(fetch(url), (err) => { - assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + assert.strictEqual(err.cause.code, untrustedLeafErrorCode); return true; }); diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index a73db3793a92..4e8e4efd8826 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', diff --git a/tools/nix/pkgs.nix b/tools/nix/pkgs.nix index 42b2e183e12a..d81bf445a793 100644 --- a/tools/nix/pkgs.nix +++ b/tools/nix/pkgs.nix @@ -21,13 +21,20 @@ else openssl_3 ; - # TODO: remove this when the pin we use has picked up https://boringssl-review.googlesource.com/c/boringssl/+/104208 boringssl = let - inherit (nixpkgs) boringssl lib writeTextFile; + inherit (nixpkgs) callPackage lib writeTextFile; + # TODO: remove this override once nixpkgs.boringssl is 0.20261020.0 or newer. + boringssl = callPackage (builtins.fetchurl { + url = "${repo}/raw/1c17f6e5f086bf6b259e7a28f68657ed8548a25a/pkgs/by-name/bo/boringssl/package.nix"; + sha256 = "10cw2np6shjanxa2vzfxsrbyz451g1b6qan2v9c1ln75i3n5zn8a"; + }) { }; in + # BoringSSL does not install the openssl.pc metadata required by configure. + # https://boringssl-review.googlesource.com/c/boringssl/+/104208 writeTextFile { inherit (boringssl) name; + passthru = { inherit (boringssl) version; }; destination = "/lib/pkgconfig/openssl.pc"; text = '' Name: BoringSSL