diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index c8aadeac0ce..1d6d6f8b257 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -171,6 +171,9 @@ has these possibilities: SecureContext). If `SNICallback` wasn't provided - default callback with high-level API will be used (see below). + - `sessionIdContext`: A string containing a opaque identifier for session + resumption. If `requestCert` is `true`, the default is MD5 hash value + generated from command-line. Otherwise, the default is not provided. #### Event: 'secureConnection' diff --git a/lib/crypto.js b/lib/crypto.js index cea601bd65b..85bbbcd65c7 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -104,6 +104,10 @@ exports.createCredentials = function(options, context) { } } + if (options.sessionIdContext) { + c.context.setSessionIdContext(options.sessionIdContext); + } + return c; }; diff --git a/lib/tls.js b/lib/tls.js index 6f26ffb4551..7e6c5b1d97f 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -803,7 +803,8 @@ function Server(/* [options], listener */) { ciphers: self.ciphers, secureProtocol: self.secureProtocol, secureOptions: self.secureOptions, - crl: self.crl + crl: self.crl, + sessionIdContext: self.sessionIdContext }); sharedCreds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); @@ -892,6 +893,13 @@ Server.prototype.setOptions = function(options) { } else { this.SNICallback = this.SNICallback.bind(this); } + if (options.sessionIdContext) { + this.sessionIdContext = options.sessionIdContext; + } else if (this.requestCert) { + this.sessionIdContext = crypto.createHash('md5') + .update(process.argv.join(' ')) + .digest('hex'); + } }; // SNI Contexts High-Level API diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 6e3e365d63d..b935aa7f0c0 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -93,6 +93,8 @@ void SecureContext::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "addRootCerts", SecureContext::AddRootCerts); NODE_SET_PROTOTYPE_METHOD(t, "setCiphers", SecureContext::SetCiphers); NODE_SET_PROTOTYPE_METHOD(t, "setOptions", SecureContext::SetOptions); + NODE_SET_PROTOTYPE_METHOD(t, "setSessionIdContext", + SecureContext::SetSessionIdContext); NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close); target->Set(String::NewSymbol("SecureContext"), t->GetFunction()); @@ -474,6 +476,38 @@ Handle SecureContext::SetOptions(const Arguments& args) { return True(); } +Handle SecureContext::SetSessionIdContext(const Arguments& args) { + HandleScope scope; + + SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); + + if (args.Length() != 1 || !args[0]->IsString()) { + return ThrowException(Exception::TypeError(String::New("Bad parameter"))); + } + + String::Utf8Value sessionIdContext(args[0]->ToString()); + const unsigned char* sid_ctx = (const unsigned char*) *sessionIdContext; + unsigned int sid_ctx_len = sessionIdContext.length(); + + int r = SSL_CTX_set_session_id_context(sc->ctx_, sid_ctx, sid_ctx_len); + if (r != 1) { + Local message; + BIO* bio; + BUF_MEM* mem; + if ((bio = BIO_new(BIO_s_mem()))) { + ERR_print_errors(bio); + BIO_get_mem_ptr(bio, &mem); + message = String::New(mem->data, mem->length); + BIO_free(bio); + } else { + message = String::New("SSL_CTX_set_session_id_context error"); + } + return ThrowException(Exception::TypeError(message)); + } + + return True(); +} + Handle SecureContext::Close(const Arguments& args) { HandleScope scope; SecureContext *sc = ObjectWrap::Unwrap(args.Holder()); diff --git a/src/node_crypto.h b/src/node_crypto.h index d28602eb573..0d6e55b030c 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -66,6 +66,7 @@ class SecureContext : ObjectWrap { static v8::Handle AddRootCerts(const v8::Arguments& args); static v8::Handle SetCiphers(const v8::Arguments& args); static v8::Handle SetOptions(const v8::Arguments& args); + static v8::Handle SetSessionIdContext(const v8::Arguments& args); static v8::Handle Close(const v8::Arguments& args); SecureContext() : ObjectWrap() { diff --git a/test/simple/test-tls-session-cache.js b/test/simple/test-tls-session-cache.js new file mode 100644 index 00000000000..53f736b84d9 --- /dev/null +++ b/test/simple/test-tls-session-cache.js @@ -0,0 +1,78 @@ +// 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); +} +require('child_process').exec('openssl version', function(err) { + if (err !== null) { + console.error("Skipping because openssl command is not available."); + process.exit(0); + } + doTest(); +}); + +function doTest() { + var common = require('../common'); + var assert = require('assert'); + var tls = require('tls'); + var fs = require('fs'); + var join = require('path').join; + var spawn = require('child_process').spawn; + + var keyFile = join(common.fixturesDir, 'agent.key'); + var certFile = join(common.fixturesDir, 'agent.crt'); + var key = fs.readFileSync(keyFile); + var cert = fs.readFileSync(certFile); + var options = { + key: key, + cert: cert, + ca: [ cert ], + requestCert: true + }; + var requestCount = 0; + + var server = tls.createServer(options, function(cleartext) { + ++requestCount; + cleartext.end(); + }); + server.listen(common.PORT, function() { + var client = spawn('openssl', [ + 's_client', + '-connect', 'localhost:' + common.PORT, + '-key', join(common.fixturesDir, 'agent.key'), + '-cert', join(common.fixturesDir, 'agent.crt'), + '-reconnect' + ], { + customFds: [0, 1, 2] + }); + client.on('exit', function(code) { + assert.equal(code, 0); + server.close(); + }); + }); + + process.on('exit', function() { + // initial request + reconnect requests (5 times) + assert.equal(requestCount, 6); + }); +}