From 75ea11fc08019bb1ffac81583ed7d0da3241a5b5 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Fri, 14 Feb 2014 17:01:34 +0400 Subject: [PATCH] tls: introduce asynchronous `newSession` fix #7105 --- doc/api/tls.markdown | 5 +++-- lib/_tls_legacy.js | 22 +++++++++++++++++++- lib/_tls_wrap.js | 29 +++++++++++++++++++++++++-- src/env.h | 1 + src/node_crypto.cc | 19 ++++++++++++++++++ src/node_crypto.h | 8 +++++++- src/tls_wrap.cc | 10 +++++++++ src/tls_wrap.h | 16 ++++++++++++--- test/simple/test-tls-session-cache.js | 16 +++++++++------ 9 files changed, 111 insertions(+), 15 deletions(-) diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 2a4c312b503c92..372bbd990fea37 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -484,10 +484,11 @@ established - it will be forwarded here. ### Event: 'newSession' -`function (sessionId, sessionData) { }` +`function (sessionId, sessionData, callback) { }` Emitted on creation of TLS session. May be used to store sessions in external -storage. +storage. `callback` must be invoked eventually, otherwise no data will be +sent or received from secure connection. NOTE: adding this event listener will have an effect only on connections established after addition of event listener. diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index be3c8aff55b961..6916c1e0d717af 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -653,7 +653,27 @@ function onclienthello(hello) { function onnewsession(key, session) { if (!this.server) return; - this.server.emit('newSession', key, session); + + var self = this; + var once = false; + + self.server.emit('newSession', key, session, function() { + if (once) + return; + once = true; + + if (self.ssl) + self.ssl.newSessionDone(); + }); +} + + +function onnewsessiondone() { + if (!this.server) return; + + // Cycle through data + this.cleartext.read(0); + this.encrypted.read(0); } diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 3cda50d2d5b3a4..c0af83a7ef0f51 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -138,8 +138,25 @@ function onclienthello(hello) { function onnewsession(key, session) { - if (this.server) - this.server.emit('newSession', key, session); + if (!this.server) + return; + + var self = this; + var once = false; + + this._newSessionPending = true; + this.server.emit('newSession', key, session, function() { + if (once) + return; + once = true; + + self.ssl.newSessionDone(); + + self._newSessionPending = false; + if (self._securePending) + self._finishInit(); + self._securePending = false; + }); } @@ -164,6 +181,8 @@ function TLSSocket(socket, options) { this._tlsOptions = options; this._secureEstablished = false; + this._securePending = false; + this._newSessionPending = false; this._controlReleased = false; this._SNICallback = null; this.ssl = null; @@ -347,6 +366,12 @@ TLSSocket.prototype._releaseControl = function() { }; TLSSocket.prototype._finishInit = function() { + // `newSession` callback wasn't called yet + if (this._newSessionPending) { + this._securePending = true; + return; + } + if (process.features.tls_npn) { this.npnProtocol = this.ssl.getNegotiatedProtocol(); } diff --git a/src/env.h b/src/env.h index f1cd4a125fe050..99f0e2092bfb4b 100644 --- a/src/env.h +++ b/src/env.h @@ -121,6 +121,7 @@ namespace node { V(onhandshakestart_string, "onhandshakestart") \ V(onmessage_string, "onmessage") \ V(onnewsession_string, "onnewsession") \ + V(onnewsessiondone_string, "onnewsessiondone") \ V(onread_string, "onread") \ V(onselect_string, "onselect") \ V(onsignal_string, "onsignal") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 1d5fd25c089c06..d0a26a605c9bf8 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -857,6 +857,7 @@ void SSLWrap::AddMethods(Handle t) { NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate); NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown); NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket); + NODE_SET_PROTOTYPE_METHOD(t, "newSessionDone", NewSessionDone); #ifdef SSL_set_max_send_fragment NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment); @@ -929,6 +930,7 @@ int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { reinterpret_cast(sess->session_id), sess->session_id_length); Local argv[] = { session, buff }; + w->new_session_wait_ = true; w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv); return 0; @@ -1267,6 +1269,16 @@ void SSLWrap::GetTLSTicket(const FunctionCallbackInfo& args) { } +template +void SSLWrap::NewSessionDone(const FunctionCallbackInfo& args) { + HandleScope scope(args.GetIsolate()); + + Base* w = Unwrap(args.This()); + w->new_session_wait_ = false; + w->NewSessionDoneCb(); +} + + #ifdef SSL_set_max_send_fragment template void SSLWrap::SetMaxSendFragment( @@ -1651,6 +1663,13 @@ void Connection::SetShutdownFlags() { } +void Connection::NewSessionDoneCb() { + HandleScope scope(env()->isolate()); + + MakeCallback(env()->onnewsessiondone_string(), 0, NULL); +} + + void Connection::Initialize(Environment* env, Handle target) { Local t = FunctionTemplate::New(Connection::New); t->InstanceTemplate()->SetInternalFieldCount(1); diff --git a/src/node_crypto.h b/src/node_crypto.h index 889d270c5caeeb..729d4fc7f4aff3 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -137,7 +137,8 @@ class SSLWrap { : env_(env), kind_(kind), next_sess_(NULL), - session_callbacks_(false) { + session_callbacks_(false), + new_session_wait_(false) { ssl_ = SSL_new(sc->ctx_); assert(ssl_ != NULL); } @@ -162,6 +163,7 @@ class SSLWrap { inline void enable_session_callbacks() { session_callbacks_ = true; } inline bool is_server() const { return kind_ == kServer; } inline bool is_client() const { return kind_ == kClient; } + inline bool is_waiting_new_session() const { return new_session_wait_; } protected: static void InitNPN(SecureContext* sc, Base* base); @@ -188,6 +190,7 @@ class SSLWrap { static void Renegotiate(const v8::FunctionCallbackInfo& args); static void Shutdown(const v8::FunctionCallbackInfo& args); static void GetTLSTicket(const v8::FunctionCallbackInfo& args); + static void NewSessionDone(const v8::FunctionCallbackInfo& args); #ifdef SSL_set_max_send_fragment static void SetMaxSendFragment( @@ -219,6 +222,7 @@ class SSLWrap { SSL_SESSION* next_sess_; SSL* ssl_; bool session_callbacks_; + bool new_session_wait_; ClientHelloParser hello_parser_; #ifdef OPENSSL_NPN_NEGOTIATED @@ -291,6 +295,7 @@ class Connection : public SSLWrap, public AsyncWrap { void ClearError(); void SetShutdownFlags(); + void NewSessionDoneCb(); Connection(Environment* env, v8::Local wrap, @@ -319,6 +324,7 @@ class Connection : public SSLWrap, public AsyncWrap { friend class ClientHelloParser; friend class SecureContext; + friend class SSLWrap; }; class CipherBase : public BaseObject { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 4e5b07f7d55ced..0e63444d7f9961 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -81,6 +81,7 @@ TLSCallbacks::TLSCallbacks(Environment* env, established_(false), shutdown_(false), error_(NULL), + cycle_depth_(0), eof_(false) { node::Wrap(object(), this); @@ -158,6 +159,11 @@ bool TLSCallbacks::InvokeQueued(int status) { } +void TLSCallbacks::NewSessionDoneCb() { + Cycle(); +} + + void TLSCallbacks::InitSSL() { // Initialize SSL enc_in_ = NodeBIO::New(); @@ -309,6 +315,10 @@ void TLSCallbacks::EncOut() { if (write_size_ != 0) return; + // Wait for `newSession` callback to be invoked + if (is_waiting_new_session()) + return; + // Split-off queue if (established_ && !QUEUE_EMPTY(&write_item_queue_)) MakePending(); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 946cc1c64d9cc6..646e70108fc26c 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -102,11 +102,18 @@ class TLSCallbacks : public crypto::SSLWrap, void ClearOut(); void MakePending(); bool InvokeQueued(int status); + void NewSessionDoneCb(); inline void Cycle() { - ClearIn(); - ClearOut(); - EncOut(); + // Prevent recursion + if (++cycle_depth_ > 1) + return; + + for (; cycle_depth_ > 0; cycle_depth_--) { + ClearIn(); + ClearOut(); + EncOut(); + } } v8::Local GetSSLError(int status, int* err, const char** msg); @@ -144,6 +151,7 @@ class TLSCallbacks : public crypto::SSLWrap, bool established_; bool shutdown_; const char* error_; + int cycle_depth_; // If true - delivered EOF to the js-land, either after `close_notify`, or // after the `UV_EOF` on socket. @@ -155,6 +163,8 @@ class TLSCallbacks : public crypto::SSLWrap, static size_t error_off_; static char error_buf_[1024]; + + friend class SSLWrap; }; } // namespace node diff --git a/test/simple/test-tls-session-cache.js b/test/simple/test-tls-session-cache.js index 46193657529566..a60ff949c4ad28 100644 --- a/test/simple/test-tls-session-cache.js +++ b/test/simple/test-tls-session-cache.js @@ -64,12 +64,16 @@ function doTest(testOptions, callback) { ++requestCount; cleartext.end(); }); - server.on('newSession', function(id, data) { - assert.ok(!session); - session = { - id: id, - data: data - }; + server.on('newSession', function(id, data, cb) { + // Emulate asynchronous store + setTimeout(function() { + assert.ok(!session); + session = { + id: id, + data: data + }; + cb(); + }, 1000); }); server.on('resumeSession', function(id, callback) { ++resumeCount;