|
@ -84,6 +84,9 @@ static Persistent<String> version_symbol; |
|
|
static Persistent<String> ext_key_usage_symbol; |
|
|
static Persistent<String> ext_key_usage_symbol; |
|
|
static Persistent<String> onhandshakestart_sym; |
|
|
static Persistent<String> onhandshakestart_sym; |
|
|
static Persistent<String> onhandshakedone_sym; |
|
|
static Persistent<String> onhandshakedone_sym; |
|
|
|
|
|
static Persistent<String> onclienthello_sym; |
|
|
|
|
|
static Persistent<String> onnewsession_sym; |
|
|
|
|
|
static Persistent<String> sessionid_sym; |
|
|
|
|
|
|
|
|
static Persistent<FunctionTemplate> secure_context_constructor; |
|
|
static Persistent<FunctionTemplate> secure_context_constructor; |
|
|
|
|
|
|
|
@ -221,15 +224,71 @@ Handle<Value> SecureContext::Init(const Arguments& args) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
sc->ctx_ = SSL_CTX_new(method); |
|
|
sc->ctx_ = SSL_CTX_new(method); |
|
|
// Enable session caching?
|
|
|
|
|
|
SSL_CTX_set_session_cache_mode(sc->ctx_, SSL_SESS_CACHE_SERVER); |
|
|
// SSL session cache configuration
|
|
|
// SSL_CTX_set_session_cache_mode(sc->ctx_,SSL_SESS_CACHE_OFF);
|
|
|
SSL_CTX_set_session_cache_mode(sc->ctx_, |
|
|
|
|
|
SSL_SESS_CACHE_SERVER | |
|
|
|
|
|
SSL_SESS_CACHE_NO_INTERNAL | |
|
|
|
|
|
SSL_SESS_CACHE_NO_AUTO_CLEAR); |
|
|
|
|
|
SSL_CTX_sess_set_get_cb(sc->ctx_, GetSessionCallback); |
|
|
|
|
|
SSL_CTX_sess_set_new_cb(sc->ctx_, NewSessionCallback); |
|
|
|
|
|
|
|
|
sc->ca_store_ = NULL; |
|
|
sc->ca_store_ = NULL; |
|
|
return True(); |
|
|
return True(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SSL_SESSION* SecureContext::GetSessionCallback(SSL* s, |
|
|
|
|
|
unsigned char* key, |
|
|
|
|
|
int len, |
|
|
|
|
|
int* copy) { |
|
|
|
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
Connection* p = static_cast<Connection*>(SSL_get_app_data(s)); |
|
|
|
|
|
|
|
|
|
|
|
*copy = 0; |
|
|
|
|
|
SSL_SESSION* sess = p->next_sess_; |
|
|
|
|
|
p->next_sess_ = NULL; |
|
|
|
|
|
|
|
|
|
|
|
return sess; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SessionDataFree(char* data, void* hint) { |
|
|
|
|
|
delete[] data; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { |
|
|
|
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
Connection* p = static_cast<Connection*>(SSL_get_app_data(s)); |
|
|
|
|
|
|
|
|
|
|
|
// Check if session is small enough to be stored
|
|
|
|
|
|
int size = i2d_SSL_SESSION(sess, NULL); |
|
|
|
|
|
if (size > kMaxSessionSize) return 0; |
|
|
|
|
|
|
|
|
|
|
|
// Serialize session
|
|
|
|
|
|
char* serialized = new char[size]; |
|
|
|
|
|
unsigned char* pserialized = reinterpret_cast<unsigned char*>(serialized); |
|
|
|
|
|
memset(serialized, 0, size); |
|
|
|
|
|
i2d_SSL_SESSION(sess, &pserialized); |
|
|
|
|
|
|
|
|
|
|
|
Handle<Value> argv[2] = { |
|
|
|
|
|
Buffer::New(reinterpret_cast<char*>(sess->session_id), |
|
|
|
|
|
sess->session_id_length)->handle_, |
|
|
|
|
|
Buffer::New(serialized, size, SessionDataFree, NULL)->handle_ |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if (onnewsession_sym.IsEmpty()) { |
|
|
|
|
|
onnewsession_sym = NODE_PSYMBOL("onnewsession"); |
|
|
|
|
|
} |
|
|
|
|
|
MakeCallback(p->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv); |
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Takes a string or buffer and loads it into a BIO.
|
|
|
// Takes a string or buffer and loads it into a BIO.
|
|
|
// Caller responsible for BIO_free-ing the returned object.
|
|
|
// Caller responsible for BIO_free-ing the returned object.
|
|
|
static BIO* LoadBIO (Handle<Value> v) { |
|
|
static BIO* LoadBIO (Handle<Value> v) { |
|
@ -667,6 +726,150 @@ Handle<Value> SecureContext::LoadPKCS12(const Arguments& args) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t ClientHelloParser::Write(const uint8_t* data, size_t len) { |
|
|
|
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
// Just accumulate data, everything will be pushed to BIO later
|
|
|
|
|
|
if (state_ == kPaused) return 0; |
|
|
|
|
|
|
|
|
|
|
|
// Copy incoming data to the internal buffer
|
|
|
|
|
|
// (which has a size of the biggest possible TLS frame)
|
|
|
|
|
|
size_t available = sizeof(data_) - offset_; |
|
|
|
|
|
size_t copied = len < available ? len : available; |
|
|
|
|
|
memcpy(data_ + offset_, data, copied); |
|
|
|
|
|
offset_ += copied; |
|
|
|
|
|
|
|
|
|
|
|
// Vars for parsing hello
|
|
|
|
|
|
bool is_clienthello = false; |
|
|
|
|
|
uint8_t session_size = -1; |
|
|
|
|
|
uint8_t* session_id = NULL; |
|
|
|
|
|
Local<Object> hello; |
|
|
|
|
|
Handle<Value> argv[1]; |
|
|
|
|
|
|
|
|
|
|
|
switch (state_) { |
|
|
|
|
|
case kWaiting: |
|
|
|
|
|
// >= 5 bytes for header parsing
|
|
|
|
|
|
if (offset_ < 5) break; |
|
|
|
|
|
|
|
|
|
|
|
if (data_[0] == kChangeCipherSpec || data_[0] == kAlert || |
|
|
|
|
|
data_[0] == kHandshake || data_[0] == kApplicationData) { |
|
|
|
|
|
frame_len_ = (data_[3] << 8) + data_[4]; |
|
|
|
|
|
state_ = kTLSHeader; |
|
|
|
|
|
body_offset_ = 5; |
|
|
|
|
|
} else { |
|
|
|
|
|
frame_len_ = (data_[0] << 8) + data_[1]; |
|
|
|
|
|
state_ = kSSLHeader; |
|
|
|
|
|
if (*data_ & 0x40) { |
|
|
|
|
|
// header with padding
|
|
|
|
|
|
body_offset_ = 3; |
|
|
|
|
|
} else { |
|
|
|
|
|
// without padding
|
|
|
|
|
|
body_offset_ = 2; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Sanity check (too big frame, or too small)
|
|
|
|
|
|
if (frame_len_ >= sizeof(data_)) { |
|
|
|
|
|
// Let OpenSSL handle it
|
|
|
|
|
|
Finish(); |
|
|
|
|
|
return copied; |
|
|
|
|
|
} |
|
|
|
|
|
case kTLSHeader: |
|
|
|
|
|
case kSSLHeader: |
|
|
|
|
|
// >= 5 + frame size bytes for frame parsing
|
|
|
|
|
|
if (offset_ < body_offset_ + frame_len_) break; |
|
|
|
|
|
|
|
|
|
|
|
// Skip unsupported frames and gather some data from frame
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Check protocol version
|
|
|
|
|
|
if (data_[body_offset_] == kClientHello) { |
|
|
|
|
|
is_clienthello = true; |
|
|
|
|
|
uint8_t* body; |
|
|
|
|
|
size_t session_offset; |
|
|
|
|
|
|
|
|
|
|
|
if (state_ == kTLSHeader) { |
|
|
|
|
|
// Skip frame header, hello header, protocol version and random data
|
|
|
|
|
|
session_offset = body_offset_ + 4 + 2 + 32; |
|
|
|
|
|
|
|
|
|
|
|
if (session_offset + 1 < offset_) { |
|
|
|
|
|
body = data_ + session_offset; |
|
|
|
|
|
session_size = *body; |
|
|
|
|
|
session_id = body + 1; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (state_ == kSSLHeader) { |
|
|
|
|
|
// Skip header, version
|
|
|
|
|
|
session_offset = body_offset_ + 3; |
|
|
|
|
|
|
|
|
|
|
|
if (session_offset + 4 < offset_) { |
|
|
|
|
|
body = data_ + session_offset; |
|
|
|
|
|
|
|
|
|
|
|
int ciphers_size = (body[0] << 8) + body[1]; |
|
|
|
|
|
|
|
|
|
|
|
if (body + 4 + ciphers_size < data_ + offset_) { |
|
|
|
|
|
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_ + offset_) { |
|
|
|
|
|
Finish(); |
|
|
|
|
|
return copied; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// TODO: Parse other things?
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Not client hello - let OpenSSL handle it
|
|
|
|
|
|
if (!is_clienthello) { |
|
|
|
|
|
Finish(); |
|
|
|
|
|
return copied; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Parse frame, call javascript handler and
|
|
|
|
|
|
// move parser into the paused state
|
|
|
|
|
|
if (onclienthello_sym.IsEmpty()) { |
|
|
|
|
|
onclienthello_sym = NODE_PSYMBOL("onclienthello"); |
|
|
|
|
|
} |
|
|
|
|
|
if (sessionid_sym.IsEmpty()) { |
|
|
|
|
|
sessionid_sym = NODE_PSYMBOL("sessionId"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
state_ = kPaused; |
|
|
|
|
|
hello = Object::New(); |
|
|
|
|
|
hello->Set(sessionid_sym, |
|
|
|
|
|
Buffer::New(reinterpret_cast<char*>(session_id), |
|
|
|
|
|
session_size)->handle_); |
|
|
|
|
|
|
|
|
|
|
|
argv[0] = hello; |
|
|
|
|
|
MakeCallback(conn_->handle_, onclienthello_sym, 1, argv); |
|
|
|
|
|
break; |
|
|
|
|
|
case kEnded: |
|
|
|
|
|
default: |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return copied; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void ClientHelloParser::Finish() { |
|
|
|
|
|
assert(state_ != kEnded); |
|
|
|
|
|
state_ = kEnded; |
|
|
|
|
|
|
|
|
|
|
|
// Write all accumulated data
|
|
|
|
|
|
int r = BIO_write(conn_->bio_read_, reinterpret_cast<char*>(data_), offset_); |
|
|
|
|
|
conn_->HandleBIOError(conn_->bio_read_, "BIO_write", r); |
|
|
|
|
|
conn_->SetShutdownFlags(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_PRINT_DEBUG |
|
|
#ifdef SSL_PRINT_DEBUG |
|
|
# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) |
|
|
# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) |
|
|
#else |
|
|
#else |
|
@ -789,6 +992,7 @@ void Connection::Initialize(Handle<Object> target) { |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", Connection::GetPeerCertificate); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", Connection::GetPeerCertificate); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getSession", Connection::GetSession); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getSession", Connection::GetSession); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSession", Connection::SetSession); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSession", Connection::SetSession); |
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "loadSession", Connection::LoadSession); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", Connection::IsSessionReused); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", Connection::IsSessionReused); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", Connection::IsInitFinished); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", Connection::IsInitFinished); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", Connection::VerifyError); |
|
|
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", Connection::VerifyError); |
|
@ -1112,9 +1316,17 @@ Handle<Value> Connection::EncIn(const Arguments& args) { |
|
|
String::New("off + len > buffer.length"))); |
|
|
String::New("off + len > buffer.length"))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
int bytes_written = BIO_write(ss->bio_read_, buffer_data + off, len); |
|
|
int bytes_written; |
|
|
|
|
|
char* data = buffer_data + off; |
|
|
|
|
|
|
|
|
|
|
|
if (ss->is_server_ && !ss->hello_parser_.ended()) { |
|
|
|
|
|
bytes_written = ss->hello_parser_.Write(reinterpret_cast<uint8_t*>(data), |
|
|
|
|
|
len); |
|
|
|
|
|
} else { |
|
|
|
|
|
bytes_written = BIO_write(ss->bio_read_, data, len); |
|
|
ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written); |
|
|
ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written); |
|
|
ss->SetShutdownFlags(); |
|
|
ss->SetShutdownFlags(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return scope.Close(Integer::New(bytes_written)); |
|
|
return scope.Close(Integer::New(bytes_written)); |
|
|
} |
|
|
} |
|
@ -1444,7 +1656,7 @@ Handle<Value> Connection::SetSession(const Arguments& args) { |
|
|
ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY); |
|
|
ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY); |
|
|
assert(wlen == slen); |
|
|
assert(wlen == slen); |
|
|
|
|
|
|
|
|
const unsigned char* p = (unsigned char*) sbuf; |
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(sbuf); |
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen); |
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen); |
|
|
|
|
|
|
|
|
delete [] sbuf; |
|
|
delete [] sbuf; |
|
@ -1463,6 +1675,30 @@ Handle<Value> Connection::SetSession(const Arguments& args) { |
|
|
return True(); |
|
|
return True(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Handle<Value> Connection::LoadSession(const Arguments& args) { |
|
|
|
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args); |
|
|
|
|
|
|
|
|
|
|
|
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { |
|
|
|
|
|
ssize_t slen = Buffer::Length(args[0].As<Object>()); |
|
|
|
|
|
char* sbuf = Buffer::Data(args[0].As<Object>()); |
|
|
|
|
|
|
|
|
|
|
|
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 (ss->next_sess_ != NULL) { |
|
|
|
|
|
SSL_SESSION_free(ss->next_sess_); |
|
|
|
|
|
} |
|
|
|
|
|
ss->next_sess_ = sess; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ss->hello_parser_.Finish(); |
|
|
|
|
|
|
|
|
|
|
|
return True(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Handle<Value> Connection::IsSessionReused(const Arguments& args) { |
|
|
Handle<Value> Connection::IsSessionReused(const Arguments& args) { |
|
|
HandleScope scope; |
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|