Skip to content
Draft
123 changes: 123 additions & 0 deletions .github/workflows/test-shared-openssl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Throwaway workflow used solely to gather test results for Node.js
# linked against OpenSSL 3.5, 3.6, and 4.0.
name: TEMP - Test Shared libraries (OpenSSL)

on:
pull_request:
push:
branches:
- tmp-openssl4

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
FLAKY_TESTS: keep_retrying

permissions:
contents: read

jobs:
build-tarball:
name: Build slim tarball
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Make tarball
run: |
export DATESTRING=$(date "+%Y-%m-%d")
export COMMIT=$(git rev-parse --short=10 "$GITHUB_SHA")
./configure && make tar -j4 SKIP_XZ=1 SKIP_SHARED_DEPS=1
env:
DISTTYPE: nightly

- name: Upload tarball artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: tarballs
path: '*.tar.gz'
compression-level: 0

build:
needs: build-tarball
strategy:
fail-fast: false
matrix:
opensslPkg:
- openssl_1_1
- openssl_3
- openssl_3_5
- openssl_3_6
- openssl_4_0
system:
- x86_64-linux
- aarch64-linux
- x86_64-darwin
- aarch64-darwin
include:
- system: x86_64-linux
runner: ubuntu-24.04
- system: aarch64-linux
runner: ubuntu-24.04-arm
- system: x86_64-darwin
runner: macos-15-intel
- system: aarch64-darwin
runner: macos-latest
name: '${{ matrix.system }}: with shared libraries (${{ matrix.opensslPkg }})'
runs-on: ${{ matrix.runner }}
# We only care about the test results here, not about gating anything.
continue-on-error: true
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: tarballs
path: tarballs

- name: Extract tarball
run: |
tar xzf tarballs/*.tar.gz -C "$RUNNER_TEMP"
echo "TAR_DIR=$RUNNER_TEMP/$(basename tarballs/*.tar.gz .tar.gz)" >> "$GITHUB_ENV"

- uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31.10.3
with:
extra_nix_config: sandbox = true

- uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17
with:
name: nodejs
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

- name: Configure sccache
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
core.exportVariable('SCCACHE_GHA_ENABLED', 'on');
core.exportVariable('ACTIONS_CACHE_SERVICE_V2', 'on');
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
core.exportVariable('NIX_SCCACHE', '(import <nixpkgs> {}).sccache');

- name: Build Node.js and run tests
env:
# Allow evaluating packages marked insecure in nixpkgs (e.g. openssl_1_1).
NIXPKGS_ALLOW_INSECURE: '1'
run: |
nix-shell \
-I "nixpkgs=$TAR_DIR/tools/nix/pkgs.nix" \
--impure \
--pure --keep TAR_DIR --keep FLAKY_TESTS --keep NIXPKGS_ALLOW_INSECURE \
--keep SCCACHE_GHA_ENABLED --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \
--arg loadJSBuiltinsDynamically false \
--arg useSeparateDerivationForV8 true \
--arg ccache "${NIX_SCCACHE:-null}" \
--arg devTools '[]' \
--arg benchmarkTools '[]' \
--arg opensslPkg '(import <nixpkgs> {}).${{ matrix.opensslPkg }}' \
${{ endsWith(matrix.system, '-darwin') && '--arg withAmaro false --arg withLief false --arg withSQLite false --arg withFFI false --arg extraConfigFlags ''["--without-inspector" "--without-node-options"]'' \' || '\' }}
--run '
make -C "$TAR_DIR" run-ci -j4 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9 --skip-tests=$CI_SKIP_TESTS"
' "$TAR_DIR/shell.nix"
2 changes: 2 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
withFFI ? true,
withSSL ? true,
withTemporal ? false,
opensslPkg ? pkgs.openssl_3_5,
sharedLibDeps ? (
import ./tools/nix/sharedLibDeps.nix {
inherit
Expand All @@ -29,6 +30,7 @@
withFFI
withSSL
withTemporal
opensslPkg
;
}
),
Expand Down
2 changes: 1 addition & 1 deletion test/addons/openssl-binding/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ inline void Initialize(v8::Local<v8::Object> 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();
Expand Down
32 changes: 22 additions & 10 deletions test/parallel/test-crypto-dh-stateless.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ const assert = require('assert');
const crypto = require('crypto');
const { hasOpenSSL } = require('../common/crypto');

// Error code for a key-type mismatch during ECDH/EdDH derivation. 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).
// TODO(panva): report the OpenSSL 4.0 generic internal error behavior
// upstream and tighten this check once 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',
Expand Down Expand Up @@ -397,9 +413,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
Expand Down Expand Up @@ -541,23 +555,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({
Expand Down
4 changes: 2 additions & 2 deletions test/parallel/test-tls-alert-handling.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}));
}
5 changes: 4 additions & 1 deletion test/parallel/test-tls-client-getephemeralkeyinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
12 changes: 9 additions & 3 deletions test/parallel/test-tls-client-mindhsize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 22 additions & 5 deletions test/parallel/test-tls-dhe.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ if (!common.hasCrypto) {

const {
opensslCli,
hasOpenSSL,
} = require('../common/crypto');

// OpenSSL has a set of security levels which affect what algorithms
Expand Down Expand Up @@ -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
Expand All @@ -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());
15 changes: 13 additions & 2 deletions test/parallel/test-tls-ecdh-multiple.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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',
};

Expand Down Expand Up @@ -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/);
Expand Down
3 changes: 3 additions & 0 deletions test/parallel/test-tls-error-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) &&
Expand Down
Loading