Browse Source

tls: introduce asynchronous `newSession`

fix #7105
v0.11.12-release
Fedor Indutny 11 years ago
parent
commit
75ea11fc08
  1. 5
      doc/api/tls.markdown
  2. 22
      lib/_tls_legacy.js
  3. 29
      lib/_tls_wrap.js
  4. 1
      src/env.h
  5. 19
      src/node_crypto.cc
  6. 8
      src/node_crypto.h
  7. 10
      src/tls_wrap.cc
  8. 10
      src/tls_wrap.h
  9. 6
      test/simple/test-tls-session-cache.js

5
doc/api/tls.markdown

@ -484,10 +484,11 @@ established - it will be forwarded here.
### Event: 'newSession' ### Event: 'newSession'
`function (sessionId, sessionData) { }` `function (sessionId, sessionData, callback) { }`
Emitted on creation of TLS session. May be used to store sessions in external 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 NOTE: adding this event listener will have an effect only on connections
established after addition of event listener. established after addition of event listener.

22
lib/_tls_legacy.js

@ -653,7 +653,27 @@ function onclienthello(hello) {
function onnewsession(key, session) { function onnewsession(key, session) {
if (!this.server) return; 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);
} }

29
lib/_tls_wrap.js

@ -138,8 +138,25 @@ function onclienthello(hello) {
function onnewsession(key, session) { function onnewsession(key, session) {
if (this.server) if (!this.server)
this.server.emit('newSession', key, session); 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._tlsOptions = options;
this._secureEstablished = false; this._secureEstablished = false;
this._securePending = false;
this._newSessionPending = false;
this._controlReleased = false; this._controlReleased = false;
this._SNICallback = null; this._SNICallback = null;
this.ssl = null; this.ssl = null;
@ -347,6 +366,12 @@ TLSSocket.prototype._releaseControl = function() {
}; };
TLSSocket.prototype._finishInit = function() { TLSSocket.prototype._finishInit = function() {
// `newSession` callback wasn't called yet
if (this._newSessionPending) {
this._securePending = true;
return;
}
if (process.features.tls_npn) { if (process.features.tls_npn) {
this.npnProtocol = this.ssl.getNegotiatedProtocol(); this.npnProtocol = this.ssl.getNegotiatedProtocol();
} }

1
src/env.h

@ -121,6 +121,7 @@ namespace node {
V(onhandshakestart_string, "onhandshakestart") \ V(onhandshakestart_string, "onhandshakestart") \
V(onmessage_string, "onmessage") \ V(onmessage_string, "onmessage") \
V(onnewsession_string, "onnewsession") \ V(onnewsession_string, "onnewsession") \
V(onnewsessiondone_string, "onnewsessiondone") \
V(onread_string, "onread") \ V(onread_string, "onread") \
V(onselect_string, "onselect") \ V(onselect_string, "onselect") \
V(onsignal_string, "onsignal") \ V(onsignal_string, "onsignal") \

19
src/node_crypto.cc

@ -857,6 +857,7 @@ void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> t) {
NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate); NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate);
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown); NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket); NODE_SET_PROTOTYPE_METHOD(t, "getTLSTicket", GetTLSTicket);
NODE_SET_PROTOTYPE_METHOD(t, "newSessionDone", NewSessionDone);
#ifdef SSL_set_max_send_fragment #ifdef SSL_set_max_send_fragment
NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment); NODE_SET_PROTOTYPE_METHOD(t, "setMaxSendFragment", SetMaxSendFragment);
@ -929,6 +930,7 @@ int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
reinterpret_cast<char*>(sess->session_id), reinterpret_cast<char*>(sess->session_id),
sess->session_id_length); sess->session_id_length);
Local<Value> argv[] = { session, buff }; Local<Value> argv[] = { session, buff };
w->new_session_wait_ = true;
w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv); w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv);
return 0; return 0;
@ -1267,6 +1269,16 @@ void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
} }
template <class Base>
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
Base* w = Unwrap<Base>(args.This());
w->new_session_wait_ = false;
w->NewSessionDoneCb();
}
#ifdef SSL_set_max_send_fragment #ifdef SSL_set_max_send_fragment
template <class Base> template <class Base>
void SSLWrap<Base>::SetMaxSendFragment( void SSLWrap<Base>::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<Object> target) { void Connection::Initialize(Environment* env, Handle<Object> target) {
Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New); Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New);
t->InstanceTemplate()->SetInternalFieldCount(1); t->InstanceTemplate()->SetInternalFieldCount(1);

8
src/node_crypto.h

@ -137,7 +137,8 @@ class SSLWrap {
: env_(env), : env_(env),
kind_(kind), kind_(kind),
next_sess_(NULL), next_sess_(NULL),
session_callbacks_(false) { session_callbacks_(false),
new_session_wait_(false) {
ssl_ = SSL_new(sc->ctx_); ssl_ = SSL_new(sc->ctx_);
assert(ssl_ != NULL); assert(ssl_ != NULL);
} }
@ -162,6 +163,7 @@ class SSLWrap {
inline void enable_session_callbacks() { session_callbacks_ = true; } inline void enable_session_callbacks() { session_callbacks_ = true; }
inline bool is_server() const { return kind_ == kServer; } inline bool is_server() const { return kind_ == kServer; }
inline bool is_client() const { return kind_ == kClient; } inline bool is_client() const { return kind_ == kClient; }
inline bool is_waiting_new_session() const { return new_session_wait_; }
protected: protected:
static void InitNPN(SecureContext* sc, Base* base); static void InitNPN(SecureContext* sc, Base* base);
@ -188,6 +190,7 @@ class SSLWrap {
static void Renegotiate(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 Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
static void NewSessionDone(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_set_max_send_fragment #ifdef SSL_set_max_send_fragment
static void SetMaxSendFragment( static void SetMaxSendFragment(
@ -219,6 +222,7 @@ class SSLWrap {
SSL_SESSION* next_sess_; SSL_SESSION* next_sess_;
SSL* ssl_; SSL* ssl_;
bool session_callbacks_; bool session_callbacks_;
bool new_session_wait_;
ClientHelloParser hello_parser_; ClientHelloParser hello_parser_;
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
@ -291,6 +295,7 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
void ClearError(); void ClearError();
void SetShutdownFlags(); void SetShutdownFlags();
void NewSessionDoneCb();
Connection(Environment* env, Connection(Environment* env,
v8::Local<v8::Object> wrap, v8::Local<v8::Object> wrap,
@ -319,6 +324,7 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
friend class ClientHelloParser; friend class ClientHelloParser;
friend class SecureContext; friend class SecureContext;
friend class SSLWrap<Connection>;
}; };
class CipherBase : public BaseObject { class CipherBase : public BaseObject {

10
src/tls_wrap.cc

@ -81,6 +81,7 @@ TLSCallbacks::TLSCallbacks(Environment* env,
established_(false), established_(false),
shutdown_(false), shutdown_(false),
error_(NULL), error_(NULL),
cycle_depth_(0),
eof_(false) { eof_(false) {
node::Wrap<TLSCallbacks>(object(), this); node::Wrap<TLSCallbacks>(object(), this);
@ -158,6 +159,11 @@ bool TLSCallbacks::InvokeQueued(int status) {
} }
void TLSCallbacks::NewSessionDoneCb() {
Cycle();
}
void TLSCallbacks::InitSSL() { void TLSCallbacks::InitSSL() {
// Initialize SSL // Initialize SSL
enc_in_ = NodeBIO::New(); enc_in_ = NodeBIO::New();
@ -309,6 +315,10 @@ void TLSCallbacks::EncOut() {
if (write_size_ != 0) if (write_size_ != 0)
return; return;
// Wait for `newSession` callback to be invoked
if (is_waiting_new_session())
return;
// Split-off queue // Split-off queue
if (established_ && !QUEUE_EMPTY(&write_item_queue_)) if (established_ && !QUEUE_EMPTY(&write_item_queue_))
MakePending(); MakePending();

10
src/tls_wrap.h

@ -102,12 +102,19 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
void ClearOut(); void ClearOut();
void MakePending(); void MakePending();
bool InvokeQueued(int status); bool InvokeQueued(int status);
void NewSessionDoneCb();
inline void Cycle() { inline void Cycle() {
// Prevent recursion
if (++cycle_depth_ > 1)
return;
for (; cycle_depth_ > 0; cycle_depth_--) {
ClearIn(); ClearIn();
ClearOut(); ClearOut();
EncOut(); EncOut();
} }
}
v8::Local<v8::Value> GetSSLError(int status, int* err, const char** msg); v8::Local<v8::Value> GetSSLError(int status, int* err, const char** msg);
const char* PrintErrors(); const char* PrintErrors();
@ -144,6 +151,7 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
bool established_; bool established_;
bool shutdown_; bool shutdown_;
const char* error_; const char* error_;
int cycle_depth_;
// If true - delivered EOF to the js-land, either after `close_notify`, or // If true - delivered EOF to the js-land, either after `close_notify`, or
// after the `UV_EOF` on socket. // after the `UV_EOF` on socket.
@ -155,6 +163,8 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
static size_t error_off_; static size_t error_off_;
static char error_buf_[1024]; static char error_buf_[1024];
friend class SSLWrap<TLSCallbacks>;
}; };
} // namespace node } // namespace node

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

@ -64,12 +64,16 @@ function doTest(testOptions, callback) {
++requestCount; ++requestCount;
cleartext.end(); cleartext.end();
}); });
server.on('newSession', function(id, data) { server.on('newSession', function(id, data, cb) {
// Emulate asynchronous store
setTimeout(function() {
assert.ok(!session); assert.ok(!session);
session = { session = {
id: id, id: id,
data: data data: data
}; };
cb();
}, 1000);
}); });
server.on('resumeSession', function(id, callback) { server.on('resumeSession', function(id, callback) {
++resumeCount; ++resumeCount;

Loading…
Cancel
Save