Files
node/test/parallel/test-tls-client-hello-fragmented.js
Tim Perry 5b940dd124 tls: drop hand-rolled TLS client hello parser
This existed for 'resumeSession', which needed to do an async lookup
though SSL_CTX_sess_set_get_cb is sync-only. Nowadays both OpenSSL &
BoringSSL have an early ClientHello callback for suspend/resume to
handle this properly, so it was redundant, in addition to being
complicated and generally a bit fragile & scary.

This PR switches to use the modern OpenSSL/BoringSSL mechanisms for this
and drops the client hello parser & related infrastructure completely.

In addition, there's a new test here, covering a fixed bug: the hello
parser silently dropped fragmented hellos, which we now do handle
correctly.

Signed-off-by: Tim Perry <pimterry@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64827
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2026-08-04 12:33:00 +00:00

76 lines
2.3 KiB
JavaScript

'use strict';
// A ClientHello split across several TLS records must still emit
// 'resumeSession'.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const net = require('net');
const tls = require('tls');
const options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'),
};
// Capture a real ClientHello record so the replayed bytes are well-formed.
function captureClientHello(callback) {
let hello = Buffer.alloc(0);
const collector = net.createServer((socket) => {
socket.on('data', (chunk) => {
hello = Buffer.concat([hello, chunk]);
if (hello.length < 5 || hello.length < 5 + hello.readUInt16BE(3)) return;
socket.destroy();
collector.close(() => callback(hello));
});
});
collector.listen(0, common.mustCall(() => {
tls.connect({ port: collector.address().port, rejectUnauthorized: false })
.on('error', () => {});
}));
}
captureClientHello(common.mustCall((hello) => {
assert.strictEqual(hello[0], 22);
// Split partway through the fixed-size header, before the session ID.
const body = hello.subarray(5);
const split = 20;
assert.ok(body.length > split);
const record = (payload) => Buffer.concat([
Buffer.from([22, hello[1], hello[2],
payload.length >> 8, payload.length & 0xff]),
payload,
]);
const server = tls.createServer(options);
server.on('tlsClientError', () => {}); // The replay never completes.
server.listen(0, common.mustCall(() => {
const client = net.connect(server.address().port, common.mustCall(() => {
client.write(record(body.subarray(0, split)));
setTimeout(() => client.write(record(body.subarray(split))), 10);
}));
client.on('error', () => {});
// Fail fast: a missing event stalls the handshake rather than erroring.
const guard = setTimeout(() => {
throw new Error('resumeSession was not emitted');
}, common.platformTimeout(10000));
server.on('resumeSession', common.mustCall((id, callback) => {
clearTimeout(guard);
assert.ok(id.length > 0);
callback(null, null);
client.destroy();
server.close();
}));
}));
}));