Skip to content

Commit 0932309

Browse files
indutnytargos
authored andcommittedJun 2, 2020
tls: emit session after verifying certificate
Prior to this patch `session` event was emitted after `secure` event on TLSSocket, but before `secureConnect` event. This is problematic for `https.Agent` because it must cache session only after verifying the remote peer's certificate. Connecting to a server that presents an invalid certificate resulted in the session being cached after the handshake with the server and evicted right after a certifiate validation error and socket's destruction. A request initiated during this narrow window would pick the faulty session, send it to the malicious server and skip the verification of the server's certificate. Fixes: https://hackerone.com/reports/811502 CVE-ID: CVE-2020-8172 PR-URL: nodejs-private/node-private#200 Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
1 parent d381426 commit 0932309

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed
 

‎lib/_tls_wrap.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ const kSNICallback = Symbol('snicallback');
9090
const kEnableTrace = Symbol('enableTrace');
9191
const kPskCallback = Symbol('pskcallback');
9292
const kPskIdentityHint = Symbol('pskidentityhint');
93+
const kPendingSession = Symbol('pendingSession');
94+
const kIsVerified = Symbol('verified');
9395

9496
const noop = () => {};
9597

@@ -273,7 +275,11 @@ function requestOCSPDone(socket) {
273275
function onnewsessionclient(sessionId, session) {
274276
debug('client emit session');
275277
const owner = this[owner_symbol];
276-
owner.emit('session', session);
278+
if (owner[kIsVerified]) {
279+
owner.emit('session', session);
280+
} else {
281+
owner[kPendingSession] = session;
282+
}
277283
}
278284

279285
function onnewsession(sessionId, session) {
@@ -473,6 +479,8 @@ function TLSSocket(socket, opts) {
473479
this.authorized = false;
474480
this.authorizationError = null;
475481
this[kRes] = null;
482+
this[kIsVerified] = false;
483+
this[kPendingSession] = null;
476484

477485
let wrap;
478486
if ((socket instanceof net.Socket && socket._handle) || !socket) {
@@ -643,6 +651,8 @@ TLSSocket.prototype._destroySSL = function _destroySSL() {
643651
this.ssl._secureContext.context = null;
644652
}
645653
this.ssl = null;
654+
this[kPendingSession] = null;
655+
this[kIsVerified] = false;
646656
};
647657

648658
// Constructor guts, arbitrarily factored out.
@@ -1525,6 +1535,12 @@ function onConnectSecure() {
15251535
this.emit('secureConnect');
15261536
}
15271537

1538+
this[kIsVerified] = true;
1539+
const session = this[kPendingSession];
1540+
this[kPendingSession] = null;
1541+
if (session)
1542+
this.emit('session', session);
1543+
15281544
this.removeListener('end', onConnectEnd);
15291545
}
15301546

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const https = require('https');
9+
const fixtures = require('../common/fixtures');
10+
11+
const options = {
12+
key: fixtures.readKey('agent1-key.pem'),
13+
14+
// NOTE: Certificate Common Name is 'agent1'
15+
cert: fixtures.readKey('agent1-cert.pem'),
16+
17+
// NOTE: TLS 1.3 creates new session ticket **after** handshake so
18+
// `getSession()` output will be different even if the session was reused
19+
// during the handshake.
20+
secureProtocol: 'TLSv1_2_method'
21+
};
22+
23+
const ca = [ fixtures.readKey('ca1-cert.pem') ];
24+
25+
const server = https.createServer(options, function(req, res) {
26+
res.end('ok');
27+
}).listen(0, common.mustCall(function() {
28+
const port = this.address().port;
29+
30+
const req = https.get({
31+
port,
32+
path: '/',
33+
ca,
34+
servername: 'nodejs.org',
35+
}, common.mustNotCall(() => {}));
36+
37+
req.on('error', common.mustCall((err) => {
38+
assert.strictEqual(
39+
err.message,
40+
'Hostname/IP does not match certificate\'s altnames: ' +
41+
'Host: nodejs.org. is not cert\'s CN: agent1');
42+
43+
const second = https.get({
44+
port,
45+
path: '/',
46+
ca,
47+
servername: 'nodejs.org',
48+
}, common.mustNotCall(() => {}));
49+
50+
second.on('error', common.mustCall((err) => {
51+
server.close();
52+
53+
assert.strictEqual(
54+
err.message,
55+
'Hostname/IP does not match certificate\'s altnames: ' +
56+
'Host: nodejs.org. is not cert\'s CN: agent1');
57+
}));
58+
}));
59+
}));
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (!common.hasCrypto)
4+
common.skip('missing crypto');
5+
const fixtures = require('../common/fixtures');
6+
const assert = require('assert');
7+
const tls = require('tls');
8+
9+
const options = {
10+
key: fixtures.readKey('agent1-key.pem'),
11+
12+
// NOTE: Certificate Common Name is 'agent1'
13+
cert: fixtures.readKey('agent1-cert.pem'),
14+
15+
// NOTE: TLS 1.3 creates new session ticket **after** handshake so
16+
// `getSession()` output will be different even if the session was reused
17+
// during the handshake.
18+
secureProtocol: 'TLSv1_2_method'
19+
};
20+
21+
const server = tls.createServer(options, common.mustCall((socket) => {
22+
socket.end();
23+
})).listen(0, common.mustCall(() => {
24+
let connected = false;
25+
let session = null;
26+
27+
const client = tls.connect({
28+
rejectUnauthorized: false,
29+
port: server.address().port,
30+
}, common.mustCall(() => {
31+
assert(!connected);
32+
assert(!session);
33+
34+
connected = true;
35+
}));
36+
37+
client.on('session', common.mustCall((newSession) => {
38+
assert(connected);
39+
assert(!session);
40+
41+
session = newSession;
42+
43+
client.end();
44+
server.close();
45+
}));
46+
}));

0 commit comments

Comments
 (0)
Please sign in to comment.