Skip to content

Commit eb99083

Browse files
Sean Cunninghambnoordhuis
authored andcommitted
tls: add client-side session resumption support
1 parent b66d225 commit eb99083

4 files changed

Lines changed: 186 additions & 0 deletions

File tree

lib/tls.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ CryptoStream.prototype.getPeerCertificate = function() {
184184
return null;
185185
};
186186

187+
CryptoStream.prototype.getSession = function() {
188+
if (this.pair.ssl) {
189+
return this.pair.ssl.getSession();
190+
}
191+
192+
return null;
193+
};
194+
195+
CryptoStream.prototype.isSessionReused = function() {
196+
if (this.pair.ssl) {
197+
return this.pair.ssl.isSessionReused();
198+
}
199+
200+
return null;
201+
};
187202

188203
CryptoStream.prototype.getCipher = function(err) {
189204
if (this.pair.ssl) {
@@ -956,6 +971,10 @@ exports.connect = function(port /* host, options, cb */) {
956971
servername: options.servername || host
957972
});
958973

974+
if (options.session) {
975+
pair.ssl.setSession(options.session);
976+
}
977+
959978
var cleartext = pipe(pair, socket);
960979

961980
socket.connect(port, host);

src/node_crypto.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,9 @@ void Connection::Initialize(Handle<Object> target) {
588588
NODE_SET_PROTOTYPE_METHOD(t, "clearPending", Connection::ClearPending);
589589
NODE_SET_PROTOTYPE_METHOD(t, "encPending", Connection::EncPending);
590590
NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", Connection::GetPeerCertificate);
591+
NODE_SET_PROTOTYPE_METHOD(t, "getSession", Connection::GetSession);
592+
NODE_SET_PROTOTYPE_METHOD(t, "setSession", Connection::SetSession);
593+
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", Connection::IsSessionReused);
591594
NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", Connection::IsInitFinished);
592595
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", Connection::VerifyError);
593596
NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", Connection::GetCurrentCipher);
@@ -1175,6 +1178,91 @@ Handle<Value> Connection::GetPeerCertificate(const Arguments& args) {
11751178
return scope.Close(info);
11761179
}
11771180

1181+
Handle<Value> Connection::GetSession(const Arguments& args) {
1182+
HandleScope scope;
1183+
1184+
Connection *ss = Connection::Unwrap(args);
1185+
1186+
if (ss->ssl_ == NULL) return Undefined();
1187+
1188+
SSL_SESSION* sess = SSL_get_session(ss->ssl_);
1189+
if (!sess) return Undefined();
1190+
1191+
int slen = i2d_SSL_SESSION(sess, NULL);
1192+
assert(slen > 0);
1193+
1194+
Local<Value> s;
1195+
1196+
if (slen > 0) {
1197+
void* pp = malloc(slen);
1198+
if (pp)
1199+
{
1200+
unsigned char* p = (unsigned char*)pp;
1201+
i2d_SSL_SESSION(sess, &p);
1202+
s = Encode(pp, slen, BINARY);
1203+
free(pp);
1204+
}
1205+
else
1206+
return False();
1207+
}
1208+
else
1209+
return False();
1210+
1211+
return scope.Close(s);
1212+
}
1213+
1214+
Handle<Value> Connection::SetSession(const Arguments& args) {
1215+
HandleScope scope;
1216+
1217+
Connection *ss = Connection::Unwrap(args);
1218+
1219+
if (args.Length() < 1 || !args[0]->IsString()) {
1220+
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
1221+
return ThrowException(exception);
1222+
}
1223+
1224+
ASSERT_IS_STRING_OR_BUFFER(args[0]);
1225+
ssize_t slen = DecodeBytes(args[0], BINARY);
1226+
1227+
if (slen < 0) {
1228+
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
1229+
return ThrowException(exception);
1230+
}
1231+
1232+
char* sbuf = new char[slen];
1233+
1234+
ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY);
1235+
assert(wlen == slen);
1236+
1237+
const unsigned char* p = (unsigned char*) sbuf;
1238+
SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen);
1239+
1240+
delete [] sbuf;
1241+
1242+
if (!sess)
1243+
return Undefined();
1244+
1245+
int r = SSL_set_session(ss->ssl_, sess);
1246+
SSL_SESSION_free(sess);
1247+
1248+
if (!r) {
1249+
Local<String> eStr = String::New("SSL_set_session error");
1250+
return ThrowException(Exception::Error(eStr));
1251+
}
1252+
1253+
return True();
1254+
}
1255+
1256+
Handle<Value> Connection::IsSessionReused(const Arguments& args) {
1257+
HandleScope scope;
1258+
1259+
Connection *ss = Connection::Unwrap(args);
1260+
1261+
if (ss->ssl_ == NULL) return False();
1262+
return SSL_session_reused(ss->ssl_) ? True() : False();
1263+
}
1264+
1265+
11781266
Handle<Value> Connection::Start(const Arguments& args) {
11791267
HandleScope scope;
11801268

src/node_crypto.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ class Connection : ObjectWrap {
120120
static v8::Handle<v8::Value> EncOut(const v8::Arguments& args);
121121
static v8::Handle<v8::Value> ClearIn(const v8::Arguments& args);
122122
static v8::Handle<v8::Value> GetPeerCertificate(const v8::Arguments& args);
123+
static v8::Handle<v8::Value> GetSession(const v8::Arguments& args);
124+
static v8::Handle<v8::Value> SetSession(const v8::Arguments& args);
125+
static v8::Handle<v8::Value> IsSessionReused(const v8::Arguments& args);
123126
static v8::Handle<v8::Value> IsInitFinished(const v8::Arguments& args);
124127
static v8::Handle<v8::Value> VerifyError(const v8::Arguments& args);
125128
static v8::Handle<v8::Value> GetCurrentCipher(const v8::Arguments& args);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
// Create an ssl server. First connection, validate that not resume.
23+
// Cache session and close connection. Use session on second connection.
24+
// ASSERT resumption.
25+
26+
if (!process.versions.openssl) {
27+
console.error("Skipping because node compiled without OpenSSL.");
28+
process.exit(0);
29+
}
30+
31+
var common = require('../common');
32+
var assert = require('assert');
33+
var tls = require('tls');
34+
var fs = require('fs');
35+
36+
var options = {
37+
key: fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'),
38+
cert: fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem')
39+
};
40+
41+
var connections = 0;
42+
43+
// create server
44+
var server = tls.Server(options, function(socket) {
45+
socket.end("Goodbye");
46+
connections++;
47+
});
48+
49+
// start listening
50+
server.listen(common.PORT, function() {
51+
52+
var session1 = null;
53+
var client1 = tls.connect(common.PORT, function () {
54+
console.log('connect1');
55+
assert.ok(!client1.isSessionReused(), "Session *should not* be reused.");
56+
session1 = client1.getSession();
57+
});
58+
59+
client1.on('close', function() {
60+
console.log('close1');
61+
62+
var client2 = tls.connect(common.PORT, {'session':session1}, function () {
63+
console.log('connect2');
64+
assert.ok(client2.isSessionReused(), "Session *should* be reused.");
65+
});
66+
67+
client2.on('close', function() {
68+
console.log('close2');
69+
server.close();
70+
});
71+
});
72+
});
73+
74+
process.on('exit', function() {
75+
assert.equal(2, connections);
76+
});

0 commit comments

Comments
 (0)