Skip to content

Commit

Permalink
tls_wrap: DRY ClientHelloParser
Browse files Browse the repository at this point in the history
Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`.

fix #5959
  • Loading branch information
indutny committed Aug 6, 2013
1 parent 6942a95 commit 8e28193
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 414 deletions.
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@
'sources': [
'src/node_crypto.cc',
'src/node_crypto_bio.cc',
'src/node_crypto_clienthello.cc',
'src/node_crypto.h',
'src/node_crypto_bio.h',
'src/node_crypto_clienthello.h',
'src/tls_wrap.cc',
'src/tls_wrap.h'
],
Expand Down
179 changes: 40 additions & 139 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -794,149 +794,38 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
}


size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
HandleScope scope(node_isolate);

// 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

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;
}
}
void Connection::OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello) {
HandleScope scope(node_isolate);
Connection* c = static_cast<Connection*>(arg);

// Not client hello - let OpenSSL handle it
if (!is_clienthello) {
Finish();
return copied;
}
if (onclienthello_sym.IsEmpty())
onclienthello_sym = String::New("onclienthello");
if (sessionid_sym.IsEmpty())
sessionid_sym = String::New("sessionId");

// Parse frame, call javascript handler and
// move parser into the paused state
if (onclienthello_sym.IsEmpty())
onclienthello_sym = String::New("onclienthello");
if (sessionid_sym.IsEmpty())
sessionid_sym = String::New("sessionId");

state_ = kPaused;
hello = Object::New();
hello->Set(sessionid_sym,
Buffer::New(reinterpret_cast<char*>(session_id),
session_size));

argv[0] = hello;
MakeCallback(conn_->handle(node_isolate),
onclienthello_sym,
ARRAY_SIZE(argv),
argv);
break;
case kEnded:
default:
break;
}
Local<Object> obj = Object::New();
obj->Set(sessionid_sym,
Buffer::New(reinterpret_cast<const char*>(hello.session_id()),
hello.session_size()));

return copied;
Handle<Value> argv[1] = { obj };
MakeCallback(c->handle(node_isolate),
onclienthello_sym,
ARRAY_SIZE(argv),
argv);
}


void ClientHelloParser::Finish() {
assert(state_ != kEnded);
state_ = kEnded;
void Connection::OnClientHelloParseEnd(void* arg) {
Connection* c = static_cast<Connection*>(arg);

// 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();
int r = BIO_write(c->bio_read_,
reinterpret_cast<char*>(c->hello_data_),
c->hello_offset_);
c->HandleBIOError(c->bio_read_, "BIO_write", r);
c->SetShutdownFlags();
}


Expand Down Expand Up @@ -1398,9 +1287,21 @@ void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
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);
if (ss->is_server_ && !ss->hello_parser_.IsEnded()) {
// Just accumulate data, everything will be pushed to BIO later
if (ss->hello_parser_.IsPaused()) {
bytes_written = 0;
} else {
// Copy incoming data to the internal buffer
// (which has a size of the biggest possible TLS frame)
size_t available = sizeof(ss->hello_data_) - ss->hello_offset_;
size_t copied = len < available ? len : available;
memcpy(ss->hello_data_ + ss->hello_offset_, data, copied);
ss->hello_offset_ += copied;

ss->hello_parser_.Parse(ss->hello_data_, ss->hello_offset_);
bytes_written = copied;
}
} else {
bytes_written = BIO_write(ss->bio_read_, data, len);
ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written);
Expand Down Expand Up @@ -1766,7 +1667,7 @@ void Connection::LoadSession(const FunctionCallbackInfo<Value>& args) {
ss->next_sess_ = sess;
}

ss->hello_parser_.Finish();
ss->hello_parser_.End();
}


Expand Down
55 changes: 11 additions & 44 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#define SRC_NODE_CRYPTO_H_

#include "node.h"
#include "node_crypto_clienthello.h" // ClientHelloParser
#include "node_crypto_clienthello-inl.h"
#include "node_object_wrap.h"

#ifdef OPENSSL_NPN_NEGOTIATED
Expand Down Expand Up @@ -117,49 +119,6 @@ class SecureContext : ObjectWrap {
private:
};

class ClientHelloParser {
public:
enum FrameType {
kChangeCipherSpec = 20,
kAlert = 21,
kHandshake = 22,
kApplicationData = 23,
kOther = 255
};

enum HandshakeType {
kClientHello = 1
};

enum ParseState {
kWaiting,
kTLSHeader,
kSSLHeader,
kPaused,
kEnded
};

explicit ClientHelloParser(Connection* c) : conn_(c),
state_(kWaiting),
offset_(0),
body_offset_(0) {
}

size_t Write(const uint8_t* data, size_t len);
void Finish();

inline bool ended() { return state_ == kEnded; }

private:
Connection* conn_;
ParseState state_;
size_t frame_len_;

uint8_t data_[18432];
size_t offset_;
size_t body_offset_;
};

class Connection : ObjectWrap {
public:
static void Initialize(v8::Handle<v8::Object> target);
Expand Down Expand Up @@ -221,6 +180,10 @@ class Connection : ObjectWrap {
static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg);
#endif

static void OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello);
static void OnClientHelloParseEnd(void* arg);

int HandleBIOError(BIO* bio, const char* func, int rv);

enum ZeroStatus {
Expand All @@ -244,10 +207,11 @@ class Connection : ObjectWrap {
return ss;
}

Connection() : ObjectWrap(), hello_parser_(this) {
Connection() : ObjectWrap(), hello_offset_(0) {
bio_read_ = bio_write_ = NULL;
ssl_ = NULL;
next_sess_ = NULL;
hello_parser_.Start(OnClientHello, OnClientHelloParseEnd, this);
}

~Connection() {
Expand Down Expand Up @@ -285,6 +249,9 @@ class Connection : ObjectWrap {
bool is_server_; /* coverity[member_decl] */
SSL_SESSION* next_sess_;

uint8_t hello_data_[18432];
size_t hello_offset_;

friend class ClientHelloParser;
friend class SecureContext;
};
Expand Down
74 changes: 74 additions & 0 deletions src/node_crypto_clienthello-inl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_
#define SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_

#include <assert.h>

namespace node {

inline void ClientHelloParser::Reset() {
frame_len_ = 0;
body_offset_ = 0;
extension_offset_ = 0;
session_size_ = 0;
session_id_ = NULL;
tls_ticket_size_ = -1;
tls_ticket_ = NULL;
}

inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb,
ClientHelloParser::OnEndCb onend_cb,
void* onend_arg) {
if (!IsEnded())
return;
Reset();

assert(onhello_cb != NULL);

state_ = kWaiting;
onhello_cb_ = onhello_cb;
onend_cb_ = onend_cb;
cb_arg_ = onend_arg;
}

inline void ClientHelloParser::End() {
if (state_ == kEnded)
return;
state_ = kEnded;
if (onend_cb_ != NULL) {
onend_cb_(cb_arg_);
onend_cb_ = NULL;
}
}

inline bool ClientHelloParser::IsEnded() const {
return state_ == kEnded;
}

inline bool ClientHelloParser::IsPaused() const {
return state_ == kPaused;
}

} // namespace node

#endif // SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_
Loading

0 comments on commit 8e28193

Please sign in to comment.