Skip to content

Commit d62c2d9

Browse files
committed
tls: share tls tickets key between cluster workers
fix nodejs#5871
1 parent 509cfbc commit d62c2d9

4 files changed

Lines changed: 187 additions & 0 deletions

File tree

lib/_tls_wrap.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ function Server(/* [options], listener */) {
352352
crl: self.crl,
353353
sessionIdContext: self.sessionIdContext
354354
});
355+
this._sharedCreds = sharedCreds;
355356

356357
var timeout = options.handshakeTimeout || (120 * 1000);
357358

@@ -363,6 +364,10 @@ function Server(/* [options], listener */) {
363364
sharedCreds.context.setSessionTimeout(self.sessionTimeout);
364365
}
365366

367+
if (self.ticketKeys) {
368+
sharedCreds.context.setTicketKeys(self.ticketKeys);
369+
}
370+
366371
// constructor call
367372
net.Server.call(this, function(raw_socket) {
368373
var socket = new TLSSocket(raw_socket, {
@@ -422,6 +427,18 @@ exports.createServer = function(options, listener) {
422427
};
423428

424429

430+
Server.prototype._getServerData = function() {
431+
return {
432+
ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex')
433+
};
434+
};
435+
436+
437+
Server.prototype._setServerData = function(data) {
438+
this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex'));
439+
};
440+
441+
425442
Server.prototype.setOptions = function(options) {
426443
if (IS_BOOLEAN(options.requestCert)) {
427444
this.requestCert = options.requestCert;

src/node_crypto.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ void SecureContext::Initialize(Handle<Object> target) {
193193
SecureContext::SetSessionTimeout);
194194
NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close);
195195
NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12);
196+
NODE_SET_PROTOTYPE_METHOD(t, "getTicketKeys", SecureContext::GetTicketKeys);
197+
NODE_SET_PROTOTYPE_METHOD(t, "setTicketKeys", SecureContext::SetTicketKeys);
196198

197199
target->Set(String::NewSymbol("SecureContext"), t->GetFunction());
198200
}
@@ -750,6 +752,47 @@ void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
750752
}
751753

752754

755+
void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
756+
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
757+
HandleScope scope(node_isolate);
758+
759+
UNWRAP(SecureContext);
760+
761+
Local<Object> buff = Buffer::New(48);
762+
if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
763+
Buffer::Data(buff),
764+
Buffer::Length(buff)) != 1) {
765+
return ThrowError("Failed to fetch tls ticket keys");
766+
}
767+
768+
args.GetReturnValue().Set(buff);
769+
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
770+
}
771+
772+
773+
void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
774+
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
775+
HandleScope scope(node_isolate);
776+
777+
if (args.Length() < 1 ||
778+
!Buffer::HasInstance(args[0]) ||
779+
Buffer::Length(args[0]) != 48) {
780+
return ThrowTypeError("Bad argument");
781+
}
782+
783+
UNWRAP(SecureContext);
784+
785+
if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_,
786+
Buffer::Data(args[0]),
787+
Buffer::Length(args[0])) != 1) {
788+
return ThrowError("Failed to fetch tls ticket keys");
789+
}
790+
791+
args.GetReturnValue().Set(true);
792+
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
793+
}
794+
795+
753796
size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
754797
HandleScope scope(node_isolate);
755798

src/node_crypto.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class SecureContext : ObjectWrap {
7979
const v8::FunctionCallbackInfo<v8::Value>& args);
8080
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
8181
static void LoadPKCS12(const v8::FunctionCallbackInfo<v8::Value>& args);
82+
static void GetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
83+
static void SetTicketKeys(const v8::FunctionCallbackInfo<v8::Value>& args);
8284

8385
static SSL_SESSION* GetSessionCallback(SSL* s,
8486
unsigned char* key,
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
if (!process.versions.openssl) {
23+
console.error('Skipping because node compiled without OpenSSL.');
24+
process.exit(0);
25+
}
26+
27+
var common = require('../common');
28+
var assert = require('assert');
29+
var cluster = require('cluster');
30+
var tls = require('tls');
31+
var fs = require('fs');
32+
var join = require('path').join;
33+
34+
var workerCount = 4;
35+
var expectedReqCount = 16;
36+
37+
if (cluster.isMaster) {
38+
var reusedCount = 0;
39+
var reqCount = 0;
40+
var lastSession = null;
41+
var shootOnce = false;
42+
43+
function shoot() {
44+
console.error('[master] connecting');
45+
var c = tls.connect(common.PORT, {
46+
session: lastSession,
47+
rejectUnauthorized: false
48+
}, function() {
49+
lastSession = c.getSession();
50+
c.end();
51+
52+
if (++reqCount === expectedReqCount) {
53+
Object.keys(cluster.workers).forEach(function(id) {
54+
cluster.workers[id].send('die');
55+
});
56+
} else {
57+
shoot();
58+
}
59+
});
60+
}
61+
62+
function fork() {
63+
var worker = cluster.fork();
64+
var workerReqCount = 0;
65+
worker.on('message', function(msg) {
66+
console.error('[master] got %j', msg);
67+
if (msg === 'reused') {
68+
++reusedCount;
69+
} else if (msg === 'listening' && !shootOnce) {
70+
shootOnce = true;
71+
shoot();
72+
}
73+
});
74+
75+
worker.on('exit', function() {
76+
console.error('[master] worker died');
77+
});
78+
}
79+
for (var i = 0; i < workerCount; i++) {
80+
fork();
81+
}
82+
83+
process.on('exit', function() {
84+
assert.equal(reqCount, expectedReqCount);
85+
assert.equal(reusedCount + 1, reqCount);
86+
});
87+
return;
88+
}
89+
90+
var keyFile = join(common.fixturesDir, 'agent.key');
91+
var certFile = join(common.fixturesDir, 'agent.crt');
92+
var key = fs.readFileSync(keyFile);
93+
var cert = fs.readFileSync(certFile);
94+
var options = {
95+
key: key,
96+
cert: cert
97+
};
98+
99+
var server = tls.createServer(options, function(c) {
100+
if (c.isSessionReused()) {
101+
process.send('reused');
102+
} else {
103+
process.send('not-reused');
104+
}
105+
c.end();
106+
});
107+
108+
server.listen(common.PORT, function() {
109+
process.send('listening');
110+
});
111+
112+
process.on('message', function listener(msg) {
113+
console.error('[worker] got %j', msg);
114+
if (msg === 'die') {
115+
server.close(function() {
116+
console.error('[worker] server close');
117+
118+
process.exit();
119+
});
120+
}
121+
});
122+
123+
process.on('exit', function() {
124+
console.error('[worker] exit');
125+
});

0 commit comments

Comments
 (0)