Merge branch 'v0.10'
authorFedor Indutny <fedor.indutny@gmail.com>
Mon, 2 Dec 2013 11:04:47 +0000 (15:04 +0400)
committerFedor Indutny <fedor.indutny@gmail.com>
Mon, 2 Dec 2013 11:04:47 +0000 (15:04 +0400)
Conflicts:
src/node_crypto.cc
src/node_crypto.h

1  2 
src/node_crypto.cc
src/node_crypto.h
src/tls_wrap.cc

@@@ -101,44 -75,29 +101,46 @@@ struct ClearErrorOnReturn 
    ~ClearErrorOnReturn() { ERR_clear_error(); }
  };
  
 -static Persistent<String> errno_symbol;
 -static Persistent<String> syscall_symbol;
 -static Persistent<String> subject_symbol;
 -static Persistent<String> subjectaltname_symbol;
 -static Persistent<String> modulus_symbol;
 -static Persistent<String> exponent_symbol;
 -static Persistent<String> issuer_symbol;
 -static Persistent<String> valid_from_symbol;
 -static Persistent<String> valid_to_symbol;
 -static Persistent<String> fingerprint_symbol;
 -static Persistent<String> name_symbol;
 -static Persistent<String> version_symbol;
 -static Persistent<String> ext_key_usage_symbol;
 -static Persistent<String> onhandshakestart_sym;
 -static Persistent<String> onhandshakedone_sym;
 -static Persistent<String> onclienthello_sym;
 -static Persistent<String> onnewsession_sym;
 -static Persistent<String> sessionid_sym;
 -
 -static Persistent<FunctionTemplate> secure_context_constructor;
 -
  static uv_rwlock_t* locks;
  
 +const char* root_certs[] = {
 +#include "node_root_certs.h"  // NOLINT(build/include_order)
 +  NULL
 +};
 +
 +X509_STORE* root_cert_store;
 +
 +// Just to generate static methods
 +template class SSLWrap<TLSCallbacks>;
 +template void SSLWrap<TLSCallbacks>::AddMethods(Handle<FunctionTemplate> t);
++template void SSLWrap<TLSCallbacks>::InitNPN(SecureContext* sc,
++                                             TLSCallbacks* base);
 +template SSL_SESSION* SSLWrap<TLSCallbacks>::GetSessionCallback(
 +    SSL* s,
 +    unsigned char* key,
 +    int len,
 +    int* copy);
 +template int SSLWrap<TLSCallbacks>::NewSessionCallback(SSL* s,
 +                                                       SSL_SESSION* sess);
 +template void SSLWrap<TLSCallbacks>::OnClientHello(
 +    void* arg,
 +    const ClientHelloParser::ClientHello& hello);
 +
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +template int SSLWrap<TLSCallbacks>::AdvertiseNextProtoCallback(
 +    SSL* s,
 +    const unsigned char** data,
 +    unsigned int* len,
 +    void* arg);
 +template int SSLWrap<TLSCallbacks>::SelectNextProtoCallback(
 +    SSL* s,
 +    unsigned char** out,
 +    unsigned char* outlen,
 +    const unsigned char* in,
 +    unsigned int inlen,
 +    void* arg);
 +#endif
 +
  
  static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
    CRYPTO_THREADID_set_numeric(tid, uv_thread_self());
@@@ -797,112 -732,185 +799,131 @@@ void SecureContext::LoadPKCS12(const Fu
  }
  
  
 -size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
 -  HandleScope scope;
 +void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
 +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
 +  HandleScope handle_scope(args.GetIsolate());
  
 -  // Just accumulate data, everything will be pushed to BIO later
 -  if (state_ == kPaused) return 0;
 +  SecureContext* wrap = Unwrap<SecureContext>(args.This());
  
 -  // 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;
 +  Local<Object> buff = Buffer::New(wrap->env(), 48);
 +  if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
 +                                     Buffer::Data(buff),
 +                                     Buffer::Length(buff)) != 1) {
 +    return ThrowError("Failed to fetch tls ticket keys");
 +  }
  
 -  // 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];
 +  args.GetReturnValue().Set(buff);
 +#endif  // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
 +}
  
 -  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;
 -      }
 -    }
 +void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
 +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
 +  HandleScope scope(node_isolate);
  
 -    // 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
 -
 -    // TODO: Check protocol version
 -    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 (args.Length() < 1 ||
 +      !Buffer::HasInstance(args[0]) ||
 +      Buffer::Length(args[0]) != 48) {
 +    return ThrowTypeError("Bad argument");
 +  }
  
 -        if (session_offset + 4 < offset_) {
 -          body = data_ + session_offset;
 +  SecureContext* wrap = Unwrap<SecureContext>(args.This());
  
 -          int ciphers_size = (body[0] << 8) + body[1];
 +  if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_,
 +                                     Buffer::Data(args[0]),
 +                                     Buffer::Length(args[0])) != 1) {
 +    return ThrowError("Failed to fetch tls ticket keys");
 +  }
  
 -          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();
 -      }
 +  args.GetReturnValue().Set(true);
 +#endif  // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
 +}
  
 -      // 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;
 -      }
  
 -      // TODO: Parse other things?
 -    }
 +template <class Base>
 +void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> t) {
 +  HandleScope scope(node_isolate);
  
 -    // Not client hello - let OpenSSL handle it
 -    if (!is_clienthello) {
 -      Finish();
 -      return copied;
 -    }
 +  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, "loadSession", LoadSession);
 +  NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused);
 +  NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", IsInitFinished);
 +  NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher);
 +  NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", ReceivedShutdown);
 +  NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser);
 +  NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate);
  
 -    // Parse frame, call javascript handler and
 -    // move parser into the paused state
 -    if (onclienthello_sym.IsEmpty()) {
 -      onclienthello_sym = NODE_PSYMBOL("onclienthello");
 -    }
 -    if (sessionid_sym.IsEmpty()) {
 -      sessionid_sym = NODE_PSYMBOL("sessionId");
 -    }
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", SetNPNProtocols);
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +}
  
 -    state_ = kPaused;
 -    hello = Object::New();
 -    hello->Set(sessionid_sym,
 -               Buffer::New(reinterpret_cast<char*>(session_id),
 -                           session_size)->handle_);
  
 -    argv[0] = hello;
 -    MakeCallback(conn_->handle_, onclienthello_sym, 1, argv);
 -    break;
 -   case kEnded:
 -   default:
 -    break;
 +template <class Base>
++void SSLWrap<Base>::InitNPN(SecureContext* sc, Base* base) {
++  if (base->is_server()) {
++#ifdef OPENSSL_NPN_NEGOTIATED
++    // Server should advertise NPN protocols
++    SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
++                                          AdvertiseNextProtoCallback,
++                                          base);
++#endif  // OPENSSL_NPN_NEGOTIATED
++  } else {
++#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, base);
++#endif  // OPENSSL_NPN_NEGOTIATED
+   }
 -
 -  return copied;
+ }
 -void ClientHelloParser::Finish() {
 -  assert(state_ != kEnded);
 -  state_ = kEnded;
 -
 -  // 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();
 -}
 -
++template <class Base>
 +SSL_SESSION* SSLWrap<Base>::GetSessionCallback(SSL* s,
 +                                               unsigned char* key,
 +                                               int len,
 +                                               int* copy) {
 +  Base* w = static_cast<Base*>(SSL_get_app_data(s));
  
 -#ifdef SSL_PRINT_DEBUG
 -# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__)
 -#else
 -# define DEBUG_PRINT(...)
 -#endif
 +  *copy = 0;
 +  SSL_SESSION* sess = w->next_sess_;
 +  w->next_sess_ = NULL;
  
 +  return sess;
 +}
  
 -int Connection::HandleBIOError(BIO *bio, const char* func, int rv) {
 -  if (rv >= 0) return rv;
  
 -  int retry = BIO_should_retry(bio);
 -  (void) retry; // unused if !defined(SSL_PRINT_DEBUG)
 +template <class Base>
 +int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
 +  Base* w = static_cast<Base*>(SSL_get_app_data(s));
 +  Environment* env = w->ssl_env();
 +  HandleScope handle_scope(env->isolate());
 +  Context::Scope context_scope(env->context());
  
 -  if (BIO_should_write(bio)) {
 -    DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n", ssl_, func, retry);
 +  if (!w->session_callbacks_)
      return 0;
  
 -  } else if (BIO_should_read(bio)) {
 -    DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry);
 +  // Check if session is small enough to be stored
 +  int size = i2d_SSL_SESSION(sess, NULL);
 +  if (size > SecureContext::kMaxSessionSize)
      return 0;
  
 -  } else {
 -   static char ssl_error_buf[512];
 -    ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf));
 -
 -    HandleScope scope;
 -    Local<Value> e = Exception::Error(String::New(ssl_error_buf));
 -    handle_->Set(String::New("error"), e);
 -
 -    DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", ssl_, func, rv, ssl_error_buf);
 +  // Serialize session
 +  Local<Object> buff = Buffer::New(env, size);
 +  unsigned char* serialized = reinterpret_cast<unsigned char*>(
 +      Buffer::Data(buff));
 +  memset(serialized, 0, size);
 +  i2d_SSL_SESSION(sess, &serialized);
  
 -    return rv;
 -  }
 +  Local<Object> session = Buffer::New(env,
 +                                      reinterpret_cast<char*>(sess->session_id),
 +                                      sess->session_id_length);
 +  Local<Value> argv[] = { session, buff };
 +  w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv);
  
    return 0;
  }
@@@ -1492,325 -1780,265 +1513,311 @@@ int Connection::HandleBIOError(BIO *bio
  }
  
  
 -Handle<Value> Connection::IsInitFinished(const Arguments& args) {
 -  HandleScope scope;
 -
 -  Connection *ss = Connection::Unwrap(args);
 -
 -  if (ss->ssl_ == NULL || SSL_is_init_finished(ss->ssl_) == false) {
 -    return False();
 -  }
 -
 -  return True();
 -}
 +int Connection::HandleSSLError(const char* func,
 +                               int rv,
 +                               ZeroStatus zs,
 +                               SyscallStatus ss) {
 +  ClearErrorOnReturn clear_error_on_return;
 +  (void) &clear_error_on_return;  // Silence unused variable warning.
  
 +  if (rv > 0)
 +    return rv;
 +  if (rv == 0 && zs == kZeroIsNotAnError)
 +    return rv;
  
 -Handle<Value> Connection::VerifyError(const Arguments& args) {
 -  HandleScope scope;
 +  int err = SSL_get_error(ssl_, rv);
  
 -  Connection *ss = Connection::Unwrap(args);
 +  if (err == SSL_ERROR_NONE) {
 +    return 0;
  
 -  if (ss->ssl_ == NULL) return Null();
 +  } else if (err == SSL_ERROR_WANT_WRITE) {
 +    DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func);
 +    return 0;
  
 +  } else if (err == SSL_ERROR_WANT_READ) {
 +    DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func);
 +    return 0;
  
 -  // XXX Do this check in JS land?
 -  X509* peer_cert = SSL_get_peer_certificate(ss->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);
 +  } else if (err == SSL_ERROR_ZERO_RETURN) {
 +    Local<Value> exception =
 +        Exception::Error(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN"));
 +    object()->Set(FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception);
 +    return rv;
  
 +  } else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) {
 +    return 0;
  
 -  long x509_verify_error = SSL_get_verify_result(ss->ssl_);
 +  } else {
 +    HandleScope scope(node_isolate);
 +    BUF_MEM* mem;
 +    BIO *bio;
  
 -  Local<String> s;
 +    assert(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL);
  
 -  switch (x509_verify_error) {
 -    case X509_V_OK:
 -      return Null();
 +    // XXX We need to drain the error queue for this thread or else OpenSSL
 +    // has the possibility of blocking connections? This problem is not well
 +    // understood. And we should be somehow propagating these errors up
 +    // into JavaScript. There is no test which demonstrates this problem.
 +    // https://github.com/joyent/node/issues/1719
 +    bio = BIO_new(BIO_s_mem());
 +    if (bio != NULL) {
 +      ERR_print_errors(bio);
 +      BIO_get_mem_ptr(bio, &mem);
 +      Local<Value> exception =
 +          Exception::Error(OneByteString(node_isolate, mem->data, mem->length));
 +      object()->Set(FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception);
 +      BIO_free_all(bio);
 +    }
  
 -    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
 -      s = String::New("UNABLE_TO_GET_ISSUER_CERT");
 -      break;
 +    return rv;
 +  }
  
 -    case X509_V_ERR_UNABLE_TO_GET_CRL:
 -      s = String::New("UNABLE_TO_GET_CRL");
 -      break;
 +  return 0;
 +}
  
 -    case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
 -      s = String::New("UNABLE_TO_DECRYPT_CERT_SIGNATURE");
 -      break;
  
 -    case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
 -      s = String::New("UNABLE_TO_DECRYPT_CRL_SIGNATURE");
 -      break;
 +void Connection::ClearError() {
 +#ifndef NDEBUG
 +  HandleScope scope(node_isolate);
  
 -    case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
 -      s = String::New("UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY");
 -      break;
 +  // We should clear the error in JS-land
 +  Local<String> error_key = FIXED_ONE_BYTE_STRING(node_isolate, "error");
 +  Local<Value> error = object()->Get(error_key);
 +  assert(error->BooleanValue() == false);
 +#endif  // NDEBUG
 +}
  
 -    case X509_V_ERR_CERT_SIGNATURE_FAILURE:
 -      s = String::New("CERT_SIGNATURE_FAILURE");
 -      break;
  
 -    case X509_V_ERR_CRL_SIGNATURE_FAILURE:
 -      s = String::New("CRL_SIGNATURE_FAILURE");
 -      break;
 +void Connection::SetShutdownFlags() {
 +  HandleScope scope(node_isolate);
  
 -    case X509_V_ERR_CERT_NOT_YET_VALID:
 -      s = String::New("CERT_NOT_YET_VALID");
 -      break;
 +  int flags = SSL_get_shutdown(ssl_);
  
 -    case X509_V_ERR_CERT_HAS_EXPIRED:
 -      s = String::New("CERT_HAS_EXPIRED");
 -      break;
 +  if (flags & SSL_SENT_SHUTDOWN) {
 +    Local<String> sent_shutdown_key =
 +        FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown");
 +    object()->Set(sent_shutdown_key, True(node_isolate));
 +  }
  
 -    case X509_V_ERR_CRL_NOT_YET_VALID:
 -      s = String::New("CRL_NOT_YET_VALID");
 -      break;
 +  if (flags & SSL_RECEIVED_SHUTDOWN) {
 +    Local<String> received_shutdown_key =
 +        FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown");
 +    object()->Set(received_shutdown_key, True(node_isolate));
 +  }
 +}
  
 -    case X509_V_ERR_CRL_HAS_EXPIRED:
 -      s = String::New("CRL_HAS_EXPIRED");
 -      break;
  
 -    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
 -      s = String::New("ERROR_IN_CERT_NOT_BEFORE_FIELD");
 -      break;
 +void Connection::Initialize(Environment* env, Handle<Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New);
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
 +  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"));
  
 -    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
 -      s = String::New("ERROR_IN_CERT_NOT_AFTER_FIELD");
 -      break;
 +  NODE_SET_PROTOTYPE_METHOD(t, "encIn", Connection::EncIn);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearOut", Connection::ClearOut);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearIn", Connection::ClearIn);
 +  NODE_SET_PROTOTYPE_METHOD(t, "encOut", Connection::EncOut);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearPending", Connection::ClearPending);
 +  NODE_SET_PROTOTYPE_METHOD(t, "encPending", Connection::EncPending);
 +  NODE_SET_PROTOTYPE_METHOD(t, "start", Connection::Start);
 +  NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Connection::Shutdown);
 +  NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);
  
 -    case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
 -      s = String::New("ERROR_IN_CRL_LAST_UPDATE_FIELD");
 -      break;
 +  SSLWrap<Connection>::AddMethods(t);
  
 -    case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
 -      s = String::New("ERROR_IN_CRL_NEXT_UPDATE_FIELD");
 -      break;
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "getNegotiatedProtocol",
 +                            Connection::GetNegotiatedProto);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "setNPNProtocols",
 +                            Connection::SetNPNProtocols);
 +#endif
  
 -    case X509_V_ERR_OUT_OF_MEM:
 -      s = String::New("OUT_OF_MEM");
 -      break;
  
 -    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
 -      s = String::New("DEPTH_ZERO_SELF_SIGNED_CERT");
 -      break;
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback",  Connection::SetSNICallback);
 +#endif
  
 -    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
 -      s = String::New("SELF_SIGNED_CERT_IN_CHAIN");
 -      break;
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"),
 +              t->GetFunction());
 +}
  
 -    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
 -      s = String::New("UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
 -      break;
  
 -    case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
 -      s = String::New("UNABLE_TO_VERIFY_LEAF_SIGNATURE");
 -      break;
 +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;
 +}
  
 -    case X509_V_ERR_CERT_CHAIN_TOO_LONG:
 -      s = String::New("CERT_CHAIN_TOO_LONG");
 -      break;
  
 -    case X509_V_ERR_CERT_REVOKED:
 -      s = String::New("CERT_REVOKED");
 -      break;
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
 +  HandleScope scope(node_isolate);
  
 -    case X509_V_ERR_INVALID_CA:
 -      s = String::New("INVALID_CA");
 -      break;
 +  Connection* conn = static_cast<Connection*>(SSL_get_app_data(s));
 +  Environment* env = conn->env();
  
 -    case X509_V_ERR_PATH_LENGTH_EXCEEDED:
 -      s = String::New("PATH_LENGTH_EXCEEDED");
 -      break;
 +  const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
  
 -    case X509_V_ERR_INVALID_PURPOSE:
 -      s = String::New("INVALID_PURPOSE");
 -      break;
 +  if (servername) {
 +    conn->servername_.Reset(node_isolate,
 +                            OneByteString(node_isolate, servername));
  
 -    case X509_V_ERR_CERT_UNTRUSTED:
 -      s = String::New("CERT_UNTRUSTED");
 -      break;
 +    // Call the SNI callback and use its return value as context
 +    if (!conn->sniObject_.IsEmpty()) {
 +      conn->sniContext_.Dispose();
  
 -    case X509_V_ERR_CERT_REJECTED:
 -      s = String::New("CERT_REJECTED");
 -      break;
 +      Local<Value> arg = PersistentToLocal(node_isolate, conn->servername_);
 +      Local<Value> ret = conn->MakeCallback(env->onselect_string(), 1, &arg);
  
 -    default:
 -      s = String::New(X509_verify_cert_error_string(x509_verify_error));
 -      break;
 +      // If ret is SecureContext
 +      Local<FunctionTemplate> secure_context_constructor_template =
 +          env->secure_context_constructor_template();
 +      if (secure_context_constructor_template->HasInstance(ret)) {
 +        conn->sniContext_.Reset(node_isolate, ret);
 +        SecureContext* sc = Unwrap<SecureContext>(ret.As<Object>());
++        InitNPN(sc, conn);
 +        SSL_set_SSL_CTX(s, sc->ctx_);
 +      } else {
 +        return SSL_TLSEXT_ERR_NOACK;
 +      }
 +    }
    }
  
 -  return scope.Close(Exception::Error(s));
 +  return SSL_TLSEXT_ERR_OK;
  }
 +#endif
  
 +void Connection::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -Handle<Value> Connection::GetCurrentCipher(const Arguments& args) {
 -  HandleScope scope;
 -
 -  Connection *ss = Connection::Unwrap(args);
 +  if (args.Length() < 1 || !args[0]->IsObject()) {
 +    return ThrowError("First argument must be a crypto module Credentials");
 +  }
  
 -  OPENSSL_CONST SSL_CIPHER *c;
 +  SecureContext* sc = Unwrap<SecureContext>(args[0]->ToObject());
 +  Environment* env = sc->env();
  
 -  if ( ss->ssl_ == NULL ) return Undefined();
 -  c = SSL_get_current_cipher(ss->ssl_);
 -  if ( c == NULL ) return Undefined();
 -  Local<Object> info = Object::New();
 -  const char* cipher_name = SSL_CIPHER_get_name(c);
 -  info->Set(name_symbol, String::New(cipher_name));
 -  const char* cipher_version = SSL_CIPHER_get_version(c);
 -  info->Set(version_symbol, String::New(cipher_version));
 -  return scope.Close(info);
 -}
 +  bool is_server = args[1]->BooleanValue();
  
 -Handle<Value> Connection::Close(const Arguments& args) {
 -  HandleScope scope;
 +  SSLWrap<Connection>::Kind kind =
 +      is_server ? SSLWrap<Connection>::kServer : SSLWrap<Connection>::kClient;
 +  Connection* conn = new Connection(env, args.This(), sc, kind);
 +  conn->ssl_ = SSL_new(sc->ctx_);
 +  conn->bio_read_ = NodeBIO::New();
 +  conn->bio_write_ = NodeBIO::New();
  
 -  Connection *ss = Connection::Unwrap(args);
 +  SSL_set_app_data(conn->ssl_, conn);
  
 -  if (ss->ssl_ != NULL) {
 -    SSL_free(ss->ssl_);
 -    ss->ssl_ = NULL;
 -  }
 -  return True();
 -}
 +  if (is_server)
 +    SSL_set_info_callback(conn->ssl_, SSLInfoCallback);
  
- #ifdef OPENSSL_NPN_NEGOTIATED
-   if (is_server) {
-     // Server should advertise NPN protocols
-     SSL_CTX_set_next_protos_advertised_cb(
-         sc->ctx_,
-         SSLWrap<Connection>::AdvertiseNextProtoCallback,
-         conn);
-   } else {
-     // Client should select protocol from advertised
-     // If server supports NPN
-     SSL_CTX_set_next_proto_select_cb(
-         sc->ctx_,
-         SSLWrap<Connection>::SelectNextProtoCallback,
-         conn);
-   }
- #endif
++  InitNPN(sc, conn);
  
 -void Connection::InitNPN(SecureContext* sc, bool is_server) {
 -#ifdef OPENSSL_NPN_NEGOTIATED
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
    if (is_server) {
 -    // Server should advertise NPN protocols
 -    SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
 -                                          AdvertiseNextProtoCallback_,
 -                                          NULL);
 -  } else {
 -    // Client should select protocol from advertised
 -    // If server supports NPN
 -    SSL_CTX_set_next_proto_select_cb(sc->ctx_,
 -                                     SelectNextProtoCallback_,
 -                                     NULL);
 +    SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_);
 +  } else if (args[2]->IsString()) {
 +    const String::Utf8Value servername(args[2]);
 +    SSL_set_tlsext_host_name(conn->ssl_, *servername);
    }
  #endif
 -}
 -
 -#ifdef OPENSSL_NPN_NEGOTIATED
 -Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) {
 -  HandleScope scope;
  
 -  Connection *ss = Connection::Unwrap(args);
 +  SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_);
  
 -  if (ss->is_server_) {
 -    const unsigned char* npn_proto;
 -    unsigned int npn_proto_len;
 +#ifdef SSL_MODE_RELEASE_BUFFERS
 +  long mode = SSL_get_mode(conn->ssl_);
 +  SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
 +#endif
  
 -    SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);
  
 -    if (!npn_proto) {
 -      return False();
 +  int verify_mode;
 +  if (is_server) {
 +    bool request_cert = args[2]->BooleanValue();
 +    if (!request_cert) {
 +      // Note reject_unauthorized ignored.
 +      verify_mode = SSL_VERIFY_NONE;
 +    } else {
 +      bool reject_unauthorized = args[3]->BooleanValue();
 +      verify_mode = SSL_VERIFY_PEER;
 +      if (reject_unauthorized)
 +        verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
      }
 -
 -    return scope.Close(String::New(reinterpret_cast<const char*>(npn_proto),
 -                                   npn_proto_len));
    } else {
 -    return ss->selectedNPNProto_;
 +    // Note request_cert and reject_unauthorized are ignored for clients.
 +    verify_mode = SSL_VERIFY_NONE;
    }
 -}
  
 -Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
 -  HandleScope scope;
  
 -  Connection *ss = Connection::Unwrap(args);
 +  // Always allow a connection. We'll reject in javascript.
 +  SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback);
  
 -  if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
 -    return ThrowException(Exception::Error(String::New(
 -           "Must give a Buffer as first argument")));
 +  if (is_server) {
 +    SSL_set_accept_state(conn->ssl_);
 +  } else {
 +    SSL_set_connect_state(conn->ssl_);
    }
 +}
  
 -  // Release old handle
 -  if (!ss->npnProtos_.IsEmpty()) {
 -    ss->npnProtos_.Dispose();
 -  }
 -  ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject());
  
 -  return True();
 -};
 -#endif
 +void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) {
 +  if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
 +    return;
  
 -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 -Handle<Value> Connection::GetServername(const Arguments& args) {
 -  HandleScope scope;
 +  // 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_);
 +  Connection* conn = static_cast<Connection*>(SSL_get_app_data(ssl));
 +  Environment* env = conn->env();
 +  HandleScope handle_scope(env->isolate());
 +  Context::Scope context_scope(env->context());
  
 -  Connection *ss = Connection::Unwrap(args);
 +  if (where & SSL_CB_HANDSHAKE_START) {
 +    conn->MakeCallback(env->onhandshakestart_string(), 0, NULL);
 +  }
  
 -  if (ss->is_server_ && !ss->servername_.IsEmpty()) {
 -    return ss->servername_;
 -  } else {
 -    return False();
 +  if (where & SSL_CB_HANDSHAKE_DONE) {
 +    conn->MakeCallback(env->onhandshakedone_string(), 0, NULL);
    }
  }
  
@@@ -118,121 -103,60 +118,122 @@@ class SecureContext : public BaseObjec
        assert(ca_store_ == NULL);
      }
    }
 -
 -  ~SecureContext() {
 -    FreeCTXMem();
 -  }
 -
 - private:
  };
  
 -class ClientHelloParser {
 +// SSLWrap implicitly depends on the inheriting class' handle having an
 +// internal pointer to the Base class.
 +template <class Base>
 +class SSLWrap {
   public:
 -  enum FrameType {
 -    kChangeCipherSpec = 20,
 -    kAlert = 21,
 -    kHandshake = 22,
 -    kApplicationData = 23,
 -    kOther = 255
 +  enum Kind {
 +    kClient,
 +    kServer
    };
  
 -  enum HandshakeType {
 -    kClientHello = 1
 -  };
 +  SSLWrap(Environment* env, SecureContext* sc, Kind kind)
 +      : env_(env),
 +        kind_(kind),
 +        next_sess_(NULL),
 +        session_callbacks_(false) {
 +    ssl_ = SSL_new(sc->ctx_);
 +    assert(ssl_ != NULL);
 +  }
  
 -  enum ParseState {
 -    kWaiting,
 -    kTLSHeader,
 -    kSSLHeader,
 -    kPaused,
 -    kEnded
 -  };
 +  ~SSLWrap() {
 +    if (ssl_ != NULL) {
 +      SSL_free(ssl_);
 +      ssl_ = NULL;
 +    }
 +    if (next_sess_ != NULL) {
 +      SSL_SESSION_free(next_sess_);
 +      next_sess_ = NULL;
 +    }
  
 -  ClientHelloParser(Connection* c) : conn_(c),
 -                                     state_(kWaiting),
 -                                     offset_(0),
 -                                     body_offset_(0) {
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +    npn_protos_.Dispose();
 +    selected_npn_proto_.Dispose();
 +#endif
    }
  
 -  size_t Write(const uint8_t* data, size_t len);
 -  void Finish();
 +  inline SSL* ssl() const { return ssl_; }
 +  inline void enable_session_callbacks() { session_callbacks_ = true; }
 +  inline bool is_server() const { return kind_ == kServer; }
 +  inline bool is_client() const { return kind_ == kClient; }
  
 -  inline bool ended() { return state_ == kEnded; }
 + protected:
++  static void InitNPN(SecureContext* sc, Base* base);
 +  static void AddMethods(v8::Handle<v8::FunctionTemplate> t);
  
 - private:
 -  Connection* conn_;
 -  ParseState state_;
 -  size_t frame_len_;
 +  static SSL_SESSION* GetSessionCallback(SSL* s,
 +                                         unsigned char* key,
 +                                         int len,
 +                                         int* copy);
 +  static int NewSessionCallback(SSL* s, SSL_SESSION* sess);
 +  static void OnClientHello(void* arg,
 +                            const ClientHelloParser::ClientHello& hello);
 +
 +  static void GetPeerCertificate(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void IsInitFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void GetCurrentCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void ReceivedShutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
 +
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  static void GetNegotiatedProto(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetNPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& 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
 +
 +  inline Environment* ssl_env() const {
 +    return env_;
 +  }
 +
 +  Environment* const env_;
 +  Kind kind_;
 +  SSL_SESSION* next_sess_;
 +  SSL* ssl_;
 +  bool session_callbacks_;
 +  ClientHelloParser hello_parser_;
  
 -  uint8_t data_[18432];
 -  size_t offset_;
 -  size_t body_offset_;
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  v8::Persistent<v8::Object> npn_protos_;
 +  v8::Persistent<v8::Value> selected_npn_proto_;
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +
 +  friend class SecureContext;
  };
  
 -class Connection : ObjectWrap {
 +// Connection inherits from AsyncWrap because SSLWrap makes calls to
 +// MakeCallback, but SSLWrap doesn't store the handle itself. Instead it
 +// assumes that any args.This() called will be the handle from Connection.
 +class Connection : public SSLWrap<Connection>, public AsyncWrap {
   public:
 -  static void Initialize(v8::Handle<v8::Object> target);
 +  ~Connection() {
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +    sniObject_.Dispose();
 +    sniContext_.Dispose();
 +    servername_.Dispose();
 +#endif
 +  }
 +
 +  static void Initialize(Environment* env, v8::Handle<v8::Object> target);
  
  #ifdef OPENSSL_NPN_NEGOTIATED
    v8::Persistent<v8::Object> npnProtos_;
diff --cc src/tls_wrap.cc
index e98ae60,0000000..6ceb861
mode 100644,000000..100644
--- /dev/null
@@@ -1,715 -1,0 +1,703 @@@
-   if (is_server()) {
-     SSL_set_accept_state(ssl_);
- #ifdef OPENSSL_NPN_NEGOTIATED
-     // Server should advertise NPN protocols
-     SSL_CTX_set_next_protos_advertised_cb(
-         sc_->ctx_,
-         SSLWrap<TLSCallbacks>::AdvertiseNextProtoCallback,
-         this);
- #endif  // OPENSSL_NPN_NEGOTIATED
 +// 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 "async-wrap.h"
 +#include "async-wrap-inl.h"
 +#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"
 +#include "node_internals.h"
 +#include "util.h"
 +#include "util-inl.h"
 +
 +namespace node {
 +
 +using crypto::SSLWrap;
 +using crypto::SecureContext;
 +using v8::Boolean;
 +using v8::Context;
 +using v8::Exception;
 +using v8::Function;
 +using v8::FunctionCallbackInfo;
 +using v8::FunctionTemplate;
 +using v8::Handle;
 +using v8::HandleScope;
 +using v8::Integer;
 +using v8::Local;
 +using v8::Null;
 +using v8::Object;
 +using v8::String;
 +using v8::Value;
 +
 +static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
 +                                 | ASN1_STRFLGS_ESC_MSB
 +                                 | XN_FLAG_SEP_MULTILINE
 +                                 | XN_FLAG_FN_SN;
 +
 +
 +TLSCallbacks::TLSCallbacks(Environment* env,
 +                           Kind kind,
 +                           Handle<Object> sc,
 +                           StreamWrapCallbacks* old)
 +    : SSLWrap<TLSCallbacks>(env, Unwrap<SecureContext>(sc), kind),
 +      StreamWrapCallbacks(old),
 +      AsyncWrap(env, env->tls_wrap_constructor_function()->NewInstance()),
 +      sc_(Unwrap<SecureContext>(sc)),
 +      sc_handle_(env->isolate(), sc),
 +      enc_in_(NULL),
 +      enc_out_(NULL),
 +      clear_in_(NULL),
 +      write_size_(0),
 +      pending_write_item_(NULL),
 +      started_(false),
 +      established_(false),
 +      shutdown_(false) {
 +  node::Wrap<TLSCallbacks>(object(), this);
 +
 +  // Initialize queue for clearIn writes
 +  QUEUE_INIT(&write_item_queue_);
 +
 +  // We've our own session callbacks
 +  SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::GetSessionCallback);
 +  SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::NewSessionCallback);
 +
 +  InitSSL();
 +}
 +
 +
 +TLSCallbacks::~TLSCallbacks() {
 +  enc_in_ = NULL;
 +  enc_out_ = NULL;
 +  delete clear_in_;
 +  clear_in_ = NULL;
 +
 +  sc_ = NULL;
 +  sc_handle_.Dispose();
 +  persistent().Dispose();
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  sni_context_.Dispose();
 +#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;
 +  }
 +}
 +
 +
 +void TLSCallbacks::InitSSL() {
 +  // Initialize SSL
 +  enc_in_ = NodeBIO::New();
 +  enc_out_ = NodeBIO::New();
 +
 +  SSL_set_bio(ssl_, enc_in_, enc_out_);
 +
 +  // NOTE: This could be overriden in SetVerifyMode
 +  SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::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);
 +
- #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_,
-         SSLWrap<TLSCallbacks>::SelectNextProtoCallback,
-         this);
- #endif  // OPENSSL_NPN_NEGOTIATED
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
++  if (is_server()) {
 +    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
++
++  InitNPN(sc_, this);
++
++  if (is_server()) {
++    SSL_set_accept_state(ssl_);
 +  } else if (is_client()) {
 +    SSL_set_connect_state(ssl_);
 +  } else {
 +    // Unexpected
 +    abort();
 +  }
 +
 +  // Initialize ring for queud clear data
 +  clear_in_ = new NodeBIO();
 +}
 +
 +
 +void TLSCallbacks::Wrap(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +
 +  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() ? SSLWrap<TLSCallbacks>::kServer :
 +                                  SSLWrap<TLSCallbacks>::kClient;
 +
 +  TLSCallbacks* callbacks = NULL;
 +  WITH_GENERIC_STREAM(env, stream, {
 +    callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks());
 +    wrap->OverrideCallbacks(callbacks);
 +  });
 +
 +  if (callbacks == NULL) {
 +    return args.GetReturnValue().SetNull();
 +  }
 +
 +  args.GetReturnValue().Set(callbacks->persistent());
 +}
 +
 +
 +void TLSCallbacks::Start(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (wrap->started_)
 +    return ThrowError("Already started.");
 +  wrap->started_ = true;
 +
 +  // Send ClientHello handshake
 +  assert(wrap->is_client());
 +  wrap->ClearOut();
 +  wrap->EncOut();
 +}
 +
 +
 +void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
 +  if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
 +    return;
 +
 +  // 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_);
 +  TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(ssl));
 +  Environment* env = c->env();
 +  HandleScope handle_scope(env->isolate());
 +  Context::Scope context_scope(env->context());
 +  Local<Object> object = c->object();
 +
 +  if (where & SSL_CB_HANDSHAKE_START) {
 +    Local<Value> callback = object->Get(env->onhandshakestart_string());
 +    if (callback->IsFunction()) {
 +      c->MakeCallback(callback.As<Function>(), 0, NULL);
 +    }
 +  }
 +
 +  if (where & SSL_CB_HANDSHAKE_DONE) {
 +    c->established_ = true;
 +    Local<Value> callback = object->Get(env->onhandshakedone_string());
 +    if (callback->IsFunction()) {
 +      c->MakeCallback(callback.As<Function>(), 0, NULL);
 +    }
 +  }
 +}
 +
 +
 +void TLSCallbacks::EncOut() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return;
 +
 +  // 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()->stream(), &buf, 1, EncOutCb);
 +
 +  // Ignore errors, this should be already handled in js
 +  if (!r) {
 +    if (wrap()->is_tcp()) {
 +      NODE_COUNT_NET_BYTES_SENT(write_size_);
 +    } else if (wrap()->is_named_pipe()) {
 +      NODE_COUNT_PIPE_BYTES_SENT(write_size_);
 +    }
 +  }
 +}
 +
 +
 +void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
 +  TLSCallbacks* callbacks = static_cast<TLSCallbacks*>(req->data);
 +  Environment* env = callbacks->env();
 +
 +  // Handle error
 +  if (status) {
 +    // Ignore errors after shutdown
 +    if (callbacks->shutdown_)
 +      return;
 +
 +    // Notify about error
 +    HandleScope handle_scope(env->isolate());
 +    Context::Scope context_scope(env->context());
 +    Local<Value> arg = String::Concat(
 +        FIXED_ONE_BYTE_STRING(node_isolate, "write cb error, status: "),
 +        Integer::New(status, node_isolate)->ToString());
 +    callbacks->MakeCallback(env->onerror_string(), 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();
 +}
 +
 +
 +Local<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(FIXED_ONE_BYTE_STRING(node_isolate, "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);
 +        Local<String> message =
 +            OneByteString(node_isolate, mem->data, mem->length);
 +        Local<Value> exception = Exception::Error(message);
 +        BIO_free_all(bio);
 +
 +        return scope.Close(exception);
 +      }
 +  }
 +  return Local<Value>();
 +}
 +
 +
 +void TLSCallbacks::ClearOut() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return;
 +
 +  HandleScope handle_scope(env()->isolate());
 +  Context::Scope context_scope(env()->context());
 +
 +  assert(ssl_ != NULL);
 +
 +  char out[kClearOutChunkSize];
 +  int read;
 +  do {
 +    read = SSL_read(ssl_, out, sizeof(out));
 +    if (read > 0) {
 +      Local<Value> argv[] = {
 +        Integer::New(read, node_isolate),
 +        Buffer::New(env(), out, read)
 +      };
 +      wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv);
 +    }
 +  } while (read > 0);
 +
 +  if (read == -1) {
 +    int err;
 +    Handle<Value> arg = GetSSLError(read, &err);
 +
 +    if (!arg.IsEmpty()) {
 +      MakeCallback(env()->onerror_string(), 1, &arg);
 +    }
 +  }
 +}
 +
 +
 +bool TLSCallbacks::ClearIn() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return false;
 +
 +  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;
 +  }
 +
 +  HandleScope handle_scope(env()->isolate());
 +  Context::Scope context_scope(env()->context());
 +
 +  // Error or partial write
 +  int err;
 +  Handle<Value> arg = GetSSLError(written, &err);
 +  if (!arg.IsEmpty()) {
 +    MakeCallback(env()->onerror_string(), 1, &arg);
 +  }
 +
 +  return false;
 +}
 +
 +
 +int TLSCallbacks::DoWrite(WriteWrap* w,
 +                          uv_buf_t* bufs,
 +                          size_t count,
 +                          uv_stream_t* send_handle,
 +                          uv_write_cb cb) {
 +  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()->stream(), 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;
 +    HandleScope handle_scope(env()->isolate());
 +    Context::Scope context_scope(env()->context());
 +    Handle<Value> arg = GetSSLError(written, &err);
 +    if (!arg.IsEmpty()) {
 +      MakeCallback(env()->onerror_string(), 1, &arg);
 +      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
 +}
 +
 +
 +void TLSCallbacks::DoAlloc(uv_handle_t* handle,
 +                           size_t suggested_size,
 +                           uv_buf_t* buf) {
 +  buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&suggested_size);
 +  buf->len = suggested_size;
 +}
 +
 +
 +void TLSCallbacks::DoRead(uv_stream_t* handle,
 +                          ssize_t nread,
 +                          const uv_buf_t* buf,
 +                          uv_handle_type pending) {
 +  if (nread < 0)  {
 +    // Error should be emitted only after all data was read
 +    ClearOut();
 +    HandleScope handle_scope(env()->isolate());
 +    Context::Scope context_scope(env()->context());
 +    Local<Value> arg = Integer::New(nread, node_isolate);
 +    wrap()->MakeCallback(env()->onread_string(), 1, &arg);
 +    return;
 +  }
 +
 +  // Only client connections can receive data
 +  assert(ssl_ != NULL);
 +
 +  // Commit read data
 +  NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_);
 +  enc_in->Commit(nread);
 +
 +  // Parse ClientHello first
 +  if (!hello_parser_.IsEnded()) {
 +    size_t avail = 0;
 +    uint8_t* data = reinterpret_cast<uint8_t*>(enc_in->Peek(&avail));
 +    assert(avail == 0 || data != NULL);
 +    return hello_parser_.Parse(data, avail);
 +  }
 +
 +  // Cycle OpenSSL's state
 +  Cycle();
 +}
 +
 +
 +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);
 +}
 +
 +
 +void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean())
 +    return ThrowTypeError("Bad arguments, expected two booleans");
 +
 +  int verify_mode;
 +  if (wrap->is_server()) {
 +    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, crypto::VerifyCallback);
 +}
 +
 +
 +void TLSCallbacks::EnableSessionCallbacks(
 +    const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  wrap->enable_session_callbacks();
 +  EnableHelloParser(args);
 +}
 +
 +
 +void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  wrap->hello_parser_.Start(SSLWrap<TLSCallbacks>::OnClientHello,
 +                            OnClientHelloParseEnd,
 +                            wrap);
 +}
 +
 +
 +void TLSCallbacks::OnClientHelloParseEnd(void* arg) {
 +  TLSCallbacks* c = static_cast<TLSCallbacks*>(arg);
 +  c->Cycle();
 +}
 +
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +void TLSCallbacks::GetServername(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  const char* servername = SSL_get_servername(wrap->ssl_,
 +                                              TLSEXT_NAMETYPE_host_name);
 +  if (servername != NULL) {
 +    args.GetReturnValue().Set(OneByteString(node_isolate, servername));
 +  } else {
 +    args.GetReturnValue().Set(false);
 +  }
 +}
 +
 +
 +void TLSCallbacks::SetServername(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (args.Length() < 1 || !args[0]->IsString())
 +    return ThrowTypeError("First argument should be a string");
 +
 +  if (wrap->started_)
 +    return ThrowError("Already started.");
 +
 +  if (!wrap->is_client())
 +    return;
 +
 +#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
 +}
 +
 +
 +int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* p = static_cast<TLSCallbacks*>(arg);
 +  Environment* env = p->env();
 +
 +  const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
 +
 +  if (servername != NULL) {
 +    // Call the SNI callback and use its return value as context
 +    Local<Object> object = p->object();
 +    Local<Value> ctx = object->Get(env->sni_context_string());
 +
 +    if (!ctx->IsObject())
 +      return SSL_TLSEXT_ERR_NOACK;
 +
 +    p->sni_context_.Dispose();
 +    p->sni_context_.Reset(node_isolate, ctx);
 +
 +    SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
++    InitNPN(sc, p);
 +    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,
 +                              Handle<Value> unused,
 +                              Handle<Context> context) {
 +  Environment* env = Environment::GetCurrent(context);
 +
 +  NODE_SET_METHOD(target, "wrap", TLSCallbacks::Wrap);
 +
 +  Local<FunctionTemplate> t = FunctionTemplate::New();
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
 +  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "TLSWrap"));
 +
 +  NODE_SET_PROTOTYPE_METHOD(t, "start", Start);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "enableSessionCallbacks",
 +                            EnableSessionCallbacks);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "enableHelloParser",
 +                            EnableHelloParser);
 +
 +  SSLWrap<TLSCallbacks>::AddMethods(t);
 +
 +#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
 +
 +  env->set_tls_wrap_constructor_function(t->GetFunction());
 +}
 +
 +}  // namespace node
 +
 +NODE_MODULE_CONTEXT_AWARE(node_tls_wrap, node::TLSCallbacks::Initialize)