tls_wrap: embed TLS encryption into streamwrap
authorFedor Indutny <fedor.indutny@gmail.com>
Tue, 11 Jun 2013 10:59:10 +0000 (12:59 +0200)
committerFedor Indutny <fedor.indutny@gmail.com>
Sun, 16 Jun 2013 07:30:14 +0000 (09:30 +0200)
node.gyp
src/node_crypto_bio.cc
src/node_crypto_bio.h
src/node_extensions.h
src/tls_wrap.cc [new file with mode: 0644]
src/tls_wrap.h [new file with mode: 0644]

index 5a1b924..931eca4 100644 (file)
--- a/node.gyp
+++ b/node.gyp
       'conditions': [
         [ 'node_use_openssl=="true"', {
           'defines': [ 'HAVE_OPENSSL=1' ],
-          'sources': [ 'src/node_crypto.cc', 'src/node_crypto_bio.cc' ],
+          'sources': [
+            'src/node_crypto.cc',
+            'src/node_crypto_bio.cc',
+            'src/tls_wrap.cc',
+            'src/tls_wrap.h'
+          ],
           'conditions': [
             [ 'node_shared_openssl=="false"', {
               'dependencies': [ './deps/openssl/openssl.gyp:openssl' ],
index 547c711..adb1e33 100644 (file)
@@ -82,6 +82,12 @@ int NodeBIO::Read(BIO* bio, char* out, int len) {
 }
 
 
+char* NodeBIO::Peek(size_t* size) {
+  *size = read_head_->write_pos_ - read_head_->read_pos_;
+  return read_head_->data_ + read_head_->read_pos_;
+}
+
+
 int NodeBIO::Write(BIO* bio, const char* data, int len) {
   BIO_clear_retry_flags(bio);
 
@@ -318,6 +324,29 @@ void NodeBIO::Write(const char* data, size_t size) {
 }
 
 
+char* NodeBIO::PeekWritable(size_t* size) {
+  size_t available = kBufferLength - write_head_->write_pos_;
+  if (*size != 0 && available > *size)
+    available = *size;
+  else
+    *size = available;
+
+  return write_head_->data_ + write_head_->write_pos_;
+}
+
+
+void NodeBIO::Commit(size_t size) {
+  write_head_->write_pos_ += size;
+  length_ += size;
+  assert(write_head_->write_pos_ <= kBufferLength);
+
+  // Allocate new buffer if write head is full,
+  // and there're no other place to go
+  TryAllocateForWrite();
+  write_head_ = write_head_->next_;
+}
+
+
 void NodeBIO::TryAllocateForWrite() {
   // If write head is full, next buffer is either read head or not empty.
   if (write_head_->write_pos_ == kBufferLength &&
index c2fd6a0..4794453 100644 (file)
@@ -30,6 +30,13 @@ class NodeBIO {
     return &method_;
   }
 
+  NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) {
+    // Loop head
+    head_.next_ = &head_;
+  }
+
+  ~NodeBIO();
+
   static int New(BIO* bio);
   static int Free(BIO* bio);
   static int Read(BIO* bio, char* out, int len);
@@ -38,27 +45,6 @@ class NodeBIO {
   static int Gets(BIO* bio, char* out, int size);
   static long Ctrl(BIO* bio, int cmd, long num, void* ptr);
 
- protected:
-  static const size_t kBufferLength = 16 * 1024;
-
-  class Buffer {
-   public:
-    Buffer() : read_pos_(0), write_pos_(0), next_(NULL) {
-    }
-
-    size_t read_pos_;
-    size_t write_pos_;
-    Buffer* next_;
-    char data_[kBufferLength];
-  };
-
-  NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) {
-    // Loop head
-    head_.next_ = &head_;
-  }
-
-  ~NodeBIO();
-
   // Allocate new buffer for write if needed
   void TryAllocateForWrite();
 
@@ -69,6 +55,10 @@ class NodeBIO {
   // Deallocate children of write head's child if they're empty
   void FreeEmpty();
 
+  // Return pointer to internal data and amount of
+  // contiguous data available to read
+  char* Peek(size_t* size);
+
   // Find first appearance of `delim` in buffer or `limit` if `delim`
   // wasn't found.
   size_t IndexOf(char delim, size_t limit);
@@ -79,6 +69,13 @@ class NodeBIO {
   // Put `len` bytes from `data` into buffer
   void Write(const char* data, size_t size);
 
+  // Return pointer to internal data and amount of
+  // contiguous data available for future writes
+  char* PeekWritable(size_t* size);
+
+  // Commit reserved data
+  void Commit(size_t size);
+
   // Return size of buffer in bytes
   size_t inline Length() {
     return length_;
@@ -89,6 +86,20 @@ class NodeBIO {
     return static_cast<NodeBIO*>(bio->ptr);
   }
 
+ protected:
+  static const size_t kBufferLength = 16 * 1024;
+
+  class Buffer {
+   public:
+    Buffer() : read_pos_(0), write_pos_(0), next_(NULL) {
+    }
+
+    size_t read_pos_;
+    size_t write_pos_;
+    Buffer* next_;
+    char data_[kBufferLength];
+  };
+
   size_t length_;
   Buffer head_;
   Buffer* read_head_;
index 4238f8c..8e1c702 100644 (file)
@@ -34,6 +34,7 @@ NODE_EXT_LIST_ITEM(node_zlib)
 // libuv rewrite
 NODE_EXT_LIST_ITEM(node_timer_wrap)
 NODE_EXT_LIST_ITEM(node_tcp_wrap)
+NODE_EXT_LIST_ITEM(node_tls_wrap)
 NODE_EXT_LIST_ITEM(node_udp_wrap)
 NODE_EXT_LIST_ITEM(node_pipe_wrap)
 NODE_EXT_LIST_ITEM(node_cares_wrap)
diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc
new file mode 100644 (file)
index 0000000..89d2790
--- /dev/null
@@ -0,0 +1,1153 @@
+// 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 "tls_wrap.h"
+#include "node_buffer.h"  // Buffer
+#include "node_crypto.h"  // SecureContext
+#include "node_crypto_bio.h"  // NodeBIO
+#include "node_wrap.h"  // WithGenericStream
+#include "node_counters.h"
+
+namespace node {
+
+using namespace v8;
+using crypto::SecureContext;
+
+static Persistent<String> onread_sym;
+static Persistent<String> onerror_sym;
+static Persistent<String> onsniselect_sym;
+static Persistent<String> onhandshakestart_sym;
+static Persistent<String> onhandshakedone_sym;
+static Persistent<String> subject_sym;
+static Persistent<String> subjectaltname_sym;
+static Persistent<String> modulus_sym;
+static Persistent<String> exponent_sym;
+static Persistent<String> issuer_sym;
+static Persistent<String> valid_from_sym;
+static Persistent<String> valid_to_sym;
+static Persistent<String> fingerprint_sym;
+static Persistent<String> name_sym;
+static Persistent<String> version_sym;
+static Persistent<String> ext_key_usage_sym;
+
+static Persistent<Function> tlsWrap;
+
+static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
+                                 | ASN1_STRFLGS_ESC_MSB
+                                 | XN_FLAG_SEP_MULTILINE
+                                 | XN_FLAG_FN_SN;
+
+
+TLSCallbacks::TLSCallbacks(Kind kind,
+                           Handle<Object> sc,
+                           StreamWrapCallbacks* old)
+    : StreamWrapCallbacks(old),
+      kind_(kind),
+      ssl_(NULL),
+      enc_in_(NULL),
+      enc_out_(NULL),
+      clear_in_(NULL),
+      write_size_(0),
+      pending_write_item_(NULL),
+      started_(false),
+      established_(false),
+      shutdown_(false) {
+
+  // Persist SecureContext
+  sc_ = ObjectWrap::Unwrap<SecureContext>(sc);
+  sc_handle_ = Persistent<Object>::New(node_isolate, sc);
+
+  handle_ = Persistent<Object>::New(node_isolate, tlsWrap->NewInstance());
+  handle_->SetAlignedPointerInInternalField(0, this);
+
+  // No session cache support
+  SSL_CTX_sess_set_get_cb(sc_->ctx_, NULL);
+  SSL_CTX_sess_set_new_cb(sc_->ctx_, NULL);
+
+  // Initialize queue for clearIn writes
+  QUEUE_INIT(&write_item_queue_);
+
+  InitSSL();
+}
+
+
+TLSCallbacks::~TLSCallbacks() {
+  SSL_free(ssl_);
+  ssl_ = NULL;
+  enc_in_ = NULL;
+  enc_out_ = NULL;
+  delete clear_in_;
+  clear_in_ = NULL;
+
+  sc_ = NULL;
+  sc_handle_.Dispose(node_isolate);
+  sc_handle_.Clear();
+
+  handle_.Dispose(node_isolate);
+  handle_.Clear();
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+  npn_protos_.Dispose(node_isolate);
+  npn_protos_.Clear();
+  selected_npn_proto_.Dispose(node_isolate);
+  selected_npn_proto_.Clear();
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  servername_.Dispose(node_isolate);
+  servername_.Clear();
+  sni_context_.Dispose(node_isolate);
+  sni_context_.Clear();
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+}
+
+
+void TLSCallbacks::InvokeQueued(int status) {
+  // Empty queue - ignore call
+  if (pending_write_item_ == NULL)
+    return;
+
+  QUEUE* q = &pending_write_item_->member_;
+  pending_write_item_ = NULL;
+
+  // Process old queue
+  while (q != &write_item_queue_) {
+    QUEUE* next = static_cast<QUEUE*>(QUEUE_NEXT(q));
+    WriteItem* wi = container_of(q, WriteItem, member_);
+    wi->cb_(&wi->w_->req_, status);
+    delete wi;
+    q = next;
+  }
+}
+
+
+static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) {
+  // Quoting SSL_set_verify(3ssl):
+  //
+  //   The VerifyCallback function is used to control the behaviour when
+  //   the SSL_VERIFY_PEER flag is set. It must be supplied by the
+  //   application and receives two arguments: preverify_ok indicates,
+  //   whether the verification of the certificate in question was passed
+  //   (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to
+  //   the complete context used for the certificate chain verification.
+  //
+  //   The certificate chain is checked starting with the deepest nesting
+  //   level (the root CA certificate) and worked upward to the peer's
+  //   certificate.  At each level signatures and issuer attributes are
+  //   checked.  Whenever a verification error is found, the error number is
+  //   stored in x509_ctx and VerifyCallback is called with preverify_ok=0.
+  //   By applying X509_CTX_store_* functions VerifyCallback can locate the
+  //   certificate in question and perform additional steps (see EXAMPLES).
+  //   If no error is found for a certificate, VerifyCallback is called
+  //   with preverify_ok=1 before advancing to the next level.
+  //
+  //   The return value of VerifyCallback controls the strategy of the
+  //   further verification process. If VerifyCallback returns 0, the
+  //   verification process is immediately stopped with "verification
+  //   failed" state. If SSL_VERIFY_PEER is set, a verification failure
+  //   alert is sent to the peer and the TLS/SSL handshake is terminated. If
+  //   VerifyCallback returns 1, the verification process is continued. If
+  //   VerifyCallback always returns 1, the TLS/SSL handshake will not be
+  //   terminated with respect to verification failures and the connection
+  //   will be established. The calling process can however retrieve the
+  //   error code of the last verification error using
+  //   SSL_get_verify_result(3) or by maintaining its own error storage
+  //   managed by VerifyCallback.
+  //
+  //   If no VerifyCallback is specified, the default callback will be
+  //   used.  Its return value is identical to preverify_ok, so that any
+  //   verification failure will lead to a termination of the TLS/SSL
+  //   handshake with an alert message, if SSL_VERIFY_PEER is set.
+  //
+  // Since we cannot perform I/O quickly enough in this callback, we ignore
+  // all preverify_ok errors and let the handshake continue. It is
+  // imparative that the user use Connection::VerifyError after the
+  // 'secure' callback has been made.
+  return 1;
+}
+
+
+void TLSCallbacks::InitSSL() {
+  assert(ssl_ == NULL);
+
+  // Initialize SSL
+  ssl_ = SSL_new(sc_->ctx_);
+  enc_in_ = BIO_new(NodeBIO::GetMethod());
+  enc_out_ = BIO_new(NodeBIO::GetMethod());
+
+  SSL_set_bio(ssl_, enc_in_, enc_out_);
+
+  // NOTE: This could be overriden in SetVerifyMode
+  SSL_set_verify(ssl_, SSL_VERIFY_NONE, VerifyCallback);
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+  long mode = SSL_get_mode(ssl_);
+  SSL_set_mode(ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
+#endif  // SSL_MODE_RELEASE_BUFFERS
+
+  SSL_set_app_data(ssl_, this);
+  SSL_set_info_callback(ssl_, SSLInfoCallback);
+
+  if (kind_ == kTLSServer) {
+    SSL_set_accept_state(ssl_);
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+    // Server should advertise NPN protocols
+    SSL_CTX_set_next_protos_advertised_cb(sc_->ctx_,
+                                          AdvertiseNextProtoCallback,
+                                          this);
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+    SSL_CTX_set_tlsext_servername_callback(sc_->ctx_, SelectSNIContextCallback);
+    SSL_CTX_set_tlsext_servername_arg(sc_->ctx_, this);
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  } else if (kind_ == kTLSClient) {
+    SSL_set_connect_state(ssl_);
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+    // Client should select protocol from list of advertised
+    // If server supports NPN
+    SSL_CTX_set_next_proto_select_cb(sc_->ctx_,
+                                     SelectNextProtoCallback,
+                                     this);
+#endif  // OPENSSL_NPN_NEGOTIATED
+  } else {
+    // Unexpected
+    abort();
+  }
+
+  // Initialize ring for queud clear data
+  clear_in_ = new NodeBIO();
+}
+
+
+Handle<Value> TLSCallbacks::Wrap(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  if (args.Length() < 1 || !args[0]->IsObject())
+    return ThrowTypeError("First argument should be a StreamWrap instance");
+  if (args.Length() < 2 || !args[1]->IsObject())
+    return ThrowTypeError("Second argument should be a SecureContext instance");
+  if (args.Length() < 3 || !args[2]->IsBoolean())
+    return ThrowTypeError("Third argument should be boolean");
+
+  Local<Object> stream = args[0].As<Object>();
+  Local<Object> sc = args[1].As<Object>();
+  Kind kind = args[2]->IsTrue() ? kTLSServer : kTLSClient;
+
+  TLSCallbacks* callbacks = NULL;
+  WITH_GENERIC_STREAM(stream, {
+    callbacks = new TLSCallbacks(kind, sc, wrap->GetCallbacks());
+    wrap->OverrideCallbacks(callbacks);
+  });
+
+  if (callbacks == NULL)
+    return Null(node_isolate);
+
+  return scope.Close(callbacks->handle_);
+}
+
+
+Handle<Value> TLSCallbacks::Start(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (wrap->started_)
+    return ThrowError("Already started.");
+  wrap->started_ = true;
+
+  // Send ClientHello handshake
+  assert(wrap->kind_ == kTLSClient);
+  wrap->ClearOut();
+  wrap->EncOut();
+
+  return Null(node_isolate);
+}
+
+
+void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
+  // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants
+  // a non-const SSL* in OpenSSL <= 0.9.7e.
+  SSL* ssl = const_cast<SSL*>(ssl_);
+  if (where & SSL_CB_HANDSHAKE_START) {
+    HandleScope scope(node_isolate);
+    TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(ssl));
+    if (c->handle_->Has(onhandshakestart_sym))
+      MakeCallback(c->handle_, onhandshakestart_sym, 0, NULL);
+  }
+  if (where & SSL_CB_HANDSHAKE_DONE) {
+    HandleScope scope(node_isolate);
+    TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(ssl));
+    c->established_ = true;
+    if (c->handle_->Has(onhandshakedone_sym))
+      MakeCallback(c->handle_, onhandshakedone_sym, 0, NULL);
+  }
+}
+
+
+void TLSCallbacks::EncOut() {
+  // Write in progress
+  if (write_size_ != 0)
+    return;
+
+  // Split-off queue
+  if (established_ && !QUEUE_EMPTY(&write_item_queue_)) {
+    pending_write_item_ = container_of(QUEUE_NEXT(&write_item_queue_),
+                                       WriteItem,
+                                       member_);
+    QUEUE_INIT(&write_item_queue_);
+  }
+
+  // No data to write
+  if (BIO_pending(enc_out_) == 0) {
+    InvokeQueued(0);
+    return;
+  }
+
+  char* data = NodeBIO::FromBIO(enc_out_)->Peek(&write_size_);
+  assert(write_size_ != 0);
+
+  write_req_.data = this;
+  uv_buf_t buf = uv_buf_init(data, write_size_);
+  int r = uv_write(&write_req_, wrap_->GetStream(), &buf, 1, EncOutCb);
+
+  // Ignore errors, this should be already handled in js
+  if (!r) {
+    if (wrap_->GetStream()->type == UV_TCP) {
+      NODE_COUNT_NET_BYTES_SENT(write_size_);
+    } else if (wrap_->GetStream()->type == UV_NAMED_PIPE) {
+      NODE_COUNT_PIPE_BYTES_SENT(write_size_);
+    }
+  }
+}
+
+
+void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
+  HandleScope scope(node_isolate);
+
+  TLSCallbacks* callbacks = static_cast<TLSCallbacks*>(req->data);
+
+  // Handle error
+  if (status) {
+    // Ignore errors after shutdown
+    if (callbacks->shutdown_)
+      return;
+
+    // Notify about error
+    SetErrno(uv_last_error(uv_default_loop()));
+    Local<Value> arg = String::Concat(
+        String::New("write cb error, status: "),
+        Integer::New(status, node_isolate)->ToString());
+    MakeCallback(callbacks->handle_, onerror_sym, 1, &arg);
+    callbacks->InvokeQueued(status);
+    return;
+  }
+
+  // Commit
+  NodeBIO::FromBIO(callbacks->enc_out_)->Read(NULL, callbacks->write_size_);
+
+  // Try writing more data
+  callbacks->write_size_ = 0;
+  callbacks->EncOut();
+}
+
+
+Handle<Value> TLSCallbacks::GetSSLError(int status, int* err) {
+  HandleScope scope(node_isolate);
+
+  *err = SSL_get_error(ssl_, status);
+  switch (*err) {
+   case SSL_ERROR_NONE:
+   case SSL_ERROR_WANT_READ:
+   case SSL_ERROR_WANT_WRITE:
+    break;
+   case SSL_ERROR_ZERO_RETURN:
+    return scope.Close(String::NewSymbol("ZERO_RETURN"));
+    break;
+   default:
+    {
+      BUF_MEM* mem;
+      BIO* bio;
+
+      assert(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL);
+
+      bio = BIO_new(BIO_s_mem());
+      assert(bio != NULL);
+      ERR_print_errors(bio);
+      BIO_get_mem_ptr(bio, &mem);
+      Handle<Value> r = Exception::Error(String::New(mem->data, mem->length));
+      BIO_free_all(bio);
+
+      return scope.Close(r);
+    }
+  }
+  return Handle<Value>();
+}
+
+
+void TLSCallbacks::ClearOut() {
+  HandleScope scope(node_isolate);
+
+  assert(ssl_ != NULL);
+
+  char out[kClearOutChunkSize];
+  int read;
+  do {
+    read = SSL_read(ssl_, out, sizeof(out));
+    if (read > 0) {
+      Local<Value> buff = Local<Value>::New(Buffer::New(out, read)->handle_);
+      Handle<Value> argv[3] = {
+        buff,
+        Integer::New(0, node_isolate),
+        Integer::New(read, node_isolate)
+      };
+      MakeCallback(Self(), onread_sym, ARRAY_SIZE(argv), argv);
+    }
+  } while (read > 0);
+
+  if (read == -1) {
+    int err;
+    Handle<Value> argv = GetSSLError(read, &err);
+
+    if (!argv.IsEmpty())
+      MakeCallback(handle_, onerror_sym, 1, &argv);
+  }
+}
+
+
+bool TLSCallbacks::ClearIn() {
+  HandleScope scope(node_isolate);
+
+  int written = 0;
+  while (clear_in_->Length() > 0) {
+    size_t avail = 0;
+    char* data = clear_in_->Peek(&avail);
+    written = SSL_write(ssl_, data, avail);
+    assert(written == -1 || written == static_cast<int>(avail));
+    if (written == -1)
+      break;
+    clear_in_->Read(NULL, avail);
+  }
+
+  // All written
+  if (clear_in_->Length() == 0) {
+    assert(written >= 0);
+    return true;
+  }
+
+  // Error or partial write
+  int err;
+  Handle<Value> argv = GetSSLError(written, &err);
+  if (!argv.IsEmpty())
+    MakeCallback(handle_, onerror_sym, 1, &argv);
+
+  return false;
+}
+
+
+int TLSCallbacks::DoWrite(WriteWrap* w,
+                          uv_buf_t* bufs,
+                          size_t count,
+                          uv_stream_t* send_handle,
+                          uv_write_cb cb) {
+  HandleScope scope(node_isolate);
+
+  assert(send_handle == NULL);
+
+  // Queue callback to execute it on next tick
+  WriteItem* wi = new WriteItem(w, cb);
+  bool empty = true;
+
+  // Empty writes should not go through encryption process
+  size_t i;
+  for (i = 0; i < count; i++)
+    if (bufs[i].len > 0) {
+      empty = false;
+      break;
+    }
+  if (empty) {
+    ClearOut();
+    // However if there any data that should be written to socket,
+    // callback should not be invoked immediately
+    if (BIO_pending(enc_out_) == 0)
+      return uv_write(&w->req_, wrap_->GetStream(), bufs, count, cb);
+  }
+
+  QUEUE_INSERT_TAIL(&write_item_queue_, &wi->member_);
+
+  // Write queued data
+  if (empty) {
+    EncOut();
+    return 0;
+  }
+
+  // Process enqueued data first
+  if (!ClearIn()) {
+    // If there're still data to process - enqueue current one
+    for (i = 0; i < count; i++)
+      clear_in_->Write(bufs[i].base, bufs[i].len);
+    return 0;
+  }
+
+  int written = 0;
+  for (i = 0; i < count; i++) {
+    written = SSL_write(ssl_, bufs[i].base, bufs[i].len);
+    assert(written == -1 || written == static_cast<int>(bufs[i].len));
+    if (written == -1)
+      break;
+  }
+
+  if (i != count) {
+    int err;
+    Handle<Value> argv = GetSSLError(written, &err);
+    if (!argv.IsEmpty()) {
+      MakeCallback(handle_, onerror_sym, 1, &argv);
+      return -1;
+    }
+
+    // No errors, queue rest
+    for (; i < count; i++)
+      clear_in_->Write(bufs[i].base, bufs[i].len);
+  }
+
+  // Try writing data immediately
+  EncOut();
+
+  return 0;
+}
+
+
+void TLSCallbacks::AfterWrite(WriteWrap* w) {
+  // Intentionally empty
+}
+
+
+uv_buf_t TLSCallbacks::DoAlloc(uv_handle_t* handle, size_t suggested_size) {
+  size_t size = suggested_size;
+  char* data = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size);
+  return uv_buf_init(data, size);
+}
+
+
+void TLSCallbacks::DoRead(uv_stream_t* handle,
+                          ssize_t nread,
+                          uv_buf_t buf,
+                          uv_handle_type pending) {
+  if (nread < 0)  {
+    uv_err_t err = uv_last_error(uv_default_loop());
+    SetErrno(err);
+
+    // Error should be emitted only after all data was read
+    ClearOut();
+    MakeCallback(Self(), onread_sym, 0, NULL);
+    return;
+  }
+
+  // Only client connections can receive data
+  assert(ssl_ != NULL);
+
+  // Commit read data
+  NodeBIO::FromBIO(enc_in_)->Commit(nread);
+
+  // Cycle OpenSSL state
+  ClearIn();
+  ClearOut();
+  EncOut();
+}
+
+
+int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) {
+  if (SSL_shutdown(ssl_) == 0)
+    SSL_shutdown(ssl_);
+  shutdown_ = true;
+  EncOut();
+  return StreamWrapCallbacks::DoShutdown(req_wrap, cb);
+}
+
+
+#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break;
+Handle<Value> TLSCallbacks::VerifyError(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  // XXX Do this check in JS land?
+  X509* peer_cert = SSL_get_peer_certificate(wrap->ssl_);
+  if (peer_cert == NULL) {
+    // We requested a certificate and they did not send us one.
+    // Definitely an error.
+    // XXX is this the right error message?
+    return scope.Close(Exception::Error(
+          String::New("UNABLE_TO_GET_ISSUER_CERT")));
+  }
+  X509_free(peer_cert);
+
+  long x509_verify_error = SSL_get_verify_result(wrap->ssl_);
+
+  const char* reason = NULL;
+  Local<String> s;
+  switch (x509_verify_error) {
+   case X509_V_OK:
+    return Null(node_isolate);
+   CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT)
+   CASE_X509_ERR(UNABLE_TO_GET_CRL)
+   CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE)
+   CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE)
+   CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY)
+   CASE_X509_ERR(CERT_SIGNATURE_FAILURE)
+   CASE_X509_ERR(CRL_SIGNATURE_FAILURE)
+   CASE_X509_ERR(CERT_NOT_YET_VALID)
+   CASE_X509_ERR(CERT_HAS_EXPIRED)
+   CASE_X509_ERR(CRL_NOT_YET_VALID)
+   CASE_X509_ERR(CRL_HAS_EXPIRED)
+   CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD)
+   CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD)
+   CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD)
+   CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD)
+   CASE_X509_ERR(OUT_OF_MEM)
+   CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT)
+   CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN)
+   CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY)
+   CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE)
+   CASE_X509_ERR(CERT_CHAIN_TOO_LONG)
+   CASE_X509_ERR(CERT_REVOKED)
+   CASE_X509_ERR(INVALID_CA)
+   CASE_X509_ERR(PATH_LENGTH_EXCEEDED)
+   CASE_X509_ERR(INVALID_PURPOSE)
+   CASE_X509_ERR(CERT_UNTRUSTED)
+   CASE_X509_ERR(CERT_REJECTED)
+   default:
+    s = String::New(X509_verify_cert_error_string(x509_verify_error));
+    break;
+  }
+
+  if (s.IsEmpty()) {
+    s = String::New(reason);
+  }
+
+  return scope.Close(Exception::Error(s));
+}
+#undef CASE_X509_ERR
+
+
+Handle<Value> TLSCallbacks::SetVerifyMode(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean())
+    return ThrowTypeError("Bad arguments, expected two booleans");
+
+  int verify_mode;
+  if (wrap->kind_ == kTLSServer) {
+    bool request_cert = args[0]->IsTrue();
+    if (!request_cert) {
+      // Note reject_unauthorized ignored.
+      verify_mode = SSL_VERIFY_NONE;
+    } else {
+      bool reject_unauthorized = args[1]->IsTrue();
+      verify_mode = SSL_VERIFY_PEER;
+      if (reject_unauthorized) verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+    }
+  } else {
+    // Note request_cert and reject_unauthorized are ignored for clients.
+    verify_mode = SSL_VERIFY_NONE;
+  }
+
+  // Always allow a connection. We'll reject in javascript.
+  SSL_set_verify(wrap->ssl_, verify_mode, VerifyCallback);
+
+  return True(node_isolate);
+}
+
+
+Handle<Value> TLSCallbacks::IsSessionReused(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  return scope.Close(Boolean::New(SSL_session_reused(wrap->ssl_)));
+}
+
+
+Handle<Value> TLSCallbacks::GetPeerCertificate(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  Local<Object> info = Object::New();
+  X509* peer_cert = SSL_get_peer_certificate(wrap->ssl_);
+  if (peer_cert != NULL) {
+    BIO* bio = BIO_new(BIO_s_mem());
+    BUF_MEM* mem;
+    if (X509_NAME_print_ex(bio,
+                           X509_get_subject_name(peer_cert),
+                           0,
+                           X509_NAME_FLAGS) > 0) {
+      BIO_get_mem_ptr(bio, &mem);
+      info->Set(subject_sym, String::New(mem->data, mem->length));
+    }
+    (void) BIO_reset(bio);
+
+    if (X509_NAME_print_ex(bio,
+                           X509_get_issuer_name(peer_cert),
+                           0,
+                           X509_NAME_FLAGS) > 0) {
+      BIO_get_mem_ptr(bio, &mem);
+      info->Set(issuer_sym, String::New(mem->data, mem->length));
+    }
+    (void) BIO_reset(bio);
+
+    int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1);
+    if (index >= 0) {
+      X509_EXTENSION* ext;
+      int rv;
+
+      ext = X509_get_ext(peer_cert, index);
+      assert(ext != NULL);
+
+      rv = X509V3_EXT_print(bio, ext, 0, 0);
+      assert(rv == 1);
+
+      BIO_get_mem_ptr(bio, &mem);
+      info->Set(subjectaltname_sym, String::New(mem->data, mem->length));
+
+      (void) BIO_reset(bio);
+    }
+
+    EVP_PKEY* pkey = NULL;
+    RSA* rsa = NULL;
+    if (NULL != (pkey = X509_get_pubkey(peer_cert)) &&
+        NULL != (rsa = EVP_PKEY_get1_RSA(pkey))) {
+      BN_print(bio, rsa->n);
+      BIO_get_mem_ptr(bio, &mem);
+      info->Set(modulus_sym, String::New(mem->data, mem->length) );
+      (void) BIO_reset(bio);
+
+      BN_print(bio, rsa->e);
+      BIO_get_mem_ptr(bio, &mem);
+      info->Set(exponent_sym, String::New(mem->data, mem->length) );
+      (void) BIO_reset(bio);
+    }
+
+    if (pkey != NULL) {
+      EVP_PKEY_free(pkey);
+      pkey = NULL;
+    }
+    if (rsa != NULL) {
+      RSA_free(rsa);
+      rsa = NULL;
+    }
+
+    ASN1_TIME_print(bio, X509_get_notBefore(peer_cert));
+    BIO_get_mem_ptr(bio, &mem);
+    info->Set(valid_from_sym, String::New(mem->data, mem->length));
+    (void) BIO_reset(bio);
+
+    ASN1_TIME_print(bio, X509_get_notAfter(peer_cert));
+    BIO_get_mem_ptr(bio, &mem);
+    info->Set(valid_to_sym, String::New(mem->data, mem->length));
+    BIO_free_all(bio);
+
+    unsigned int md_size, i;
+    unsigned char md[EVP_MAX_MD_SIZE];
+    if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) {
+      const char hex[] = "0123456789ABCDEF";
+      char fingerprint[EVP_MAX_MD_SIZE * 3];
+
+      for (i = 0; i < md_size; i++) {
+        fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4];
+        fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)];
+        fingerprint[(3*i)+2] = ':';
+      }
+
+      if (md_size > 0)
+        fingerprint[(3*(md_size-1))+2] = '\0';
+      else
+        fingerprint[0] = '\0';
+
+      info->Set(fingerprint_sym, String::New(fingerprint));
+    }
+
+    STACK_OF(ASN1_OBJECT)* eku = static_cast<STACK_OF(ASN1_OBJECT)*>(
+        X509_get_ext_d2i(peer_cert,
+                         NID_ext_key_usage,
+                         NULL,
+                         NULL));
+    if (eku != NULL) {
+      Local<Array> ext_key_usage = Array::New();
+      char buf[256];
+
+      for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
+        memset(buf, 0, sizeof(buf));
+        OBJ_obj2txt(buf, sizeof(buf) - 1, sk_ASN1_OBJECT_value(eku, i), 1);
+        ext_key_usage->Set(Integer::New(i, node_isolate), String::New(buf));
+      }
+
+      sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free);
+      info->Set(ext_key_usage_sym, ext_key_usage);
+    }
+
+    X509_free(peer_cert);
+  }
+
+  return scope.Close(info);
+}
+
+
+Handle<Value> TLSCallbacks::GetSession(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  SSL_SESSION* sess = SSL_get_session(wrap->ssl_);
+  if (!sess)
+    return Undefined(node_isolate);
+
+  int slen = i2d_SSL_SESSION(sess, NULL);
+  assert(slen > 0);
+
+  if (slen > 0) {
+    unsigned char* sbuf = new unsigned char[slen];
+    unsigned char* p = sbuf;
+    i2d_SSL_SESSION(sess, &p);
+    Local<Value> s = Encode(sbuf, slen, BINARY);
+    delete[] sbuf;
+    return scope.Close(s);
+  }
+
+  return Null(node_isolate);
+}
+
+
+Handle<Value> TLSCallbacks::SetSession(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (wrap->started_)
+    return ThrowError("Already started.");
+
+  if (args.Length() < 1 ||
+      (!args[0]->IsString() && !Buffer::HasInstance(args[0]))) {
+    return ThrowTypeError("Bad argument");
+  }
+
+  size_t slen = Buffer::Length(args[0]);
+  char* sbuf = new char[slen];
+
+  ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY);
+  assert(wlen == static_cast<ssize_t>(slen));
+
+  const unsigned char* p = reinterpret_cast<const unsigned char*>(sbuf);
+  SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen);
+
+  delete[] sbuf;
+
+  if (!sess)
+    return Undefined(node_isolate);
+
+  int r = SSL_set_session(wrap->ssl_, sess);
+  SSL_SESSION_free(sess);
+
+  if (!r) {
+    Local<String> eStr = String::New("SSL_set_session error");
+    return ThrowException(Exception::Error(eStr));
+  }
+
+  return True(node_isolate);
+}
+
+
+Handle<Value> TLSCallbacks::GetCurrentCipher(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  const SSL_CIPHER* c;
+
+  c = SSL_get_current_cipher(wrap->ssl_);
+  if (c == NULL)
+    return Undefined(node_isolate);
+
+  const char* cipher_name = SSL_CIPHER_get_name(c);
+  const char* cipher_version = SSL_CIPHER_get_version(c);
+
+  Local<Object> info = Object::New();
+  info->Set(name_sym, String::New(cipher_name));
+  info->Set(version_sym, String::New(cipher_version));
+  return scope.Close(info);
+}
+
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+int TLSCallbacks::AdvertiseNextProtoCallback(SSL* s,
+                                             const unsigned char** data,
+                                             unsigned int* len,
+                                             void* arg) {
+  TLSCallbacks* p = static_cast<TLSCallbacks*>(arg);
+
+  if (p->npn_protos_.IsEmpty()) {
+    // No initialization - no NPN protocols
+    *data = reinterpret_cast<const unsigned char*>("");
+    *len = 0;
+  } else {
+    *data = reinterpret_cast<const unsigned char*>(
+        Buffer::Data(p->npn_protos_));
+    *len = Buffer::Length(p->npn_protos_);
+  }
+
+  return SSL_TLSEXT_ERR_OK;
+}
+
+
+int TLSCallbacks::SelectNextProtoCallback(SSL* s,
+                                          unsigned char** out,
+                                          unsigned char* outlen,
+                                          const unsigned char* in,
+                                          unsigned int inlen,
+                                          void* arg) {
+  TLSCallbacks* p = static_cast<TLSCallbacks*>(arg);
+
+  // Release old protocol handler if present
+  if (!p->selected_npn_proto_.IsEmpty()) {
+    p->selected_npn_proto_.Dispose(node_isolate);
+  }
+
+  if (p->npn_protos_.IsEmpty()) {
+    // We should at least select one protocol
+    // If server is using NPN
+    *out = reinterpret_cast<unsigned char*>(const_cast<char*>("http/1.1"));
+    *outlen = 8;
+
+    // set status: unsupported
+    p->selected_npn_proto_ = Persistent<Value>::New(node_isolate,
+                                                    False(node_isolate));
+
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+  const unsigned char* npn_protos =
+      reinterpret_cast<const unsigned char*>(Buffer::Data(p->npn_protos_));
+  size_t len = Buffer::Length(p->npn_protos_);
+
+  int status = SSL_select_next_proto(out, outlen, in, inlen, npn_protos, len);
+  Handle<Value> result;
+  switch (status) {
+   case OPENSSL_NPN_UNSUPPORTED:
+    result = Null(node_isolate);
+    break;
+   case OPENSSL_NPN_NEGOTIATED:
+    result = String::New(reinterpret_cast<const char*>(*out), *outlen);
+    break;
+   case OPENSSL_NPN_NO_OVERLAP:
+    result = False(node_isolate);
+    break;
+   default:
+    break;
+  }
+
+  if (!result.IsEmpty())
+    p->selected_npn_proto_ = Persistent<Value>::New(node_isolate, result);
+
+  return SSL_TLSEXT_ERR_OK;
+}
+
+
+Handle<Value> TLSCallbacks::GetNegotiatedProto(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (wrap->kind_ == kTLSClient) {
+    if (wrap->selected_npn_proto_.IsEmpty())
+      return Undefined(node_isolate);
+    else
+      return wrap->selected_npn_proto_;
+  }
+
+  const unsigned char* npn_proto;
+  unsigned int npn_proto_len;
+
+  SSL_get0_next_proto_negotiated(wrap->ssl_, &npn_proto, &npn_proto_len);
+
+  if (!npn_proto)
+    return False(node_isolate);
+
+  return scope.Close(String::New(reinterpret_cast<const char*>(npn_proto),
+                                 npn_proto_len));
+}
+
+
+Handle<Value> TLSCallbacks::SetNPNProtocols(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
+    return ThrowTypeError("Must give a Buffer as first argument");
+
+  // Release old handle
+  if (!wrap->npn_protos_.IsEmpty())
+    wrap->npn_protos_.Dispose(node_isolate);
+
+  wrap->npn_protos_ =
+      Persistent<Object>::New(node_isolate, args[0]->ToObject());
+
+  return True(node_isolate);
+}
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+Handle<Value> TLSCallbacks::GetServername(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (wrap->kind_ == kTLSServer && !wrap->servername_.IsEmpty()) {
+    return wrap->servername_;
+  } else {
+    return False(node_isolate);
+  }
+}
+
+
+Handle<Value> TLSCallbacks::SetServername(const Arguments& args) {
+  HandleScope scope(node_isolate);
+
+  UNWRAP(TLSCallbacks);
+
+  if (args.Length() < 1 || !args[0]->IsString())
+    return ThrowTypeError("First argument should be a string");
+
+  if (wrap->started_)
+    return ThrowException(Exception::Error(String::New("Already started.")));
+
+  if (wrap->kind_ != kTLSClient)
+    return False(node_isolate);
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  String::Utf8Value servername(args[0].As<String>());
+  SSL_set_tlsext_host_name(wrap->ssl_, *servername);
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+
+  return Null(node_isolate);
+}
+
+
+int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
+  HandleScope scope(node_isolate);
+
+  TLSCallbacks* p = static_cast<TLSCallbacks*>(arg);
+
+  const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+
+  if (servername) {
+    if (!p->servername_.IsEmpty())
+      p->servername_.Dispose(node_isolate);
+    p->servername_ = Persistent<String>::New(node_isolate,
+                                             String::New(servername));
+
+    // Call the SNI callback and use its return value as context
+    if (p->handle_->Has(onsniselect_sym)) {
+      if (!p->sni_context_.IsEmpty())
+        p->sni_context_.Dispose(node_isolate);
+
+      // Get callback init args
+      Local<Value> argv[1] = {*p->servername_};
+
+      // Call it
+      Local<Value> ret = Local<Value>::New(node_isolate,
+                                           MakeCallback(p->handle_,
+                                                        onsniselect_sym,
+                                                        ARRAY_SIZE(argv),
+                                                        argv));
+
+      // If ret is SecureContext
+      if (ret->IsUndefined())
+        return SSL_TLSEXT_ERR_NOACK;
+
+      p->sni_context_ = Persistent<Value>::New(node_isolate, ret);
+      SecureContext* sc = ObjectWrap::Unwrap<SecureContext>(ret.As<Object>());
+      SSL_set_SSL_CTX(s, sc->ctx_);
+    }
+  }
+
+  return SSL_TLSEXT_ERR_OK;
+}
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+
+
+void TLSCallbacks::Initialize(Handle<Object> target) {
+  HandleScope scope(node_isolate);
+
+  NODE_SET_METHOD(target, "wrap", TLSCallbacks::Wrap);
+
+  Local<FunctionTemplate> t = FunctionTemplate::New();
+  t->InstanceTemplate()->SetInternalFieldCount(1);
+  t->SetClassName(String::NewSymbol("TLSWrap"));
+
+  NODE_SET_PROTOTYPE_METHOD(t, "start", Start);
+  NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", GetPeerCertificate);
+  NODE_SET_PROTOTYPE_METHOD(t, "getSession", GetSession);
+  NODE_SET_PROTOTYPE_METHOD(t, "setSession", SetSession);
+  NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher);
+  NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError);
+  NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode);
+  NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused);
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+  NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto);
+  NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", SetNPNProtocols);
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  NODE_SET_PROTOTYPE_METHOD(t, "getServername", GetServername);
+  NODE_SET_PROTOTYPE_METHOD(t, "setServername", SetServername);
+#endif  // SSL_CRT_SET_TLSEXT_SERVERNAME_CB
+
+  tlsWrap = Persistent<Function>::New(node_isolate, t->GetFunction());
+
+  onread_sym = NODE_PSYMBOL("onread");
+  onsniselect_sym = NODE_PSYMBOL("onsniselect");
+  onerror_sym = NODE_PSYMBOL("onerror");
+  onhandshakestart_sym = NODE_PSYMBOL("onhandshakestart");
+  onhandshakedone_sym = NODE_PSYMBOL("onhandshakedone");
+
+  subject_sym = NODE_PSYMBOL("subject");
+  issuer_sym = NODE_PSYMBOL("issuer");
+  valid_from_sym = NODE_PSYMBOL("valid_from");
+  valid_to_sym = NODE_PSYMBOL("valid_to");
+  subjectaltname_sym = NODE_PSYMBOL("subjectaltname");
+  modulus_sym = NODE_PSYMBOL("modulus");
+  exponent_sym = NODE_PSYMBOL("exponent");
+  fingerprint_sym = NODE_PSYMBOL("fingerprint");
+  name_sym = NODE_PSYMBOL("name");
+  version_sym = NODE_PSYMBOL("version");
+  ext_key_usage_sym = NODE_PSYMBOL("ext_key_usage");
+}
+
+}  // namespace node
+
+NODE_MODULE(node_tls_wrap, node::TLSCallbacks::Initialize)
diff --git a/src/tls_wrap.h b/src/tls_wrap.h
new file mode 100644 (file)
index 0000000..8650756
--- /dev/null
@@ -0,0 +1,154 @@
+// 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_TLS_WRAP_H_
+#define SRC_TLS_WRAP_H_
+
+#include <openssl/ssl.h>
+
+#include "v8.h"
+#include "stream_wrap.h"
+#include "queue.h"
+
+namespace node {
+
+// Forward-declarations
+class NodeBIO;
+class WriteWrap;
+namespace crypto {
+  class SecureContext;
+}
+
+class TLSCallbacks : public StreamWrapCallbacks {
+ public:
+  enum Kind {
+    kTLSClient,
+    kTLSServer
+  };
+
+  static void Initialize(v8::Handle<v8::Object> target);
+
+  int DoWrite(WriteWrap* w,
+              uv_buf_t* bufs,
+              size_t count,
+              uv_stream_t* send_handle,
+              uv_write_cb cb);
+  void AfterWrite(WriteWrap* w);
+  uv_buf_t DoAlloc(uv_handle_t* handle, size_t suggested_size);
+  void DoRead(uv_stream_t* handle,
+              ssize_t nread,
+              uv_buf_t buf,
+              uv_handle_type pending);
+  int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb);
+
+ protected:
+  static const int kClearOutChunkSize = 1024;
+
+  class WriteItem {
+   public:
+    WriteItem(WriteWrap* w, uv_write_cb cb) : w_(w), cb_(cb) {
+    }
+    ~WriteItem() {
+      w_ = NULL;
+      cb_ = NULL;
+    }
+
+    WriteWrap* w_;
+    uv_write_cb cb_;
+    QUEUE member_;
+  };
+
+  TLSCallbacks(Kind kind, v8::Handle<v8::Object> sc, StreamWrapCallbacks* old);
+  ~TLSCallbacks();
+
+  static void SSLInfoCallback(const SSL* ssl_, int where, int ret);
+  void InitSSL();
+  void EncOut();
+  static void EncOutCb(uv_write_t* req, int status);
+  bool ClearIn();
+  void ClearOut();
+  void InvokeQueued(int status);
+
+  v8::Handle<v8::Value> GetSSLError(int status, int* err);
+
+  static v8::Handle<v8::Value> Wrap(const v8::Arguments& args);
+  static v8::Handle<v8::Value> Start(const v8::Arguments& args);
+  static v8::Handle<v8::Value> GetPeerCertificate(const v8::Arguments& args);
+  static v8::Handle<v8::Value> GetSession(const v8::Arguments& args);
+  static v8::Handle<v8::Value> SetSession(const v8::Arguments& args);
+  static v8::Handle<v8::Value> LoadSession(const v8::Arguments& args);
+  static v8::Handle<v8::Value> GetCurrentCipher(const v8::Arguments& args);
+  static v8::Handle<v8::Value> VerifyError(const v8::Arguments& args);
+  static v8::Handle<v8::Value> SetVerifyMode(const v8::Arguments& args);
+  static v8::Handle<v8::Value> IsSessionReused(const v8::Arguments& args);
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+  static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
+  static v8::Handle<v8::Value> SetNPNProtocols(const v8::Arguments& args);
+  static int AdvertiseNextProtoCallback(SSL* s,
+                                        const unsigned char** data,
+                                        unsigned int* len,
+                                        void* arg);
+  static int SelectNextProtoCallback(SSL* s,
+                                     unsigned char** out,
+                                     unsigned char* outlen,
+                                     const unsigned char* in,
+                                     unsigned int inlen,
+                                     void* arg);
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  static v8::Handle<v8::Value> GetServername(const v8::Arguments& args);
+  static v8::Handle<v8::Value> SetServername(const v8::Arguments& args);
+  static int SelectSNIContextCallback(SSL* s, int* ad, void* arg);
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+
+  Kind kind_;
+  crypto::SecureContext* sc_;
+  v8::Persistent<v8::Object> sc_handle_;
+  v8::Persistent<v8::Object> handle_;
+  SSL* ssl_;
+  BIO* enc_in_;
+  BIO* enc_out_;
+  NodeBIO* clear_in_;
+  uv_write_t write_req_;
+  size_t write_size_;
+  size_t write_queue_size_;
+  QUEUE write_item_queue_;
+  WriteItem* pending_write_item_;
+  bool started_;
+  bool established_;
+  bool shutdown_;
+
+#ifdef OPENSSL_NPN_NEGOTIATED
+  v8::Persistent<v8::Object> npn_protos_;
+  v8::Persistent<v8::Value> selected_npn_proto_;
+#endif  // OPENSSL_NPN_NEGOTIATED
+
+#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+  v8::Persistent<v8::String> servername_;
+  v8::Persistent<v8::Value> sni_context_;
+#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
+};
+
+}  // namespace node
+
+#endif  // SRC_TLS_WRAP_H_