Files
node/test/parallel/test-crypto-off-thread-first-use.js
Christian Aurich Zanettini Martins 96fd36a8b5 test: add test for concurrent first use of OpenSSL
OpenSSL 3 builds its table of algorithm names on first use. When
43d3fe9558 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 <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66966
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2026-10-30 17:34:42 +00:00

60 lines
2.4 KiB
JavaScript

'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());
}