From b9a0eb0688104f3619e75e1b9a698080e10d1ec7 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Thu, 15 Aug 2013 19:28:26 +0400 Subject: [PATCH] tls, crypto: deduplicate code Commit 03e008d introduced src/tls_wrap.cc and src/tls_wrap.h but said files copied on the order of 1 kLoC from src/node_crypto.cc and src/node_crypto.h. This commit undoes some of the duplication. Fixes #6024. --- lib/_tls_legacy.js | 1 + src/node_crypto.cc | 1875 ++++++++++++++++++++++++++-------------------------- src/node_crypto.h | 154 +++-- src/tls_wrap.cc | 670 ++----------------- src/tls_wrap.h | 63 +- 5 files changed, 1095 insertions(+), 1668 deletions(-) diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js index cb71920..ae9e72c 100644 --- a/lib/_tls_legacy.js +++ b/lib/_tls_legacy.js @@ -604,6 +604,7 @@ function onclienthello(hello) { if (err) return self.socket.destroy(err); self.ssl.loadSession(session); + self.ssl.endParser(); // Cycle data self._resumingSession = false; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0344d20..7f7de32 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -25,6 +25,7 @@ #include "node_crypto_bio.h" #include "node_crypto_groups.h" #include "node_root_certs.h" +#include "tls_wrap.h" // TLSCallbacks #include "string_bytes.h" #include "v8.h" @@ -68,6 +69,7 @@ namespace node { namespace crypto { using v8::Array; +using v8::Boolean; using v8::Exception; using v8::False; using v8::FunctionCallbackInfo; @@ -109,11 +111,39 @@ static Cached onhandshakedone_sym; static Cached onclienthello_sym; static Cached onnewsession_sym; static Cached sessionid_sym; +static Cached servername_sym; +static Cached tls_ticket_sym; static Persistent secure_context_constructor; static uv_rwlock_t* locks; +// Just to generate static methods +template class SSLWrap; +template void SSLWrap::AddMethods(Handle t); +template SSL_SESSION* SSLWrap::GetSessionCallback( + SSL* s, + unsigned char* key, + int len, + int* copy); +template int SSLWrap::NewSessionCallback(SSL* s, + SSL_SESSION* sess); +template void SSLWrap::OnClientHello( + void* arg, + const ClientHelloParser::ClientHello& hello); +template int SSLWrap::AdvertiseNextProtoCallback( + SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg); +template int SSLWrap::SelectNextProtoCallback( + SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); + static void crypto_threadid_cb(CRYPTO_THREADID* tid) { CRYPTO_THREADID_set_numeric(tid, uv_thread_self()); @@ -279,63 +309,13 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); - SSL_CTX_sess_set_get_cb(sc->ctx_, GetSessionCallback); - SSL_CTX_sess_set_new_cb(sc->ctx_, NewSessionCallback); + SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap::GetSessionCallback); + SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap::NewSessionCallback); sc->ca_store_ = NULL; } -SSL_SESSION* SecureContext::GetSessionCallback(SSL* s, - unsigned char* key, - int len, - int* copy) { - HandleScope scope(node_isolate); - - Connection* conn = static_cast(SSL_get_app_data(s)); - - *copy = 0; - SSL_SESSION* sess = conn->next_sess_; - conn->next_sess_ = NULL; - - return sess; -} - - -int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { - HandleScope scope(node_isolate); - - Connection* conn = static_cast(SSL_get_app_data(s)); - - // Check if session is small enough to be stored - int size = i2d_SSL_SESSION(sess, NULL); - if (size > kMaxSessionSize) return 0; - - // Serialize session - char* serialized = new char[size]; - unsigned char* pserialized = reinterpret_cast(serialized); - memset(serialized, 0, size); - i2d_SSL_SESSION(sess, &pserialized); - - Handle argv[2] = { - Buffer::New(reinterpret_cast(sess->session_id), - sess->session_id_length), - Buffer::Use(serialized, size) - }; - - if (onnewsession_sym.IsEmpty()) { - onnewsession_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onnewsession"); - } - - MakeCallback(conn->handle(node_isolate), - onnewsession_sym, - ARRAY_SIZE(argv), - argv); - - return 0; -} - - // Takes a string or buffer and loads it into a BIO. // Caller responsible for BIO_free_all-ing the returned object. static BIO* LoadBIO(Handle v) { @@ -809,292 +789,483 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { } -void Connection::OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello) { +template +void SSLWrap::AddMethods(Handle t) { HandleScope scope(node_isolate); - Connection* conn = static_cast(arg); - - if (onclienthello_sym.IsEmpty()) - onclienthello_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onclienthello"); - if (sessionid_sym.IsEmpty()) - sessionid_sym = FIXED_ONE_BYTE_STRING(node_isolate, "sessionId"); - Local obj = Object::New(); - obj->Set(sessionid_sym, - Buffer::New(reinterpret_cast(hello.session_id()), - hello.session_size())); + 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); - Handle argv[1] = { obj }; - MakeCallback(conn->handle(node_isolate), - onclienthello_sym, - ARRAY_SIZE(argv), - argv); +#ifdef OPENSSL_NPN_NEGOTIATED + NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto); + NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", SetNPNProtocols); +#endif // OPENSSL_NPN_NEGOTIATED } -void Connection::OnClientHelloParseEnd(void* arg) { - Connection* conn = static_cast(arg); +template +SSL_SESSION* SSLWrap::GetSessionCallback(SSL* s, + unsigned char* key, + int len, + int* copy) { + HandleScope scope(node_isolate); - // Write all accumulated data - int r = BIO_write(conn->bio_read_, - reinterpret_cast(conn->hello_data_), - conn->hello_offset_); - conn->HandleBIOError(conn->bio_read_, "BIO_write", r); - conn->SetShutdownFlags(); -} + Base* w = static_cast(SSL_get_app_data(s)); + *copy = 0; + SSL_SESSION* sess = w->next_sess_; + w->next_sess_ = NULL; -#ifdef SSL_PRINT_DEBUG -# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) -#else -# define DEBUG_PRINT(...) -#endif + return sess; +} -int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { - if (rv >= 0) return rv; +template +int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { + HandleScope scope(node_isolate); - int retry = BIO_should_retry(bio); - (void) retry; // unused if !defined(SSL_PRINT_DEBUG) + Base* w = static_cast(SSL_get_app_data(s)); - 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(node_isolate); - Local exception = - Exception::Error(OneByteString(node_isolate, ssl_error_buf)); - handle(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); - - DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", - ssl_, - func, - rv, - ssl_error_buf); + // Serialize session + Local buff = Buffer::New(size); + unsigned char* serialized = reinterpret_cast( + Buffer::Data(buff)); + memset(serialized, 0, size); + i2d_SSL_SESSION(sess, &serialized); - return rv; - } + Local session = Buffer::New(reinterpret_cast(sess->session_id), + sess->session_id_length); + Local argv[] = { session, buff }; + if (onnewsession_sym.IsEmpty()) + onnewsession_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onnewsession"); + MakeCallback(w->handle(node_isolate), + onnewsession_sym, + ARRAY_SIZE(argv), + argv); return 0; } -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; +template +void SSLWrap::OnClientHello(void* arg, + const ClientHelloParser::ClientHello& hello) { + HandleScope scope(node_isolate); - int err = SSL_get_error(ssl_, rv); + Base* w = static_cast(arg); - if (err == SSL_ERROR_NONE) { - return 0; + if (onclienthello_sym.IsEmpty()) + onclienthello_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onclienthello"); + if (sessionid_sym.IsEmpty()) + sessionid_sym = FIXED_ONE_BYTE_STRING(node_isolate, "sessionId"); + if (servername_sym.IsEmpty()) + servername_sym = FIXED_ONE_BYTE_STRING(node_isolate, "servername"); + if (tls_ticket_sym.IsEmpty()) + tls_ticket_sym = FIXED_ONE_BYTE_STRING(node_isolate, "tlsTicket"); + + Local hello_obj = Object::New(); + Local buff = Buffer::New( + reinterpret_cast(hello.session_id()), + hello.session_size()); + hello_obj->Set(sessionid_sym, buff); + if (hello.servername() == NULL) { + hello_obj->Set(servername_sym, String::Empty(node_isolate)); + } else { + Local servername = OneByteString(node_isolate, + hello.servername(), + hello.servername_size()); + hello_obj->Set(servername_sym, servername); + } + hello_obj->Set(tls_ticket_sym, Boolean::New(hello.has_ticket())); - } else if (err == SSL_ERROR_WANT_WRITE) { - DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func); - return 0; + Local argv[] = { hello_obj }; + MakeCallback(w->handle(node_isolate), + onclienthello_sym, + ARRAY_SIZE(argv), + argv); +} - } else if (err == SSL_ERROR_WANT_READ) { - DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func); - return 0; - } else if (err == SSL_ERROR_ZERO_RETURN) { - Local exception = - Exception::Error(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN")); - handle(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); - return rv; +// TODO(indutny): Split it into multiple smaller functions +template +void SSLWrap::GetPeerCertificate( + const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); - } else if ((err == SSL_ERROR_SYSCALL) && (ss == kIgnoreSyscall)) { - return 0; + Base* w = ObjectWrap::Unwrap(args.This()); - } else { - HandleScope scope(node_isolate); + Local info = Object::New(); + X509* peer_cert = SSL_get_peer_certificate(w->ssl_); + if (peer_cert != NULL) { + BIO* bio = BIO_new(BIO_s_mem()); BUF_MEM* mem; - BIO *bio; - - assert(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL); + if (X509_NAME_print_ex(bio, + X509_get_subject_name(peer_cert), + 0, + X509_NAME_FLAGS) > 0) { + BIO_get_mem_ptr(bio, &mem); + info->Set(subject_symbol, + OneByteString(node_isolate, mem->data, mem->length)); + } + (void) BIO_reset(bio); - // 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 - if ((bio = BIO_new(BIO_s_mem()))) { - ERR_print_errors(bio); + X509_NAME* issuer_name = X509_get_issuer_name(peer_cert); + if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) { BIO_get_mem_ptr(bio, &mem); - Local exception = - Exception::Error(OneByteString(node_isolate, mem->data, mem->length)); - handle(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); - BIO_free_all(bio); + info->Set(issuer_symbol, + OneByteString(node_isolate, mem->data, mem->length)); } + (void) BIO_reset(bio); - return rv; - } + int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1); + if (index >= 0) { + X509_EXTENSION* ext; + int rv; - return 0; -} + ext = X509_get_ext(peer_cert, index); + assert(ext != NULL); + rv = X509V3_EXT_print(bio, ext, 0, 0); + assert(rv == 1); -void Connection::ClearError() { -#ifndef NDEBUG - HandleScope scope(node_isolate); + BIO_get_mem_ptr(bio, &mem); + info->Set(subjectaltname_symbol, + OneByteString(node_isolate, mem->data, mem->length)); - // We should clear the error in JS-land - Local error_key = FIXED_ONE_BYTE_STRING(node_isolate, "error"); - Local error = handle(node_isolate)->Get(error_key); - assert(error->BooleanValue() == false); -#endif // NDEBUG -} + (void) BIO_reset(bio); + } + EVP_PKEY* pkey = X509_get_pubkey(peer_cert); + RSA* rsa = NULL; + if (pkey != NULL) + rsa = EVP_PKEY_get1_RSA(pkey); -void Connection::SetShutdownFlags() { - HandleScope scope(node_isolate); + if (rsa != NULL) { + BN_print(bio, rsa->n); + BIO_get_mem_ptr(bio, &mem); + info->Set(modulus_symbol, + OneByteString(node_isolate, mem->data, mem->length)); + (void) BIO_reset(bio); - int flags = SSL_get_shutdown(ssl_); + BN_print(bio, rsa->e); + BIO_get_mem_ptr(bio, &mem); + info->Set(exponent_symbol, + OneByteString(node_isolate, mem->data, mem->length)); + (void) BIO_reset(bio); + } - if (flags & SSL_SENT_SHUTDOWN) { - Local sent_shutdown_key = - FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown"); - handle(node_isolate)->Set(sent_shutdown_key, True(node_isolate)); - } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + if (rsa != NULL) { + RSA_free(rsa); + rsa = NULL; + } - if (flags & SSL_RECEIVED_SHUTDOWN) { - Local received_shutdown_key = - FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown"); - handle(node_isolate)->Set(received_shutdown_key, True(node_isolate)); - } -} + ASN1_TIME_print(bio, X509_get_notBefore(peer_cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(valid_from_symbol, + OneByteString(node_isolate, mem->data, mem->length)); + (void) BIO_reset(bio); + ASN1_TIME_print(bio, X509_get_notAfter(peer_cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(valid_to_symbol, + OneByteString(node_isolate, mem->data, mem->length)); + BIO_free_all(bio); -void Connection::Initialize(Handle target) { - HandleScope scope(node_isolate); + unsigned int md_size, i; + unsigned char md[EVP_MAX_MD_SIZE]; + if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) { + const char hex[] = "0123456789ABCDEF"; + char fingerprint[EVP_MAX_MD_SIZE * 3]; - Local t = FunctionTemplate::New(Connection::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Connection")); + // TODO(indutny): Unify it with buffer's code + for (i = 0; i < md_size; i++) { + fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; + fingerprint[(3*i)+2] = ':'; + } - 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, - "getPeerCertificate", - Connection::GetPeerCertificate); - NODE_SET_PROTOTYPE_METHOD(t, "getSession", Connection::GetSession); - NODE_SET_PROTOTYPE_METHOD(t, "setSession", Connection::SetSession); - NODE_SET_PROTOTYPE_METHOD(t, "loadSession", Connection::LoadSession); - NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", Connection::IsSessionReused); - NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", Connection::IsInitFinished); - NODE_SET_PROTOTYPE_METHOD(t, "verifyError", Connection::VerifyError); - NODE_SET_PROTOTYPE_METHOD(t, - "getCurrentCipher", - Connection::GetCurrentCipher); - NODE_SET_PROTOTYPE_METHOD(t, "start", Connection::Start); - NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Connection::Shutdown); - NODE_SET_PROTOTYPE_METHOD(t, - "receivedShutdown", - Connection::ReceivedShutdown); - NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close); + if (md_size > 0) { + fingerprint[(3*(md_size-1))+2] = '\0'; + } else { + fingerprint[0] = '\0'; + } -#ifdef OPENSSL_NPN_NEGOTIATED - NODE_SET_PROTOTYPE_METHOD(t, - "getNegotiatedProtocol", - Connection::GetNegotiatedProto); - NODE_SET_PROTOTYPE_METHOD(t, - "setNPNProtocols", - Connection::SetNPNProtocols); -#endif + info->Set(fingerprint_symbol, OneByteString(node_isolate, fingerprint)); + } + STACK_OF(ASN1_OBJECT)* eku = static_cast( + X509_get_ext_d2i(peer_cert, NID_ext_key_usage, NULL, NULL)); + if (eku != NULL) { + Local ext_key_usage = Array::New(); + char buf[256]; -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername); - NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback", Connection::SetSNICallback); + int j = 0; + for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { + if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku, i), 1) >= 0) + ext_key_usage->Set(j++, OneByteString(node_isolate, buf)); + } + + sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); + info->Set(ext_key_usage_symbol, ext_key_usage); + } + + X509_free(peer_cert); + } + + args.GetReturnValue().Set(info); +} + + +template +void SSLWrap::GetSession(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + SSL_SESSION* sess = SSL_get_session(w->ssl_); + if (sess == NULL) + return; + + int slen = i2d_SSL_SESSION(sess, NULL); + assert(slen > 0); + + unsigned char* sbuf = new unsigned char[slen]; + unsigned char* p = sbuf; + i2d_SSL_SESSION(sess, &p); + args.GetReturnValue().Set(Encode(sbuf, slen, BINARY)); + delete[] sbuf; +} + + +template +void SSLWrap::SetSession(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + if (args.Length() < 1 || + (!args[0]->IsString() && !Buffer::HasInstance(args[0]))) { + return ThrowTypeError("Bad argument"); + } + + ASSERT_IS_BUFFER(args[0]); + ssize_t slen = Buffer::Length(args[0]); + + if (slen < 0) + return ThrowTypeError("Bad argument"); + + char* sbuf = new char[slen]; + + ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY); + assert(wlen == slen); + + const unsigned char* p = reinterpret_cast(sbuf); + SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen); + + delete[] sbuf; + + if (sess == NULL) + return; + + int r = SSL_set_session(w->ssl_, sess); + SSL_SESSION_free(sess); + + if (!r) + return ThrowError("SSL_set_session error"); +} + + +template +void SSLWrap::LoadSession(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { + ssize_t slen = Buffer::Length(args[0]); + char* sbuf = Buffer::Data(args[0]); + + const unsigned char* p = reinterpret_cast(sbuf); + SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, slen); + + // Setup next session and move hello to the BIO buffer + if (w->next_sess_ != NULL) + SSL_SESSION_free(w->next_sess_); + w->next_sess_ = sess; + + Local info = Object::New(); +#ifndef OPENSSL_NO_TLSEXT + if (servername_sym.IsEmpty()) + servername_sym = FIXED_ONE_BYTE_STRING(node_isolate, "servername"); + if (sess->tlsext_hostname == NULL) { + info->Set(servername_sym, False(node_isolate)); + } else { + info->Set(servername_sym, + OneByteString(node_isolate, sess->tlsext_hostname)); + } #endif + args.GetReturnValue().Set(info); + } +} - target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"), - t->GetFunction()); + +template +void SSLWrap::IsSessionReused(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + Base* w = ObjectWrap::Unwrap(args.This()); + bool yes = SSL_session_reused(w->ssl_); + args.GetReturnValue().Set(yes); } -static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) { - // Quoting SSL_set_verify(3ssl): - // - // The VerifyCallback function is used to control the behaviour when - // the SSL_VERIFY_PEER flag is set. It must be supplied by the - // application and receives two arguments: preverify_ok indicates, - // whether the verification of the certificate in question was passed - // (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to - // the complete context used for the certificate chain verification. - // - // The certificate chain is checked starting with the deepest nesting - // level (the root CA certificate) and worked upward to the peer's - // certificate. At each level signatures and issuer attributes are - // checked. Whenever a verification error is found, the error number is - // stored in x509_ctx and VerifyCallback is called with preverify_ok=0. - // By applying X509_CTX_store_* functions VerifyCallback can locate the - // certificate in question and perform additional steps (see EXAMPLES). - // If no error is found for a certificate, VerifyCallback is called - // with preverify_ok=1 before advancing to the next level. - // - // The return value of VerifyCallback controls the strategy of the - // further verification process. If VerifyCallback returns 0, the - // verification process is immediately stopped with "verification - // failed" state. If SSL_VERIFY_PEER is set, a verification failure - // alert is sent to the peer and the TLS/SSL handshake is terminated. If - // VerifyCallback returns 1, the verification process is continued. If - // VerifyCallback always returns 1, the TLS/SSL handshake will not be - // terminated with respect to verification failures and the connection - // will be established. The calling process can however retrieve the - // error code of the last verification error using - // SSL_get_verify_result(3) or by maintaining its own error storage - // managed by VerifyCallback. - // - // If no VerifyCallback is specified, the default callback will be - // used. Its return value is identical to preverify_ok, so that any - // verification failure will lead to a termination of the TLS/SSL - // handshake with an alert message, if SSL_VERIFY_PEER is set. - // - // Since we cannot perform I/O quickly enough in this callback, we ignore - // all preverify_ok errors and let the handshake continue. It is - // imparative that the user use Connection::VerifyError after the - // 'secure' callback has been made. - return 1; +template +void SSLWrap::ReceivedShutdown(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + Base* w = ObjectWrap::Unwrap(args.This()); + bool yes = SSL_get_shutdown(w->ssl_) == SSL_RECEIVED_SHUTDOWN; + args.GetReturnValue().Set(yes); } -#ifdef OPENSSL_NPN_NEGOTIATED -int Connection::AdvertiseNextProtoCallback_(SSL *s, - const unsigned char** data, - unsigned int *len, - void *arg) { - Connection* conn = static_cast(SSL_get_app_data(s)); +template +void SSLWrap::EndParser(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + w->hello_parser_.End(); +} + + +template +void SSLWrap::IsInitFinished(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + Base* w = ObjectWrap::Unwrap(args.This()); + bool yes = SSL_is_init_finished(w->ssl_); + args.GetReturnValue().Set(yes); +} + + +#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break; +template +void SSLWrap::VerifyError(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + // XXX(indutny) Do this check in JS land? + X509* peer_cert = SSL_get_peer_certificate(w->ssl_); + if (peer_cert == NULL) { + // We requested a certificate and they did not send us one. + // Definitely an error. + // XXX(indutny) is this the right error message? + Local s = + FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT"); + return args.GetReturnValue().Set(Exception::Error(s)); + } + X509_free(peer_cert); + + long x509_verify_error = SSL_get_verify_result(w->ssl_); + + const char* reason = NULL; + Local s; + switch (x509_verify_error) { + case X509_V_OK: + return args.GetReturnValue().SetNull(); + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) + CASE_X509_ERR(UNABLE_TO_GET_CRL) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) + CASE_X509_ERR(CERT_SIGNATURE_FAILURE) + CASE_X509_ERR(CRL_SIGNATURE_FAILURE) + CASE_X509_ERR(CERT_NOT_YET_VALID) + CASE_X509_ERR(CERT_HAS_EXPIRED) + CASE_X509_ERR(CRL_NOT_YET_VALID) + CASE_X509_ERR(CRL_HAS_EXPIRED) + CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) + CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) + CASE_X509_ERR(OUT_OF_MEM) + CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) + CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) + CASE_X509_ERR(CERT_CHAIN_TOO_LONG) + CASE_X509_ERR(CERT_REVOKED) + CASE_X509_ERR(INVALID_CA) + CASE_X509_ERR(PATH_LENGTH_EXCEEDED) + CASE_X509_ERR(INVALID_PURPOSE) + CASE_X509_ERR(CERT_UNTRUSTED) + CASE_X509_ERR(CERT_REJECTED) + default: + s = OneByteString(node_isolate, + X509_verify_cert_error_string(x509_verify_error)); + break; + } + + if (s.IsEmpty()) + s = OneByteString(node_isolate, reason); + + args.GetReturnValue().Set(Exception::Error(s)); +} +#undef CASE_X509_ERR - if (conn->npnProtos_.IsEmpty()) { + +template +void SSLWrap::GetCurrentCipher(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + OPENSSL_CONST SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_); + if (c == NULL) + return; + + Local info = Object::New(); + const char* cipher_name = SSL_CIPHER_get_name(c); + info->Set(name_symbol, OneByteString(node_isolate, cipher_name)); + const char* cipher_version = SSL_CIPHER_get_version(c); + info->Set(version_symbol, OneByteString(node_isolate, cipher_version)); + args.GetReturnValue().Set(info); +} + + +#ifdef OPENSSL_NPN_NEGOTIATED +template +int SSLWrap::AdvertiseNextProtoCallback(SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg) { + Base* w = static_cast(arg); + + if (w->npn_protos_.IsEmpty()) { // No initialization - no NPN protocols *data = reinterpret_cast(""); *len = 0; } else { - Local obj = PersistentToLocal(node_isolate, conn->npnProtos_); + Local obj = PersistentToLocal(node_isolate, w->npn_protos_); *data = reinterpret_cast(Buffer::Data(obj)); *len = Buffer::Length(obj); } @@ -1102,900 +1273,744 @@ int Connection::AdvertiseNextProtoCallback_(SSL *s, return SSL_TLSEXT_ERR_OK; } -int Connection::SelectNextProtoCallback_(SSL *s, - unsigned char** out, unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, void *arg) { - Connection* conn = static_cast(SSL_get_app_data(s)); + +template +int SSLWrap::SelectNextProtoCallback(SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg) { + Base* w = static_cast(arg); // Release old protocol handler if present - conn->selectedNPNProto_.Dispose(); + w->selected_npn_proto_.Dispose(); - if (conn->npnProtos_.IsEmpty()) { + if (w->npn_protos_.IsEmpty()) { // We should at least select one protocol // If server is using NPN *out = reinterpret_cast(const_cast("http/1.1")); *outlen = 8; - // set status unsupported - conn->selectedNPNProto_.Reset(node_isolate, False(node_isolate)); + // set status: unsupported + w->selected_npn_proto_.Reset(node_isolate, False(node_isolate)); return SSL_TLSEXT_ERR_OK; } - Local obj = PersistentToLocal(node_isolate, conn->npnProtos_); - const unsigned char* npnProtos = + Local obj = PersistentToLocal(node_isolate, w->npn_protos_); + const unsigned char* npn_protos = reinterpret_cast(Buffer::Data(obj)); + size_t len = Buffer::Length(obj); - int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos, - Buffer::Length(obj)); - + int status = SSL_select_next_proto(out, outlen, in, inlen, npn_protos, len); + Handle result; switch (status) { case OPENSSL_NPN_UNSUPPORTED: - conn->selectedNPNProto_.Reset(node_isolate, Null(node_isolate)); + result = Null(node_isolate); break; case OPENSSL_NPN_NEGOTIATED: - { - Local string = OneByteString(node_isolate, *out, *outlen); - conn->selectedNPNProto_.Reset(node_isolate, string); - break; - } + result = OneByteString(node_isolate, *out, *outlen); + break; case OPENSSL_NPN_NO_OVERLAP: - conn->selectedNPNProto_.Reset(node_isolate, False(node_isolate)); + result = False(node_isolate); break; default: break; } + if (!result.IsEmpty()) + w->selected_npn_proto_.Reset(node_isolate, result); + return SSL_TLSEXT_ERR_OK; } -#endif -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB -int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { - HandleScope scope(node_isolate); - Connection* conn = static_cast(SSL_get_app_data(s)); +template +void SSLWrap::GetNegotiatedProto( + const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); - const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + Base* w = ObjectWrap::Unwrap(args.This()); - if (servername) { - conn->servername_.Reset(node_isolate, - OneByteString(node_isolate, servername)); + if (w->is_client()) { + if (w->selected_npn_proto_.IsEmpty() == false) { + args.GetReturnValue().Set(w->selected_npn_proto_); + } + return; + } - // Call the SNI callback and use its return value as context - if (!conn->sniObject_.IsEmpty()) { - conn->sniContext_.Dispose(); + const unsigned char* npn_proto; + unsigned int npn_proto_len; - Local arg = PersistentToLocal(node_isolate, conn->servername_); - Local ret = MakeCallback(conn->sniObject_, "onselect", 1, &arg); + SSL_get0_next_proto_negotiated(w->ssl_, &npn_proto, &npn_proto_len); - // If ret is SecureContext - if (HasInstance(secure_context_constructor, ret)) { - conn->sniContext_.Reset(node_isolate, ret); - SecureContext* sc = ObjectWrap::Unwrap(ret.As()); - SSL_set_SSL_CTX(s, sc->ctx_); - } else { - return SSL_TLSEXT_ERR_NOACK; - } - } - } + if (!npn_proto) + return args.GetReturnValue().Set(false); - return SSL_TLSEXT_ERR_OK; + args.GetReturnValue().Set( + OneByteString(node_isolate, npn_proto, npn_proto_len)); } -#endif -void Connection::New(const FunctionCallbackInfo& args) { + +template +void SSLWrap::SetNPNProtocols(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); - Connection* conn = new Connection; - conn->Wrap(args.This()); + Base* w = ObjectWrap::Unwrap(args.This()); - if (args.Length() < 1 || !args[0]->IsObject()) { - return ThrowError("First argument must be a crypto module Credentials"); - } + if (args.Length() < 1 || !Buffer::HasInstance(args[0])) + return ThrowTypeError("Must give a Buffer as first argument"); - SecureContext *sc = ObjectWrap::Unwrap(args[0]->ToObject()); + w->npn_protos_.Reset(node_isolate, args[0].As()); +} +#endif // OPENSSL_NPN_NEGOTIATED - bool is_server = args[1]->BooleanValue(); - conn->ssl_ = SSL_new(sc->ctx_); - conn->bio_read_ = BIO_new(NodeBIO::GetMethod()); - conn->bio_write_ = BIO_new(NodeBIO::GetMethod()); +void Connection::OnClientHelloParseEnd(void* arg) { + Connection* conn = static_cast(arg); - SSL_set_app_data(conn->ssl_, conn); + // Write all accumulated data + int r = BIO_write(conn->bio_read_, + reinterpret_cast(conn->hello_data_), + conn->hello_offset_); + conn->HandleBIOError(conn->bio_read_, "BIO_write", r); + conn->SetShutdownFlags(); +} - 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_, - AdvertiseNextProtoCallback_, - NULL); - } else { - // Client should select protocol from advertised - // If server supports NPN - SSL_CTX_set_next_proto_select_cb(sc->ctx_, - SelectNextProtoCallback_, - NULL); - } +#ifdef SSL_PRINT_DEBUG +# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) +#else +# define DEBUG_PRINT(...) #endif -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - if (is_server) { - SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_); - } else { - const String::Utf8Value servername(args[2]); - SSL_set_tlsext_host_name(conn->ssl_, *servername); - } -#endif - SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_); +int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { + if (rv >= 0) return rv; -#ifdef SSL_MODE_RELEASE_BUFFERS - long mode = SSL_get_mode(conn->ssl_); - SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS); -#endif + int retry = BIO_should_retry(bio); + (void) retry; // unused if !defined(SSL_PRINT_DEBUG) + if (BIO_should_write(bio)) { + DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n", + ssl_, + func, + retry); + return 0; + + } else if (BIO_should_read(bio)) { + DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry); + return 0; - 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; - } } else { - // Note request_cert and reject_unauthorized are ignored for clients. - verify_mode = SSL_VERIFY_NONE; - } + static char ssl_error_buf[512]; + ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf)); + HandleScope scope(node_isolate); + Local exception = + Exception::Error(OneByteString(node_isolate, ssl_error_buf)); + handle(node_isolate)->Set( + FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); - // Always allow a connection. We'll reject in javascript. - SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback); + DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", + ssl_, + func, + rv, + ssl_error_buf); - if ((conn->is_server_ = is_server)) { - SSL_set_accept_state(conn->ssl_); - } else { - SSL_set_connect_state(conn->ssl_); + return rv; } + + return 0; } -void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) { - // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants - // a non-const SSL* in OpenSSL <= 0.9.7e. - SSL* ssl = const_cast(ssl_); - if (where & SSL_CB_HANDSHAKE_START) { - HandleScope scope(node_isolate); - Connection* conn = static_cast(SSL_get_app_data(ssl)); - if (onhandshakestart_sym.IsEmpty()) { - onhandshakestart_sym = - FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakestart"); - } - MakeCallback(conn->handle(node_isolate), onhandshakestart_sym, 0, NULL); - } - if (where & SSL_CB_HANDSHAKE_DONE) { - HandleScope scope(node_isolate); - Connection* conn = static_cast(SSL_get_app_data(ssl)); - if (onhandshakedone_sym.IsEmpty()) { - onhandshakedone_sym = - FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakedone"); - } - MakeCallback(conn->handle(node_isolate), onhandshakedone_sym, 0, NULL); - } -} +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; -void Connection::EncIn(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); + int err = SSL_get_error(ssl_, rv); - Connection* conn = Connection::Unwrap(args.This()); + if (err == SSL_ERROR_NONE) { + return 0; - if (args.Length() < 3) { - return ThrowTypeError("Takes 3 parameters"); - } + } else if (err == SSL_ERROR_WANT_WRITE) { + DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func); + return 0; - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("Second argument should be a buffer"); - } + } else if (err == SSL_ERROR_WANT_READ) { + DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func); + return 0; - char* buffer_data = Buffer::Data(args[0]); - size_t buffer_length = Buffer::Length(args[0]); + } else if (err == SSL_ERROR_ZERO_RETURN) { + Local exception = + Exception::Error(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN")); + handle(node_isolate)->Set( + FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); + return rv; - size_t off = args[1]->Int32Value(); - size_t len = args[2]->Int32Value(); - if (off + len > buffer_length) { - return ThrowError("off + len > buffer.length"); - } + } else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) { + return 0; - int bytes_written; - char* data = buffer_data + off; + } else { + HandleScope scope(node_isolate); + BUF_MEM* mem; + BIO *bio; - if (conn->is_server_ && !conn->hello_parser_.IsEnded()) { - // Just accumulate data, everything will be pushed to BIO later - if (conn->hello_parser_.IsPaused()) { - bytes_written = 0; - } else { - // Copy incoming data to the internal buffer - // (which has a size of the biggest possible TLS frame) - size_t available = sizeof(conn->hello_data_) - conn->hello_offset_; - size_t copied = len < available ? len : available; - memcpy(conn->hello_data_ + conn->hello_offset_, data, copied); - conn->hello_offset_ += copied; + assert(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL); - conn->hello_parser_.Parse(conn->hello_data_, conn->hello_offset_); - bytes_written = copied; + // 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 exception = + Exception::Error(OneByteString(node_isolate, mem->data, mem->length)); + handle(node_isolate)->Set( + FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); + BIO_free_all(bio); } - } else { - bytes_written = BIO_write(conn->bio_read_, data, len); - conn->HandleBIOError(conn->bio_read_, "BIO_write", bytes_written); - conn->SetShutdownFlags(); + + return rv; } - args.GetReturnValue().Set(bytes_written); + return 0; } -void Connection::ClearOut(const FunctionCallbackInfo& args) { +void Connection::ClearError() { +#ifndef NDEBUG HandleScope scope(node_isolate); - Connection* conn = Connection::Unwrap(args.This()); + // We should clear the error in JS-land + Local error_key = FIXED_ONE_BYTE_STRING(node_isolate, "error"); + Local error = handle(node_isolate)->Get(error_key); + assert(error->BooleanValue() == false); +#endif // NDEBUG +} - if (args.Length() < 3) { - return ThrowTypeError("Takes 3 parameters"); - } - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("Second argument should be a buffer"); - } +void Connection::SetShutdownFlags() { + HandleScope scope(node_isolate); - char* buffer_data = Buffer::Data(args[0]); - size_t buffer_length = Buffer::Length(args[0]); + int flags = SSL_get_shutdown(ssl_); - size_t off = args[1]->Int32Value(); - size_t len = args[2]->Int32Value(); - if (off + len > buffer_length) { - return ThrowError("off + len > buffer.length"); + if (flags & SSL_SENT_SHUTDOWN) { + Local sent_shutdown_key = + FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown"); + handle(node_isolate)->Set(sent_shutdown_key, True(node_isolate)); } - if (!SSL_is_init_finished(conn->ssl_)) { - int rv; - - if (conn->is_server_) { - rv = SSL_accept(conn->ssl_); - conn->HandleSSLError("SSL_accept:ClearOut", - rv, - kZeroIsAnError, - kSyscallError); - } else { - rv = SSL_connect(conn->ssl_); - conn->HandleSSLError("SSL_connect:ClearOut", - rv, - kZeroIsAnError, - kSyscallError); - } - - if (rv < 0) { - return args.GetReturnValue().Set(rv); - } + if (flags & SSL_RECEIVED_SHUTDOWN) { + Local received_shutdown_key = + FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown"); + handle(node_isolate)->Set(received_shutdown_key, True(node_isolate)); } - - int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len); - conn->HandleSSLError("SSL_read:ClearOut", - bytes_read, - kZeroIsNotAnError, - kSyscallError); - conn->SetShutdownFlags(); - - args.GetReturnValue().Set(bytes_read); } -void Connection::ClearPending(const FunctionCallbackInfo& args) { +void Connection::Initialize(Handle target) { HandleScope scope(node_isolate); - Connection* conn = Connection::Unwrap(args.This()); - int bytes_pending = BIO_pending(conn->bio_read_); - args.GetReturnValue().Set(bytes_pending); -} + Local t = FunctionTemplate::New(Connection::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Connection")); -void Connection::EncPending(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - Connection* conn = Connection::Unwrap(args.This()); - int bytes_pending = BIO_pending(conn->bio_write_); - args.GetReturnValue().Set(bytes_pending); -} - - -void Connection::EncOut(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - Connection* conn = Connection::Unwrap(args.This()); + 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); - if (args.Length() < 3) { - return ThrowTypeError("Takes 3 parameters"); - } + SSLWrap::AddMethods(t); - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("Second argument should be a buffer"); - } +#ifdef OPENSSL_NPN_NEGOTIATED + NODE_SET_PROTOTYPE_METHOD(t, + "getNegotiatedProtocol", + Connection::GetNegotiatedProto); + NODE_SET_PROTOTYPE_METHOD(t, + "setNPNProtocols", + Connection::SetNPNProtocols); +#endif - char* buffer_data = Buffer::Data(args[0]); - size_t buffer_length = Buffer::Length(args[0]); - size_t off = args[1]->Int32Value(); - size_t len = args[2]->Int32Value(); - if (off + len > buffer_length) { - return ThrowError("off + len > buffer.length"); - } +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername); + NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback", Connection::SetSNICallback); +#endif - int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len); + target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"), + t->GetFunction()); +} - conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read); - conn->SetShutdownFlags(); - args.GetReturnValue().Set(bytes_read); +int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { + // Quoting SSL_set_verify(3ssl): + // + // The VerifyCallback function is used to control the behaviour when + // the SSL_VERIFY_PEER flag is set. It must be supplied by the + // application and receives two arguments: preverify_ok indicates, + // whether the verification of the certificate in question was passed + // (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to + // the complete context used for the certificate chain verification. + // + // The certificate chain is checked starting with the deepest nesting + // level (the root CA certificate) and worked upward to the peer's + // certificate. At each level signatures and issuer attributes are + // checked. Whenever a verification error is found, the error number is + // stored in x509_ctx and VerifyCallback is called with preverify_ok=0. + // By applying X509_CTX_store_* functions VerifyCallback can locate the + // certificate in question and perform additional steps (see EXAMPLES). + // If no error is found for a certificate, VerifyCallback is called + // with preverify_ok=1 before advancing to the next level. + // + // The return value of VerifyCallback controls the strategy of the + // further verification process. If VerifyCallback returns 0, the + // verification process is immediately stopped with "verification + // failed" state. If SSL_VERIFY_PEER is set, a verification failure + // alert is sent to the peer and the TLS/SSL handshake is terminated. If + // VerifyCallback returns 1, the verification process is continued. If + // VerifyCallback always returns 1, the TLS/SSL handshake will not be + // terminated with respect to verification failures and the connection + // will be established. The calling process can however retrieve the + // error code of the last verification error using + // SSL_get_verify_result(3) or by maintaining its own error storage + // managed by VerifyCallback. + // + // If no VerifyCallback is specified, the default callback will be + // used. Its return value is identical to preverify_ok, so that any + // verification failure will lead to a termination of the TLS/SSL + // handshake with an alert message, if SSL_VERIFY_PEER is set. + // + // Since we cannot perform I/O quickly enough in this callback, we ignore + // all preverify_ok errors and let the handshake continue. It is + // imparative that the user use Connection::VerifyError after the + // 'secure' callback has been made. + return 1; } -void Connection::ClearIn(const FunctionCallbackInfo& args) { +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { HandleScope scope(node_isolate); - Connection* conn = Connection::Unwrap(args.This()); - - if (args.Length() < 3) { - return ThrowTypeError("Takes 3 parameters"); - } + Connection* conn = static_cast(SSL_get_app_data(s)); - if (!Buffer::HasInstance(args[0])) { - return ThrowTypeError("Second argument should be a buffer"); - } + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); - char* buffer_data = Buffer::Data(args[0]); - size_t buffer_length = Buffer::Length(args[0]); + if (servername) { + conn->servername_.Reset(node_isolate, + OneByteString(node_isolate, servername)); - size_t off = args[1]->Int32Value(); - size_t len = args[2]->Int32Value(); - if (off + len > buffer_length) { - return ThrowError("off + len > buffer.length"); - } + // Call the SNI callback and use its return value as context + if (!conn->sniObject_.IsEmpty()) { + conn->sniContext_.Dispose(); - if (!SSL_is_init_finished(conn->ssl_)) { - int rv; - if (conn->is_server_) { - rv = SSL_accept(conn->ssl_); - conn->HandleSSLError("SSL_accept:ClearIn", - rv, - kZeroIsAnError, - kSyscallError); - } else { - rv = SSL_connect(conn->ssl_); - conn->HandleSSLError("SSL_connect:ClearIn", - rv, - kZeroIsAnError, - kSyscallError); - } + Local arg = PersistentToLocal(node_isolate, conn->servername_); + Local ret = MakeCallback(conn->sniObject_, "onselect", 1, &arg); - if (rv < 0) { - return args.GetReturnValue().Set(rv); + // If ret is SecureContext + if (HasInstance(secure_context_constructor, ret)) { + conn->sniContext_.Reset(node_isolate, ret); + SecureContext* sc = ObjectWrap::Unwrap(ret.As()); + SSL_set_SSL_CTX(s, sc->ctx_); + } else { + return SSL_TLSEXT_ERR_NOACK; + } } } - int bytes_written = SSL_write(conn->ssl_, buffer_data + off, len); - - conn->HandleSSLError("SSL_write:ClearIn", - bytes_written, - len == 0 ? kZeroIsNotAnError : kZeroIsAnError, - kSyscallError); - conn->SetShutdownFlags(); - - args.GetReturnValue().Set(bytes_written); + return SSL_TLSEXT_ERR_OK; } +#endif - -void Connection::GetPeerCertificate(const FunctionCallbackInfo& args) { +void Connection::New(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); - Connection* conn = Connection::Unwrap(args.This()); - - if (conn->ssl_ == NULL) return; - Local info = Object::New(); - X509* peer_cert = SSL_get_peer_certificate(conn->ssl_); - if (peer_cert != NULL) { - BIO* bio = BIO_new(BIO_s_mem()); - BUF_MEM* mem; - if (X509_NAME_print_ex(bio, X509_get_subject_name(peer_cert), 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(subject_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - } - (void) BIO_reset(bio); - - X509_NAME* issuer_name = X509_get_issuer_name(peer_cert); - if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(issuer_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - } - (void) BIO_reset(bio); - - int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1); - if (index >= 0) { - X509_EXTENSION* ext; - int rv; - - ext = X509_get_ext(peer_cert, index); - assert(ext != NULL); + if (args.Length() < 1 || !args[0]->IsObject()) { + return ThrowError("First argument must be a crypto module Credentials"); + } - rv = X509V3_EXT_print(bio, ext, 0, 0); - assert(rv == 1); + SecureContext* sc = ObjectWrap::Unwrap(args[0]->ToObject()); - BIO_get_mem_ptr(bio, &mem); - info->Set(subjectaltname_symbol, - OneByteString(node_isolate, mem->data, mem->length)); + bool is_server = args[1]->BooleanValue(); - (void) BIO_reset(bio); - } + Connection* conn = new Connection( + sc, + is_server ? SSLWrap::kServer : SSLWrap::kClient); + conn->Wrap(args.This()); - EVP_PKEY *pkey = NULL; - RSA *rsa = NULL; - if (NULL != (pkey = X509_get_pubkey(peer_cert)) && - NULL != (rsa = EVP_PKEY_get1_RSA(pkey))) { - BN_print(bio, rsa->n); - BIO_get_mem_ptr(bio, &mem); - info->Set(modulus_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); + conn->ssl_ = SSL_new(sc->ctx_); + conn->bio_read_ = BIO_new(NodeBIO::GetMethod()); + conn->bio_write_ = BIO_new(NodeBIO::GetMethod()); - BN_print(bio, rsa->e); - BIO_get_mem_ptr(bio, &mem); - info->Set(exponent_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); - } + SSL_set_app_data(conn->ssl_, conn); - if (pkey != NULL) { - EVP_PKEY_free(pkey); - pkey = NULL; - } - if (rsa != NULL) { - RSA_free(rsa); - rsa = NULL; - } + if (is_server) + SSL_set_info_callback(conn->ssl_, SSLInfoCallback); - ASN1_TIME_print(bio, X509_get_notBefore(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(valid_from_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); +#ifdef OPENSSL_NPN_NEGOTIATED + if (is_server) { + // Server should advertise NPN protocols + SSL_CTX_set_next_protos_advertised_cb( + sc->ctx_, + SSLWrap::AdvertiseNextProtoCallback, + NULL); + } else { + // Client should select protocol from advertised + // If server supports NPN + SSL_CTX_set_next_proto_select_cb( + sc->ctx_, + SSLWrap::SelectNextProtoCallback, + NULL); + } +#endif - ASN1_TIME_print(bio, X509_get_notAfter(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(valid_to_symbol, - OneByteString(node_isolate, mem->data, mem->length)); - BIO_free_all(bio); +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + if (is_server) { + SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_); + } else { + const String::Utf8Value servername(args[2]); + SSL_set_tlsext_host_name(conn->ssl_, *servername); + } +#endif - unsigned int md_size, i; - unsigned char md[EVP_MAX_MD_SIZE]; - if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) { - const char hex[] = "0123456789ABCDEF"; - char fingerprint[EVP_MAX_MD_SIZE * 3]; + SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_); - for (i = 0; i < md_size; i++) { - fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; - fingerprint[(3*i)+2] = ':'; - } +#ifdef SSL_MODE_RELEASE_BUFFERS + long mode = SSL_get_mode(conn->ssl_); + SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS); +#endif - if (md_size > 0) { - fingerprint[(3*(md_size-1))+2] = '\0'; - } else { - fingerprint[0] = '\0'; - } - info->Set(fingerprint_symbol, OneByteString(node_isolate, fingerprint)); + 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; } + } else { + // Note request_cert and reject_unauthorized are ignored for clients. + verify_mode = SSL_VERIFY_NONE; + } - STACK_OF(ASN1_OBJECT) *eku = (STACK_OF(ASN1_OBJECT) *)X509_get_ext_d2i( - peer_cert, NID_ext_key_usage, NULL, NULL); - if (eku != NULL) { - Local ext_key_usage = Array::New(); - char buf[256]; - - for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { - memset(buf, 0, sizeof(buf)); - OBJ_obj2txt(buf, sizeof(buf) - 1, sk_ASN1_OBJECT_value(eku, i), 1); - ext_key_usage->Set(i, OneByteString(node_isolate, buf)); - } - sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); - info->Set(ext_key_usage_symbol, ext_key_usage); - } + // Always allow a connection. We'll reject in javascript. + SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback); - X509_free(peer_cert); + if (is_server) { + SSL_set_accept_state(conn->ssl_); + } else { + SSL_set_connect_state(conn->ssl_); } - - args.GetReturnValue().Set(info); } -void Connection::GetSession(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - Connection* conn = Connection::Unwrap(args.This()); - - if (conn->ssl_ == NULL) return; - - SSL_SESSION* sess = SSL_get_session(conn->ssl_); - if (!sess) return; - - int slen = i2d_SSL_SESSION(sess, NULL); - assert(slen > 0); - - unsigned char* sbuf = new unsigned char[slen]; - unsigned char* p = sbuf; - i2d_SSL_SESSION(sess, &p); - args.GetReturnValue().Set(Encode(sbuf, slen, BINARY)); - delete[] sbuf; +void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) { + // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants + // a non-const SSL* in OpenSSL <= 0.9.7e. + SSL* ssl = const_cast(ssl_); + if (where & SSL_CB_HANDSHAKE_START) { + HandleScope scope(node_isolate); + Connection* conn = static_cast(SSL_get_app_data(ssl)); + if (onhandshakestart_sym.IsEmpty()) { + onhandshakestart_sym = + FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakestart"); + } + MakeCallback(conn->handle(node_isolate), onhandshakestart_sym, 0, NULL); + } + if (where & SSL_CB_HANDSHAKE_DONE) { + HandleScope scope(node_isolate); + Connection* conn = static_cast(SSL_get_app_data(ssl)); + if (onhandshakedone_sym.IsEmpty()) { + onhandshakedone_sym = + FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakedone"); + } + MakeCallback(conn->handle(node_isolate), onhandshakedone_sym, 0, NULL); + } } -void Connection::SetSession(const FunctionCallbackInfo& args) { +void Connection::EncIn(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - if (args.Length() < 1 || - (!args[0]->IsString() && !Buffer::HasInstance(args[0]))) { - return ThrowTypeError("Bad argument"); - } - - ASSERT_IS_BUFFER(args[0]); - ssize_t slen = Buffer::Length(args[0]); - - if (slen < 0) { - return ThrowTypeError("Bad argument"); + if (args.Length() < 3) { + return ThrowTypeError("Takes 3 parameters"); } - char* sbuf = new char[slen]; - - ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY); - assert(wlen == slen); - - const unsigned char* p = reinterpret_cast(sbuf); - SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen); - - delete [] sbuf; - - if (!sess) return; - - int r = SSL_set_session(conn->ssl_, sess); - SSL_SESSION_free(sess); - - if (!r) { - return ThrowError("SSL_set_session error"); + if (!Buffer::HasInstance(args[0])) { + return ThrowTypeError("Second argument should be a buffer"); } -} - -void Connection::LoadSession(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); - Connection* conn = Connection::Unwrap(args.This()); + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + if (off + len > buffer_length) { + return ThrowError("off + len > buffer.length"); + } - if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { - ssize_t slen = Buffer::Length(args[0].As()); - char* sbuf = Buffer::Data(args[0].As()); + int bytes_written; + char* data = buffer_data + off; - const unsigned char* p = reinterpret_cast(sbuf); - SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, slen); + if (conn->is_server() && !conn->hello_parser_.IsEnded()) { + // Just accumulate data, everything will be pushed to BIO later + if (conn->hello_parser_.IsPaused()) { + bytes_written = 0; + } else { + // Copy incoming data to the internal buffer + // (which has a size of the biggest possible TLS frame) + size_t available = sizeof(conn->hello_data_) - conn->hello_offset_; + size_t copied = len < available ? len : available; + memcpy(conn->hello_data_ + conn->hello_offset_, data, copied); + conn->hello_offset_ += copied; - // Setup next session and move hello to the BIO buffer - if (conn->next_sess_ != NULL) { - SSL_SESSION_free(conn->next_sess_); + conn->hello_parser_.Parse(conn->hello_data_, conn->hello_offset_); + bytes_written = copied; } - conn->next_sess_ = sess; + } else { + bytes_written = BIO_write(conn->bio_read_, data, len); + conn->HandleBIOError(conn->bio_read_, "BIO_write", bytes_written); + conn->SetShutdownFlags(); } - conn->hello_parser_.End(); + args.GetReturnValue().Set(bytes_written); } -void Connection::IsSessionReused(const FunctionCallbackInfo& args) { +void Connection::ClearOut(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); + Connection* conn = Connection::Unwrap(args.This()); - bool yes = conn->ssl_ && SSL_session_reused(conn->ssl_); - args.GetReturnValue().Set(yes); -} + if (args.Length() < 3) { + return ThrowTypeError("Takes 3 parameters"); + } + + if (!Buffer::HasInstance(args[0])) { + return ThrowTypeError("Second argument should be a buffer"); + } -void Connection::Start(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); - Connection* conn = Connection::Unwrap(args.This()); + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + if (off + len > buffer_length) { + return ThrowError("off + len > buffer.length"); + } - int rv = 0; if (!SSL_is_init_finished(conn->ssl_)) { - if (conn->is_server_) { + int rv; + + if (conn->is_server()) { rv = SSL_accept(conn->ssl_); - conn->HandleSSLError("SSL_accept:Start", + conn->HandleSSLError("SSL_accept:ClearOut", rv, kZeroIsAnError, kSyscallError); } else { rv = SSL_connect(conn->ssl_); - conn->HandleSSLError("SSL_connect:Start", + conn->HandleSSLError("SSL_connect:ClearOut", rv, kZeroIsAnError, kSyscallError); } - } - args.GetReturnValue().Set(rv); -} - - -void Connection::Shutdown(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - Connection* conn = Connection::Unwrap(args.This()); - if (conn->ssl_ == NULL) { - return args.GetReturnValue().Set(false); + if (rv < 0) { + return args.GetReturnValue().Set(rv); + } } - int rv = SSL_shutdown(conn->ssl_); - conn->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall); + int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len); + conn->HandleSSLError("SSL_read:ClearOut", + bytes_read, + kZeroIsNotAnError, + kSyscallError); conn->SetShutdownFlags(); - args.GetReturnValue().Set(rv); + + args.GetReturnValue().Set(bytes_read); } -void Connection::ReceivedShutdown(const FunctionCallbackInfo& args) { +void Connection::ClearPending(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - bool yes = - conn->ssl_ && SSL_get_shutdown(conn->ssl_) == SSL_RECEIVED_SHUTDOWN; - args.GetReturnValue().Set(yes); + int bytes_pending = BIO_pending(conn->bio_read_); + args.GetReturnValue().Set(bytes_pending); } -void Connection::IsInitFinished(const FunctionCallbackInfo& args) { +void Connection::EncPending(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - bool yes = conn->ssl_ && SSL_is_init_finished(conn->ssl_); - args.GetReturnValue().Set(yes); + int bytes_pending = BIO_pending(conn->bio_write_); + args.GetReturnValue().Set(bytes_pending); } -void Connection::VerifyError(const FunctionCallbackInfo& args) { +void Connection::EncOut(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - if (conn->ssl_ == NULL) { - return args.GetReturnValue().SetNull(); + if (args.Length() < 3) { + return ThrowTypeError("Takes 3 parameters"); } - // XXX Do this check in JS land? - X509* peer_cert = SSL_get_peer_certificate(conn->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? - Local error_message = - FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT"); - Local exception = Exception::Error(error_message); - return args.GetReturnValue().Set(exception); + if (!Buffer::HasInstance(args[0])) { + return ThrowTypeError("Second argument should be a buffer"); } - X509_free(peer_cert); - - long x509_verify_error = SSL_get_verify_result(conn->ssl_); - - Local s; - - switch (x509_verify_error) { - case X509_V_OK: - break; - - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - s = FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT"); - break; - - case X509_V_ERR_UNABLE_TO_GET_CRL: - s = FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_CRL"); - break; - - case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: - s = FIXED_ONE_BYTE_STRING(node_isolate, - "UNABLE_TO_DECRYPT_CERT_SIGNATURE"); - break; - - case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: - s = FIXED_ONE_BYTE_STRING(node_isolate, - "UNABLE_TO_DECRYPT_CRL_SIGNATURE"); - break; - - case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: - s = FIXED_ONE_BYTE_STRING(node_isolate, - "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"); - break; - - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_SIGNATURE_FAILURE"); - break; - - case X509_V_ERR_CRL_SIGNATURE_FAILURE: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_SIGNATURE_FAILURE"); - break; - - case X509_V_ERR_CERT_NOT_YET_VALID: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_NOT_YET_VALID"); - break; - - case X509_V_ERR_CERT_HAS_EXPIRED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_HAS_EXPIRED"); - break; - - case X509_V_ERR_CRL_NOT_YET_VALID: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_NOT_YET_VALID"); - break; - - case X509_V_ERR_CRL_HAS_EXPIRED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_HAS_EXPIRED"); - break; - - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CERT_NOT_BEFORE_FIELD"); - break; - - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CERT_NOT_AFTER_FIELD"); - break; - - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CRL_LAST_UPDATE_FIELD"); - break; - - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CRL_NEXT_UPDATE_FIELD"); - break; - - case X509_V_ERR_OUT_OF_MEM: - s = FIXED_ONE_BYTE_STRING(node_isolate, "OUT_OF_MEM"); - break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - s = FIXED_ONE_BYTE_STRING(node_isolate, "DEPTH_ZERO_SELF_SIGNED_CERT"); - break; + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - s = FIXED_ONE_BYTE_STRING(node_isolate, "SELF_SIGNED_CERT_IN_CHAIN"); - break; + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + if (off + len > buffer_length) { + return ThrowError("off + len > buffer.length"); + } - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - s = FIXED_ONE_BYTE_STRING(node_isolate, - "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"); - break; + int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len); - case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: - s = FIXED_ONE_BYTE_STRING(node_isolate, - "UNABLE_TO_VERIFY_LEAF_SIGNATURE"); - break; + conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read); + conn->SetShutdownFlags(); - case X509_V_ERR_CERT_CHAIN_TOO_LONG: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_CHAIN_TOO_LONG"); - break; + args.GetReturnValue().Set(bytes_read); +} - case X509_V_ERR_CERT_REVOKED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_REVOKED"); - break; - case X509_V_ERR_INVALID_CA: - s = FIXED_ONE_BYTE_STRING(node_isolate, "INVALID_CA"); - break; +void Connection::ClearIn(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); - case X509_V_ERR_PATH_LENGTH_EXCEEDED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "PATH_LENGTH_EXCEEDED"); - break; + Connection* conn = Connection::Unwrap(args.This()); - case X509_V_ERR_INVALID_PURPOSE: - s = FIXED_ONE_BYTE_STRING(node_isolate, "INVALID_PURPOSE"); - break; + if (args.Length() < 3) { + return ThrowTypeError("Takes 3 parameters"); + } - case X509_V_ERR_CERT_UNTRUSTED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_UNTRUSTED"); - break; + if (!Buffer::HasInstance(args[0])) { + return ThrowTypeError("Second argument should be a buffer"); + } - case X509_V_ERR_CERT_REJECTED: - s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_REJECTED"); - break; + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); - default: - s = OneByteString(node_isolate, - X509_verify_cert_error_string(x509_verify_error)); - break; + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + if (off + len > buffer_length) { + return ThrowError("off + len > buffer.length"); } - if (s.IsEmpty()) - args.GetReturnValue().SetNull(); - else - args.GetReturnValue().Set(Exception::Error(s)); -} - + if (!SSL_is_init_finished(conn->ssl_)) { + int rv; + if (conn->is_server()) { + rv = SSL_accept(conn->ssl_); + conn->HandleSSLError("SSL_accept:ClearIn", + rv, + kZeroIsAnError, + kSyscallError); + } else { + rv = SSL_connect(conn->ssl_); + conn->HandleSSLError("SSL_connect:ClearIn", + rv, + kZeroIsAnError, + kSyscallError); + } -void Connection::GetCurrentCipher(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); + if (rv < 0) { + return args.GetReturnValue().Set(rv); + } + } - Connection* conn = Connection::Unwrap(args.This()); - if (conn->ssl_ == NULL) return; + int bytes_written = SSL_write(conn->ssl_, buffer_data + off, len); - OPENSSL_CONST SSL_CIPHER *c = SSL_get_current_cipher(conn->ssl_); - if (c == NULL) return; + conn->HandleSSLError("SSL_write:ClearIn", + bytes_written, + len == 0 ? kZeroIsNotAnError : kZeroIsAnError, + kSyscallError); + conn->SetShutdownFlags(); - Local info = Object::New(); - const char* cipher_name = SSL_CIPHER_get_name(c); - info->Set(name_symbol, OneByteString(node_isolate, cipher_name)); - const char* cipher_version = SSL_CIPHER_get_version(c); - info->Set(version_symbol, OneByteString(node_isolate, cipher_version)); - args.GetReturnValue().Set(info); + args.GetReturnValue().Set(bytes_written); } -void Connection::Close(const FunctionCallbackInfo& args) { +void Connection::Start(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - if (conn->ssl_ != NULL) { - SSL_free(conn->ssl_); - conn->ssl_ = NULL; + int rv = 0; + if (!SSL_is_init_finished(conn->ssl_)) { + if (conn->is_server()) { + rv = SSL_accept(conn->ssl_); + conn->HandleSSLError("SSL_accept:Start", + rv, + kZeroIsAnError, + kSyscallError); + } else { + rv = SSL_connect(conn->ssl_); + conn->HandleSSLError("SSL_connect:Start", + rv, + kZeroIsAnError, + kSyscallError); + } } + args.GetReturnValue().Set(rv); } -#ifdef OPENSSL_NPN_NEGOTIATED -void Connection::GetNegotiatedProto(const FunctionCallbackInfo& args) { +void Connection::Shutdown(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - if (conn->is_server_) { - const unsigned char* npn_proto; - unsigned int npn_proto_len; - - SSL_get0_next_proto_negotiated(conn->ssl_, &npn_proto, &npn_proto_len); - - if (!npn_proto) { - return args.GetReturnValue().Set(false); - } - - args.GetReturnValue().Set( - OneByteString(node_isolate, npn_proto, npn_proto_len)); - } else { - args.GetReturnValue().Set(conn->selectedNPNProto_); + if (conn->ssl_ == NULL) { + return args.GetReturnValue().Set(false); } + + int rv = SSL_shutdown(conn->ssl_); + conn->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall); + conn->SetShutdownFlags(); + args.GetReturnValue().Set(rv); } -void Connection::SetNPNProtocols(const FunctionCallbackInfo& args) { +void Connection::Close(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Connection* conn = Connection::Unwrap(args.This()); - if (args.Length() < 1 || !Buffer::HasInstance(args[0])) { - return ThrowError("Must give a Buffer as first argument"); + if (conn->ssl_ != NULL) { + SSL_free(conn->ssl_); + conn->ssl_ = NULL; } - - conn->npnProtos_.Reset(node_isolate, args[0].As()); } -#endif #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB @@ -2004,7 +2019,7 @@ void Connection::GetServername(const FunctionCallbackInfo& args) { Connection* conn = Connection::Unwrap(args.This()); - if (conn->is_server_ && !conn->servername_.IsEmpty()) { + if (conn->is_server() && !conn->servername_.IsEmpty()) { args.GetReturnValue().Set(conn->servername_); } else { args.GetReturnValue().Set(false); diff --git a/src/node_crypto.h b/src/node_crypto.h index d48bea2..56055a9 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -49,6 +49,8 @@ namespace node { namespace crypto { +extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); + static X509_STORE* root_cert_store; // Forward declaration @@ -84,12 +86,6 @@ class SecureContext : ObjectWrap { static void GetTicketKeys(const v8::FunctionCallbackInfo& args); static void SetTicketKeys(const v8::FunctionCallbackInfo& args); - static SSL_SESSION* GetSessionCallback(SSL* s, - unsigned char* key, - int len, - int* copy); - static int NewSessionCallback(SSL* s, SSL_SESSION* sess); - SecureContext() : ObjectWrap() { ctx_ = NULL; ca_store_ = NULL; @@ -119,7 +115,96 @@ class SecureContext : ObjectWrap { private: }; -class Connection : ObjectWrap { +template +class SSLWrap { + public: + enum Kind { + kClient, + kServer + }; + + SSLWrap(SecureContext* sc, Kind kind) : kind_(kind), + next_sess_(NULL), + session_callbacks_(false) { + ssl_ = SSL_new(sc->ctx_); + assert(ssl_ != NULL); + } + + ~SSLWrap() { + if (ssl_ != NULL) { + SSL_free(ssl_); + ssl_ = NULL; + } + if (next_sess_ != NULL) { + SSL_SESSION_free(next_sess_); + next_sess_ = NULL; + } + +#ifdef OPENSSL_NPN_NEGOTIATED + npn_protos_.Dispose(); + selected_npn_proto_.Dispose(); +#endif + } + + 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; } + + protected: + static void AddMethods(v8::Handle t); + + 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& args); + static void GetSession(const v8::FunctionCallbackInfo& args); + static void SetSession(const v8::FunctionCallbackInfo& args); + static void LoadSession(const v8::FunctionCallbackInfo& args); + static void IsSessionReused(const v8::FunctionCallbackInfo& args); + static void IsInitFinished(const v8::FunctionCallbackInfo& args); + static void VerifyError(const v8::FunctionCallbackInfo& args); + static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); + static void ReceivedShutdown(const v8::FunctionCallbackInfo& args); + static void EndParser(const v8::FunctionCallbackInfo& args); + +#ifdef OPENSSL_NPN_NEGOTIATED + static void GetNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetNPNProtocols(const v8::FunctionCallbackInfo& 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 + + Kind kind_; + SSL_SESSION* next_sess_; + SSL* ssl_; + bool session_callbacks_; + ClientHelloParser hello_parser_; + +#ifdef OPENSSL_NPN_NEGOTIATED + v8::Persistent npn_protos_; + v8::Persistent selected_npn_proto_; +#endif // OPENSSL_NPN_NEGOTIATED + + friend class SecureContext; +}; + +class Connection : public SSLWrap, public ObjectWrap { public: static void Initialize(v8::Handle target); @@ -142,37 +227,10 @@ class Connection : ObjectWrap { static void EncPending(const v8::FunctionCallbackInfo& args); static void EncOut(const v8::FunctionCallbackInfo& args); static void ClearIn(const v8::FunctionCallbackInfo& args); - static void GetPeerCertificate( - const v8::FunctionCallbackInfo& args); - static void GetSession(const v8::FunctionCallbackInfo& args); - static void SetSession(const v8::FunctionCallbackInfo& args); - static void LoadSession(const v8::FunctionCallbackInfo& args); - static void IsSessionReused(const v8::FunctionCallbackInfo& args); - static void IsInitFinished(const v8::FunctionCallbackInfo& args); - static void VerifyError(const v8::FunctionCallbackInfo& args); - static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); static void Shutdown(const v8::FunctionCallbackInfo& args); - static void ReceivedShutdown(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); -#ifdef OPENSSL_NPN_NEGOTIATED - // NPN - static void GetNegotiatedProto( - const v8::FunctionCallbackInfo& args); - static void SetNPNProtocols(const v8::FunctionCallbackInfo& 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 - #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB // SNI static void GetServername(const v8::FunctionCallbackInfo& args); @@ -180,8 +238,6 @@ class Connection : ObjectWrap { static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); #endif - static void OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello); static void OnClientHelloParseEnd(void* arg); int HandleBIOError(BIO* bio, const char* func, int rv); @@ -207,11 +263,15 @@ class Connection : ObjectWrap { return conn; } - Connection() : ObjectWrap(), hello_offset_(0) { + Connection(SecureContext* sc, SSLWrap::Kind kind) + : SSLWrap(sc, kind), + hello_offset_(0) { bio_read_ = bio_write_ = NULL; ssl_ = NULL; - next_sess_ = NULL; - hello_parser_.Start(OnClientHello, OnClientHelloParseEnd, this); + hello_parser_.Start(SSLWrap::OnClientHello, + OnClientHelloParseEnd, + this); + enable_session_callbacks(); } ~Connection() { @@ -220,16 +280,6 @@ class Connection : ObjectWrap { ssl_ = NULL; } - if (next_sess_ != NULL) { - SSL_SESSION_free(next_sess_); - next_sess_ = NULL; - } - -#ifdef OPENSSL_NPN_NEGOTIATED - npnProtos_.Dispose(); - selectedNPNProto_.Dispose(); -#endif - #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sniObject_.Dispose(); sniContext_.Dispose(); @@ -242,12 +292,6 @@ class Connection : ObjectWrap { BIO *bio_read_; BIO *bio_write_; - SSL *ssl_; - - ClientHelloParser hello_parser_; - - bool is_server_; /* coverity[member_decl] */ - SSL_SESSION* next_sess_; uint8_t hello_data_[18432]; size_t hello_offset_; diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 65a5d31..cf51b98 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -32,6 +32,7 @@ namespace node { using crypto::SecureContext; +using crypto::SSLWrap; using v8::Array; using v8::Boolean; using v8::Exception; @@ -53,7 +54,6 @@ static Cached onerror_sym; static Cached onhandshakestart_sym; static Cached onhandshakedone_sym; static Cached onclienthello_sym; -static Cached onnewsession_sym; static Cached subject_sym; static Cached subjectaltname_sym; static Cached modulus_sym; @@ -65,9 +65,6 @@ static Cached fingerprint_sym; static Cached name_sym; static Cached version_sym; static Cached ext_key_usage_sym; -static Cached sessionid_sym; -static Cached tls_ticket_sym; -static Cached servername_sym; static Cached sni_context_sym; static Persistent tlsWrap; @@ -81,9 +78,8 @@ static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL TLSCallbacks::TLSCallbacks(Kind kind, Handle sc, StreamWrapCallbacks* old) - : StreamWrapCallbacks(old), - kind_(kind), - ssl_(NULL), + : SSLWrap(ObjectWrap::Unwrap(sc), kind), + StreamWrapCallbacks(old), enc_in_(NULL), enc_out_(NULL), clear_in_(NULL), @@ -91,9 +87,7 @@ TLSCallbacks::TLSCallbacks(Kind kind, pending_write_item_(NULL), started_(false), established_(false), - shutdown_(false), - session_callbacks_(false), - next_sess_(NULL) { + shutdown_(false) { // Persist SecureContext sc_ = ObjectWrap::Unwrap(sc); @@ -107,60 +101,14 @@ TLSCallbacks::TLSCallbacks(Kind kind, QUEUE_INIT(&write_item_queue_); // We've our own session callbacks - SSL_CTX_sess_set_get_cb(sc_->ctx_, GetSessionCallback); - SSL_CTX_sess_set_new_cb(sc_->ctx_, NewSessionCallback); + SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap::GetSessionCallback); + SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap::NewSessionCallback); InitSSL(); } -SSL_SESSION* TLSCallbacks::GetSessionCallback(SSL* s, - unsigned char* key, - int len, - int* copy) { - HandleScope scope(node_isolate); - - TLSCallbacks* c = static_cast(SSL_get_app_data(s)); - - *copy = 0; - SSL_SESSION* sess = c->next_sess_; - c->next_sess_ = NULL; - - return sess; -} - - -int TLSCallbacks::NewSessionCallback(SSL* s, SSL_SESSION* sess) { - HandleScope scope(node_isolate); - - TLSCallbacks* c = static_cast(SSL_get_app_data(s)); - if (!c->session_callbacks_) - return 0; - - // Check if session is small enough to be stored - int size = i2d_SSL_SESSION(sess, NULL); - if (size > SecureContext::kMaxSessionSize) - return 0; - - // Serialize session - Local buff = Buffer::New(size); - unsigned char* serialized = reinterpret_cast( - Buffer::Data(buff)); - memset(serialized, 0, size); - i2d_SSL_SESSION(sess, &serialized); - - Local session = Buffer::New(reinterpret_cast(sess->session_id), - sess->session_id_length); - Handle argv[2] = { session, buff }; - MakeCallback(c->object(), onnewsession_sym, ARRAY_SIZE(argv), argv); - - return 0; -} - - TLSCallbacks::~TLSCallbacks() { - SSL_free(ssl_); - ssl_ = NULL; enc_in_ = NULL; enc_out_ = NULL; delete clear_in_; @@ -170,11 +118,6 @@ TLSCallbacks::~TLSCallbacks() { sc_handle_.Dispose(); persistent().Dispose(); -#ifdef OPENSSL_NPN_NEGOTIATED - npn_protos_.Dispose(); - selected_npn_proto_.Dispose(); -#endif // OPENSSL_NPN_NEGOTIATED - #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sni_context_.Dispose(); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB @@ -200,64 +143,15 @@ void TLSCallbacks::InvokeQueued(int status) { } -static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) { - // Quoting SSL_set_verify(3ssl): - // - // The VerifyCallback function is used to control the behaviour when - // the SSL_VERIFY_PEER flag is set. It must be supplied by the - // application and receives two arguments: preverify_ok indicates, - // whether the verification of the certificate in question was passed - // (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to - // the complete context used for the certificate chain verification. - // - // The certificate chain is checked starting with the deepest nesting - // level (the root CA certificate) and worked upward to the peer's - // certificate. At each level signatures and issuer attributes are - // checked. Whenever a verification error is found, the error number is - // stored in x509_ctx and VerifyCallback is called with preverify_ok=0. - // By applying X509_CTX_store_* functions VerifyCallback can locate the - // certificate in question and perform additional steps (see EXAMPLES). - // If no error is found for a certificate, VerifyCallback is called - // with preverify_ok=1 before advancing to the next level. - // - // The return value of VerifyCallback controls the strategy of the - // further verification process. If VerifyCallback returns 0, the - // verification process is immediately stopped with "verification - // failed" state. If SSL_VERIFY_PEER is set, a verification failure - // alert is sent to the peer and the TLS/SSL handshake is terminated. If - // VerifyCallback returns 1, the verification process is continued. If - // VerifyCallback always returns 1, the TLS/SSL handshake will not be - // terminated with respect to verification failures and the connection - // will be established. The calling process can however retrieve the - // error code of the last verification error using - // SSL_get_verify_result(3) or by maintaining its own error storage - // managed by VerifyCallback. - // - // If no VerifyCallback is specified, the default callback will be - // used. Its return value is identical to preverify_ok, so that any - // verification failure will lead to a termination of the TLS/SSL - // handshake with an alert message, if SSL_VERIFY_PEER is set. - // - // Since we cannot perform I/O quickly enough in this callback, we ignore - // all preverify_ok errors and let the handshake continue. It is - // imparative that the user use Connection::VerifyError after the - // 'secure' callback has been made. - return 1; -} - - void TLSCallbacks::InitSSL() { - assert(ssl_ == NULL); - // Initialize SSL - ssl_ = SSL_new(sc_->ctx_); enc_in_ = BIO_new(NodeBIO::GetMethod()); enc_out_ = BIO_new(NodeBIO::GetMethod()); SSL_set_bio(ssl_, enc_in_, enc_out_); // NOTE: This could be overriden in SetVerifyMode - SSL_set_verify(ssl_, SSL_VERIFY_NONE, VerifyCallback); + SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::VerifyCallback); #ifdef SSL_MODE_RELEASE_BUFFERS long mode = SSL_get_mode(ssl_); @@ -267,29 +161,31 @@ void TLSCallbacks::InitSSL() { SSL_set_app_data(ssl_, this); SSL_set_info_callback(ssl_, SSLInfoCallback); - if (kind_ == kTLSServer) { + 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_, - AdvertiseNextProtoCallback, - this); + SSL_CTX_set_next_protos_advertised_cb( + sc_->ctx_, + SSLWrap::AdvertiseNextProtoCallback, + this); #endif // OPENSSL_NPN_NEGOTIATED #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB SSL_CTX_set_tlsext_servername_callback(sc_->ctx_, SelectSNIContextCallback); SSL_CTX_set_tlsext_servername_arg(sc_->ctx_, this); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - } else if (kind_ == kTLSClient) { + } else if (is_client()) { SSL_set_connect_state(ssl_); #ifdef OPENSSL_NPN_NEGOTIATED // Client should select protocol from list of advertised // If server supports NPN - SSL_CTX_set_next_proto_select_cb(sc_->ctx_, - SelectNextProtoCallback, - this); + SSL_CTX_set_next_proto_select_cb( + sc_->ctx_, + SSLWrap::SelectNextProtoCallback, + this); #endif // OPENSSL_NPN_NEGOTIATED } else { // Unexpected @@ -313,7 +209,8 @@ void TLSCallbacks::Wrap(const FunctionCallbackInfo& args) { Local stream = args[0].As(); Local sc = args[1].As(); - Kind kind = args[2]->IsTrue() ? kTLSServer : kTLSClient; + Kind kind = args[2]->IsTrue() ? SSLWrap::kServer : + SSLWrap::kClient; TLSCallbacks* callbacks = NULL; WITH_GENERIC_STREAM(stream, { @@ -340,7 +237,7 @@ void TLSCallbacks::Start(const FunctionCallbackInfo& args) { wrap->started_ = true; // Send ClientHello handshake - assert(wrap->kind_ == kTLSClient); + assert(wrap->is_client()); wrap->ClearOut(); wrap->EncOut(); } @@ -353,7 +250,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { if (where & SSL_CB_HANDSHAKE_START) { HandleScope scope(node_isolate); TLSCallbacks* c = static_cast(SSL_get_app_data(ssl)); - Local object = c->object(); + Local object = c->object(node_isolate); if (object->Has(onhandshakestart_sym)) MakeCallback(object, onhandshakestart_sym, 0, NULL); } @@ -361,7 +258,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { HandleScope scope(node_isolate); TLSCallbacks* c = static_cast(SSL_get_app_data(ssl)); c->established_ = true; - Local object = c->object(); + Local object = c->object(node_isolate); if (object->Has(onhandshakedone_sym)) MakeCallback(object, onhandshakedone_sym, 0, NULL); } @@ -370,7 +267,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { void TLSCallbacks::EncOut() { // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_.IsEnded()) + if (!hello_parser_.IsEnded()) return; // Write in progress @@ -424,7 +321,7 @@ void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { Local arg = String::Concat( FIXED_ONE_BYTE_STRING(node_isolate, "write cb error, status: "), Integer::New(status, node_isolate)->ToString()); - MakeCallback(callbacks->object(), onerror_sym, 1, &arg); + MakeCallback(callbacks->object(node_isolate), onerror_sym, 1, &arg); callbacks->InvokeQueued(status); return; } @@ -475,7 +372,7 @@ Handle TLSCallbacks::GetSSLError(int status, int* err) { void TLSCallbacks::ClearOut() { // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_.IsEnded()) + if (!hello_parser_.IsEnded()) return; HandleScope scope(node_isolate); @@ -500,14 +397,14 @@ void TLSCallbacks::ClearOut() { Handle argv = GetSSLError(read, &err); if (!argv.IsEmpty()) - MakeCallback(object(), onerror_sym, 1, &argv); + MakeCallback(object(node_isolate), onerror_sym, 1, &argv); } } bool TLSCallbacks::ClearIn() { // Ignore cycling data if ClientHello wasn't yet parsed - if (!hello_.IsEnded()) + if (!hello_parser_.IsEnded()) return false; HandleScope scope(node_isolate); @@ -533,7 +430,7 @@ bool TLSCallbacks::ClearIn() { int err; Handle argv = GetSSLError(written, &err); if (!argv.IsEmpty()) - MakeCallback(object(), onerror_sym, 1, &argv); + MakeCallback(object(node_isolate), onerror_sym, 1, &argv); return false; } @@ -595,7 +492,7 @@ int TLSCallbacks::DoWrite(WriteWrap* w, int err; Handle argv = GetSSLError(written, &err); if (!argv.IsEmpty()) { - MakeCallback(object(), onerror_sym, 1, &argv); + MakeCallback(object(node_isolate), onerror_sym, 1, &argv); return -1; } @@ -643,11 +540,11 @@ void TLSCallbacks::DoRead(uv_stream_t* handle, enc_in->Commit(nread); // Parse ClientHello first - if (!hello_.IsEnded()) { + if (!hello_parser_.IsEnded()) { size_t avail = 0; uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); assert(avail == 0 || data != NULL); - return hello_.Parse(data, avail); + return hello_parser_.Parse(data, avail); } // Cycle OpenSSL's state @@ -664,74 +561,6 @@ int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { } -#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: reason = #CODE; break; -void TLSCallbacks::VerifyError(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - // XXX Do this check in JS land? - X509* peer_cert = SSL_get_peer_certificate(wrap->ssl_); - if (peer_cert == NULL) { - // We requested a certificate and they did not send us one. - // Definitely an error. - // XXX is this the right error message? - Local s = - FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT"); - return args.GetReturnValue().Set(Exception::Error(s)); - } - X509_free(peer_cert); - - long x509_verify_error = SSL_get_verify_result(wrap->ssl_); - - const char* reason = NULL; - Local s; - switch (x509_verify_error) { - case X509_V_OK: - return args.GetReturnValue().SetNull(); - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) - CASE_X509_ERR(UNABLE_TO_GET_CRL) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) - CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) - CASE_X509_ERR(CERT_SIGNATURE_FAILURE) - CASE_X509_ERR(CRL_SIGNATURE_FAILURE) - CASE_X509_ERR(CERT_NOT_YET_VALID) - CASE_X509_ERR(CERT_HAS_EXPIRED) - CASE_X509_ERR(CRL_NOT_YET_VALID) - CASE_X509_ERR(CRL_HAS_EXPIRED) - CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) - CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) - CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) - CASE_X509_ERR(OUT_OF_MEM) - CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) - CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) - CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) - CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) - CASE_X509_ERR(CERT_CHAIN_TOO_LONG) - CASE_X509_ERR(CERT_REVOKED) - CASE_X509_ERR(INVALID_CA) - CASE_X509_ERR(PATH_LENGTH_EXCEEDED) - CASE_X509_ERR(INVALID_PURPOSE) - CASE_X509_ERR(CERT_UNTRUSTED) - CASE_X509_ERR(CERT_REJECTED) - default: - s = OneByteString(node_isolate, - X509_verify_cert_error_string(x509_verify_error)); - break; - } - - if (s.IsEmpty()) { - s = OneByteString(node_isolate, reason); - } - - args.GetReturnValue().Set(Exception::Error(s)); -} -#undef CASE_X509_ERR - - void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -742,7 +571,7 @@ void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo& args) { return ThrowTypeError("Bad arguments, expected two booleans"); int verify_mode; - if (wrap->kind_ == kTLSServer) { + if (wrap->is_server()) { bool request_cert = args[0]->IsTrue(); if (!request_cert) { // Note reject_unauthorized ignored. @@ -758,16 +587,7 @@ void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo& args) { } // Always allow a connection. We'll reject in javascript. - SSL_set_verify(wrap->ssl_, verify_mode, VerifyCallback); -} - - -void TLSCallbacks::IsSessionReused(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - bool yes = SSL_session_reused(wrap->ssl_); - args.GetReturnValue().Set(yes); + SSL_set_verify(wrap->ssl_, verify_mode, crypto::VerifyCallback); } @@ -778,7 +598,7 @@ void TLSCallbacks::EnableSessionCallbacks( TLSCallbacks* wrap; NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - wrap->session_callbacks_ = true; + wrap->enable_session_callbacks(); EnableHelloParser(args); } @@ -789,33 +609,9 @@ void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo& args) { TLSCallbacks* wrap; NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - wrap->hello_.Start(OnClientHello, OnClientHelloParseEnd, wrap); -} - - -void TLSCallbacks::OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello) { - HandleScope scope(node_isolate); - - TLSCallbacks* c = static_cast(arg); - - Local hello_obj = Object::New(); - Local buff = Buffer::New( - reinterpret_cast(hello.session_id()), - hello.session_size()); - hello_obj->Set(sessionid_sym, buff); - if (hello.servername() == NULL) { - hello_obj->Set(servername_sym, String::Empty(node_isolate)); - } else { - Local servername = OneByteString(node_isolate, - hello.servername(), - hello.servername_size()); - hello_obj->Set(servername_sym, servername); - } - hello_obj->Set(tls_ticket_sym, Boolean::New(hello.has_ticket())); - - Handle argv[1] = { hello_obj }; - MakeCallback(c->object(), onclienthello_sym, 1, argv); + wrap->hello_parser_.Start(SSLWrap::OnClientHello, + OnClientHelloParseEnd, + wrap); } @@ -825,377 +621,6 @@ void TLSCallbacks::OnClientHelloParseEnd(void* arg) { } -void TLSCallbacks::GetPeerCertificate(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - Local info = Object::New(); - X509* peer_cert = SSL_get_peer_certificate(wrap->ssl_); - if (peer_cert != NULL) { - BIO* bio = BIO_new(BIO_s_mem()); - BUF_MEM* mem; - if (X509_NAME_print_ex(bio, - X509_get_subject_name(peer_cert), - 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(subject_sym, - OneByteString(node_isolate, mem->data, mem->length)); - } - (void) BIO_reset(bio); - - if (X509_NAME_print_ex(bio, - X509_get_issuer_name(peer_cert), - 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(issuer_sym, - OneByteString(node_isolate, mem->data, mem->length)); - } - (void) BIO_reset(bio); - - int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1); - if (index >= 0) { - X509_EXTENSION* ext; - int rv; - - ext = X509_get_ext(peer_cert, index); - assert(ext != NULL); - - rv = X509V3_EXT_print(bio, ext, 0, 0); - assert(rv == 1); - - BIO_get_mem_ptr(bio, &mem); - info->Set(subjectaltname_sym, - OneByteString(node_isolate, mem->data, mem->length)); - - (void) BIO_reset(bio); - } - - EVP_PKEY* pkey = NULL; - RSA* rsa = NULL; - if (NULL != (pkey = X509_get_pubkey(peer_cert)) && - NULL != (rsa = EVP_PKEY_get1_RSA(pkey))) { - BN_print(bio, rsa->n); - BIO_get_mem_ptr(bio, &mem); - info->Set(modulus_sym, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); - - BN_print(bio, rsa->e); - BIO_get_mem_ptr(bio, &mem); - info->Set(exponent_sym, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); - } - - if (pkey != NULL) { - EVP_PKEY_free(pkey); - pkey = NULL; - } - if (rsa != NULL) { - RSA_free(rsa); - rsa = NULL; - } - - ASN1_TIME_print(bio, X509_get_notBefore(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(valid_from_sym, - OneByteString(node_isolate, mem->data, mem->length)); - (void) BIO_reset(bio); - - ASN1_TIME_print(bio, X509_get_notAfter(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(valid_to_sym, - OneByteString(node_isolate, mem->data, mem->length)); - BIO_free_all(bio); - - unsigned int md_size, i; - unsigned char md[EVP_MAX_MD_SIZE]; - if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) { - const char hex[] = "0123456789ABCDEF"; - char fingerprint[EVP_MAX_MD_SIZE * 3]; - - for (i = 0; i < md_size; i++) { - fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; - fingerprint[(3*i)+2] = ':'; - } - - if (md_size > 0) - fingerprint[(3*(md_size-1))+2] = '\0'; - else - fingerprint[0] = '\0'; - - info->Set(fingerprint_sym, OneByteString(node_isolate, fingerprint)); - } - - STACK_OF(ASN1_OBJECT)* eku = static_cast( - X509_get_ext_d2i(peer_cert, - NID_ext_key_usage, - NULL, - NULL)); - if (eku != NULL) { - Local ext_key_usage = Array::New(); - char buf[256]; - - for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { - memset(buf, 0, sizeof(buf)); - OBJ_obj2txt(buf, sizeof(buf) - 1, sk_ASN1_OBJECT_value(eku, i), 1); - ext_key_usage->Set(i, OneByteString(node_isolate, buf)); - } - - sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); - info->Set(ext_key_usage_sym, ext_key_usage); - } - - X509_free(peer_cert); - } - - args.GetReturnValue().Set(info); -} - - -void TLSCallbacks::GetSession(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - SSL_SESSION* sess = SSL_get_session(wrap->ssl_); - if (!sess) return; - - int slen = i2d_SSL_SESSION(sess, NULL); - assert(slen > 0); - - if (slen > 0) { - unsigned char* sbuf = new unsigned char[slen]; - unsigned char* p = sbuf; - i2d_SSL_SESSION(sess, &p); - Local s = Encode(sbuf, slen, BINARY); - args.GetReturnValue().Set(s); - delete[] sbuf; - return; - } - - args.GetReturnValue().SetNull(); -} - - -void TLSCallbacks::SetSession(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - if (wrap->started_) - return ThrowError("Already started."); - - if (args.Length() < 1 || - (!args[0]->IsString() && !Buffer::HasInstance(args[0]))) { - return ThrowTypeError("Bad argument"); - } - - size_t slen = Buffer::Length(args[0]); - char* sbuf = new char[slen]; - - ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY); - assert(wlen == static_cast(slen)); - - const unsigned char* p = reinterpret_cast(sbuf); - SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen); - - delete[] sbuf; - - if (!sess) return; - - int r = SSL_set_session(wrap->ssl_, sess); - SSL_SESSION_free(sess); - - if (!r) { - return ThrowError("SSL_set_session error"); - } -} - - -void TLSCallbacks::LoadSession(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { - ssize_t slen = Buffer::Length(args[0]); - char* sbuf = Buffer::Data(args[0]); - - const unsigned char* p = reinterpret_cast(sbuf); - SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, slen); - - // Setup next session and move hello to the BIO buffer - if (wrap->next_sess_ != NULL) - SSL_SESSION_free(wrap->next_sess_); - wrap->next_sess_ = sess; - - Local info = Object::New(); -#ifndef OPENSSL_NO_TLSEXT - if (sess->tlsext_hostname == NULL) { - info->Set(servername_sym, False(node_isolate)); - } else { - info->Set(servername_sym, - OneByteString(node_isolate, sess->tlsext_hostname)); - } -#endif - args.GetReturnValue().Set(info); - } -} - -void TLSCallbacks::EndParser(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - wrap->hello_.End(); -} - - -void TLSCallbacks::GetCurrentCipher(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - const SSL_CIPHER* c; - - c = SSL_get_current_cipher(wrap->ssl_); - if (c == NULL) - return; - - const char* cipher_name = SSL_CIPHER_get_name(c); - const char* cipher_version = SSL_CIPHER_get_version(c); - - Local info = Object::New(); - info->Set(name_sym, OneByteString(node_isolate, cipher_name)); - info->Set(version_sym, OneByteString(node_isolate, cipher_version)); - args.GetReturnValue().Set(info); -} - - -#ifdef OPENSSL_NPN_NEGOTIATED -int TLSCallbacks::AdvertiseNextProtoCallback(SSL* s, - const unsigned char** data, - unsigned int* len, - void* arg) { - TLSCallbacks* p = static_cast(arg); - - if (p->npn_protos_.IsEmpty()) { - // No initialization - no NPN protocols - *data = reinterpret_cast(""); - *len = 0; - } else { - Local obj = PersistentToLocal(node_isolate, p->npn_protos_); - *data = reinterpret_cast(Buffer::Data(obj)); - *len = Buffer::Length(obj); - } - - return SSL_TLSEXT_ERR_OK; -} - - -int TLSCallbacks::SelectNextProtoCallback(SSL* s, - unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg) { - TLSCallbacks* p = static_cast(arg); - - // Release old protocol handler if present - p->selected_npn_proto_.Dispose(); - - if (p->npn_protos_.IsEmpty()) { - // We should at least select one protocol - // If server is using NPN - *out = reinterpret_cast(const_cast("http/1.1")); - *outlen = 8; - - // set status: unsupported - p->selected_npn_proto_.Reset(node_isolate, False(node_isolate)); - - return SSL_TLSEXT_ERR_OK; - } - - Local obj = PersistentToLocal(node_isolate, p->npn_protos_); - const unsigned char* npn_protos = - reinterpret_cast(Buffer::Data(obj)); - size_t len = Buffer::Length(obj); - - int status = SSL_select_next_proto(out, outlen, in, inlen, npn_protos, len); - Handle result; - switch (status) { - case OPENSSL_NPN_UNSUPPORTED: - result = Null(node_isolate); - break; - case OPENSSL_NPN_NEGOTIATED: - result = OneByteString(node_isolate, *out, *outlen); - break; - case OPENSSL_NPN_NO_OVERLAP: - result = False(node_isolate); - break; - default: - break; - } - - if (!result.IsEmpty()) - p->selected_npn_proto_.Reset(node_isolate, result); - - return SSL_TLSEXT_ERR_OK; -} - - -void TLSCallbacks::GetNegotiatedProto(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - if (wrap->kind_ == kTLSClient) { - if (wrap->selected_npn_proto_.IsEmpty() == false) { - args.GetReturnValue().Set(wrap->selected_npn_proto_); - } - return; - } - - const unsigned char* npn_proto; - unsigned int npn_proto_len; - - SSL_get0_next_proto_negotiated(wrap->ssl_, &npn_proto, &npn_proto_len); - - if (!npn_proto) { - return args.GetReturnValue().Set(false); - } - - args.GetReturnValue().Set( - OneByteString(node_isolate, npn_proto, npn_proto_len)); -} - - -void TLSCallbacks::SetNPNProtocols(const FunctionCallbackInfo& args) { - HandleScope scope(node_isolate); - - TLSCallbacks* wrap; - NODE_UNWRAP(args.This(), TLSCallbacks, wrap); - - if (args.Length() < 1 || !Buffer::HasInstance(args[0])) - return ThrowTypeError("Must give a Buffer as first argument"); - - wrap->npn_protos_.Reset(node_isolate, args[0].As()); -} -#endif // OPENSSL_NPN_NEGOTIATED - - #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB void TLSCallbacks::GetServername(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); @@ -1225,7 +650,7 @@ void TLSCallbacks::SetServername(const FunctionCallbackInfo& args) { if (wrap->started_) return ThrowError("Already started."); - if (wrap->kind_ != kTLSClient) + if (!wrap->is_client()) return; #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB @@ -1244,7 +669,7 @@ int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { if (servername != NULL) { // Call the SNI callback and use its return value as context - Local object = p->object(); + Local object = p->object(node_isolate); Local ctx; if (object->Has(sni_context_sym)) { ctx = object->Get(sni_context_sym); @@ -1275,15 +700,7 @@ void TLSCallbacks::Initialize(Handle target) { t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "TLSWrap")); NODE_SET_PROTOTYPE_METHOD(t, "start", Start); - NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", GetPeerCertificate); - NODE_SET_PROTOTYPE_METHOD(t, "getSession", GetSession); - NODE_SET_PROTOTYPE_METHOD(t, "setSession", SetSession); - NODE_SET_PROTOTYPE_METHOD(t, "loadSession", LoadSession); - NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser); - NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher); - NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError); NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode); - NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused); NODE_SET_PROTOTYPE_METHOD(t, "enableSessionCallbacks", EnableSessionCallbacks); @@ -1291,10 +708,7 @@ void TLSCallbacks::Initialize(Handle target) { "enableHelloParser", EnableHelloParser); -#ifdef OPENSSL_NPN_NEGOTIATED - NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto); - NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", SetNPNProtocols); -#endif // OPENSSL_NPN_NEGOTIATED + SSLWrap::AddMethods(t); #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB NODE_SET_PROTOTYPE_METHOD(t, "getServername", GetServername); @@ -1309,7 +723,6 @@ void TLSCallbacks::Initialize(Handle target) { FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakestart"); onhandshakedone_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakedone"); onclienthello_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onclienthello"); - onnewsession_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onnewsession"); subject_sym = FIXED_ONE_BYTE_STRING(node_isolate, "subject"); issuer_sym = FIXED_ONE_BYTE_STRING(node_isolate, "issuer"); @@ -1322,9 +735,6 @@ void TLSCallbacks::Initialize(Handle target) { name_sym = FIXED_ONE_BYTE_STRING(node_isolate, "name"); version_sym = FIXED_ONE_BYTE_STRING(node_isolate, "version"); ext_key_usage_sym = FIXED_ONE_BYTE_STRING(node_isolate, "ext_key_usage"); - sessionid_sym = FIXED_ONE_BYTE_STRING(node_isolate, "sessionId"); - tls_ticket_sym = FIXED_ONE_BYTE_STRING(node_isolate, "tlsTicket"); - servername_sym = FIXED_ONE_BYTE_STRING(node_isolate, "servername"); sni_context_sym = FIXED_ONE_BYTE_STRING(node_isolate, "sni_context"); } diff --git a/src/tls_wrap.h b/src/tls_wrap.h index bb56794..6470643 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -23,7 +23,7 @@ #define SRC_TLS_WRAP_H_ #include "node.h" -#include "node_crypto_clienthello.h" +#include "node_crypto.h" // SSLWrap #include "queue.h" #include "stream_wrap.h" #include "v8.h" @@ -39,13 +39,9 @@ namespace crypto { class SecureContext; } -class TLSCallbacks : public StreamWrapCallbacks { +class TLSCallbacks : public crypto::SSLWrap, + public StreamWrapCallbacks { public: - enum Kind { - kTLSClient, - kTLSServer - }; - static void Initialize(v8::Handle target); int DoWrite(WriteWrap* w, @@ -61,6 +57,11 @@ class TLSCallbacks : public StreamWrapCallbacks { uv_handle_type pending); int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); + // Just for compliance with Connection + inline v8::Local handle(v8::Isolate* isolate) { + return object(isolate); + } + protected: static const int kClearOutChunkSize = 1024; @@ -97,69 +98,33 @@ class TLSCallbacks : public StreamWrapCallbacks { } v8::Handle GetSSLError(int status, int* err); - static void OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello); static void OnClientHelloParseEnd(void* arg); static void Wrap(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); - static void GetPeerCertificate( - const v8::FunctionCallbackInfo& args); - static void GetSession(const v8::FunctionCallbackInfo& args); - static void SetSession(const v8::FunctionCallbackInfo& args); - static void LoadSession(const v8::FunctionCallbackInfo& args); - static void EndParser(const v8::FunctionCallbackInfo& args); - static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); - static void VerifyError(const v8::FunctionCallbackInfo& args); static void SetVerifyMode(const v8::FunctionCallbackInfo& args); - static void IsSessionReused(const v8::FunctionCallbackInfo& args); static void EnableSessionCallbacks( const v8::FunctionCallbackInfo& args); static void EnableHelloParser( const v8::FunctionCallbackInfo& args); - // TLS Session API - static SSL_SESSION* GetSessionCallback(SSL* s, - unsigned char* key, - int len, - int* copy); - static int NewSessionCallback(SSL* s, SSL_SESSION* sess); - -#ifdef OPENSSL_NPN_NEGOTIATED - static void GetNegotiatedProto( - const v8::FunctionCallbackInfo& args); - static void SetNPNProtocols(const v8::FunctionCallbackInfo& args); - static int AdvertiseNextProtoCallback(SSL* s, - const unsigned char** data, - unsigned int* len, - void* arg); - static int SelectNextProtoCallback(SSL* s, - unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg); -#endif // OPENSSL_NPN_NEGOTIATED - #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB static void GetServername(const v8::FunctionCallbackInfo& args); static void SetServername(const v8::FunctionCallbackInfo& args); static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - inline v8::Local object() { - return PersistentToLocal(node_isolate, persistent()); + inline v8::Local object(v8::Isolate* isolate) { + return PersistentToLocal(isolate, persistent()); } inline v8::Persistent& persistent() { return object_; } - Kind kind_; crypto::SecureContext* sc_; v8::Persistent sc_handle_; v8::Persistent object_; - SSL* ssl_; BIO* enc_in_; BIO* enc_out_; NodeBIO* clear_in_; @@ -171,14 +136,6 @@ class TLSCallbacks : public StreamWrapCallbacks { bool started_; bool established_; bool shutdown_; - bool session_callbacks_; - SSL_SESSION* next_sess_; - ClientHelloParser hello_; - -#ifdef OPENSSL_NPN_NEGOTIATED - v8::Persistent npn_protos_; - v8::Persistent selected_npn_proto_; -#endif // OPENSSL_NPN_NEGOTIATED #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB v8::Persistent sni_context_; -- 2.7.4