From 3da95969ea3ec90d17c76667a6bd2a69a9f8a351 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Thu, 22 Oct 2026 16:54:34 -0300 Subject: [PATCH 1/2] test: add test for concurrent first use of OpenSSL OpenSSL 3 builds its table of algorithm names on first use. When 43d3fe95581 moved that first use after other threads had started, an RSA-PSS key decoded while the table was being built could fail with `ERR_OSSL_EVP_DECODE_ERROR`, and every later RSA-PSS decode in the process failed too. #66903 reverted that change. The new test fails if it comes back: Workers decode an RSA-PSS certificate while the root certificate loader or a thread pool crypto job uses OpenSSL for the first time. It is based on a reproduction by Filip Skokan in #66903. Refs: https://github.com/nodejs/node/pull/66903 Refs: https://github.com/nodejs/node/pull/66935 Signed-off-by: Christian Aurich Zanettini Martins --- .../test-crypto-off-thread-first-use.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/parallel/test-crypto-off-thread-first-use.js diff --git a/test/parallel/test-crypto-off-thread-first-use.js b/test/parallel/test-crypto-off-thread-first-use.js new file mode 100644 index 00000000000..e6a1012a0e6 --- /dev/null +++ b/test/parallel/test-crypto-off-thread-first-use.js @@ -0,0 +1,59 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { hasOpenSSL } = require('../common/crypto'); +if (!hasOpenSSL(3)) + common.skip('this test requires OpenSSL 3.x'); + +// OpenSSL 3 builds its table of algorithm names on first use. If several +// threads use OpenSSL for the first time at once, an RSA-PSS key decoded while +// the table is still being built can miss the RSA-PSS name, and every later +// RSA-PSS decode in the process fails as well. Here Workers decode an RSA-PSS +// certificate while the root certificate loader or a thread pool crypto job +// uses OpenSSL for the first time. + +const assert = require('assert'); +const { X509Certificate } = require('crypto'); +const { once } = require('events'); +const { Worker, parentPort, workerData } = require('worker_threads'); +const fixtures = require('../common/fixtures'); + +if (workerData) { + parentPort.postMessage('ready'); + Atomics.wait(workerData.barrier, 0, 0); + const cert = new X509Certificate(workerData.pem); + assert.strictEqual(cert.publicKey.asymmetricKeyType, 'rsa-pss'); + assert(Buffer.isBuffer(cert.toLegacyObject().pubkey)); +} else if (process.argv[2] === 'child') { + // OpenSSL must not have been used in this process before the barrier opens. + const pem = fixtures.readKey('rsa_pss_cert_2048.pem'); + const barrier = new Int32Array(new SharedArrayBuffer(4)); + const workers = Array.from({ length: 8 }, () => + new Worker(__filename, { workerData: { pem, barrier } })); + Promise.all(workers.map((w) => once(w, 'message'))).then(common.mustCall(() => { + if (process.argv[3] === 'tls') { + require('tls'); + } else { + require('crypto').pbkdf2('password', '0123456789abcdef', 1000, 32, + 'sha256', common.mustSucceed()); + } + Atomics.store(barrier, 0, 1); + Atomics.notify(barrier, 0); + })); +} else { + (async () => { + for (const mode of ['tls', 'pbkdf2']) { + for (let round = 0; round < 3; round++) { + const children = Array.from({ length: 4 }, () => + common.spawnPromisified(process.execPath, + [__filename, 'child', mode])); + for (const { code, signal, stderr } of await Promise.all(children)) { + assert.strictEqual(signal, null, `${mode}: ${stderr}`); + assert.strictEqual(code, 0, `${mode}: ${stderr}`); + } + } + } + })().then(common.mustCall()); +} From 62dc0584385e45df6395347c44691b5bf9bbc9e8 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Fri, 23 Oct 2026 14:27:33 -0300 Subject: [PATCH 2/2] Update test/parallel/test-crypto-off-thread-first-use.js Co-authored-by: Filip Skokan --- test/parallel/test-crypto-off-thread-first-use.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-crypto-off-thread-first-use.js b/test/parallel/test-crypto-off-thread-first-use.js index e6a1012a0e6..51528d4a859 100644 --- a/test/parallel/test-crypto-off-thread-first-use.js +++ b/test/parallel/test-crypto-off-thread-first-use.js @@ -5,7 +5,7 @@ if (!common.hasCrypto) const { hasOpenSSL } = require('../common/crypto'); if (!hasOpenSSL(3)) - common.skip('this test requires OpenSSL 3.x'); + common.skip('this test requires OpenSSL >= 3.x'); // OpenSSL 3 builds its table of algorithm names on first use. If several // threads use OpenSSL for the first time at once, an RSA-PSS key decoded while