Skip to content

Commit f275ff1

Browse files
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: nodejs#14772 Fixes: nodejs#14736 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 581fe1f commit f275ff1

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed

lib/_http_agent.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/,
151151
if (options.socketPath)
152152
options.path = options.socketPath;
153153

154-
if (!options.servername) {
155-
options.servername = options.host;
156-
const hostHeader = req.getHeader('host');
157-
if (hostHeader) {
158-
options.servername = hostHeader.replace(/:.*$/, '');
159-
}
160-
}
154+
if (!options.servername)
155+
options.servername = calculateServerName(options, req);
161156

162157
var name = this.getName(options);
163158
if (!this.sockets[name]) {
@@ -204,13 +199,8 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
204199
if (options.socketPath)
205200
options.path = options.socketPath;
206201

207-
if (!options.servername) {
208-
options.servername = options.host;
209-
const hostHeader = req.getHeader('host');
210-
if (hostHeader) {
211-
options.servername = hostHeader.replace(/:.*$/, '');
212-
}
213-
}
202+
if (!options.servername)
203+
options.servername = calculateServerName(options, req);
214204

215205
var name = this.getName(options);
216206
options._agentKey = name;
@@ -239,6 +229,29 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
239229
oncreate(null, newSocket);
240230
};
241231

232+
function calculateServerName(options, req) {
233+
let servername = options.host;
234+
const hostHeader = req.getHeader('host');
235+
if (hostHeader) {
236+
// abc => abc
237+
// abc:123 => abc
238+
// [::1] => ::1
239+
// [::1]:123 => ::1
240+
if (hostHeader.startsWith('[')) {
241+
const index = hostHeader.indexOf(']');
242+
if (index === -1) {
243+
// Leading '[', but no ']'. Need to do something...
244+
servername = hostHeader;
245+
} else {
246+
servername = hostHeader.substr(1, index - 1);
247+
}
248+
} else {
249+
servername = hostHeader.split(':', 1)[0];
250+
}
251+
}
252+
return servername;
253+
}
254+
242255
function installListeners(agent, s, options) {
243256
function onFree() {
244257
debug('CLIENT socket onFree');

lib/tls.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const net = require('net');
2929
const url = require('url');
3030
const binding = process.binding('crypto');
3131
const Buffer = require('buffer').Buffer;
32+
const canonicalizeIP = process.binding('cares_wrap').canonicalizeIP;
3233

3334
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
3435
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
@@ -179,7 +180,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
179180
const uri = url.parse(name.slice(4));
180181
uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme.
181182
} else if (name.startsWith('IP Address:')) {
182-
ips.push(name.slice(11));
183+
ips.push(canonicalizeIP(name.slice(11)));
183184
}
184185
}
185186
}
@@ -188,7 +189,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
188189
let reason = 'Unknown reason';
189190

190191
if (net.isIP(host)) {
191-
valid = ips.includes(host);
192+
valid = ips.includes(canonicalizeIP(host));
192193
if (!valid)
193194
reason = `IP: ${host} is not in the cert's list: ${ips.join(', ')}`;
194195
// TODO(bnoordhuis) Also check URI SANs that are IP addresses.

src/cares_wrap.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,27 @@ void IsIPv6(const FunctionCallbackInfo<Value>& args) {
19431943
}
19441944
}
19451945

1946+
void CanonicalizeIP(const FunctionCallbackInfo<Value>& args) {
1947+
v8::Isolate* isolate = args.GetIsolate();
1948+
node::Utf8Value ip(isolate, args[0]);
1949+
char address_buffer[sizeof(struct in6_addr)];
1950+
char canonical_ip[INET6_ADDRSTRLEN];
1951+
1952+
int af;
1953+
if (uv_inet_pton(AF_INET, *ip, &address_buffer) == 0)
1954+
af = AF_INET;
1955+
else if (uv_inet_pton(AF_INET6, *ip, &address_buffer) == 0)
1956+
af = AF_INET6;
1957+
else
1958+
return;
1959+
1960+
int err = uv_inet_ntop(af, address_buffer, canonical_ip,
1961+
sizeof(canonical_ip));
1962+
CHECK_EQ(err, 0);
1963+
1964+
args.GetReturnValue().Set(String::NewFromUtf8(isolate, canonical_ip));
1965+
}
1966+
19461967
void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
19471968
Environment* env = Environment::GetCurrent(args);
19481969

@@ -2163,6 +2184,7 @@ void Initialize(Local<Object> target,
21632184
env->SetMethod(target, "isIP", IsIP);
21642185
env->SetMethod(target, "isIPv4", IsIPv4);
21652186
env->SetMethod(target, "isIPv6", IsIPv6);
2187+
env->SetMethod(target, "canonicalizeIP", CanonicalizeIP);
21662188

21672189
env->SetMethod(target, "strerror", StrError);
21682190

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)