Skip to content

Commit 864b97b

Browse files
mkrawczukruyadorno
authored andcommitted
tls: use recently added matching SecureContext in default SNICallback
PR-URL: #36072 Fixes: #34110 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent e73b107 commit 864b97b

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

doc/api/tls.md

+3
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,9 @@ added: v0.5.3
627627
The `server.addContext()` method adds a secure context that will be used if
628628
the client request's SNI name matches the supplied `hostname` (or wildcard).
629629

630+
When there are multiple matching contexts, the most recently added one is
631+
used.
632+
630633
### `server.address()`
631634
<!-- YAML
632635
added: v0.6.0

lib/_tls_wrap.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1458,7 +1458,8 @@ Server.prototype[EE.captureRejectionSymbol] = function(
14581458
function SNICallback(servername, callback) {
14591459
const contexts = this.server._contexts;
14601460

1461-
for (const elem of contexts) {
1461+
for (let i = contexts.length - 1; i >= 0; --i) {
1462+
const elem = contexts[i];
14621463
if (RegExpPrototypeTest(elem[0], servername)) {
14631464
callback(null, elem[1]);
14641465
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
const common = require('../common');
3+
const fixtures = require('../common/fixtures');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
// This test ensures that when a TLS connection is established, the server
9+
// selects the most recently added SecureContext that matches the servername.
10+
11+
const assert = require('assert');
12+
const tls = require('tls');
13+
14+
function loadPEM(n) {
15+
return fixtures.readKey(`${n}.pem`);
16+
}
17+
18+
const serverOptions = {
19+
key: loadPEM('agent2-key'),
20+
cert: loadPEM('agent2-cert'),
21+
requestCert: true,
22+
rejectUnauthorized: false,
23+
};
24+
25+
const badSecureContext = {
26+
key: loadPEM('agent1-key'),
27+
cert: loadPEM('agent1-cert'),
28+
ca: [ loadPEM('ca2-cert') ]
29+
};
30+
31+
const goodSecureContext = {
32+
key: loadPEM('agent1-key'),
33+
cert: loadPEM('agent1-cert'),
34+
ca: [ loadPEM('ca1-cert') ]
35+
};
36+
37+
const server = tls.createServer(serverOptions, (c) => {
38+
// The 'a' and 'b' subdomains are used to distinguish between client
39+
// connections.
40+
// Connection to subdomain 'a' is made when the 'bad' secure context is
41+
// the only one in use.
42+
if ('a.example.com' === c.servername) {
43+
assert.strictEqual(c.authorized, false);
44+
}
45+
// Connection to subdomain 'b' is made after the 'good' context has been
46+
// added.
47+
if ('b.example.com' === c.servername) {
48+
assert.strictEqual(c.authorized, true);
49+
}
50+
});
51+
52+
// 1. Add the 'bad' secure context. A connection using this context will not be
53+
// authorized.
54+
server.addContext('*.example.com', badSecureContext);
55+
56+
server.listen(0, () => {
57+
const options = {
58+
port: server.address().port,
59+
key: loadPEM('agent1-key'),
60+
cert: loadPEM('agent1-cert'),
61+
ca: [loadPEM('ca1-cert')],
62+
servername: 'a.example.com',
63+
rejectUnauthorized: false,
64+
};
65+
66+
// 2. Make a connection using servername 'a.example.com'. Since a 'bad'
67+
// secure context is used, this connection should not be authorized.
68+
const client = tls.connect(options, () => {
69+
client.end();
70+
});
71+
72+
client.on('close', common.mustCall(() => {
73+
// 3. Add a 'good' secure context.
74+
server.addContext('*.example.com', goodSecureContext);
75+
76+
options.servername = 'b.example.com';
77+
// 4. Make a connection using servername 'b.example.com'. This connection
78+
// should be authorized because the 'good' secure context is the most
79+
// recently added matching context.
80+
81+
const other = tls.connect(options, () => {
82+
other.end();
83+
});
84+
85+
other.on('close', common.mustCall(() => {
86+
// 5. Make another connection using servername 'b.example.com' to ensure
87+
// that the array of secure contexts is not reversed in place with each
88+
// SNICallback call, as someone might be tempted to refactor this piece of
89+
// code by using Array.prototype.reverse() method.
90+
const onemore = tls.connect(options, () => {
91+
onemore.end();
92+
});
93+
94+
onemore.on('close', common.mustCall(() => {
95+
server.close();
96+
}));
97+
}));
98+
}));
99+
});

0 commit comments

Comments
 (0)