Files
node/test/parallel/test-dtls-error-queue.mjs
James M Snell bd2b98f549 lib,src: apply multiple updates to dtls implementation
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opecode
PR-URL: https://github.com/nodejs/node/pull/65511
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2026-09-16 00:39:35 +00:00

85 lines
2.9 KiB
JavaScript

// Flags: --experimental-dtls --no-warnings
// Test: DTLS does not leave entries behind in the OpenSSL error queue.
//
// The queue is per-thread and shared with every other OpenSSL consumer in the
// process. DTLS spends most of its time handling unauthenticated input, so
// failures are routine: rejected handshakes, and DTLSv1_listen() choking on
// garbage datagrams from the accept path. If those entries are not discarded
// they are picked up by whatever crypto operation runs next and reported as
// its error -- e.g. crypto.createPrivateKey() failing with
// "SSL routines::record too small".
import { hasCrypto, skip } from '../common/index.mjs';
import { setTimeout } from 'node:timers/promises';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
if (!hasCrypto) {
skip('missing crypto');
}
if (!process.features.dtls) {
skip('DTLS is not enabled');
}
const crypto = await import('node:crypto');
const dgram = await import('node:dgram');
const { connect, listen } = await import('node:dtls');
const cert = fixtures.readKey('agent1-cert.pem');
const key = fixtures.readKey('agent1-key.pem');
const ca1 = fixtures.readKey('ca1-cert.pem');
const ca2 = fixtures.readKey('ca2-cert.pem');
// The server accepts each session (cookie exchange completes) and only the
// client rejects, so sessions do arrive here; nothing needs doing with them.
const endpoint = listen(() => {}, {
cert, key, ca: [ca1], host: '127.0.0.1', port: 0,
});
const { port } = endpoint.address;
// Provoke failures from both directions.
// 1. Rejected handshakes: the server certificate does not chain to ca2.
for (let i = 0; i < 3; i++) {
const client = connect('127.0.0.1', port, {
servername: 'agent1',
ca: [ca2],
});
await assert.rejects(client.opened, (err) => {
assert.match(err.message, /certificate verify failed/);
return true;
});
}
// 2. Garbage datagrams: each one reaches DTLSv1_listen() and fails there.
// The leading 22 makes them look enough like a DTLS handshake record to get
// past a cheap length/type screen.
const socket = dgram.createSocket('udp4');
for (let i = 0; i < 32; i++) {
socket.send(Buffer.from([22, 254, 253, 0, 0, i & 0xff]), port, '127.0.0.1');
}
// Let the datagrams reach the endpoint and be processed before closing;
// dgram.close() does not flush queued sends.
await setTimeout(200);
await new Promise((resolve) => socket.close(resolve));
// An unrelated crypto failure must report its own cause. Before the error
// queue was drained this surfaced a stale "SSL routines::..." entry left by
// the DTLS activity above, with the real DECODER error demoted into
// opensslErrorStack.
assert.throws(
() => crypto.createPrivateKey(
'-----BEGIN PRIVATE KEY-----\nZm9v\n-----END PRIVATE KEY-----\n'),
(err) => {
assert.doesNotMatch(
err.message, /SSL routines/,
`crypto error contaminated by a stale DTLS entry: ${err.message}`);
return true;
},
);
await endpoint.close();