Skip to content

Commit 75ea11f

Browse files
committed
tls: introduce asynchronous newSession
fix nodejs#7105
1 parent a4436ba commit 75ea11f

File tree

9 files changed

+111
-15
lines changed

9 files changed

+111
-15
lines changed

doc/api/tls.markdown

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,11 @@ established - it will be forwarded here.
484484

485485
### Event: 'newSession'
486486

487-
`function (sessionId, sessionData) { }`
487+
`function (sessionId, sessionData, callback) { }`
488488

489489
Emitted on creation of TLS session. May be used to store sessions in external
490-
storage.
490+
storage. `callback` must be invoked eventually, otherwise no data will be
491+
sent or received from secure connection.
491492

492493
NOTE: adding this event listener will have an effect only on connections
493494
established after addition of event listener.

lib/_tls_legacy.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,27 @@ function onclienthello(hello) {
653653

654654
function onnewsession(key, session) {
655655
if (!this.server) return;
656-
this.server.emit('newSession', key, session);
656+
657+
var self = this;
658+
var once = false;
659+
660+
self.server.emit('newSession', key, session, function() {
661+
if (once)
662+
return;
663+
once = true;
664+
665+
if (self.ssl)
666+
self.ssl.newSessionDone();
667+
});
668+
}
669+
670+
671+
function onnewsessiondone() {
672+
if (!this.server) return;
673+
674+
// Cycle through data
675+
this.cleartext.read(0);
676+
this.encrypted.read(0);
657677
}
658678

659679

lib/_tls_wrap.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,25 @@ function onclienthello(hello) {
138138

139139

140140
function onnewsession(key, session) {
141-
if (this.server)
142-
this.server.emit('newSession', key, session);
141+
if (!this.server)
142+
return;
143+
144+
var self = this;
145+
var once = false;
146+
147+
this._newSessionPending = true;
148+
this.server.emit('newSession', key, session, function() {
149+
if (once)
150+
return;
151+
once = true;
152+
153+
self.ssl.newSessionDone();
154+
155+
self._newSessionPending = false;
156+
if (self._securePending)
157+
self._finishInit();
158+
self._securePending = false;
159+
});
143160
}
144161

145162

@@ -164,6 +181,8 @@ function TLSSocket(socket, options) {
164181

165182
this._tlsOptions = options;
166183
this._secureEstablished = false;
184+
this._securePending = false;
185+
this._newSessionPending = false;
167186
this._controlReleased = false;
168187
this._SNICallback = null;
169188
this.ssl = null;
@@ -347,6 +366,12 @@ TLSSocket.prototype._releaseControl = function() {
347366
};
348367

349368
TLSSocket.prototype._finishInit = function() {
369+
// `newSession` callback wasn't called yet
370+
if (this._newSessionPending) {
371+
this._securePending = true;
372+
return;
373+
}
374+
350375
if (process.features.tls_npn) {
351376
this.npnProtocol = this.ssl.getNegotiatedProtocol();
352377
}

src/env.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ namespace node {
121121
V(onhandshakestart_string, "onhandshakestart") \
122122
V(onmessage_string, "onmessage") \
123123
V(onnewsession_string, "onnewsession") \
124+
V(onnewsessiondone_string, "onnewsessiondone") \
124125
V(onread_string, "onread") \
125126
V(onselect_string, "onselect") \
126127
V(onsignal_string, "onsignal") \

src/node_crypto.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> t) {
857857
NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate);
858858
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
859859
NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket);
860+
NODE_SET_PROTOTYPE_METHOD(t, "newSessionDone", NewSessionDone);
860861

861862
#ifdef SSL_set_max_send_fragment
862863
NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment);
@@ -929,6 +930,7 @@ int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
929930
reinterpret_cast<char*>(sess->session_id),
930931
sess->session_id_length);
931932
Local<Value> argv[] = { session, buff };
933+
w->new_session_wait_ = true;
932934
w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv);
933935

934936
return 0;
@@ -1267,6 +1269,16 @@ void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
12671269
}
12681270

12691271

1272+
template <class Base>
1273+
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
1274+
HandleScope scope(args.GetIsolate());
1275+
1276+
Base* w = Unwrap<Base>(args.This());
1277+
w->new_session_wait_ = false;
1278+
w->NewSessionDoneCb();
1279+
}
1280+
1281+
12701282
#ifdef SSL_set_max_send_fragment
12711283
template <class Base>
12721284
void SSLWrap<Base>::SetMaxSendFragment(
@@ -1651,6 +1663,13 @@ void Connection::SetShutdownFlags() {
16511663
}
16521664

16531665

1666+
void Connection::NewSessionDoneCb() {
1667+
HandleScope scope(env()->isolate());
1668+
1669+
MakeCallback(env()->onnewsessiondone_string(), 0, NULL);
1670+
}
1671+
1672+
16541673
void Connection::Initialize(Environment* env, Handle<Object> target) {
16551674
Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New);
16561675
t->InstanceTemplate()->SetInternalFieldCount(1);

src/node_crypto.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ class SSLWrap {
137137
: env_(env),
138138
kind_(kind),
139139
next_sess_(NULL),
140-
session_callbacks_(false) {
140+
session_callbacks_(false),
141+
new_session_wait_(false) {
141142
ssl_ = SSL_new(sc->ctx_);
142143
assert(ssl_ != NULL);
143144
}
@@ -162,6 +163,7 @@ class SSLWrap {
162163
inline void enable_session_callbacks() { session_callbacks_ = true; }
163164
inline bool is_server() const { return kind_ == kServer; }
164165
inline bool is_client() const { return kind_ == kClient; }
166+
inline bool is_waiting_new_session() const { return new_session_wait_; }
165167

166168
protected:
167169
static void InitNPN(SecureContext* sc, Base* base);
@@ -188,6 +190,7 @@ class SSLWrap {
188190
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
189191
static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
190192
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
193+
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
191194

192195
#ifdef SSL_set_max_send_fragment
193196
static void SetMaxSendFragment(
@@ -219,6 +222,7 @@ class SSLWrap {
219222
SSL_SESSION* next_sess_;
220223
SSL* ssl_;
221224
bool session_callbacks_;
225+
bool new_session_wait_;
222226
ClientHelloParser hello_parser_;
223227

224228
#ifdef OPENSSL_NPN_NEGOTIATED
@@ -291,6 +295,7 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
291295

292296
void ClearError();
293297
void SetShutdownFlags();
298+
void NewSessionDoneCb();
294299

295300
Connection(Environment* env,
296301
v8::Local<v8::Object> wrap,
@@ -319,6 +324,7 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
319324

320325
friend class ClientHelloParser;
321326
friend class SecureContext;
327+
friend class SSLWrap<Connection>;
322328
};
323329

324330
class CipherBase : public BaseObject {

src/tls_wrap.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ TLSCallbacks::TLSCallbacks(Environment* env,
8181
established_(false),
8282
shutdown_(false),
8383
error_(NULL),
84+
cycle_depth_(0),
8485
eof_(false) {
8586
node::Wrap<TLSCallbacks>(object(), this);
8687

@@ -158,6 +159,11 @@ bool TLSCallbacks::InvokeQueued(int status) {
158159
}
159160

160161

162+
void TLSCallbacks::NewSessionDoneCb() {
163+
Cycle();
164+
}
165+
166+
161167
void TLSCallbacks::InitSSL() {
162168
// Initialize SSL
163169
enc_in_ = NodeBIO::New();
@@ -309,6 +315,10 @@ void TLSCallbacks::EncOut() {
309315
if (write_size_ != 0)
310316
return;
311317

318+
// Wait for `newSession` callback to be invoked
319+
if (is_waiting_new_session())
320+
return;
321+
312322
// Split-off queue
313323
if (established_ && !QUEUE_EMPTY(&write_item_queue_))
314324
MakePending();

src/tls_wrap.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,18 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
102102
void ClearOut();
103103
void MakePending();
104104
bool InvokeQueued(int status);
105+
void NewSessionDoneCb();
105106

106107
inline void Cycle() {
107-
ClearIn();
108-
ClearOut();
109-
EncOut();
108+
// Prevent recursion
109+
if (++cycle_depth_ > 1)
110+
return;
111+
112+
for (; cycle_depth_ > 0; cycle_depth_--) {
113+
ClearIn();
114+
ClearOut();
115+
EncOut();
116+
}
110117
}
111118

112119
v8::Local<v8::Value> GetSSLError(int status, int* err, const char** msg);
@@ -144,6 +151,7 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
144151
bool established_;
145152
bool shutdown_;
146153
const char* error_;
154+
int cycle_depth_;
147155

148156
// If true - delivered EOF to the js-land, either after `close_notify`, or
149157
// after the `UV_EOF` on socket.
@@ -155,6 +163,8 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
155163

156164
static size_t error_off_;
157165
static char error_buf_[1024];
166+
167+
friend class SSLWrap<TLSCallbacks>;
158168
};
159169

160170
} // namespace node

test/simple/test-tls-session-cache.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,16 @@ function doTest(testOptions, callback) {
6464
++requestCount;
6565
cleartext.end();
6666
});
67-
server.on('newSession', function(id, data) {
68-
assert.ok(!session);
69-
session = {
70-
id: id,
71-
data: data
72-
};
67+
server.on('newSession', function(id, data, cb) {
68+
// Emulate asynchronous store
69+
setTimeout(function() {
70+
assert.ok(!session);
71+
session = {
72+
id: id,
73+
data: data
74+
};
75+
cb();
76+
}, 1000);
7377
});
7478
server.on('resumeSession', function(id, callback) {
7579
++resumeCount;

0 commit comments

Comments
 (0)