Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFedor Indutny <fedor.indutny@gmail.com>2013-08-02 16:16:13 +0400
committerFedor Indutny <fedor.indutny@gmail.com>2013-08-06 16:13:01 +0400
commit8e28193cc239a8389a1edda6810402005c1336cc (patch)
tree9eb0be6b32f20c768c67ce0b4feb972c156fb57d /src
parent6942a95ae1863503ad050113581b6eb708a3a6f3 (diff)
tls_wrap: DRY ClientHelloParser
Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`. fix #5959
Diffstat (limited to 'src')
-rw-r--r--src/node_crypto.cc179
-rw-r--r--src/node_crypto.h55
-rw-r--r--src/node_crypto_clienthello-inl.h74
-rw-r--r--src/node_crypto_clienthello.cc240
-rw-r--r--src/node_crypto_clienthello.h124
-rw-r--r--src/tls_wrap.cc247
-rw-r--r--src/tls_wrap.h29
7 files changed, 534 insertions, 414 deletions
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index e68d91dccbe..a4687a9d186 100644
--- a/src/node_crypto.cc
+++ b/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) {
- 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();
}
@@ -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);
@@ -1766,7 +1667,7 @@ void Connection::LoadSession(const FunctionCallbackInfo<Value>& args) {
ss->next_sess_ = sess;
}
- ss->hello_parser_.Finish();
+ ss->hello_parser_.End();
}
diff --git a/src/node_crypto.h b/src/node_crypto.h
index aa275cc32dc..d695994ce32 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -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
@@ -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);
@@ -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 {
@@ -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() {
@@ -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;
};
diff --git a/src/node_crypto_clienthello-inl.h b/src/node_crypto_clienthello-inl.h
new file mode 100644
index 00000000000..82c0d2782c2
--- /dev/null
+++ b/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_
diff --git a/src/node_crypto_clienthello.cc b/src/node_crypto_clienthello.cc
new file mode 100644
index 00000000000..5c1ecfaa97a
--- /dev/null
+++ b/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
diff --git a/src/node_crypto_clienthello.h b/src/node_crypto_clienthello.h
new file mode 100644
index 00000000000..6c98f5c22f7
--- /dev/null
+++ b/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_
diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc
index f7fe4e1e1c4..0fbb7e5741f 100644
--- a/src/tls_wrap.cc
+++ b/src/tls_wrap.cc
@@ -23,6 +23,8 @@
#include "node_buffer.h" // Buffer
#include "node_crypto.h" // SecureContext
#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_counters.h"
@@ -102,11 +104,6 @@ TLSCallbacks::TLSCallbacks(Kind kind,
// 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);
@@ -371,7 +368,7 @@ 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)
+ if (!hello_.IsEnded())
return;
// Write in progress
@@ -474,7 +471,7 @@ Handle<Value> TLSCallbacks::GetSSLError(int status, int* err) {
void TLSCallbacks::ClearOut() {
// Ignore cycling data if ClientHello wasn't yet parsed
- if (hello_.state != kParseEnded)
+ if (!hello_.IsEnded())
return;
HandleScope scope(node_isolate);
@@ -506,7 +503,7 @@ void TLSCallbacks::ClearOut() {
bool TLSCallbacks::ClearIn() {
// Ignore cycling data if ClientHello wasn't yet parsed
- if (hello_.state != kParseEnded)
+ if (!hello_.IsEnded())
return false;
HandleScope scope(node_isolate);
@@ -638,11 +635,17 @@ void TLSCallbacks::DoRead(uv_stream_t* handle,
assert(ssl_ != NULL);
// Commit read data
- NodeBIO::FromBIO(enc_in_)->Commit(nread);
+ NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_);
+ enc_in->Commit(nread);
// Parse ClientHello first
- if (hello_.state != kParseEnded)
- return ParseClientHello();
+ if (!hello_.IsEnded()) {
+ 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();
@@ -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;
void TLSCallbacks::VerifyError(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate);
@@ -959,9 +770,31 @@ void TLSCallbacks::EnableSessionCallbacks(
UNWRAP(TLSCallbacks);
wrap->session_callbacks_ = true;
- wrap->hello_.state = kParseWaiting;
- wrap->hello_.frame_len = 0;
- wrap->hello_.body_offset = 0;
+ wrap->hello_.Start(OnClientHello, OnClientHelloParseEnd, wrap);
+}
+
+
+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->ParseFinish();
+ wrap->hello_.End();
}
diff --git a/src/tls_wrap.h b/src/tls_wrap.h
index 6db4b6e41b3..eb8d7ce15d1 100644
--- a/src/tls_wrap.h
+++ b/src/tls_wrap.h
@@ -23,6 +23,7 @@
#define SRC_TLS_WRAP_H_
#include "node.h"
+#include "node_crypto_clienthello.h"
#include "queue.h"
#include "stream_wrap.h"
#include "v8.h"
@@ -62,22 +63,6 @@ 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 {
@@ -104,12 +89,6 @@ 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();
@@ -118,6 +97,9 @@ class TLSCallbacks : public StreamWrapCallbacks {
}
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 Start(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -183,13 +165,12 @@ 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_;
+ ClientHelloParser hello_;
#ifdef OPENSSL_NPN_NEGOTIATED
v8::Persistent<v8::Object> npn_protos_;