From 41531b2fccf845278375666417dc9d16ce57fd36 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 22 Oct 2026 11:15:41 +0200 Subject: [PATCH 1/4] tls,tools: update BoringSSL pin and TLS errors Use BoringSSL 0.20261020.0 for shared-library builds and expose the package wrapper's version to the matrix updater. The new verifier no longer emits UNABLE_TO_VERIFY_LEAF_SIGNATURE after Node's verification callback accepts a missing-issuer error. Expect UNABLE_TO_GET_ISSUER_CERT_LOCALLY for the affected leaf certificates and suggest --use-system-ca for that error. Drop the SSL_get_server_tmp_key() fallback now that BoringSSL supplies SSL_get_peer_tmp_key() as a compatibility stub. Refs: https://boringssl-review.googlesource.com/c/boringssl/+/103849 Refs: https://github.com/electron/electron/pull/55110/changes/b457e70e339e8bb3066b210edfb6f96582cdb374 Signed-off-by: Filip Skokan Assisted-by: Codex --- deps/ncrypto/ncrypto.cc | 4 ---- src/crypto/crypto_common.cc | 3 +++ .../test-https-proxy-request-handshake-failure.mjs | 5 +++-- test/common/tls.js | 6 ++++++ test/internet/test-tls-add-ca-cert.js | 2 +- .../test-http2-session-reuse-different-authority.js | 5 +++-- test/parallel/test-https-agent-create-connection.js | 7 ++++--- .../test-https-session-reuse-different-servername.js | 5 +++-- test/parallel/test-https-simple.js | 4 +++- test/parallel/test-https-strict.js | 10 ++++++---- test/parallel/test-tls-addca.js | 3 ++- test/parallel/test-tls-client-auth.js | 3 ++- test/parallel/test-tls-client-cert-resumption.js | 3 ++- test/parallel/test-tls-friendly-error-message.js | 4 +++- .../test-tls-session-reuse-different-servername.js | 5 +++-- ...t-tls-set-default-ca-certificates-append-fetch.mjs | 4 ++-- ...et-default-ca-certificates-append-https-request.js | 4 ++-- ...st-tls-set-default-ca-certificates-reset-fetch.mjs | 3 ++- ...set-default-ca-certificates-reset-https-request.js | 3 ++- test/parallel/test-tls-socket-default-options.js | 10 +++++----- test/system-ca/test-native-intermediate-certs.mjs | 4 +++- ...t-set-default-ca-certificates-append-system-ca.mjs | 3 ++- test/system-ca/test-use-system-ca-worker-disable.mjs | 4 +++- test/system-ca/test-use-system-ca-worker-enable.mjs | 4 +++- tools/nix/pkgs.nix | 11 +++++++++-- 25 files changed, 77 insertions(+), 42 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index d35d601abc70..7a4193813b5e 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -4998,11 +4998,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); } 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/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/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/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-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-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-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-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/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/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 From dd40de0e7231ceee0c874652d82dc977e359c092 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 22 Oct 2026 11:15:44 +0200 Subject: [PATCH 2/4] crypto: wire up SHA-3 with BoringSSL Add SHA3-256, SHA3-384, and SHA3-512 to name lookup and getHashes(). BoringSSL exposes EVP_sha3_* descriptors but omits them from its digest registry. Map the names directly to those descriptors so node:crypto can use them too. When using BoringSSL, limit WebCrypto SHA-3 support to digest operations. Signed-off-by: Filip Skokan Assisted-by: Codex --- deps/ncrypto/ncrypto.cc | 7 + lib/internal/crypto/util.js | 15 +- lib/internal/crypto/webidl.js | 4 +- src/crypto/crypto_hash.cc | 6 + test/fixtures/crypto/ecdsa.js | 11 +- test/fixtures/crypto/hmac.js | 46 ++--- test/fixtures/crypto/rsa_pkcs.js | 7 +- test/fixtures/crypto/rsa_pss.js | 174 +++++++++--------- .../webcrypto/supports-modern-algorithms.mjs | 3 +- test/fixtures/webcrypto/supports-sha3.mjs | 68 +++---- .../test-crypto-boringssl-evp-list.js | 3 + .../test-crypto-boringssl-sha3-signatures.js | 104 +++++++++++ test/parallel/test-crypto-hash-stream-pipe.js | 11 +- .../test-crypto-key-objects-to-crypto-key.js | 21 ++- test/parallel/test-crypto-sha3.js | 117 ++++++++++++ .../parallel/test-webcrypto-boringssl-sha3.js | 80 ++++++++ .../test-webcrypto-derivebits-hkdf.js | 2 +- test/parallel/test-webcrypto-derivekey.js | 8 +- test/parallel/test-webcrypto-digest.js | 74 ++++---- .../test-webcrypto-encrypt-decrypt.js | 15 +- .../test-webcrypto-export-import-rsa.js | 31 ++-- test/parallel/test-webcrypto-export-import.js | 23 ++- test/parallel/test-webcrypto-keygen.js | 4 +- .../test-webcrypto-normalization-boundary.js | 2 +- .../test-webcrypto-rsa-jwk-sha3-alg.js | 13 +- .../test-webcrypto-sign-verify-hmac.js | 5 + .../test-webcrypto-sign-verify-rsa.js | 7 + test/wpt/status/WebCryptoAPI.cjs | 1 - 28 files changed, 616 insertions(+), 246 deletions(-) create mode 100644 test/parallel/test-crypto-boringssl-sha3-signatures.js create mode 100644 test/parallel/test-crypto-sha3.js create mode 100644 test/parallel/test-webcrypto-boringssl-sha3.js diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 7a4193813b5e..c9d19f1d94ec 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); } 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/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/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/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-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-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-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/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', From 9ebf6daee91d3a2733f1e245a5383c25a06d07b4 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 22 Oct 2026 11:20:43 +0200 Subject: [PATCH 3/4] tls: configure TLS 1.3 ciphers with BoringSSL Translate cipher-suite names into protocol IDs for BoringSSL's TLS 1.3 configuration API. Handle duplicates and unknown names, reject nonempty lists without a supported suite, and implement honorCipherOrder through preference flags. Cover suite restrictions, explicit preference ordering, legacy-only lists, runtime defaults, and command-line overrides. Signed-off-by: Filip Skokan Assisted-by: Codex --- deps/ncrypto/ncrypto.cc | 52 ++++++- lib/internal/tls/wrap.js | 4 +- test/parallel/test-tls-getcipher.js | 5 +- test/parallel/test-tls-set-ciphers-error.js | 10 +- test/parallel/test-tls-set-ciphers.js | 13 +- test/parallel/test-tls13-ciphers.js | 151 ++++++++++++++++++++ 6 files changed, 215 insertions(+), 20 deletions(-) create mode 100644 test/parallel/test-tls13-ciphers.js diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index c9d19f1d94ec..a6d1a99559b4 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -5103,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/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/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-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-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, + ], {}); +} From c76790ab8f4675b4ef894d264163b9be06010a85 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 22 Oct 2026 11:20:45 +0200 Subject: [PATCH 4/4] test: remove obsolete BoringSSL skips Run supported DH, ECDH, legacy TLS, TLS 1.2 PSK, cipher metadata, raw key, and X.509 date cases with BoringSSL. Assert its DH flags, curve format errors, and name-constraint behavior. Narrow key/cert array exclusions to the failing multi-identity cases. Use modp5 for the DH group setter checks and remove the unused legacy protocol fallback helper. Signed-off-by: Filip Skokan Assisted-by: Codex --- test/common/boringssl.js | 28 -- test/parallel/test-crypto-dh-curves.js | 256 +++++++++--------- test/parallel/test-crypto-dh-group-setters.js | 7 +- test/parallel/test-crypto-dh-modp2-views.js | 9 +- test/parallel/test-crypto-dh-modp2.js | 9 +- test/parallel/test-crypto-getcipherinfo.js | 16 +- test/parallel/test-crypto-key-objects-raw.js | 6 +- test/parallel/test-crypto-x509.js | 18 +- .../test-https-options-boolean-check.js | 45 ++- test/parallel/test-tls-getprotocol.js | 20 +- test/parallel/test-tls-min-max-version.js | 58 ++-- test/parallel/test-tls-name-constraints-cn.js | 18 +- .../test-tls-options-boolean-check.js | 45 ++- ...ls-psk-alpn-callback-exception-handling.js | 16 +- test/parallel/test-tls-psk-circuit.js | 15 +- test/parallel/test-tls-psk-server.js | 11 +- test/sequential/test-tls-psk-client.js | 11 +- 17 files changed, 276 insertions(+), 312 deletions(-) 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/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-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-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-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-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/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.