镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit 06af164

Browse files
pimterryaduh95
authored andcommitted
tls: disable resumption for mTLS with no session id context
Signed-off-by: Tim Perry <pimterry@gmail.com> Backport-PR-URL: nodejs-private/node-private#1054 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: nodejs-private/node-private#1030 CVE-ID: CVE-2026-123872
1 parent 209c7e4 commit 06af164

3 files changed

Lines changed: 93 additions & 1 deletion

File tree

‎src/crypto/crypto_tls.cc‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ SSL_SESSION* GetSessionCallback(
102102
int* copy) {
103103
TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
104104
*copy = 0;
105+
if (w->is_resumption_disabled()) return nullptr;
105106
return w->ReleaseSession();
106107
}
107108

@@ -1519,7 +1520,15 @@ bool TLSWrap::PinServerName(std::string_view servername) {
15191520
}
15201521

15211522
bool TLSWrap::BindSessionsToServerName(std::string_view servername) {
1522-
if (sc_->session_id_context().empty()) return true;
1523+
// If we have no session id context and we need to verify peers, we reject
1524+
// all resumption since we can't do so securely.
1525+
if (sc_->session_id_context().empty()) {
1526+
if (SSL_get_verify_mode(ssl_.get()) & SSL_VERIFY_PEER) {
1527+
SSL_set_options(ssl_.get(), SSL_OP_NO_TICKET);
1528+
resumption_disabled_ = true;
1529+
}
1530+
return true;
1531+
}
15231532

15241533
// Server names can't contain NULs, so a NUL keeps the parts apart.
15251534
std::string input = sc_->session_id_context();

‎src/crypto/crypto_tls.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class TLSWrap : public AsyncWrap,
6565
void set_cert_cb_running(bool on = true) { cert_cb_running_ = on; }
6666
void set_awaiting_new_session(bool on = true) { awaiting_new_session_ = on; }
6767
void enable_session_callbacks() { session_callbacks_ = true; }
68+
bool is_resumption_disabled() const { return resumption_disabled_; }
6869
bool BindSessionsToServerName(std::string_view servername);
6970
bool PinServerName(std::string_view servername);
7071
bool is_server() const { return kind_ == Kind::kServer; }
@@ -284,6 +285,7 @@ class TLSWrap : public AsyncWrap,
284285
bool established_ = false;
285286
bool write_callback_scheduled_ = false;
286287
bool servername_pinned_ = false;
288+
bool resumption_disabled_ = false;
287289

288290
int cycle_depth_ = 0;
289291

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict';
2+
3+
// A server with no session id context of its own - possible when creating a
4+
// TLSSocket directly - can't tell which application a session belongs to. One
5+
// that verifies client certificates mustn't resume any, or a session from an
6+
// application sharing its secure context (and so its ticket keys) would skip
7+
// the client's certificate check. It gets a full handshake instead.
8+
9+
const common = require('../common');
10+
11+
if (!common.hasCrypto)
12+
common.skip('missing crypto');
13+
14+
const assert = require('node:assert');
15+
const net = require('node:net');
16+
const tls = require('node:tls');
17+
const fixtures = require('../common/fixtures');
18+
19+
const secureContext = tls.createSecureContext({
20+
key: fixtures.readKey('agent2-key.pem'),
21+
cert: fixtures.readKey('agent2-cert.pem'),
22+
});
23+
24+
// Whether the server resumed each connection it accepted, in order.
25+
const reused = [];
26+
27+
async function listen(requestCert) {
28+
const server = net.createServer((socket) => {
29+
const tlsSocket = new tls.TLSSocket(socket, {
30+
isServer: true,
31+
secureContext,
32+
requestCert,
33+
});
34+
tlsSocket.on('secure', () => {
35+
reused.push(tlsSocket.isSessionReused());
36+
// End must be deferred on v22.x, or TLS 1.3 session tickets aren't sent:
37+
setImmediate(() => tlsSocket.end());
38+
});
39+
});
40+
await new Promise((resolve) => server.listen(0, resolve));
41+
return server;
42+
}
43+
44+
// Resolves with the first session issued, once the connection closes. This is
45+
// the raw session from the TLS library: on BoringSSL the 'session' event's
46+
// TLS 1.3 session can't be resumed.
47+
function connect(server, options) {
48+
return new Promise((resolve) => {
49+
let session;
50+
const socket = tls.connect({
51+
port: server.address().port,
52+
rejectUnauthorized: false,
53+
...options,
54+
});
55+
const { onnewsession } = socket._handle;
56+
socket._handle.onnewsession = function(id, raw) {
57+
session ??= raw;
58+
return onnewsession.call(this, id, raw);
59+
};
60+
socket.on('session', () => {});
61+
socket.on('close', () => resolve(session));
62+
socket.resume();
63+
});
64+
}
65+
66+
(async () => {
67+
const open = await listen(false);
68+
const verifying = await listen(true);
69+
70+
for (const maxVersion of ['TLSv1.2', 'TLSv1.3']) {
71+
reused.length = 0;
72+
const session = await connect(open, { maxVersion });
73+
assert.ok(session);
74+
await connect(open, { maxVersion, session });
75+
await connect(verifying, { maxVersion, session });
76+
assert.deepStrictEqual(reused, [false, true, false], maxVersion);
77+
}
78+
79+
open.close();
80+
verifying.close();
81+
})().then(common.mustCall());

0 commit comments

Comments
 (0)