Skip to content

Commit

Permalink
fix: Handle ipv6 addresses in host-header correctly with TLS (#53)
Browse files Browse the repository at this point in the history
When a url uses an ipv6-addresses as the host part, the host-header of the request will be

[::1]:3000

(for ipv6 address ::1). To verify the IP address against a TLS certificate, we need to extract the
IP-address correctly.

Requires node with nodejs/node#14736 resolved to work.

* Add ca certificate and check that certs are valid
* Add tests for certificates with ipv6 addresses
* Fix check for supported node version
* Skip test if ipv6 is not available
  • Loading branch information
mattiash authored and fengmk2 committed Mar 8, 2018
1 parent 55a7a5c commit 4d3a3b1
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 45 deletions.
41 changes: 27 additions & 14 deletions lib/_http_agent.js
Expand Up @@ -196,13 +196,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/,
options = util._extend({}, options);
options = util._extend(options, this.options);

if (!options.servername) {
options.servername = options.host;
const hostHeader = req.getHeader('host');
if (hostHeader) {
options.servername = hostHeader.replace(/:.*$/, '');
}
}
if (!options.servername)
options.servername = calculateServerName(options, req);

var name = this.getName(options);
if (!this.sockets[name]) {
Expand Down Expand Up @@ -258,13 +253,8 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
options = util._extend({}, options);
options = util._extend(options, self.options);

if (!options.servername) {
options.servername = options.host;
const hostHeader = req.getHeader('host');
if (hostHeader) {
options.servername = hostHeader.replace(/:.*$/, '');
}
}
if (!options.servername)
options.servername = calculateServerName(options, req);

var name = self.getName(options);
options._agentKey = name;
Expand Down Expand Up @@ -342,6 +332,29 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
}
};

function calculateServerName(options, req) {
let servername = options.host;
const hostHeader = req.getHeader('host');
if (hostHeader) {
// abc => abc
// abc:123 => abc
// [::1] => ::1
// [::1]:123 => ::1
if (hostHeader.startsWith('[')) {
const index = hostHeader.indexOf(']');
if (index === -1) {
// Leading '[', but no ']'. Need to do something...
servername = hostHeader;
} else {
servername = hostHeader.substr(1, index - 1);
}
} else {
servername = hostHeader.split(':', 1)[0];
}
}
return servername;
}

Agent.prototype.removeSocket = function removeSocket(s, options) {
var name = this.getName(options);
debug('removeSocket', name, 'writable:', s.writable);
Expand Down
29 changes: 16 additions & 13 deletions test/fixtures/agenttest-cert.pem
@@ -1,15 +1,18 @@
-----BEGIN CERTIFICATE-----
MIICaTCCAdICCQDsGV9pOh7bwjANBgkqhkiG9w0BAQUFADB5MQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRAwDgYDVQQDEwdmZW5nbWsyMSAwHgYJKoZIhvcNAQkBFhFmZW5n
bWsyQGdtYWlsLmNvbTAeFw0xMzAzMjIwNzQ2MjBaFw0xMzA0MjEwNzQ2MjBaMHkx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQxEDAOBgNVBAMTB2ZlbmdtazIxIDAeBgkqhkiG
9w0BCQEWEWZlbmdtazJAZ21haWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQCjZLaMW373Af9BkrGQ1il8Focy/DZUz4YXXMAiBWFE8dudrdVPQaDPoYv2
p7ZJr8K0UxGf4svdhsqWkoQ+LcYaHn1Ffez5SR/6Vbic4k8w30RR+a5Iyf2K9ag9
0jCqbBlU8roSQ2fx52oMlbhTQsodcLikoCs9DqYSptmlAmnOCwIDAQABMA0GCSqG
SIb3DQEBBQUAA4GBAJodC7/MlaUYlrG+7PqI3B7226sk2TrdllJOkDVz+bCjQ9HD
/ngeHMKbAh5t4+A5zXtTpPM0XTE6vMmkMsXGNUj1P8BHWPBfGM9+80NphhHX8CzE
qXUvbxHo0TM3qx434OjkDH1ksIBBGUoJDr7YVzhw4guYFvHBnGOzDHdh7QiM
MIIC+jCCAeKgAwIBAgIJAMuPffHFVqVzMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
BAYTAlNFMRIwEAYDVQQIDAlTdG9ja2hvbG0xEjAQBgNVBAcMCVN0b2NraG9sbTEL
MAkGA1UEAwwCY2ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTE3
MTAyMDExMzQwOFoXDTIwMDcxNTExMzQwOFowUjELMAkGA1UEBhMCU0UxEjAQBgNV
BAgMCVN0b2NraG9sbTESMBAGA1UEBwwJU3RvY2tob2xtMRswGQYDVQQDDBJhZ2Vu
dGtlZXBhbGl2ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOWRbiod
/IXLR3z3bDfN4DMqzXn8A5+V7Qz3Z/FpgMoQxV/fUgvJIh4l6CwCN+aBa9pBvRUf
U77CnGuSn1uQAKIRWvlJ5KqrzHqCyU03bFRcAPDwFDFrD5+49Ici5M5tTSuIsj7P
jZuVIOL4oXZ4Biq8P8KGFl9g9Bs8Ttj4trmPAgMBAAGjSDBGMCwGA1UdEQQlMCOH
BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxvY2FsaG9zdDAJBgNVHRMEAjAAMAsG
A1UdDwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAsD/3f+HO1FJo9kOSitaJV4z7
+5NLX3CRpn9vYJU1D6KFLyHJ2Nzuv8lJm0lnMhkO85xfDNCOotokK236vFTFQZpT
VQMZraHzU7aJ0xHC01vXMMXrtY1IoBWZQoyONoU4texmoHcYte408innxHcfTzuf
xFE6id1R9KdrHe/tIt2b0E+7aBCh3RUSP9uEx1/HgNkAEGULo9JAh3JaRJT5ft3M
xCqMGf9RsqUhENOJ/UonEHoaR8gT5QqOrbwq9HezRDSK2LyppCEZ9NE7Yezm3HIP
BhtYMKeNMlwZigyJ0k/ygWXRADSzwcSbrhuB/QZoDdxqhinJCTS4OestY1nQoA==
-----END CERTIFICATE-----
26 changes: 13 additions & 13 deletions test/fixtures/agenttest-key.pem
@@ -1,15 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCjZLaMW373Af9BkrGQ1il8Focy/DZUz4YXXMAiBWFE8dudrdVP
QaDPoYv2p7ZJr8K0UxGf4svdhsqWkoQ+LcYaHn1Ffez5SR/6Vbic4k8w30RR+a5I
yf2K9ag90jCqbBlU8roSQ2fx52oMlbhTQsodcLikoCs9DqYSptmlAmnOCwIDAQAB
AoGAKBW3KyvvNA4LXzzrzwqbVtP4CywQ8DGRRf39LNuBB3cGV+KjqDQDjaymN7bh
Y6Z47+BJPJ5ZQVmmLdZ4FwEHzlA1PhoeIhJ5jXB1orqiadkEn+4lRXR3twexpvzo
GU/2VgN80hVwQQhEKH5ZFXFpIwle+c9TD34L1s8XhqHrEtECQQDSMHfVEsmjyjBY
Ff5b0Q42bN5EsgiMTmV0T1/13aGnAvvILrW1WQ74h43Mb3hQV6KXEvUzui/HWHg+
+fPJHAqfAkEAxwFDPZhsdatjnBPfQu6KKf8VZwKgUhwglCgR0ofceQwwsb5yhg9K
/lej2HMNil6Jtq50oiZc55akMvltXxuxFQJBAKCfTVPU0aaLAjquQ/yiW3wX4hsY
+hNObZVeevSGc9wPGZ22pEF7V0dxP1k07fpnneZZJGxtIcnyv6FQnY3YEf0CQBJd
a3cjud6iEm805kWm/dkiUTdQZrstHVSO3hYvs4j4NwYwLSFyB9mw/M3c0EMUtmDF
eL3+DFTS8hRCMHW2eIECQEuKwy8HOJIoM/BbIlkXupeMypD0Y/aIQ+YLinunHK7v
Cq5mFf6oALSTpDQm2yjmBS3d7+geZ4YTH/iUq1q8nvQ=
MIICXgIBAAKBgQDlkW4qHfyFy0d892w3zeAzKs15/AOfle0M92fxaYDKEMVf31IL
ySIeJegsAjfmgWvaQb0VH1O+wpxrkp9bkACiEVr5SeSqq8x6gslNN2xUXADw8BQx
aw+fuPSHIuTObU0riLI+z42blSDi+KF2eAYqvD/ChhZfYPQbPE7Y+La5jwIDAQAB
AoGAf4Icb1ZCeUnkRhvjRseZ/LGMeXGpzYznoqkUWblg6FsSVeLjXlp0Ecy6PR9q
TySZdBvJWx8QU8ciPHmu+5trTYwHyTcoj4vZ7yIi5bgJLzIcbKv7zeeiKF+u9+iX
VkLHVdGXWkpw+AsgKZWHfAp9hKcUnSMGW72bU7buUXe0dLkCQQD0xoJKkc4WeLxb
cEqrmks9cIAVxn9QQpqsP5E174Pe6Nq+YPpCiz3vURPb/uTM487NhOX4qwIbmn/F
8lW+4e6rAkEA8Bhl33lMPy3mSD8t+Vl8Nzd96OSl4O9MI1X//hwAK3Unpir0POzb
LprTftVK2w015vFZlWe3Smf3R8pFMllQrQJBAKeX4027Vyf7srvIvteP5URD6u79
4d3KPK0DOSF8xQWy2VLQg4lcXSOml7phY7cFo2sEO5FvRRoxRpr5ucvgVdECQQDf
6tEvvw+WKLeJD6tPzt4jHRTHXF87zdFjbzRlCDY8UXHd7leEbp3n9CtlMYUzkDmC
HfsfdPAja5zajlFEqLmtAkEAsxUKoiBkrJ1TQUmepETKtPNk6eNU8O3Gf484E2Aa
ooNrBva7VvyuOw+OgQKnXZ4sH37uNkS1KQHDp/x5kZX+DQ==
-----END RSA PRIVATE KEY-----
28 changes: 28 additions & 0 deletions test/fixtures/ca.cnf
@@ -0,0 +1,28 @@
[ ca ]
default_ca = CA_default

[ CA_default ]
serial = ca-serial
crl = ca-crl.pem
database = ca-database.txt
name_opt = CA_default
cert_opt = CA_default
default_crl_days = 9999
default_md = md5

[ req ]
default_bits = 2048
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no

[ req_distinguished_name ]
C = SE
ST = Stockholm
L = Stockholm
CN = ca
emailAddress = ca@example.com

[ req_attributes ]
challengePassword = test
28 changes: 28 additions & 0 deletions test/fixtures/ca.key
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDirhR/SK8uxDp3
pZANtV3IeTKFwLdA2G6xNgo7isgCG+OnzEN8zKePSL6vSoUJnYH5KYmNOlO2zc0b
qrvUQWd375WB/zl3MEjJS1+taLxyO+lFlq7fPoB/V4XO+s0l2cA4PyGAkCcisJ5s
LCI2oaxrbjkOPzHC4W05Cj8Svn440kf/2faeHYIdhHGVs36t6nb5d2WnivPvmucp
FOShhK+LFBxL7AQIdoeDdpkjdukT9CnnRT5S1LUq6ReXzjhYqhGU+nmn6GqznpPn
cnZFIRdBQvM3b5BL2NLb5VzglUa2fNYqYGKNGgzj9G/depW8QHOxk3VUJS7vaY0z
PlkTgPO3AgMBAAECggEBAMk4Pnm+y8N37W7ISVfh555N98tDh2jIt3oXvn2cdG9q
0mvhpwbhpNxMdvij7fTbHMVRWglD/YwIpEorBREl/fM5ej4rkZd8BSCrCAOnNaEy
DaD4YcNKeEaSKvXRLMqswTUs1VCKpjLlFbxwcO6OFcBH194Nut6DvbEkp1i8QM+u
LwF4Sqm6r/CbJMMG6RSamb+nwSu2s8HH+B/n+ba8rNQNqJIwxueGuZGSzQ82SoYp
Mtq8v/Lb0Ea+OeN6cG1zhHe5vwT8o/vfPzkH50tI6kq4lltai3MaqxcaV3XRc8R0
lndGL4Rf/Q+AiF2nYgZZYpVdkbW06e7GSFfE7OmLPgECgYEA9HKuOHklNwzsOyDo
KMQ6iSGCPwUQSBgyNOF/uu+wwPnOxGU3GaSsC8QFNsgKlDfXjTFefJwdzhvGB5ES
JQxIYwa1TwCODavrNow7LUIExC8P6NI7DyWOeZZenVIHhBEejrbzMaiAS0/hklL8
EZpeNniQHED/S37kWZdEp3SE+1MCgYEA7WRxiHxARu54A1lIv2b+3NSSS7lTqweW
ELswyl6CGm2HQlt4iWccSqM5xMb9idJPDehijj92rXR1k64SurEL2OQstKg3ivfq
TcKKyVmj1WDm2v56LXIpjAb7Iy6ewbRmC9WWs4Wfqf6KK7Nm3FzDpKf5Uarqm3eh
Z0vvQMPpfY0CgYEAogkKt2CGbLFiPeeYPL2mV5QgtAl1O3TegvMfKhkMPz7X8pNt
LNBdQwdStXdwm8NQXMVm7o7FqwP6BrYBIxG7QfkGYjHp9+IH7oaSC3QBmNHhZ+FD
SM0KXkpwuTPQy5hVeyCGoFojgMiYq9faQwjifpT4YeIr2C1qzIBa/+1a4QUCgYEA
5+PgC2TkHOXBEfRbXaysdOao6ZNlKYJFkp5oMKZVDJ/FKorTmdTBDB+ZxKBk9gYb
9wfzjeRsd091swatgPSFEB8DlI1lhDhcBg1tKPaJVVxM5csDafVEpGYFV/6oUat4
q0K+7SowJwxfyAR9C/EJo4P5xU7h0W/wmEjSsz8si9kCgYBDA5wGbBcr55zkEO5L
7bnTq8jCtUmFcZaosWlRmsSB7gHQva+bmnmtJbSpzKbGgGHp6c21BowRXO0E7lZv
aIZz2SzIR9+JQw9GtVy2e797oZzhG2+u/BIkxGwowlPRVsiM468fID/j7VMRWXsM
FZBgmTZ8TpJPtO+1VTB1j5QdPg==
-----END PRIVATE KEY-----
20 changes: 20 additions & 0 deletions test/fixtures/ca.pem
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDPjCCAiYCCQDsuRUq6wfGEDANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJT
RTESMBAGA1UECAwJU3RvY2tob2xtMRIwEAYDVQQHDAlTdG9ja2hvbG0xCzAJBgNV
BAMMAmNhMR0wGwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbTAeFw0xNzEwMjAx
MTM0MDhaFw00NTAzMDYxMTM0MDhaMGExCzAJBgNVBAYTAlNFMRIwEAYDVQQIDAlT
dG9ja2hvbG0xEjAQBgNVBAcMCVN0b2NraG9sbTELMAkGA1UEAwwCY2ExHTAbBgkq
hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA4q4Uf0ivLsQ6d6WQDbVdyHkyhcC3QNhusTYKO4rIAhvjp8xDfMyn
j0i+r0qFCZ2B+SmJjTpTts3NG6q71EFnd++Vgf85dzBIyUtfrWi8cjvpRZau3z6A
f1eFzvrNJdnAOD8hgJAnIrCebCwiNqGsa245Dj8xwuFtOQo/Er5+ONJH/9n2nh2C
HYRxlbN+rep2+Xdlp4rz75rnKRTkoYSvixQcS+wECHaHg3aZI3bpE/Qp50U+UtS1
KukXl844WKoRlPp5p+hqs56T53J2RSEXQULzN2+QS9jS2+Vc4JVGtnzWKmBijRoM
4/Rv3XqVvEBzsZN1VCUu72mNMz5ZE4DztwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
AQAgR8F1/E3slNqbHEk5pLqPw4V0Trk3jB8yNwpkhdpYJKSeAxuX4FdS+vCd1wG6
V3GG7VR/iQABlH/YQjhqAMGjMhmGbZgvEENr9hRYf6Rp7eEC4gddwn4zq/xv90n6
/St4V5Ek4/jnTXdwaZFWR1UHiwhYJ4qUYsKR1TiA3nGckKtd52+Veu+xu9DCEOlI
R2PAKca7bwH+M9GosIV6SJdh+YT3+7hp0d2xUjPvDDXVcb2ezGyxsfDJmXsa0YEt
V5NvocKipnU2ZYHUvORLix/7OyaHkrsfdwvaFlvRNBKge3F/l+bcs50WhD5daB+g
bdLER/TWIIcc6x4karOucHS8
-----END CERTIFICATE-----
23 changes: 20 additions & 3 deletions test/fixtures/genkey.sh 100644 → 100755
@@ -1,5 +1,22 @@
#!/bin/bash
# DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# Generate ca key and pem
openssl req -new -x509 -nodes -days 9999 -config ca.cnf -keyout ca.key -out ca.pem

# Generate server key
# openssl genrsa -out server.key 2048
openssl genrsa -out agenttest-key.pem 1024
openssl req -new -key agenttest-key.pem -out certrequest.csr
openssl x509 -req -in certrequest.csr -signkey agenttest-key.pem -out agenttest-cert.pem
rm certrequest.csr

# Generate a certificate signing request for server.key
# openssl req -new -key server.key -out server.csr
openssl req -new -key agenttest-key.pem -out agenttest.csr -config server.cnf

# Sign the csr with the ca certificate, generating server.pem
openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -extensions v3_req -in agenttest.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out agenttest-cert.pem

# openssl req -new -key agenttest-key.pem -out certrequest.csr
rm agenttest.csr ca.srl
# http://www.hacksparrow.com/node-js-https-ssl-certificate.html


35 changes: 35 additions & 0 deletions test/fixtures/server.cnf
@@ -0,0 +1,35 @@
[ req ]
default_bits = 2048
days = 9999
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
x509_extensions = v3_ca
req_extensions = v3_req

[ req_distinguished_name ]
C = SE
ST = Stockholm
L = Stockholm
CN = agentkeepalive.com

[ req_attributes ]
challengePassword = password

[ v3_ca ]
authorityInfoAccess = @issuer_info

[ v3_req ]
subjectAltName = @alt_names
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ issuer_info ]
OCSP;URI.0 = http://ocsp.example.com/
caIssuers;URI.0 = http://example.com/ca.cert

[ alt_names ]
IP.1 = 127.0.0.1
IP.2 = ::1
DNS.1 = localhost
4 changes: 2 additions & 2 deletions test/https_agent.test.js
Expand Up @@ -56,7 +56,7 @@ describe('test/https_agent.test.js', () => {
agent: agentkeepalive,
port,
path: '/',
rejectUnauthorized: false,
ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'),
}, res => {
assert(res.statusCode === 200);
res.resume();
Expand All @@ -76,7 +76,7 @@ describe('test/https_agent.test.js', () => {
agent: agentkeepalive,
port,
path: '/',
rejectUnauthorized: false,
ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'),
}, res => {
assert(res.statusCode === 200);
res.resume();
Expand Down
90 changes: 90 additions & 0 deletions test/test-https-ipv6.test.js
@@ -0,0 +1,90 @@
'use strict';

const https = require('https');
const assert = require('assert');
const fs = require('fs');
const constants = require('constants');
const HttpsAgent = require('..').HttpsAgent;
const os = require('os');

function isIPv6Available() {
const networkInterfaces = os.networkInterfaces();
return !!Object.keys(networkInterfaces).find(ifName => {
const addresses = networkInterfaces[ifName];
return !!addresses.find(addr => addr.family === 'IPv6');
});
}

describe('test/test-ipv6.test.js', () => {
let port;
let server;
const httpsAgent = new HttpsAgent({
keepAlive: true,
});
const options = {
key: fs.readFileSync(__dirname + '/fixtures/agenttest-key.pem'),
cert: fs.readFileSync(__dirname + '/fixtures/agenttest-cert.pem'),
secureOptions: constants.SSL_OP_NO_TICKET,
};

before(function(done) {
if (!isIPv6Available()) {
this.skip();
}

// Create TLS1.2 server
server = https.createServer(options, (req, res) => {
res.end('ohai');
});
server.listen(0, () => {
port = server.address().port;
done();
});
});

it('should GET / success with 200 status from ::1', function(done) {
const m = process.version.match(/^v(\d+)\.(\d+)/);
const major = parseInt(m[1]);
const minor = parseInt(m[2]);
if (major < 8 || (major === 8 && minor < 10) || (major === 9 && minor < 1)) {
// This only works in node-versions with the fix for
// https://github.com/nodejs/node/issues/14736 included.
this.skip();
}

https.get({
agent: httpsAgent,
hostname: '::1',
port,
path: '/',
ca: fs.readFileSync(__dirname + '/fixtures/ca.pem'),
}, res => {
assert(res.statusCode === 200);
res.resume();
res.on('end', () => {
process.nextTick(() => {
assert(Object.keys(httpsAgent.sockets).length === 0);
assert(Object.keys(httpsAgent.freeSockets).length === 1);
done();
});
});
});
assert(Object.keys(httpsAgent.sockets).length === 1);
});

it('should not crash with invalid host-header', done => {
https.get({
agent: httpsAgent,
hostname: '::1',
port,
path: '/',
headers: {
host: '[::1:80',
},
rejectUnauthorized: false,
}, () => {
done();
});
});

});

0 comments on commit 4d3a3b1

Please sign in to comment.