~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());
}
-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;
}
}
-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);
}
}
--- /dev/null
- 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)