From 48d18bdb0daf1b09d7eacd5d9ce8ba5657bb6ee3 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 11:32:23 +0200 Subject: [PATCH 1/8] test: update test/addons/openssl-binding for OpenSSL 4.0 TLS_server_method() was added in OpenSSL 1.1.0. All version-specific methods were deprecated in OpenSSL 1.1.0. All version-specific methods were removed in OpenSSL 4.0. Signed-off-by: Filip Skokan --- test/addons/openssl-binding/binding.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/addons/openssl-binding/binding.cc b/test/addons/openssl-binding/binding.cc index fbb96fd3b20c56..6c85f914097733 100644 --- a/test/addons/openssl-binding/binding.cc +++ b/test/addons/openssl-binding/binding.cc @@ -46,7 +46,7 @@ inline void Initialize(v8::Local exports, .ToLocalChecked(); assert(exports->Set(context, key, value).IsJust()); - const SSL_METHOD* method = TLSv1_2_server_method(); + const SSL_METHOD* method = TLS_server_method(); assert(method != nullptr); key = v8::String::NewFromUtf8(isolate, "hash").ToLocalChecked(); From 0acaef82fa1ce698655ca2611207c1d919d31130 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 12:18:18 +0200 Subject: [PATCH 2/8] test: accept renamed OpenSSL 4.0 error code and reason Signed-off-by: Filip Skokan --- test/parallel/test-tls-alert-handling.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-tls-alert-handling.js b/test/parallel/test-tls-alert-handling.js index e0ed4db98fbdfe..c319e766ce8a02 100644 --- a/test/parallel/test-tls-alert-handling.js +++ b/test/parallel/test-tls-alert-handling.js @@ -97,11 +97,11 @@ function sendBADTLSRecord() { // Different OpenSSL versions send different TLS alerts when the peer // receives an invalid record on an established connection. assert.match(err.code, - /ERR_SSL_(TLSV1_ALERT_PROTOCOL_VERSION|TLSV1_ALERT_RECORD_OVERFLOW|SSL\/TLS_ALERT_UNEXPECTED_MESSAGE)/); + /ERR_SSL_(TLSV1_ALERT_PROTOCOL_VERSION|TLSV1_ALERT_RECORD_OVERFLOW|(SSL\/)?TLS_ALERT_UNEXPECTED_MESSAGE)/); assert.strictEqual(err.library, 'SSL routines'); if (!hasOpenSSL3 && !process.features.openssl_is_boringssl) assert.strictEqual(err.function, 'ssl3_read_bytes'); assert.match(err.reason, - /tlsv1[\s_]alert[\s_]protocol[\s_]version|tlsv1[\s_]alert[\s_]record[\s_]overflow|ssl\/tls[\s_]alert[\s_]unexpected[\s_]message/i); + /tlsv1[\s_]alert[\s_]protocol[\s_]version|tlsv1[\s_]alert[\s_]record[\s_]overflow|(ssl\/)?tls[\s_]alert[\s_]unexpected[\s_]message/i); })); } From c59298d3a54989d9af247cf37d0aa731870fa136 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 12:18:18 +0200 Subject: [PATCH 3/8] test: skip test-tls-error-stack when engines are unsupported Signed-off-by: Filip Skokan --- test/parallel/test-tls-error-stack.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/parallel/test-tls-error-stack.js b/test/parallel/test-tls-error-stack.js index 36deb05b511234..02021b060ecb83 100644 --- a/test/parallel/test-tls-error-stack.js +++ b/test/parallel/test-tls-error-stack.js @@ -11,6 +11,9 @@ const tls = require('tls'); assert.throws(() => { tls.createSecureContext({ clientCertEngine: 'x' }); }, (err) => { + if (err.code === 'ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED') + common.skip('OpenSSL dropped engine support'); + return err.name === 'Error' && /could not load the shared library/.test(err.message) && Array.isArray(err.opensslErrorStack) && From affcd80ac5b62319d08c414bcc43187905b18a1c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 13:02:15 +0200 Subject: [PATCH 4/8] test: use valid DER OCSP responses Signed-off-by: Filip Skokan --- test/parallel/test-tls-ocsp-callback.js | 27 ++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-tls-ocsp-callback.js b/test/parallel/test-tls-ocsp-callback.js index 3a2d8e45f772ac..3a54ca84eb89b8 100644 --- a/test/parallel/test-tls-ocsp-callback.js +++ b/test/parallel/test-tls-ocsp-callback.js @@ -29,6 +29,7 @@ if (!common.hasCrypto) { const crypto = require('crypto'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); @@ -90,7 +91,10 @@ function test(testOptions, cb) { client.on('OCSPResponse', common.mustCall((resp) => { if (testOptions.response) { - assert.strictEqual(resp.toString(), testOptions.response); + if (Buffer.isBuffer(testOptions.response)) + assert.deepStrictEqual(resp, testOptions.response); + else + assert.strictEqual(resp.toString(), testOptions.response); client.destroy(); } else { assert.strictEqual(resp, null); @@ -103,10 +107,27 @@ function test(testOptions, cb) { })); } +// OpenSSL 3.6+ validates that the value passed to +// SSL_set_tlsext_status_ocsp_resp parses as DER, so the test responses need +// to be valid DER-encoded OCSPResponse values. +// Minimal OCSPResponse is SEQUENCE { ENUMERATED responseStatus } where +// 0 = successful and 1 = malformedRequest. +const response1 = Buffer.from([0x30, 0x03, 0x0a, 0x01, 0x00]); +const response2 = Buffer.from([0x30, 0x03, 0x0a, 0x01, 0x01]); + test({ ocsp: true, response: false }); -test({ ocsp: true, response: 'hello world' }); +test({ ocsp: true, response: response1 }); test({ ocsp: false }); if (!crypto.getFips()) { - test({ ocsp: true, response: 'hello pfx', pfx: pfx, passphrase: 'sample' }); + test({ ocsp: true, response: response2, pfx: pfx, passphrase: 'sample' }); +} + +// Older OpenSSL versions accept arbitrary bytes (not just DER) as the OCSP +// response, so additionally exercise the string path there. +if (!hasOpenSSL(3, 6)) { + test({ ocsp: true, response: 'hello world' }); + if (!crypto.getFips()) { + test({ ocsp: true, response: 'hello pfx', pfx: pfx, passphrase: 'sample' }); + } } From 7829598496620c70f0442b7cd57f84fccf487ea5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 13:12:14 +0200 Subject: [PATCH 5/8] test: use an always invalid cipher and cover OpenSSL 4.0 behaviours Signed-off-by: Filip Skokan --- test/parallel/test-tls-set-ciphers-error.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-tls-set-ciphers-error.js b/test/parallel/test-tls-set-ciphers-error.js index 0df5a9288de1df..3cfc8c391bf7d5 100644 --- a/test/parallel/test-tls-set-ciphers-error.js +++ b/test/parallel/test-tls-set-ciphers-error.js @@ -7,12 +7,13 @@ if (!common.hasCrypto) const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); { const options = { key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem'), - ciphers: 'aes256-sha' + ciphers: 'DES-CBC-SHA' }; assert.throws(() => tls.createServer(options, common.mustNotCall()), /no[_ ]cipher[_ ]match/i); @@ -23,3 +24,19 @@ const fixtures = require('../common/fixtures'); assert.throws(() => tls.createServer(options, common.mustNotCall()), /no[_ ]cipher[_ ]match/i); } + +// Cipher name matching is case-sensitive prior to OpenSSL 4.0, and +// case-insensitive starting with OpenSSL 4.0. +{ + const options = { + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + ciphers: 'aes256-sha', + }; + if (hasOpenSSL(4, 0)) { + tls.createServer(options).close(); + } else { + assert.throws(() => tls.createServer(options, common.mustNotCall()), + /no[_ ]cipher[_ ]match/i); + } +} From 0c177ec13666219e8ba9744d6c476134b7af4a1a Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 13:15:48 +0200 Subject: [PATCH 6/8] test: skip tls-deprecated secp256k1 on OpenSSL 4.0 Signed-off-by: Filip Skokan --- test/parallel/test-tls-ecdh-multiple.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-tls-ecdh-multiple.js b/test/parallel/test-tls-ecdh-multiple.js index 957f8e0407a6de..ee52f288610956 100644 --- a/test/parallel/test-tls-ecdh-multiple.js +++ b/test/parallel/test-tls-ecdh-multiple.js @@ -8,7 +8,7 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } -const { opensslCli } = require('../common/crypto'); +const { opensslCli, hasOpenSSL } = require('../common/crypto'); const crypto = require('crypto'); if (!opensslCli) { @@ -24,11 +24,17 @@ function loadPEM(n) { return fixtures.readKey(`${n}.pem`); } +// OpenSSL 4.0 disables support for deprecated elliptic curves from RFC 8422 +// (including secp256k1) by default. +const ecdhCurve = hasOpenSSL(4, 0) ? + 'prime256v1:secp521r1' : + 'secp256k1:prime256v1:secp521r1'; + const options = { key: loadPEM('agent2-key'), cert: loadPEM('agent2-cert'), ciphers: '-ALL:ECDHE-RSA-AES128-SHA256', - ecdhCurve: 'secp256k1:prime256v1:secp521r1', + ecdhCurve, maxVersion: 'TLSv1.2', }; @@ -60,6 +66,11 @@ const server = tls.createServer(options, (conn) => { unsupportedCurves.push('brainpoolP256r1'); } + // Deprecated RFC 8422 curves are disabled by default in OpenSSL 4.0. + if (hasOpenSSL(4, 0)) { + unsupportedCurves.push('secp256k1'); + } + unsupportedCurves.forEach((ecdhCurve) => { assert.throws(() => tls.createServer({ ecdhCurve }), /Error: Failed to set ECDH curve/); From fca19237984f0e1b26b7677f069963bd32b613a3 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 13:19:36 +0200 Subject: [PATCH 7/8] test: account for RFC 7919 FFDHE negotiation in OpenSSL 4.0 Signed-off-by: Filip Skokan --- .../test-tls-client-getephemeralkeyinfo.js | 5 +++- test/parallel/test-tls-client-mindhsize.js | 12 ++++++--- test/parallel/test-tls-dhe.js | 27 +++++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js index 0f132c565e4400..19728e3733d868 100644 --- a/test/parallel/test-tls-client-getephemeralkeyinfo.js +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -70,7 +70,10 @@ function test(size, type, name, cipher) { test(undefined, undefined, undefined, 'AES256-SHA256'); test('auto', 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); -if (!hasOpenSSL(3, 2)) { +if (hasOpenSSL(4, 0)) { + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // always selects FFDHE-2048 regardless of the server-supplied dhparam. +} else if (!hasOpenSSL(3, 2)) { test(1024, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); } else { test(3072, 'DH', undefined, 'DHE-RSA-AES256-GCM-SHA384'); diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index 778e4b710b4e92..cd7b16ea566fe8 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -13,6 +13,7 @@ const secLevel = require('internal/crypto/util').getOpenSSLSecLevel(); const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); +const { hasOpenSSL } = require('../common/crypto'); const key = fixtures.readKey('agent2-key.pem'); const cert = fixtures.readKey('agent2-cert.pem'); @@ -24,7 +25,7 @@ function loadDHParam(n) { return fixtures.readKey(`dh${n}.pem`); } -function test(size, err, next) { +function test(size, err, next, minDHSizeOverride) { const options = { key: key, cert: cert, @@ -46,7 +47,7 @@ function test(size, err, next) { // so that it fails when it makes a connection to the tls // server where is too small. This depends on the openssl // security level - const minDHSize = (secLevel > 1) ? 3072 : 2048; + const minDHSize = minDHSizeOverride ?? ((secLevel > 1) ? 3072 : 2048); const client = tls.connect({ minDHSize: minDHSize, port: this.address().port, @@ -84,7 +85,12 @@ function testDHE3072() { test(3072, false, null); } -if (secLevel > 1) { +if (hasOpenSSL(4, 0)) { + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 + // success case is therefore replaced by a 2048 success case. + testDHE2048(true, () => test(2048, false, null, 2048)); +} else if (secLevel > 1) { // Minimum size for OpenSSL security level 2 and above is 2048 by default testDHE2048(true, testDHE3072); } else { diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 89cda5f52a6b31..03750bc206adbe 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -28,6 +28,7 @@ if (!common.hasCrypto) { const { opensslCli, + hasOpenSSL, } = require('../common/crypto'); // OpenSSL has a set of security levels which affect what algorithms @@ -104,9 +105,15 @@ function testCustomParam(keylen, expectedCipher) { } (async () => { - // By default, DHE is disabled while ECDHE is enabled. + // By default, DHE is disabled while ECDHE is enabled. OpenSSL 4.0 + // implements RFC 7919 FFDHE negotiation for TLS 1.2 which enables DHE + // (with FFDHE-2048) even without a server-supplied dhparam. for (const dhparam of [undefined, null]) { - await test(dhparam, null, ecdheCipher); + if (hasOpenSSL(4, 0)) { + await test(dhparam, 2048, dheCipher); + } else { + await test(dhparam, null, ecdheCipher); + } } // The DHE parameters selected by OpenSSL depend on the strength of the @@ -124,14 +131,24 @@ function testCustomParam(keylen, expectedCipher) { // Custom DHE parameters are supported (but discouraged). // 1024 is disallowed at security level 2 and above so use 3072 instead - // for higher security levels + // for higher security levels. + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // ignores the server-supplied dhparam in favor of FFDHE-2048, so the + // negotiated key length is always 2048. if (secLevel < 2) { await testCustomParam(1024, dheCipher); + } else if (hasOpenSSL(4, 0)) { + await test(loadDHParam(3072), 2048, dheCipher); } else { await testCustomParam(3072, dheCipher); } await testCustomParam(2048, dheCipher); - // Invalid DHE parameters are discarded. ECDHE remains enabled. - await testCustomParam('error', ecdheCipher); + // Invalid DHE parameters are discarded. Prior to OpenSSL 4.0 this + // disabled DHE and ECDHE was negotiated; since 4.0, FFDHE-2048 is used. + if (hasOpenSSL(4, 0)) { + await test(loadDHParam('error'), 2048, dheCipher); + } else { + await testCustomParam('error', ecdheCipher); + } })().then(common.mustCall()); From 4f3785fa76b7e449bf6f1be2fc310bed97bba882 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sat, 18 Apr 2026 14:31:19 +0200 Subject: [PATCH 8/8] test: accept OpenSSL 4 generic internal error for DH key-type mismatches Signed-off-by: Filip Skokan --- test/parallel/test-crypto-dh-stateless.js | 31 +++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/test/parallel/test-crypto-dh-stateless.js b/test/parallel/test-crypto-dh-stateless.js index 86846a613c0a52..9e13c62595ef26 100644 --- a/test/parallel/test-crypto-dh-stateless.js +++ b/test/parallel/test-crypto-dh-stateless.js @@ -7,6 +7,21 @@ const assert = require('assert'); const crypto = require('crypto'); const { hasOpenSSL } = require('../common/crypto'); +// Error code for a key-type mismatch during (EC)DH. The underlying OpenSSL +// error code varies by version, and in OpenSSL 4.0 by platform: some builds +// report a generic internal error instead of a typed key-type mismatch. +// https://github.com/openssl/openssl/issues/30895 +// TODO(panva): Tighten this check once/if fixed. +let keyTypeMismatchCode; +if (hasOpenSSL(4, 0)) { + keyTypeMismatchCode = + /^ERR_OSSL_EVP_(OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE|INTERNAL_ERROR)$/; +} else if (hasOpenSSL(3)) { + keyTypeMismatchCode = 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE'; +} else { + keyTypeMismatchCode = 'ERR_OSSL_EVP_DIFFERENT_KEY_TYPES'; +} + assert.throws(() => crypto.diffieHellman(), { name: 'TypeError', code: 'ERR_INVALID_ARG_TYPE', @@ -397,9 +412,7 @@ test(crypto.generateKeyPairSync('x25519'), privateKey: crypto.generateKeyPairSync('x448').privateKey, publicKey: crypto.generateKeyPairSync('x25519').publicKey, }; - testDHError(options, { code: hasOpenSSL(3) ? - 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' : - 'ERR_OSSL_EVP_DIFFERENT_KEY_TYPES' }); + testDHError(options, { code: keyTypeMismatchCode }); } // Test all key encoding formats @@ -541,23 +554,21 @@ for (const { privateKey: alicePriv, publicKey: bobPub } of [ testDHError({ privateKey: privKey(ec256.privateKey), publicKey: pubKey(x25519.publicKey), - }, { code: hasOpenSSL(3) ? - 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' : - 'ERR_OSSL_EVP_DIFFERENT_KEY_TYPES' }); + }, { code: keyTypeMismatchCode }); // Unsupported key type (ed25519) testDHError({ privateKey: privKey(ed25519.privateKey), publicKey: pubKey(ed25519.publicKey), - }, { code: 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' }); + }, { code: hasOpenSSL(4, 0) ? + /^ERR_OSSL_EVP_(OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE|INTERNAL_ERROR)$/ : + 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' }); // Incompatible key types (x448 + x25519) testDHError({ privateKey: privKey(x448.privateKey), publicKey: pubKey(x25519.publicKey), - }, { code: hasOpenSSL(3) ? - 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' : - 'ERR_OSSL_EVP_DIFFERENT_KEY_TYPES' }); + }, { code: keyTypeMismatchCode }); // Zero x25519 public key testDHError({