Browse Source

tls_wrap: DRY ClientHelloParser

Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`.

fix #5959
v0.11.5-release
Fedor Indutny 12 years ago
parent
commit
8e28193cc2
  1. 2
      node.gyp
  2. 179
      src/node_crypto.cc
  3. 55
      src/node_crypto.h
  4. 74
      src/node_crypto_clienthello-inl.h
  5. 240
      src/node_crypto_clienthello.cc
  6. 124
      src/node_crypto_clienthello.h
  7. 247
      src/tls_wrap.cc
  8. 29
      src/tls_wrap.h

2
node.gyp

@ -161,8 +161,10 @@
'sources': [ 'sources': [
'src/node_crypto.cc', 'src/node_crypto.cc',
'src/node_crypto_bio.cc', 'src/node_crypto_bio.cc',
'src/node_crypto_clienthello.cc',
'src/node_crypto.h', 'src/node_crypto.h',
'src/node_crypto_bio.h', 'src/node_crypto_bio.h',
'src/node_crypto_clienthello.h',
'src/tls_wrap.cc', 'src/tls_wrap.cc',
'src/tls_wrap.h' 'src/tls_wrap.h'
], ],

179
src/node_crypto.cc

@ -794,149 +794,38 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
} }
size_t ClientHelloParser::Write(const uint8_t* data, size_t len) { void Connection::OnClientHello(void* arg,
HandleScope scope(node_isolate); const ClientHelloParser::ClientHello& hello) {
HandleScope scope(node_isolate);
// Just accumulate data, everything will be pushed to BIO later Connection* c = static_cast<Connection*>(arg);
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;
}
}
// Not client hello - let OpenSSL handle it if (onclienthello_sym.IsEmpty())
if (!is_clienthello) { onclienthello_sym = String::New("onclienthello");
Finish(); if (sessionid_sym.IsEmpty())
return copied; sessionid_sym = String::New("sessionId");
}
// Parse frame, call javascript handler and Local<Object> obj = Object::New();
// move parser into the paused state obj->Set(sessionid_sym,
if (onclienthello_sym.IsEmpty()) Buffer::New(reinterpret_cast<const char*>(hello.session_id()),
onclienthello_sym = String::New("onclienthello"); hello.session_size()));
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;
}
return copied; Handle<Value> argv[1] = { obj };
MakeCallback(c->handle(node_isolate),
onclienthello_sym,
ARRAY_SIZE(argv),
argv);
} }
void ClientHelloParser::Finish() { void Connection::OnClientHelloParseEnd(void* arg) {
assert(state_ != kEnded); Connection* c = static_cast<Connection*>(arg);
state_ = kEnded;
// Write all accumulated data // Write all accumulated data
int r = BIO_write(conn_->bio_read_, reinterpret_cast<char*>(data_), offset_); int r = BIO_write(c->bio_read_,
conn_->HandleBIOError(conn_->bio_read_, "BIO_write", r); reinterpret_cast<char*>(c->hello_data_),
conn_->SetShutdownFlags(); c->hello_offset_);
c->HandleBIOError(c->bio_read_, "BIO_write", r);
c->SetShutdownFlags();
} }
@ -1398,9 +1287,21 @@ void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
int bytes_written; int bytes_written;
char* data = buffer_data + off; char* data = buffer_data + off;
if (ss->is_server_ && !ss->hello_parser_.ended()) { if (ss->is_server_ && !ss->hello_parser_.IsEnded()) {
bytes_written = ss->hello_parser_.Write(reinterpret_cast<uint8_t*>(data), // Just accumulate data, everything will be pushed to BIO later
len); 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 { } else {
bytes_written = BIO_write(ss->bio_read_, data, len); 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);
@ -1766,7 +1667,7 @@ void Connection::LoadSession(const FunctionCallbackInfo<Value>& args) {
ss->next_sess_ = sess; ss->next_sess_ = sess;
} }
ss->hello_parser_.Finish(); ss->hello_parser_.End();
} }

55
src/node_crypto.h

@ -23,6 +23,8 @@
#define SRC_NODE_CRYPTO_H_ #define SRC_NODE_CRYPTO_H_
#include "node.h" #include "node.h"
#include "node_crypto_clienthello.h" // ClientHelloParser
#include "node_crypto_clienthello-inl.h"
#include "node_object_wrap.h" #include "node_object_wrap.h"
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
@ -117,49 +119,6 @@ class SecureContext : ObjectWrap {
private: 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 { class Connection : ObjectWrap {
public: public:
static void Initialize(v8::Handle<v8::Object> target); static void Initialize(v8::Handle<v8::Object> target);
@ -221,6 +180,10 @@ class Connection : ObjectWrap {
static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg);
#endif #endif
static void OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello);
static void OnClientHelloParseEnd(void* arg);
int HandleBIOError(BIO* bio, const char* func, int rv); int HandleBIOError(BIO* bio, const char* func, int rv);
enum ZeroStatus { enum ZeroStatus {
@ -244,10 +207,11 @@ class Connection : ObjectWrap {
return ss; return ss;
} }
Connection() : ObjectWrap(), hello_parser_(this) { Connection() : ObjectWrap(), hello_offset_(0) {
bio_read_ = bio_write_ = NULL; bio_read_ = bio_write_ = NULL;
ssl_ = NULL; ssl_ = NULL;
next_sess_ = NULL; next_sess_ = NULL;
hello_parser_.Start(OnClientHello, OnClientHelloParseEnd, this);
} }
~Connection() { ~Connection() {
@ -285,6 +249,9 @@ class Connection : ObjectWrap {
bool is_server_; /* coverity[member_decl] */ bool is_server_; /* coverity[member_decl] */
SSL_SESSION* next_sess_; SSL_SESSION* next_sess_;
uint8_t hello_data_[18432];
size_t hello_offset_;
friend class ClientHelloParser; friend class ClientHelloParser;
friend class SecureContext; friend class SecureContext;
}; };

74
src/node_crypto_clienthello-inl.h

@ -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_

240
src/node_crypto_clienthello.cc

@ -0,0 +1,240 @@
// 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.
#include "node_crypto_clienthello.h"
#include "node_crypto_clienthello-inl.h"
#include "node_buffer.h" // Buffer
namespace node {
void ClientHelloParser::Parse(const uint8_t* data, size_t avail) {
switch (state_) {
case kWaiting:
if (!ParseRecordHeader(data, avail))
break;
// Fall through
case kTLSHeader:
case kSSL2Header:
ParseHeader(data, avail);
break;
case kPaused:
// Just nop
case kEnded:
// Already ended, just ignore it
break;
default:
break;
}
}
bool ClientHelloParser::ParseRecordHeader(const uint8_t* data, size_t avail) {
// >= 5 bytes for header parsing
if (avail < 5)
return false;
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 {
#ifdef OPENSSL_NO_SSL2
frame_len_ = ((data[0] << 8) & kSSL2HeaderMask) + data[1];
state_ = kSSL2Header;
if (data[0] & kSSL2TwoByteHeaderBit) {
// header without padding
body_offset_ = 2;
} else {
// header with padding
body_offset_ = 3;
}
#else
End();
return false;
#endif // OPENSSL_NO_SSL2
}
// Sanity check (too big frame, or too small)
// Let OpenSSL handle it
if (frame_len_ >= kMaxTLSFrameLen) {
End();
return false;
}
return true;
}
void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
// >= 5 + frame size bytes for frame parsing
if (body_offset_ + frame_len_ > avail)
return;
// Skip unsupported frames and gather some data from frame
// TODO(indutny): Check hello protocol version
if (data[body_offset_] == kClientHello) {
if (state_ == kTLSHeader) {
if (!ParseTLSClientHello(data, avail))
return End();
} else if (state_ == kSSL2Header) {
#ifdef OPENSSL_NO_SSL2
if (!ParseSSL2ClientHello(data, avail))
return End();
#else
abort(); // Unreachable
#endif // OPENSSL_NO_SSL2
} else {
// We couldn't get here, but whatever
return End();
}
// 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 End();
}
}
state_ = kPaused;
ClientHello hello;
hello.session_id_ = session_id_;
hello.session_size_ = session_size_;
hello.has_ticket_ = tls_ticket_ != NULL && tls_ticket_size_ != 0;
onhello_cb_(cb_arg_, hello);
}
void ClientHelloParser::ParseExtension(ClientHelloParser::ExtensionType type,
const uint8_t* data,
size_t len) {
// NOTE: In case of anything we're just returning back, ignoring the problem.
// That's because we're heavily relying on OpenSSL to solve any problem with
// incoming data.
switch (type) {
case kTLSSessionTicket:
tls_ticket_size_ = len;
tls_ticket_ = data + len;
break;
default:
// Ignore
break;
}
}
bool ClientHelloParser::ParseTLSClientHello(const uint8_t* data, size_t avail) {
const uint8_t* body;
// Skip frame header, hello header, protocol version and random data
size_t session_offset = body_offset_ + 4 + 2 + 32;
if (session_offset + 1 >= avail)
return false;
body = data + session_offset;
session_size_ = *body;
session_id_ = body + 1;
size_t cipher_offset = session_offset + 1 + session_size_;
// Session OOB failure
if (cipher_offset + 1 >= avail)
return false;
uint16_t cipher_len =
(data[cipher_offset] << 8) + data[cipher_offset + 1];
size_t comp_offset = cipher_offset + 2 + cipher_len;
// Cipher OOB failure
if (comp_offset >= avail)
return false;
uint8_t comp_len = data[comp_offset];
size_t extension_offset = comp_offset + 1 + comp_len;
// Compression OOB failure
if (extension_offset > avail)
return false;
// No extensions present
if (extension_offset == avail)
return true;
size_t ext_off = extension_offset + 2;
// Parse known extensions
while (ext_off < avail) {
// Extension OOB
if (ext_off + 4 > avail)
return false;
uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1];
uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3];
ext_off += 4;
// Extension OOB
if (ext_off + ext_len > avail)
return false;
ParseExtension(static_cast<ExtensionType>(ext_type),
data + ext_off,
ext_len);
ext_off += ext_len;
}
// Extensions OOB failure
if (ext_off > avail)
return false;
return true;
}
#ifdef OPENSSL_NO_SSL2
bool ClientHelloParser::ParseSSL2ClientHello(const uint8_t* data,
size_t avail) {
const uint8_t* body;
// Skip header, version
size_t session_offset = body_offset_ + 3;
if (session_offset + 4 < avail) {
body = data + session_offset;
uint16_t 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;
}
}
return true;
}
#endif // OPENSSL_NO_SSL2
} // namespace node

124
src/node_crypto_clienthello.h

@ -0,0 +1,124 @@
// 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_H_
#define SRC_NODE_CRYPTO_CLIENTHELLO_H_
#include "node.h"
#include <stddef.h> // size_t
#include <stdlib.h> // NULL
namespace node {
class ClientHelloParser {
public:
ClientHelloParser() : state_(kEnded),
onhello_cb_(NULL),
onend_cb_(NULL),
cb_arg_(NULL) {
Reset();
}
class ClientHello {
public:
ClientHello() {
}
inline uint8_t session_size() const { return session_size_; }
inline const uint8_t* session_id() const { return session_id_; }
inline bool has_ticket() const { return has_ticket_; }
private:
uint8_t session_size_;
const uint8_t* session_id_;
bool has_ticket_;
friend class ClientHelloParser;
};
typedef void (*OnHelloCb)(void* arg, const ClientHello& hello);
typedef void (*OnEndCb)(void* arg);
void Parse(const uint8_t* data, size_t avail);
inline void Reset();
inline void Start(OnHelloCb onhello_cb, OnEndCb onend_cb, void* onend_arg);
inline void End();
inline bool IsPaused() const;
inline bool IsEnded() const;
private:
static const uint8_t kSSL2TwoByteHeaderBit = 0x80;
static const uint8_t kSSL2HeaderMask = 0x3f;
static const size_t kMaxTLSFrameLen = 16 * 1024 + 5;
static const size_t kMaxSSLExFrameLen = 32 * 1024;
enum ParseState {
kWaiting,
kTLSHeader,
kSSL2Header,
kPaused,
kEnded
};
enum FrameType {
kChangeCipherSpec = 20,
kAlert = 21,
kHandshake = 22,
kApplicationData = 23,
kOther = 255
};
enum HandshakeType {
kClientHello = 1
};
enum ExtensionType {
kTLSSessionTicket = 35
};
bool ParseRecordHeader(const uint8_t* data, size_t avail);
void ParseHeader(const uint8_t* data, size_t avail);
void ParseExtension(ExtensionType type,
const uint8_t* data,
size_t len);
bool ParseTLSClientHello(const uint8_t* data, size_t avail);
#ifdef OPENSSL_NO_SSL2
bool ParseSSL2ClientHello(const uint8_t* data, size_t avail);
#endif // OPENSSL_NO_SSL2
ParseState state_;
OnHelloCb onhello_cb_;
OnEndCb onend_cb_;
void* cb_arg_;
size_t frame_len_;
size_t body_offset_;
size_t extension_offset_;
uint8_t session_size_;
const uint8_t* session_id_;
uint16_t tls_ticket_size_;
const uint8_t* tls_ticket_;
};
} // namespace node
#endif // SRC_NODE_CRYPTO_CLIENTHELLO_H_

247
src/tls_wrap.cc

@ -23,6 +23,8 @@
#include "node_buffer.h" // Buffer #include "node_buffer.h" // Buffer
#include "node_crypto.h" // SecureContext #include "node_crypto.h" // SecureContext
#include "node_crypto_bio.h" // NodeBIO #include "node_crypto_bio.h" // NodeBIO
#include "node_crypto_clienthello.h" // ClientHelloParser
#include "node_crypto_clienthello-inl.h"
#include "node_wrap.h" // WithGenericStream #include "node_wrap.h" // WithGenericStream
#include "node_counters.h" #include "node_counters.h"
@ -102,11 +104,6 @@ TLSCallbacks::TLSCallbacks(Kind kind,
// Initialize queue for clearIn writes // Initialize queue for clearIn writes
QUEUE_INIT(&write_item_queue_); 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 // We've our own session callbacks
SSL_CTX_sess_set_get_cb(sc_->ctx_, GetSessionCallback); SSL_CTX_sess_set_get_cb(sc_->ctx_, GetSessionCallback);
SSL_CTX_sess_set_new_cb(sc_->ctx_, NewSessionCallback); SSL_CTX_sess_set_new_cb(sc_->ctx_, NewSessionCallback);
@ -371,7 +368,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
void TLSCallbacks::EncOut() { void TLSCallbacks::EncOut() {
// Ignore cycling data if ClientHello wasn't yet parsed // Ignore cycling data if ClientHello wasn't yet parsed
if (hello_.state != kParseEnded) if (!hello_.IsEnded())
return; return;
// Write in progress // Write in progress
@ -474,7 +471,7 @@ Handle<Value> TLSCallbacks::GetSSLError(int status, int* err) {
void TLSCallbacks::ClearOut() { void TLSCallbacks::ClearOut() {
// Ignore cycling data if ClientHello wasn't yet parsed // Ignore cycling data if ClientHello wasn't yet parsed
if (hello_.state != kParseEnded) if (!hello_.IsEnded())
return; return;
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -506,7 +503,7 @@ void TLSCallbacks::ClearOut() {
bool TLSCallbacks::ClearIn() { bool TLSCallbacks::ClearIn() {
// Ignore cycling data if ClientHello wasn't yet parsed // Ignore cycling data if ClientHello wasn't yet parsed
if (hello_.state != kParseEnded) if (!hello_.IsEnded())
return false; return false;
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -638,11 +635,17 @@ void TLSCallbacks::DoRead(uv_stream_t* handle,
assert(ssl_ != NULL); assert(ssl_ != NULL);
// Commit read data // Commit read data
NodeBIO::FromBIO(enc_in_)->Commit(nread); NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_);
enc_in->Commit(nread);
// Parse ClientHello first // Parse ClientHello first
if (hello_.state != kParseEnded) if (!hello_.IsEnded()) {
return ParseClientHello(); assert(session_callbacks_);
size_t avail = 0;
uint8_t* data = reinterpret_cast<uint8_t*>(enc_in->Peek(&avail));
assert(avail == 0 || data != NULL);
return hello_.Parse(data, avail);
}
// Cycle OpenSSL's state // Cycle OpenSSL's state
Cycle(); Cycle();
@ -658,198 +661,6 @@ 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;
uint16_t tls_ticket_size = -1;
uint8_t* tls_ticket = 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;
}
size_t cipher_offset = session_offset + 1 + session_size;
// Session OOB failure
if (cipher_offset + 1 >= avail)
return ParseFinish();
uint16_t cipher_len =
(data[cipher_offset] << 8) + data[cipher_offset + 1];
size_t comp_offset = cipher_offset + 2 + cipher_len;
// Cipher OOB failure
if (comp_offset >= avail)
return ParseFinish();
uint8_t comp_len = data[comp_offset];
size_t extension_offset = comp_offset + 1 + comp_len;
// Compression OOB failure
if (extension_offset > avail)
return ParseFinish();
// Extensions present
if (extension_offset != avail) {
size_t ext_off = extension_offset + 2;
// Parse known extensions
while (ext_off < avail) {
// Extension OOB
if (avail - ext_off < 4)
return ParseFinish();
uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1];
uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3];
// Extension OOB
if (ext_off + ext_len + 4 > avail)
return ParseFinish();
ext_off += 4;
// TLS Session Ticket
if (ext_type == 35) {
tls_ticket_size = ext_len;
tls_ticket = data + ext_off;
}
ext_off += ext_len;
}
// Extensions OOB failure
if (ext_off > avail)
return ParseFinish();
}
} 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));
bool have_tls_ticket = (tls_ticket != NULL && tls_ticket_size != 0);
hello_obj->Set(tls_ticket_sym, Boolean::New(have_tls_ticket));
argv[0] = hello_obj;
MakeCallback(object(), onclienthello_sym, 1, argv);
}
break;
case kParseEnded:
default:
break;
}
}
#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break; #define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break;
void TLSCallbacks::VerifyError(const FunctionCallbackInfo<Value>& args) { void TLSCallbacks::VerifyError(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
@ -959,9 +770,31 @@ void TLSCallbacks::EnableSessionCallbacks(
UNWRAP(TLSCallbacks); UNWRAP(TLSCallbacks);
wrap->session_callbacks_ = true; wrap->session_callbacks_ = true;
wrap->hello_.state = kParseWaiting; wrap->hello_.Start(OnClientHello, OnClientHelloParseEnd, wrap);
wrap->hello_.frame_len = 0; }
wrap->hello_.body_offset = 0;
void TLSCallbacks::OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello) {
HandleScope scope(node_isolate);
TLSCallbacks* c = static_cast<TLSCallbacks*>(arg);
Local<Object> hello_obj = Object::New();
Local<Object> buff = Buffer::New(
reinterpret_cast<const char*>(hello.session_id()),
hello.session_size());
hello_obj->Set(sessionid_sym, buff);
hello_obj->Set(tls_ticket_sym, Boolean::New(hello.has_ticket()));
Handle<Value> argv[1] = { hello_obj };
MakeCallback(c->object(), onclienthello_sym, 1, argv);
}
void TLSCallbacks::OnClientHelloParseEnd(void* arg) {
TLSCallbacks* c = static_cast<TLSCallbacks*>(arg);
c->Cycle();
} }
@ -1168,7 +1001,7 @@ void TLSCallbacks::LoadSession(const FunctionCallbackInfo<Value>& args) {
wrap->next_sess_ = sess; wrap->next_sess_ = sess;
} }
wrap->ParseFinish(); wrap->hello_.End();
} }

29
src/tls_wrap.h

@ -23,6 +23,7 @@
#define SRC_TLS_WRAP_H_ #define SRC_TLS_WRAP_H_
#include "node.h" #include "node.h"
#include "node_crypto_clienthello.h"
#include "queue.h" #include "queue.h"
#include "stream_wrap.h" #include "stream_wrap.h"
#include "v8.h" #include "v8.h"
@ -62,22 +63,6 @@ class TLSCallbacks : public StreamWrapCallbacks {
protected: protected:
static const int kClearOutChunkSize = 1024; 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 // Write callback queue's item
class WriteItem { class WriteItem {
@ -104,12 +89,6 @@ class TLSCallbacks : public StreamWrapCallbacks {
bool ClearIn(); bool ClearIn();
void ClearOut(); void ClearOut();
void InvokeQueued(int status); void InvokeQueued(int status);
void ParseClientHello();
inline void ParseFinish() {
hello_.state = kParseEnded;
Cycle();
}
inline void Cycle() { inline void Cycle() {
ClearIn(); ClearIn();
@ -118,6 +97,9 @@ class TLSCallbacks : public StreamWrapCallbacks {
} }
v8::Handle<v8::Value> GetSSLError(int status, int* err); v8::Handle<v8::Value> GetSSLError(int status, int* err);
static void OnClientHello(void* arg,
const ClientHelloParser::ClientHello& hello);
static void OnClientHelloParseEnd(void* arg);
static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args); static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -183,13 +165,12 @@ class TLSCallbacks : public StreamWrapCallbacks {
size_t write_queue_size_; size_t write_queue_size_;
QUEUE write_item_queue_; QUEUE write_item_queue_;
WriteItem* pending_write_item_; WriteItem* pending_write_item_;
HelloState hello_;
int hello_body_;
bool started_; bool started_;
bool established_; bool established_;
bool shutdown_; bool shutdown_;
bool session_callbacks_; bool session_callbacks_;
SSL_SESSION* next_sess_; SSL_SESSION* next_sess_;
ClientHelloParser hello_;
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
v8::Persistent<v8::Object> npn_protos_; v8::Persistent<v8::Object> npn_protos_;

Loading…
Cancel
Save