Skip to content

Commit b7afc71

Browse files
test: add snicallback prioritization test
Depending on the content of a cert, it seems that OpenSSL will prioritize the root cert over the context returned by SNICallback. This behavior is unexpected. This PR adds a test that demonstrates the behavior. Ideally there is a fix, or a explanation for this behavior we can add to the docs. Fixes: #54235 PR-URL: #54251
1 parent 926503b commit b7afc71

File tree

1 file changed

+94
-0
lines changed

1 file changed

+94
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { it, describe } = require('node:test');
5+
const assert = require('node:assert');
6+
const tls = require('node:tls');
7+
const https = require('node:https');
8+
const dns = require('node:dns');
9+
const events = require('node:events');
10+
11+
const fixtures = require('../common/fixtures');
12+
const crypto = require('node:crypto');
13+
14+
const root = {
15+
cert: fixtures.readKey('ca5-cert.pem'),
16+
key: fixtures.readKey('ca5-key.pem')
17+
};
18+
19+
const agent1 = {
20+
cert: fixtures.readKey('agent1-cert.pem'),
21+
key: fixtures.readKey('agent1-key.pem')
22+
};
23+
24+
const PORT = 8443;
25+
26+
const sni = {
27+
'ca5.com': {
28+
cert: new crypto.X509Certificate(root.cert),
29+
secureContext: tls.createSecureContext(root)
30+
},
31+
'agent1.com': {
32+
cert: new crypto.X509Certificate(agent1.cert),
33+
context: tls.createSecureContext(agent1),
34+
},
35+
};
36+
37+
const agent = new https.Agent({
38+
lookup: (hostname, options, cb) => {
39+
if (Object.keys(sni).includes(hostname))
40+
hostname = 'localhost';
41+
return dns.lookup(hostname, options, cb);
42+
}
43+
});
44+
45+
function makeRequest(url, expectedCert) {
46+
return new Promise((resolve, reject) => {
47+
https.get(url, { rejectUnauthorized: false, agent }, common.mustCall((response) => {
48+
const actualCert = response.socket.getPeerX509Certificate();
49+
50+
assert.deepStrictEqual(actualCert.subject, expectedCert.subject);
51+
52+
response.on('data', common.mustCall((chunk) => {
53+
assert.strictEqual(chunk.toString(), 'Hello, World!');
54+
resolve();
55+
}));
56+
57+
response.on('error', reject);
58+
})).on('error', reject);
59+
});
60+
}
61+
62+
describe('Regression test for SNICallback / Certification prioritization issue', () => {
63+
it('should use certificates from SNICallback', async (t) => {
64+
let snicbCount = 0;
65+
const server = https.createServer({
66+
cert: root.cert,
67+
key: root.key,
68+
SNICallback: (servername, cb) => {
69+
snicbCount++;
70+
// This returns the secure context generated from the respective certificate
71+
cb(null, sni[servername].context);
72+
}
73+
}, (req, res) => {
74+
res.writeHead(200);
75+
res.end('Hello, World!');
76+
}).on('error', common.mustNotCall);
77+
78+
server.listen(PORT);
79+
await events.once(server, 'listening');
80+
81+
// Assert that raw IP address gets the root cert
82+
await makeRequest(`https://127.0.0.1:${PORT}`, sni['ca5.com'].cert);
83+
84+
for (const [hostname, { cert: expectedCert }] of Object.entries(sni)) {
85+
// Currently, the agent1 request will fail as it receives the root cert (ca5) instead.
86+
await makeRequest(`https://${hostname}:${PORT}`, expectedCert);
87+
}
88+
89+
// SNICallback should only be called for the hostname requests, not the IP one
90+
assert.strictEqual(snicbCount, 2);
91+
92+
server.close();
93+
});
94+
});

0 commit comments

Comments
 (0)