Skip to content

Commit 8e7ac25

Browse files
mattiasholmlundMylesBorins
authored andcommitted
http, tls: better support for IPv6 addresses
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: #14772 Fixes: #14736 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 9509820 commit 8e7ac25

File tree

4 files changed

+84
-16
lines changed

4 files changed

+84
-16
lines changed

lib/_http_agent.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,8 @@ Agent.prototype.addRequest = function(req, options) {
124124
options = util._extend({}, options);
125125
options = util._extend(options, this.options);
126126

127-
if (!options.servername) {
128-
options.servername = options.host;
129-
const hostHeader = req.getHeader('host');
130-
if (hostHeader) {
131-
options.servername = hostHeader.replace(/:.*$/, '');
132-
}
133-
}
127+
if (!options.servername)
128+
options.servername = calculateServerName(options, req);
134129

135130
var name = this.getName(options);
136131
if (!this.sockets[name]) {
@@ -178,13 +173,8 @@ Agent.prototype.createSocket = function(req, options, cb) {
178173
options = util._extend({}, options);
179174
options = util._extend(options, self.options);
180175

181-
if (!options.servername) {
182-
options.servername = options.host;
183-
const hostHeader = req.getHeader('host');
184-
if (hostHeader) {
185-
options.servername = hostHeader.replace(/:.*$/, '');
186-
}
187-
}
176+
if (!options.servername)
177+
options.servername = calculateServerName(options, req);
188178

189179
var name = self.getName(options);
190180
options._agentKey = name;
@@ -211,6 +201,29 @@ Agent.prototype.createSocket = function(req, options, cb) {
211201
}
212202
};
213203

204+
function calculateServerName(options, req) {
205+
let servername = options.host;
206+
const hostHeader = req.getHeader('host');
207+
if (hostHeader) {
208+
// abc => abc
209+
// abc:123 => abc
210+
// [::1] => ::1
211+
// [::1]:123 => ::1
212+
if (hostHeader.startsWith('[')) {
213+
const index = hostHeader.indexOf(']');
214+
if (index === -1) {
215+
// Leading '[', but no ']'. Need to do something...
216+
servername = hostHeader;
217+
} else {
218+
servername = hostHeader.substr(1, index - 1);
219+
}
220+
} else {
221+
servername = hostHeader.split(':', 1)[0];
222+
}
223+
}
224+
return servername;
225+
}
226+
214227
function installListeners(agent, s, options) {
215228
function onFree() {
216229
debug('CLIENT socket onFree');

lib/tls.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const net = require('net');
77
const url = require('url');
88
const binding = process.binding('crypto');
99
const Buffer = require('buffer').Buffer;
10+
const canonicalizeIP = process.binding('cares_wrap').canonicalizeIP;
1011

1112
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
1213
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
@@ -156,7 +157,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
156157
const uri = url.parse(name.slice(4));
157158
uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme.
158159
} else if (name.startsWith('IP Address:')) {
159-
ips.push(name.slice(11));
160+
ips.push(canonicalizeIP(name.slice(11)));
160161
}
161162
}
162163
}
@@ -165,7 +166,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
165166
let reason = 'Unknown reason';
166167

167168
if (net.isIP(host)) {
168-
valid = ips.includes(host);
169+
valid = ips.includes(canonicalizeIP(host));
169170
if (!valid)
170171
reason = `IP: ${host} is not in the cert's list: ${ips.join(', ')}`;
171172
// TODO(bnoordhuis) Also check URI SANs that are IP addresses.

src/cares_wrap.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,28 @@ static void IsIPv6(const FunctionCallbackInfo<Value>& args) {
12811281
}
12821282
}
12831283

1284+
1285+
void CanonicalizeIP(const FunctionCallbackInfo<Value>& args) {
1286+
v8::Isolate* isolate = args.GetIsolate();
1287+
node::Utf8Value ip(isolate, args[0]);
1288+
char address_buffer[sizeof(struct in6_addr)];
1289+
char canonical_ip[INET6_ADDRSTRLEN];
1290+
1291+
int af;
1292+
if (uv_inet_pton(AF_INET, *ip, &address_buffer) == 0)
1293+
af = AF_INET;
1294+
else if (uv_inet_pton(AF_INET6, *ip, &address_buffer) == 0)
1295+
af = AF_INET6;
1296+
else
1297+
return;
1298+
1299+
int err = uv_inet_ntop(af, address_buffer, canonical_ip,
1300+
sizeof(canonical_ip));
1301+
CHECK_EQ(err, 0);
1302+
1303+
args.GetReturnValue().Set(String::NewFromUtf8(isolate, canonical_ip));
1304+
}
1305+
12841306
static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
12851307
Environment* env = Environment::GetCurrent(args);
12861308

@@ -1514,6 +1536,7 @@ static void Initialize(Local<Object> target,
15141536
env->SetMethod(target, "isIP", IsIP);
15151537
env->SetMethod(target, "isIPv4", IsIPv4);
15161538
env->SetMethod(target, "isIPv6", IsIPv6);
1539+
env->SetMethod(target, "canonicalizeIP", CanonicalizeIP);
15171540

15181541
env->SetMethod(target, "strerror", StrError);
15191542
env->SetMethod(target, "getServers", GetServers);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
require('../common');
3+
4+
// Test conversion of IP addresses to the format returned
5+
// for addresses in Subject Alternative Name section
6+
// of a TLS certificate
7+
8+
const assert = require('assert');
9+
const { canonicalizeIP } = process.binding('cares_wrap');
10+
11+
assert.strictEqual(canonicalizeIP('127.0.0.1'), '127.0.0.1');
12+
assert.strictEqual(canonicalizeIP('10.1.0.1'), '10.1.0.1');
13+
assert.strictEqual(canonicalizeIP('::1'), '::1');
14+
assert.strictEqual(canonicalizeIP('fe80:0:0:0:0:0:0:1'), 'fe80::1');
15+
assert.strictEqual(canonicalizeIP('fe80:0:0:0:0:0:0:0'), 'fe80::');
16+
assert.strictEqual(canonicalizeIP('fe80::0000:0010:0001'), 'fe80::10:1');
17+
assert.strictEqual(canonicalizeIP('0001:2222:3333:4444:5555:6666:7777:0088'),
18+
'1:2222:3333:4444:5555:6666:7777:88');
19+
20+
assert.strictEqual(canonicalizeIP('0001:2222:3333:4444:5555:6666::'),
21+
'1:2222:3333:4444:5555:6666::');
22+
23+
assert.strictEqual(canonicalizeIP('a002:B12:00Ba:4444:5555:6666:0:0'),
24+
'a002:b12:ba:4444:5555:6666::');
25+
26+
// IPv4 address represented in IPv6
27+
assert.strictEqual(canonicalizeIP('0:0:0:0:0:ffff:c0a8:101'),
28+
'::ffff:192.168.1.1');
29+
30+
assert.strictEqual(canonicalizeIP('::ffff:192.168.1.1'),
31+
'::ffff:192.168.1.1');

0 commit comments

Comments
 (0)