diff --git a/lib/http.js b/lib/http.js index f7b2f786b6..87199ea3ac 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1067,7 +1067,7 @@ Agent.prototype.addRequest = function(req, host, port, localAddress) { } if (this.sockets[name].length < this.maxSockets) { // If we are under maxSockets create a new one. - req.onSocket(this.createSocket(name, host, port, localAddress)); + req.onSocket(this.createSocket(name, host, port, localAddress, req)); } else { // We are over limit so we'll add it to the queue. if (!this.requests[name]) { @@ -1076,12 +1076,21 @@ Agent.prototype.addRequest = function(req, host, port, localAddress) { this.requests[name].push(req); } }; -Agent.prototype.createSocket = function(name, host, port, localAddress) { +Agent.prototype.createSocket = function(name, host, port, localAddress, req) { var self = this; var options = util._extend({}, self.options); options.port = port; options.host = host; options.localAddress = localAddress; + + options.servername = host; + if (req) { + var hostHeader = req.getHeader('host'); + if (hostHeader) { + options.servername = hostHeader.replace(/:.*$/, ''); + } + } + var s = self.createConnection(options); if (!self.sockets[name]) { self.sockets[name] = []; @@ -1122,8 +1131,9 @@ Agent.prototype.removeSocket = function(s, name, host, port, localAddress) { } } if (this.requests[name] && this.requests[name].length) { + var req = this.requests[name][0]; // If we have pending requests and a socket gets closed a new one - this.createSocket(name, host, port, localAddress).emit('free'); + this.createSocket(name, host, port, localAddress, req).emit('free'); } }; diff --git a/test/simple/test-https-strict.js b/test/simple/test-https-strict.js new file mode 100644 index 0000000000..e62c0d51a7 --- /dev/null +++ b/test/simple/test-https-strict.js @@ -0,0 +1,218 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +if (!process.versions.openssl) { + console.error('Skipping because node compiled without OpenSSL.'); + process.exit(0); +} + +var common = require('../common'); +var assert = require('assert'); + +var fs = require('fs'); +var path = require('path'); +var https = require('https'); + +function file(fname) { + return path.resolve(common.fixturesDir, 'keys', fname); +} + +function read(fname) { + return fs.readFileSync(file(fname)); +} + +// key1 is signed by ca1. +var key1 = read('agent1-key.pem'); +var cert1 = read('agent1-cert.pem'); + +// key2 has a self signed cert +var key2 = read('agent2-key.pem'); +var cert2 = read('agent2-cert.pem'); + +// key3 is signed by ca2. +var key3 = read('agent3-key.pem'); +var cert3 = read('agent3-cert.pem'); + +var ca1 = read('ca1-cert.pem'); +var ca2 = read('ca2-cert.pem'); + +// different agents to use different CA lists. +// this api is beyond bad. +var agent0 = new https.Agent(); +var agent1 = new https.Agent({ ca: [ca1] }); +var agent2 = new https.Agent({ ca: [ca2] }); +var agent3 = new https.Agent({ ca: [ca1, ca2] }); + +var options1 = { + key: key1, + cert: cert1 +}; + +var options2 = { + key: key2, + cert: cert2 +}; + +var options3 = { + key: key3, + cert: cert3 +}; + +var server1 = server(options1); +var server2 = server(options2); +var server3 = server(options3); + +var listenWait = 0; + +var port = common.PORT; +var port1 = port++; +var port2 = port++; +var port3 = port++; +server1.listen(port1, listening()); +server2.listen(port2, listening()); +server3.listen(port3, listening()); + +var responseErrors = {}; +var expectResponseCount = 0; +var responseCount = 0; +var pending = 0; + + + +function server(options, port) { + var s = https.createServer(options, handler); + s.requests = []; + s.expectCount = 0; + return s; +} + +function handler(req, res) { + this.requests.push(req.url); + res.statusCode = 200; + res.setHeader('foo', 'bar'); + res.end('hello, world\n'); +} + +function listening() { + listenWait++; + return function() { + listenWait--; + if (listenWait === 0) { + allListening(); + } + } +} + +function makeReq(path, port, error, host, ca) { + pending++; + var options = { + port: port, + path: path, + ca: ca + }; + var whichCa = 0; + if (!ca) { + options.agent = agent0; + } else { + if (!Array.isArray(ca)) ca = [ca]; + if (-1 !== ca.indexOf(ca1) && -1 !== ca.indexOf(ca2)) { + options.agent = agent3; + } else if (-1 !== ca.indexOf(ca1)) { + options.agent = agent1; + } else if (-1 !== ca.indexOf(ca2)) { + options.agent = agent2; + } else { + options.agent = agent0; + } + } + + if (host) { + options.headers = { host: host } + } + var req = https.get(options); + expectResponseCount++; + var server = port === port1 ? server1 + : port === port2 ? server2 + : port === port3 ? server3 + : null; + + if (!server) throw new Error('invalid port: '+port); + server.expectCount++; + + req.on('response', function(res) { + responseCount++; + assert.equal(res.connection.authorizationError, error); + responseErrors[path] = res.connection.authorizationError; + pending--; + if (pending === 0) { + server1.close(); + server2.close(); + server3.close(); + } + }) +} + +function allListening() { + // ok, ready to start the tests! + + // server1: host 'agent1', signed by ca1 + makeReq('/inv1', port1, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + makeReq('/inv1-ca1', port1, + 'Hostname/IP doesn\'t match certificate\'s altnames', + null, ca1); + makeReq('/inv1-ca1ca2', port1, + 'Hostname/IP doesn\'t match certificate\'s altnames', + null, [ca1, ca2]); + makeReq('/val1-ca1', port1, null, 'agent1', ca1); + makeReq('/val1-ca1ca2', port1, null, 'agent1', [ca1, ca2]); + makeReq('/inv1-ca2', port1, + 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'agent1', ca2); + + // server2: self-signed, host = 'agent2' + // doesn't matter that thename matches, all of these will error. + makeReq('/inv2', port2, 'DEPTH_ZERO_SELF_SIGNED_CERT'); + makeReq('/inv2-ca1', port2, 'DEPTH_ZERO_SELF_SIGNED_CERT', + 'agent2', ca1); + makeReq('/inv2-ca1ca2', port2, 'DEPTH_ZERO_SELF_SIGNED_CERT', + 'agent2', [ca1, ca2]); + + // server3: host 'agent3', signed by ca2 + makeReq('/inv3', port3, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'); + makeReq('/inv3-ca2', port3, + 'Hostname/IP doesn\'t match certificate\'s altnames', + null, ca2); + makeReq('/inv3-ca1ca2', port3, + 'Hostname/IP doesn\'t match certificate\'s altnames', + null, [ca1, ca2]); + makeReq('/val3-ca2', port3, null, 'agent3', ca2); + makeReq('/val3-ca1ca2', port3, null, 'agent3', [ca1, ca2]); + makeReq('/inv3-ca1', port3, + 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'agent1', ca1); + +} + +process.on('exit', function() { + console.error(responseErrors); + assert.equal(server1.requests.length, server1.expectCount); + assert.equal(server2.requests.length, server2.expectCount); + assert.equal(server3.requests.length, server3.expectCount); + assert.equal(responseCount, expectResponseCount); +});