tls_wrap: DRY ClientHelloParser
authorFedor Indutny <fedor.indutny@gmail.com>
Fri, 2 Aug 2013 12:16:13 +0000 (16:16 +0400)
committerFedor Indutny <fedor.indutny@gmail.com>
Tue, 6 Aug 2013 12:13:01 +0000 (16:13 +0400)
Share ClientHelloParser code between `tls_wrap.cc` and `node_crypto.cc`.

fix #5959

node.gyp
src/node_crypto.cc
src/node_crypto.h
src/node_crypto_clienthello-inl.h [new file with mode: 0644]
src/node_crypto_clienthello.cc [new file with mode: 0644]
src/node_crypto_clienthello.h [new file with mode: 0644]
src/tls_wrap.cc
src/tls_wrap.h

index b06f692..d6aefae 100644 (file)
--- a/node.gyp
+++ b/node.gyp
           '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'
           ],
index e68d91d..a4687a9 100644 (file)
@@ -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();
 }
 
 
index aa275cc..d695994 100644 (file)
@@ -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 (file)
index 0000000..82c0d27
--- /dev/null
@@ -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 (file)
index 0000000..5c1ecfa
--- /dev/null
@@ -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 (file)
index 0000000..6c98f5c
--- /dev/null
@@ -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_
index f7fe4e1..0fbb7e5 100644 (file)
@@ -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();
 }
 
 
index 6db4b6e..eb8d7ce 100644 (file)
@@ -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_;