summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFedor Indutny <fedor@indutny.com>2015-07-22 21:18:38 -0700
committerFedor Indutny <fedor@indutny.com>2015-07-27 11:48:36 -0700
commit2ca5a3db47b930912161074c7b514c769113433b (patch)
tree67787831376a235fa1c50594317bc475da36f773
parent4e78cd71c00d0f109f414bcc01160be3704a4bb4 (diff)
downloadandroid-node-v8-2ca5a3db47b930912161074c7b514c769113433b.tar.gz
android-node-v8-2ca5a3db47b930912161074c7b514c769113433b.tar.bz2
android-node-v8-2ca5a3db47b930912161074c7b514c769113433b.zip
https: reuse TLS sessions in Agent
Fix: #1499 PR-URL: https://github.com/nodejs/io.js/pull/2228 Reviewed-By: Shigeki Ohtsu <ohtsu@iij.ad.jp> Reviewed-By: Trevor Norris <trev.norris@gmail.com>
-rw-r--r--lib/_http_agent.js1
-rw-r--r--lib/_tls_wrap.js14
-rw-r--r--lib/https.js50
-rw-r--r--test/parallel/test-https-agent-session-reuse.js130
4 files changed, 193 insertions, 2 deletions
diff --git a/lib/_http_agent.js b/lib/_http_agent.js
index 02f5e0412c..9208044a94 100644
--- a/lib/_http_agent.js
+++ b/lib/_http_agent.js
@@ -171,6 +171,7 @@ Agent.prototype.createSocket = function(req, options) {
}
var name = self.getName(options);
+ options._agentKey = name;
debug('createConnection', name, options);
options.encoding = null;
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index f42da43ddd..182346904c 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -584,6 +584,17 @@ TLSSocket.prototype._start = function() {
this._handle.start();
};
+TLSSocket.prototype._isSessionResumed = function _isSessionResumed(session) {
+ if (!session)
+ return false;
+
+ var next = this.getSession();
+ if (!next)
+ return false;
+
+ return next.equals(session);
+};
+
TLSSocket.prototype.setServername = function(name) {
this._handle.setServername(name);
};
@@ -999,7 +1010,8 @@ exports.connect = function(/* [port, host], options, cb */) {
var verifyError = socket._handle.verifyError();
// Verify that server's identity matches it's certificate's names
- if (!verifyError) {
+ // Unless server has resumed our existing session
+ if (!verifyError && !socket._isSessionResumed(options.session)) {
var cert = socket.getPeerCertificate();
verifyError = options.checkServerIdentity(hostname, cert);
}
diff --git a/lib/https.js b/lib/https.js
index 21103b71af..abe4a20907 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -58,7 +58,25 @@ function createConnection(port, host, options) {
}
debug('createConnection', options);
- return tls.connect(options);
+
+ if (options._agentKey) {
+ const session = this._getSession(options._agentKey);
+ if (session) {
+ debug('reuse session for %j', options._agentKey);
+ options = util._extend({
+ session: session
+ }, options);
+ }
+ }
+
+ const self = this;
+ const socket = tls.connect(options, function() {
+ if (!options._agentKey)
+ return;
+
+ self._cacheSession(options._agentKey, socket.getSession());
+ });
+ return socket;
}
@@ -66,6 +84,14 @@ function Agent(options) {
http.Agent.call(this, options);
this.defaultPort = 443;
this.protocol = 'https:';
+ this.maxCachedSessions = this.options.maxCachedSessions;
+ if (this.maxCachedSessions === undefined)
+ this.maxCachedSessions = 100;
+
+ this._sessionCache = {
+ map: {},
+ list: []
+ };
}
inherits(Agent, http.Agent);
Agent.prototype.createConnection = createConnection;
@@ -100,6 +126,28 @@ Agent.prototype.getName = function(options) {
return name;
};
+Agent.prototype._getSession = function _getSession(key) {
+ return this._sessionCache.map[key];
+};
+
+Agent.prototype._cacheSession = function _cacheSession(key, session) {
+ // Fast case - update existing entry
+ if (this._sessionCache.map[key]) {
+ this._sessionCache.map[key] = session;
+ return;
+ }
+
+ // Put new entry
+ if (this._sessionCache.list.length >= this.maxCachedSessions) {
+ const oldKey = this._sessionCache.list.shift();
+ debug('evicting %j', oldKey);
+ delete this._sessionCache.map[oldKey];
+ }
+
+ this._sessionCache.list.push(key);
+ this._sessionCache.map[key] = session;
+};
+
const globalAgent = new Agent();
exports.globalAgent = globalAgent;
diff --git a/test/parallel/test-https-agent-session-reuse.js b/test/parallel/test-https-agent-session-reuse.js
new file mode 100644
index 0000000000..2c20252445
--- /dev/null
+++ b/test/parallel/test-https-agent-session-reuse.js
@@ -0,0 +1,130 @@
+'use strict';
+var common = require('../common');
+var assert = require('assert');
+
+if (!common.hasCrypto) {
+ console.log('1..0 # Skipped: missing crypto');
+ return;
+}
+
+var https = require('https');
+var crypto = require('crypto');
+
+var fs = require('fs');
+
+var options = {
+ key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
+ cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
+};
+
+var ca = fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem');
+
+var clientSessions = {};
+var serverRequests = 0;
+
+var agent = new https.Agent({
+ maxCachedSessions: 1
+});
+
+var server = https.createServer(options, function(req, res) {
+ if (req.url === '/drop-key')
+ server.setTicketKeys(crypto.randomBytes(48));
+
+ serverRequests++;
+ res.end('ok');
+}).listen(common.PORT, function() {
+ var queue = [
+ {
+ name: 'first',
+
+ method: 'GET',
+ path: '/',
+ servername: 'agent1',
+ ca: ca,
+ port: common.PORT
+ },
+ {
+ name: 'first-reuse',
+
+ method: 'GET',
+ path: '/',
+ servername: 'agent1',
+ ca: ca,
+ port: common.PORT
+ },
+ {
+ name: 'cipher-change',
+
+ method: 'GET',
+ path: '/',
+ servername: 'agent1',
+
+ // Choose different cipher to use different cache entry
+ ciphers: 'AES256-SHA',
+ ca: ca,
+ port: common.PORT
+ },
+ // Change the ticket key to ensure session is updated in cache
+ {
+ name: 'before-drop',
+
+ method: 'GET',
+ path: '/drop-key',
+ servername: 'agent1',
+ ca: ca,
+ port: common.PORT
+ },
+
+ // Ticket will be updated starting from this
+ {
+ name: 'after-drop',
+
+ method: 'GET',
+ path: '/',
+ servername: 'agent1',
+ ca: ca,
+ port: common.PORT
+ },
+ {
+ name: 'after-drop-reuse',
+
+ method: 'GET',
+ path: '/',
+ servername: 'agent1',
+ ca: ca,
+ port: common.PORT
+ }
+ ];
+
+ function request() {
+ var options = queue.shift();
+ options.agent = agent;
+ https.request(options, function(res) {
+ clientSessions[options.name] = res.socket.getSession();
+
+ res.resume();
+ res.on('end', function() {
+ if (queue.length !== 0)
+ return request();
+ server.close();
+ });
+ }).end();
+ }
+ request();
+});
+
+process.on('exit', function() {
+ assert.equal(serverRequests, 6);
+ assert.equal(clientSessions['first'].toString('hex'),
+ clientSessions['first-reuse'].toString('hex'));
+ assert.notEqual(clientSessions['first'].toString('hex'),
+ clientSessions['cipher-change'].toString('hex'));
+ assert.notEqual(clientSessions['first'].toString('hex'),
+ clientSessions['before-drop'].toString('hex'));
+ assert.notEqual(clientSessions['cipher-change'].toString('hex'),
+ clientSessions['before-drop'].toString('hex'));
+ assert.notEqual(clientSessions['before-drop'].toString('hex'),
+ clientSessions['after-drop'].toString('hex'));
+ assert.equal(clientSessions['after-drop'].toString('hex'),
+ clientSessions['after-drop-reuse'].toString('hex'));
+});