tls: more session configuration options, methods

Introduce `ticketKeys` server option, `session` client option,
`getSession()` and `getTLSTicket()` methods.

fix #7032
This commit is contained in:
Fedor Indutny committed 2014-02-05 23:28:34 +04:00
1 parent 5ce458032f
commit 528a3ce3ed
5 files changed
+148 -1

No files matched your search

+22
View File
@@ -205,6 +205,12 @@ automatically set as a listener for the [secureConnection][] event. The
session identifiers and TLS session tickets created by the server are
timed out. See [SSL_CTX_set_timeout] for more details.
- `ticketKeys`: A 48-byte `Buffer` instance consisting of 16-byte prefix,
16-byte hmac key, 16-byte AES key. You could use it to accept tls session
tickets on multiple instances of tls server.
NOTE: Automatically shared between `cluster` module workers.
- `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.
@@ -314,6 +320,8 @@ Creates a new client connection to the given `port` and `host` (old API) or
SSL version 3. The possible values depend on your installation of
OpenSSL and are defined in the constant [SSL_METHODS][].
- `session`: A `Buffer` instance, containing TLS session.
The `callback` parameter will be added as a listener for the
['secureConnect'][] event.
@@ -398,6 +406,8 @@ Construct a new TLSSocket object from existing TCP socket.
- `SNICallback`: Optional, see [tls.createServer][]
- `session`: Optional, a `Buffer` instance, containing TLS session
## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized])
Stability: 0 - Deprecated. Use tls.TLSSocket instead.
@@ -646,6 +656,18 @@ and their processing can be delayed due to packet loss or reordering. However,
smaller fragments add extra TLS framing bytes and CPU overhead, which may
decrease overall server throughput.
### tlsSocket.getSession()
Return ASN.1 encoded TLS session or `undefined` if none was negotiated. Could
be used to speed up handshake establishment when reconnecting to the server.
### tlsSocket.getTLSTicket()
NOTE: Works only with client TLS sockets. Useful only for debugging, for
session reuse provide `session` option to `tls.connect`.
Return TLS session ticket or `undefined` if none was negotiated.
### tlsSocket.address()
Returns the bound address, the address family name and port of the
+9
View File
@@ -232,6 +232,9 @@ TLSSocket.prototype._init = function(socket) {
} else {
this.ssl.onhandshakestart = function() {};
this.ssl.onhandshakedone = this._finishInit.bind(this);
if (options.session)
this.ssl.setSession(options.session);
}
this.ssl.onerror = function(err) {
@@ -321,6 +324,10 @@ TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
return this.ssl.setMaxSendFragment(size) == 1;
};
TLSSocket.prototype.getTLSTicket = function getTLSTicket() {
return this.ssl.getTLSTicket();
};
TLSSocket.prototype._handleTimeout = function() {
this._tlsError(new Error('TLS handshake timeout'));
};
@@ -620,6 +627,7 @@ Server.prototype.setOptions = function(options) {
if (!util.isUndefined(options.ecdhCurve))
this.ecdhCurve = options.ecdhCurve;
if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
if (options.ticketKeys) this.ticketKeys = options.ticketKeys;
var secureOptions = options.secureOptions || 0;
if (options.honorCipherOrder) {
secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
@@ -747,6 +755,7 @@ exports.connect = function(/* [port, host], options, cb */) {
isServer: false,
requestCert: true,
rejectUnauthorized: options.rejectUnauthorized,
session: options.session,
NPNProtocols: NPN.NPNProtocols
});
result = socket;
+21 -1
View File
@@ -856,6 +856,7 @@ void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> t) {
NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser);
NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate);
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket);
#ifdef SSL_set_max_send_fragment
NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment);
@@ -1129,7 +1130,7 @@ void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
unsigned char* sbuf = new unsigned char[slen];
unsigned char* p = sbuf;
i2d_SSL_SESSION(sess, &p);
args.GetReturnValue().Set(Encode(sbuf, slen, BINARY));
args.GetReturnValue().Set(Encode(sbuf, slen, BUFFER));
delete[] sbuf;
}
@@ -1247,6 +1248,25 @@ void SSLWrap<Base>::Shutdown(const FunctionCallbackInfo<Value>& args) {
}
template <class Base>
void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
Base* w = Unwrap<Base>(args.This());
Environment* env = w->ssl_env();
SSL_SESSION* sess = SSL_get_session(w->ssl_);
if (sess == NULL || sess->tlsext_tick == NULL)
return;
Local<Object> buf = Buffer::New(env,
reinterpret_cast<char*>(sess->tlsext_tick),
sess->tlsext_ticklen);
args.GetReturnValue().Set(buf);
}
#ifdef SSL_set_max_send_fragment
template <class Base>
void SSLWrap<Base>::SetMaxSendFragment(
+1
View File
@@ -187,6 +187,7 @@ class SSLWrap {
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment(
+95
View File
@@ -0,0 +1,95 @@
// 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 assert = require('assert');
var fs = require('fs');
var net = require('net');
var tls = require('tls');
var crypto = require('crypto');
var common = require('../common');
var keys = crypto.randomBytes(48);
var serverLog = [];
var ticketLog = [];
var serverCount = 0;
function createServer() {
var id = serverCount++;
var server = tls.createServer({
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'),
ticketKeys: keys
}, function(c) {
serverLog.push(id);
c.end();
});
return server;
}
var servers = [ createServer(), createServer(), createServer(), createServer(), createServer(), createServer() ];
// Create one TCP server and balance sockets to multiple TLS server instances
var shared = net.createServer(function(c) {
servers.shift().emit('connection', c);
}).listen(common.PORT, function() {
start(function() {
shared.close();
});
});
function start(callback) {
var sess = null;
var left = servers.length;
function connect() {
var s = tls.connect(common.PORT, {
session: sess,
rejectUnauthorized: false
}, function() {
sess = s.getSession() || sess;
ticketLog.push(s.getTLSTicket().toString('hex'));
});
s.on('close', function() {
if (--left === 0)
callback();
else
connect();
});
}
connect();
}
process.on('exit', function() {
assert.equal(ticketLog.length, serverLog.length);
for (var i = 0; i < serverLog.length - 1; i++) {
assert.notEqual(serverLog[i], serverLog[i + 1]);
assert.equal(ticketLog[i], ticketLog[i + 1]);
}
});