summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFedor Indutny <fedor.indutny@gmail.com>2013-06-17 12:11:13 +0200
committerFedor Indutny <fedor.indutny@gmail.com>2013-06-17 14:00:26 +0200
commit212e9cd8c9b10da3c471638ca9b0f8ebef2a67bb (patch)
treed4ebca0c270b8b332a0b7da427453edf59198932 /src
parent0a4260c8c0deff518d0586c6b0484d459f9a79f4 (diff)
downloadnode-212e9cd8c9b10da3c471638ca9b0f8ebef2a67bb.tar.gz
tls: session API returns
Diffstat (limited to 'src')
-rw-r--r--src/node_crypto.h3
-rw-r--r--src/node_crypto_bio.h4
-rw-r--r--src/tls_wrap.cc265
-rw-r--r--src/tls_wrap.h41
4 files changed, 302 insertions, 11 deletions
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 2d5500a4e..c3c4a89a8 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -60,9 +60,10 @@ class SecureContext : ObjectWrap {
// TODO: ca_store_ should probably be removed, it's not used anywhere.
X509_STORE *ca_store_;
- protected:
static const int kMaxSessionSize = 10 * 1024;
+ protected:
+
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> Init(const v8::Arguments& args);
static v8::Handle<v8::Value> SetKey(const v8::Arguments& args);
diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h
index 4794453ef..dc45faee2 100644
--- a/src/node_crypto_bio.h
+++ b/src/node_crypto_bio.h
@@ -87,7 +87,9 @@ class NodeBIO {
}
protected:
- static const size_t kBufferLength = 16 * 1024;
+ // NOTE: Size is maximum TLS frame length, this is required if we want
+ // to fit whole ClientHello into one Buffer of NodeBIO.
+ static const size_t kBufferLength = 16 * 1024 + 5;
class Buffer {
public:
diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc
index 89d2790d7..c42898de8 100644
--- a/src/tls_wrap.cc
+++ b/src/tls_wrap.cc
@@ -36,6 +36,8 @@ static Persistent<String> onerror_sym;
static Persistent<String> onsniselect_sym;
static Persistent<String> onhandshakestart_sym;
static Persistent<String> onhandshakedone_sym;
+static Persistent<String> onclienthello_sym;
+static Persistent<String> onnewsession_sym;
static Persistent<String> subject_sym;
static Persistent<String> subjectaltname_sym;
static Persistent<String> modulus_sym;
@@ -47,6 +49,7 @@ static Persistent<String> fingerprint_sym;
static Persistent<String> name_sym;
static Persistent<String> version_sym;
static Persistent<String> ext_key_usage_sym;
+static Persistent<String> sessionid_sym;
static Persistent<Function> tlsWrap;
@@ -69,7 +72,9 @@ TLSCallbacks::TLSCallbacks(Kind kind,
pending_write_item_(NULL),
started_(false),
established_(false),
- shutdown_(false) {
+ shutdown_(false),
+ session_callbacks_(false),
+ next_sess_(NULL) {
// Persist SecureContext
sc_ = ObjectWrap::Unwrap<SecureContext>(sc);
@@ -78,17 +83,68 @@ TLSCallbacks::TLSCallbacks(Kind kind,
handle_ = Persistent<Object>::New(node_isolate, tlsWrap->NewInstance());
handle_->SetAlignedPointerInInternalField(0, this);
- // No session cache support
- SSL_CTX_sess_set_get_cb(sc_->ctx_, NULL);
- SSL_CTX_sess_set_new_cb(sc_->ctx_, NULL);
-
// Initialize queue for clearIn writes
QUEUE_INIT(&write_item_queue_);
+ // Initialize hello parser
+ hello_.state = kParseEnded;
+ hello_.frame_len = 0;
+ hello_.body_offset = 0;
+
+ // We've our own session callbacks
+ SSL_CTX_sess_set_get_cb(sc_->ctx_, GetSessionCallback);
+ SSL_CTX_sess_set_new_cb(sc_->ctx_, NewSessionCallback);
+
InitSSL();
}
+SSL_SESSION* TLSCallbacks::GetSessionCallback(SSL* s,
+ unsigned char* key,
+ int len,
+ int* copy) {
+ HandleScope scope(node_isolate);
+
+ TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(s));
+
+ *copy = 0;
+ SSL_SESSION* sess = c->next_sess_;
+ c->next_sess_ = NULL;
+
+ return sess;
+}
+
+
+int TLSCallbacks::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
+ HandleScope scope(node_isolate);
+
+ TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(s));
+ if (!c->session_callbacks_)
+ return 0;
+
+ // Check if session is small enough to be stored
+ int size = i2d_SSL_SESSION(sess, NULL);
+ if (size > SecureContext::kMaxSessionSize)
+ return 0;
+
+ // Serialize session
+ Local<Object> buff = Local<Object>::New(Buffer::New(size)->handle_);
+ unsigned char* serialized = reinterpret_cast<unsigned char*>(
+ Buffer::Data(buff));
+ memset(serialized, 0, size);
+ i2d_SSL_SESSION(sess, &serialized);
+
+ Local<Object> session = Local<Object>::New(
+ Buffer::New(reinterpret_cast<char*>(sess->session_id),
+ sess->session_id_length)->handle_);
+ Handle<Value> argv[2] = { session, buff };
+
+ MakeCallback(c->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv);
+
+ return 0;
+}
+
+
TLSCallbacks::~TLSCallbacks() {
SSL_free(ssl_);
ssl_ = NULL;
@@ -306,6 +362,10 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
void TLSCallbacks::EncOut() {
+ // Ignore cycling data if ClientHello wasn't yet parsed
+ if (hello_.state != kParseEnded)
+ return;
+
// Write in progress
if (write_size_ != 0)
return;
@@ -406,6 +466,10 @@ Handle<Value> TLSCallbacks::GetSSLError(int status, int* err) {
void TLSCallbacks::ClearOut() {
+ // Ignore cycling data if ClientHello wasn't yet parsed
+ if (hello_.state != kParseEnded)
+ return;
+
HandleScope scope(node_isolate);
assert(ssl_ != NULL);
@@ -436,6 +500,10 @@ void TLSCallbacks::ClearOut() {
bool TLSCallbacks::ClearIn() {
+ // Ignore cycling data if ClientHello wasn't yet parsed
+ if (hello_.state != kParseEnded)
+ return false;
+
HandleScope scope(node_isolate);
int written = 0;
@@ -569,10 +637,12 @@ void TLSCallbacks::DoRead(uv_stream_t* handle,
// Commit read data
NodeBIO::FromBIO(enc_in_)->Commit(nread);
- // Cycle OpenSSL state
- ClearIn();
- ClearOut();
- EncOut();
+ // Parse ClientHello first
+ if (hello_.state != kParseEnded)
+ return ParseClientHello();
+
+ // Cycle OpenSSL's state
+ Cycle();
}
@@ -585,6 +655,138 @@ int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) {
}
+void TLSCallbacks::ParseClientHello() {
+ enum FrameType {
+ kChangeCipherSpec = 20,
+ kAlert = 21,
+ kHandshake = 22,
+ kApplicationData = 23,
+ kOther = 255
+ };
+
+ enum HandshakeType {
+ kClientHello = 1
+ };
+
+ assert(session_callbacks_);
+ HandleScope scope(node_isolate);
+
+ NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_);
+
+ size_t avail = 0;
+ uint8_t* data = reinterpret_cast<uint8_t*>(enc_in->Peek(&avail));
+ assert(avail == 0 || data != NULL);
+
+ // Vars for parsing hello
+ bool is_clienthello = false;
+ uint8_t session_size = -1;
+ uint8_t* session_id = NULL;
+ Local<Object> hello_obj;
+ Handle<Value> argv[1];
+
+ switch (hello_.state) {
+ case kParseWaiting:
+ // >= 5 bytes for header parsing
+ if (avail < 5)
+ break;
+
+ if (data[0] == kChangeCipherSpec ||
+ data[0] == kAlert ||
+ data[0] == kHandshake ||
+ data[0] == kApplicationData) {
+ hello_.frame_len = (data[3] << 8) + data[4];
+ hello_.state = kParseTLSHeader;
+ hello_.body_offset = 5;
+ } else {
+ hello_.frame_len = (data[0] << 8) + data[1];
+ hello_.state = kParseSSLHeader;
+ if (*data & 0x40) {
+ // header with padding
+ hello_.body_offset = 3;
+ } else {
+ // without padding
+ hello_.body_offset = 2;
+ }
+ }
+
+ // Sanity check (too big frame, or too small)
+ // Let OpenSSL handle it
+ if (hello_.frame_len >= kMaxTLSFrameLen)
+ return ParseFinish();
+
+ // Fall through
+ case kParseTLSHeader:
+ case kParseSSLHeader:
+ // >= 5 + frame size bytes for frame parsing
+ if (avail < hello_.body_offset + hello_.frame_len)
+ break;
+
+ // Skip unsupported frames and gather some data from frame
+
+ // TODO(indutny): Check protocol version
+ if (data[hello_.body_offset] == kClientHello) {
+ is_clienthello = true;
+ uint8_t* body;
+ size_t session_offset;
+
+ if (hello_.state == kParseTLSHeader) {
+ // Skip frame header, hello header, protocol version and random data
+ session_offset = hello_.body_offset + 4 + 2 + 32;
+
+ if (session_offset + 1 < avail) {
+ body = data + session_offset;
+ session_size = *body;
+ session_id = body + 1;
+ }
+ } else if (hello_.state == kParseSSLHeader) {
+ // Skip header, version
+ session_offset = hello_.body_offset + 3;
+
+ if (session_offset + 4 < avail) {
+ body = data + session_offset;
+
+ int ciphers_size = (body[0] << 8) + body[1];
+
+ if (body + 4 + ciphers_size < data + avail) {
+ session_size = (body[2] << 8) + body[3];
+ session_id = body + 4 + ciphers_size;
+ }
+ }
+ } else {
+ // Whoa? How did we get here?
+ abort();
+ }
+
+ // Check if we overflowed (do not reply with any private data)
+ if (session_id == NULL ||
+ session_size > 32 ||
+ session_id + session_size > data + avail) {
+ return ParseFinish();
+ }
+
+ // TODO(indutny): Parse other things?
+ }
+
+ // Not client hello - let OpenSSL handle it
+ if (!is_clienthello)
+ return ParseFinish();
+
+ hello_.state = kParsePaused;
+ hello_obj = Object::New();
+ hello_obj->Set(sessionid_sym,
+ Buffer::New(reinterpret_cast<char*>(session_id),
+ session_size)->handle_);
+
+ argv[0] = hello_obj;
+ MakeCallback(handle_, onclienthello_sym, 1, argv);
+ break;
+ case kParseEnded:
+ default:
+ break;
+ }
+}
+
+
#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break;
Handle<Value> TLSCallbacks::VerifyError(const Arguments& args) {
HandleScope scope(node_isolate);
@@ -690,6 +892,20 @@ Handle<Value> TLSCallbacks::IsSessionReused(const Arguments& args) {
}
+Handle<Value> TLSCallbacks::EnableSessionCallbacks(const Arguments& args) {
+ HandleScope scope(node_isolate);
+
+ UNWRAP(TLSCallbacks);
+
+ wrap->session_callbacks_ = true;
+ wrap->hello_.state = kParseWaiting;
+ wrap->hello_.frame_len = 0;
+ wrap->hello_.body_offset = 0;
+
+ return scope.Close(Null(node_isolate));
+}
+
+
Handle<Value> TLSCallbacks::GetPeerCertificate(const Arguments& args) {
HandleScope scope(node_isolate);
@@ -879,6 +1095,30 @@ Handle<Value> TLSCallbacks::SetSession(const Arguments& args) {
}
+Handle<Value> TLSCallbacks::LoadSession(const Arguments& args) {
+ HandleScope scope(node_isolate);
+
+ UNWRAP(TLSCallbacks);
+
+ if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
+ ssize_t slen = Buffer::Length(args[0]);
+ char* sbuf = Buffer::Data(args[0]);
+
+ const unsigned char* p = reinterpret_cast<unsigned char*>(sbuf);
+ SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, slen);
+
+ // Setup next session and move hello to the BIO buffer
+ if (wrap->next_sess_ != NULL)
+ SSL_SESSION_free(wrap->next_sess_);
+ wrap->next_sess_ = sess;
+ }
+
+ wrap->ParseFinish();
+
+ return True(node_isolate);
+}
+
+
Handle<Value> TLSCallbacks::GetCurrentCipher(const Arguments& args) {
HandleScope scope(node_isolate);
@@ -1112,10 +1352,14 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", GetPeerCertificate);
NODE_SET_PROTOTYPE_METHOD(t, "getSession", GetSession);
NODE_SET_PROTOTYPE_METHOD(t, "setSession", SetSession);
+ NODE_SET_PROTOTYPE_METHOD(t, "loadSession", LoadSession);
NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher);
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError);
NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode);
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused);
+ NODE_SET_PROTOTYPE_METHOD(t,
+ "enableSessionCallbacks",
+ EnableSessionCallbacks);
#ifdef OPENSSL_NPN_NEGOTIATED
NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto);
@@ -1134,6 +1378,8 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
onerror_sym = NODE_PSYMBOL("onerror");
onhandshakestart_sym = NODE_PSYMBOL("onhandshakestart");
onhandshakedone_sym = NODE_PSYMBOL("onhandshakedone");
+ onclienthello_sym = NODE_PSYMBOL("onclienthello");
+ onnewsession_sym = NODE_PSYMBOL("onnewsession");
subject_sym = NODE_PSYMBOL("subject");
issuer_sym = NODE_PSYMBOL("issuer");
@@ -1146,6 +1392,7 @@ void TLSCallbacks::Initialize(Handle<Object> target) {
name_sym = NODE_PSYMBOL("name");
version_sym = NODE_PSYMBOL("version");
ext_key_usage_sym = NODE_PSYMBOL("ext_key_usage");
+ sessionid_sym = NODE_PSYMBOL("sessionId");
}
} // namespace node
diff --git a/src/tls_wrap.h b/src/tls_wrap.h
index 86507567b..b36a5b8d4 100644
--- a/src/tls_wrap.h
+++ b/src/tls_wrap.h
@@ -61,7 +61,24 @@ class TLSCallbacks : public StreamWrapCallbacks {
protected:
static const int kClearOutChunkSize = 1024;
+ static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
+
+ // ClientHello parser types
+ enum ParseState {
+ kParseWaiting,
+ kParseTLSHeader,
+ kParseSSLHeader,
+ kParsePaused,
+ kParseEnded
+ };
+
+ struct HelloState {
+ ParseState state;
+ size_t frame_len;
+ size_t body_offset;
+ };
+ // Write callback queue's item
class WriteItem {
public:
WriteItem(WriteWrap* w, uv_write_cb cb) : w_(w), cb_(cb) {
@@ -86,6 +103,18 @@ class TLSCallbacks : public StreamWrapCallbacks {
bool ClearIn();
void ClearOut();
void InvokeQueued(int status);
+ void ParseClientHello();
+
+ inline void ParseFinish() {
+ hello_.state = kParseEnded;
+ Cycle();
+ }
+
+ inline void Cycle() {
+ ClearIn();
+ ClearOut();
+ EncOut();
+ }
v8::Handle<v8::Value> GetSSLError(int status, int* err);
@@ -99,6 +128,14 @@ class TLSCallbacks : public StreamWrapCallbacks {
static v8::Handle<v8::Value> VerifyError(const v8::Arguments& args);
static v8::Handle<v8::Value> SetVerifyMode(const v8::Arguments& args);
static v8::Handle<v8::Value> IsSessionReused(const v8::Arguments& args);
+ static v8::Handle<v8::Value> EnableSessionCallbacks(const v8::Arguments& args);
+
+ // TLS Session API
+ static SSL_SESSION* GetSessionCallback(SSL* s,
+ unsigned char* key,
+ int len,
+ int* copy);
+ static int NewSessionCallback(SSL* s, SSL_SESSION* sess);
#ifdef OPENSSL_NPN_NEGOTIATED
static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
@@ -134,9 +171,13 @@ class TLSCallbacks : public StreamWrapCallbacks {
size_t write_queue_size_;
QUEUE write_item_queue_;
WriteItem* pending_write_item_;
+ HelloState hello_;
+ int hello_body_;
bool started_;
bool established_;
bool shutdown_;
+ bool session_callbacks_;
+ SSL_SESSION* next_sess_;
#ifdef OPENSSL_NPN_NEGOTIATED
v8::Persistent<v8::Object> npn_protos_;