diff options
author | Fedor Indutny <fedor@indutny.com> | 2015-04-18 10:19:23 +0200 |
---|---|---|
committer | Fedor Indutny <fedor@indutny.com> | 2015-05-01 16:56:55 +0200 |
commit | 550c2638c0885f9cbb1022f8f5234015e21836fe (patch) | |
tree | 5bc382b6cef0b84dc1851f719a33b7eb975339b6 /src | |
parent | 30b7349176da785cd7294fec8c31cfb9c5f791e8 (diff) | |
download | android-node-v8-550c2638c0885f9cbb1022f8f5234015e21836fe.tar.gz android-node-v8-550c2638c0885f9cbb1022f8f5234015e21836fe.tar.bz2 android-node-v8-550c2638c0885f9cbb1022f8f5234015e21836fe.zip |
tls: use `SSL_set_cert_cb` for async SNI/OCSP
Do not enable ClientHello parser for async SNI/OCSP. Use new
OpenSSL-1.0.2's API `SSL_set_cert_cb` to pause the handshake process and
load the cert/OCSP response asynchronously. Hopefuly this will make
whole async SNI/OCSP process much faster and will eventually let us
remove the ClientHello parser itself (which is currently used only for
async session, see #1462 for the discussion of removing it).
NOTE: Ported our code to `SSL_CTX_add1_chain_cert` to use
`SSL_CTX_get0_chain_certs` in `CertCbDone`. Test provided for this
feature.
Fix: https://github.com/iojs/io.js/issues/1423
PR-URL: https://github.com/iojs/io.js/pull/1464
Reviewed-By: Shigeki Ohtsu <ohtsu@iij.ad.jp>
Diffstat (limited to 'src')
-rw-r--r-- | src/env.h | 1 | ||||
-rw-r--r-- | src/node_crypto.cc | 132 | ||||
-rw-r--r-- | src/node_crypto.h | 26 | ||||
-rw-r--r-- | src/tls_wrap.cc | 17 | ||||
-rw-r--r-- | src/tls_wrap.h | 6 |
5 files changed, 164 insertions, 18 deletions
@@ -55,6 +55,7 @@ namespace node { V(bytes_parsed_string, "bytesParsed") \ V(callback_string, "callback") \ V(change_string, "change") \ + V(oncertcb_string, "oncertcb") \ V(onclose_string, "_onclose") \ V(code_string, "code") \ V(compare_string, "compare") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 97a105879f..e49545810d 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -132,6 +132,8 @@ template int SSLWrap<TLSWrap>::SelectNextProtoCallback( #endif template int SSLWrap<TLSWrap>::TLSExtStatusCallback(SSL* s, void* arg); template void SSLWrap<TLSWrap>::DestroySSL(); +template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg); +template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg); static void crypto_threadid_cb(CRYPTO_THREADID* tid) { @@ -511,7 +513,8 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, } while ((ca = PEM_read_bio_X509(in, nullptr, CryptoPemCallback, nullptr))) { - r = SSL_CTX_add_extra_chain_cert(ctx, ca); + // NOTE: Increments reference count on `ca` + r = SSL_CTX_add1_chain_cert(ctx, ca); if (!r) { X509_free(ca); @@ -987,6 +990,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) { env->SetProtoMethod(t, "verifyError", VerifyError); env->SetProtoMethod(t, "getCurrentCipher", GetCurrentCipher); env->SetProtoMethod(t, "endParser", EndParser); + env->SetProtoMethod(t, "certCbDone", CertCbDone); env->SetProtoMethod(t, "renegotiate", Renegotiate); env->SetProtoMethod(t, "shutdownSSL", Shutdown); env->SetProtoMethod(t, "getTLSTicket", GetTLSTicket); @@ -1870,6 +1874,122 @@ int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) { template <class Base> +void SSLWrap<Base>::WaitForCertCb(CertCb cb, void* arg) { + cert_cb_ = cb; + cert_cb_arg_ = arg; +} + + +template <class Base> +int SSLWrap<Base>::SSLCertCallback(SSL* s, void* arg) { + Base* w = static_cast<Base*>(SSL_get_app_data(s)); + + if (!w->is_server()) + return 1; + + if (!w->is_waiting_cert_cb()) + return 1; + + if (w->cert_cb_running_) + return -1; + + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + w->cert_cb_running_ = true; + + Local<Object> info = Object::New(env->isolate()); + + SSL_SESSION* sess = SSL_get_session(s); + if (sess != nullptr) { + if (sess->tlsext_hostname == nullptr) { + info->Set(env->servername_string(), String::Empty(env->isolate())); + } else { + Local<String> servername = OneByteString(env->isolate(), + sess->tlsext_hostname, + strlen(sess->tlsext_hostname)); + info->Set(env->servername_string(), servername); + } + info->Set(env->tls_ticket_string(), + Boolean::New(env->isolate(), sess->tlsext_ticklen != 0)); + } + bool ocsp = s->tlsext_status_type == TLSEXT_STATUSTYPE_ocsp; + info->Set(env->ocsp_request_string(), Boolean::New(env->isolate(), ocsp)); + + Local<Value> argv[] = { info }; + w->MakeCallback(env->oncertcb_string(), ARRAY_SIZE(argv), argv); + + if (!w->cert_cb_running_) + return 1; + + // Performing async action, wait... + return -1; +} + + +template <class Base> +void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) { + Base* w = Unwrap<Base>(args.Holder()); + Environment* env = w->env(); + + CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_); + + Local<Object> object = w->object(); + Local<Value> ctx = object->Get(env->sni_context_string()); + Local<FunctionTemplate> cons = env->secure_context_constructor_template(); + + // Not an object, probably undefined or null + if (!ctx->IsObject()) + goto fire_cb; + + if (cons->HasInstance(ctx)) { + SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>()); + w->sni_context_.Reset(); + w->sni_context_.Reset(env->isolate(), ctx); + + int rv; + + // NOTE: reference count is not increased by this API methods + X509* x509 = SSL_CTX_get0_certificate(sc->ctx_); + EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_); + STACK_OF(X509)* chain; + + rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain); + if (rv) + rv = SSL_use_certificate(w->ssl_, x509); + if (rv) + rv = SSL_use_PrivateKey(w->ssl_, pkey); + if (rv && chain != nullptr) + rv = SSL_set1_chain(w->ssl_, chain); + if (!rv) { + unsigned long err = ERR_get_error(); + if (!err) + return env->ThrowError("CertCbDone"); + return ThrowCryptoError(env, err); + } + } else { + // Failure: incorrect SNI context object + Local<Value> err = Exception::TypeError(env->sni_context_err_string()); + w->MakeCallback(env->onerror_string(), 1, &err); + return; + } + + fire_cb: + CertCb cb; + void* arg; + + cb = w->cert_cb_; + arg = w->cert_cb_arg_; + + w->cert_cb_running_ = false; + w->cert_cb_ = nullptr; + w->cert_cb_arg_ = nullptr; + + cb(arg); +} + + +template <class Base> void SSLWrap<Base>::SSLGetter(Local<String> property, const PropertyCallbackInfo<Value>& info) { HandleScope scope(info.GetIsolate()); @@ -1975,6 +2095,10 @@ int Connection::HandleSSLError(const char* func, DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func); return 0; + } else if (err == SSL_ERROR_WANT_X509_LOOKUP) { + DEBUG_PRINT("[%p] SSL: %s want x509 lookup\n", ssl_, func); + return 0; + } else if (err == SSL_ERROR_ZERO_RETURN) { HandleScope scope(ssl_env()->isolate()); @@ -2140,7 +2264,7 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { // Call the SNI callback and use its return value as context if (!conn->sniObject_.IsEmpty()) { - conn->sniContext_.Reset(); + conn->sni_context_.Reset(); Local<Value> arg = PersistentToLocal(env->isolate(), conn->servername_); Local<Value> ret = conn->MakeCallback(env->onselect_string(), 1, &arg); @@ -2149,7 +2273,7 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { Local<FunctionTemplate> secure_context_constructor_template = env->secure_context_constructor_template(); if (secure_context_constructor_template->HasInstance(ret)) { - conn->sniContext_.Reset(env->isolate(), ret); + conn->sni_context_.Reset(env->isolate(), ret); SecureContext* sc = Unwrap<SecureContext>(ret.As<Object>()); InitNPN(sc); SSL_set_SSL_CTX(s, sc->ctx_); @@ -2188,6 +2312,8 @@ void Connection::New(const FunctionCallbackInfo<Value>& args) { InitNPN(sc); + SSL_set_cert_cb(conn->ssl_, SSLWrap<Connection>::SSLCertCallback, conn); + #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB if (is_server) { SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_); diff --git a/src/node_crypto.h b/src/node_crypto.h index f6069f8841..179543bd50 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -143,7 +143,10 @@ class SSLWrap { kind_(kind), next_sess_(nullptr), session_callbacks_(false), - new_session_wait_(false) { + new_session_wait_(false), + cert_cb_(nullptr), + cert_cb_arg_(nullptr), + cert_cb_running_(false) { ssl_ = SSL_new(sc->ctx_); env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); CHECK_NE(ssl_, nullptr); @@ -160,6 +163,9 @@ class SSLWrap { npn_protos_.Reset(); selected_npn_proto_.Reset(); #endif +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sni_context_.Reset(); +#endif #ifdef NODE__HAVE_TLSEXT_STATUS_CB ocsp_response_.Reset(); #endif // NODE__HAVE_TLSEXT_STATUS_CB @@ -170,8 +176,11 @@ class SSLWrap { inline bool is_server() const { return kind_ == kServer; } inline bool is_client() const { return kind_ == kClient; } inline bool is_waiting_new_session() const { return new_session_wait_; } + inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } protected: + typedef void (*CertCb)(void* arg); + // Size allocated by OpenSSL: one for SSL structure, one for SSL3_STATE and // some for buffers. // NOTE: Actually it is much more than this @@ -199,6 +208,7 @@ class SSLWrap { static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetCurrentCipher(const v8::FunctionCallbackInfo<v8::Value>& args); static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args); + static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args); static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args); static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args); static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -227,10 +237,12 @@ class SSLWrap { void* arg); #endif // OPENSSL_NPN_NEGOTIATED static int TLSExtStatusCallback(SSL* s, void* arg); + static int SSLCertCallback(SSL* s, void* arg); static void SSLGetter(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info); void DestroySSL(); + void WaitForCertCb(CertCb cb, void* arg); inline Environment* ssl_env() const { return env_; @@ -242,6 +254,12 @@ class SSLWrap { SSL* ssl_; bool session_callbacks_; bool new_session_wait_; + + // SSL_set_cert_cb + CertCb cert_cb_; + void* cert_cb_arg_; + bool cert_cb_running_; + ClientHelloParser hello_parser_; #ifdef NODE__HAVE_TLSEXT_STATUS_CB @@ -253,6 +271,10 @@ class SSLWrap { v8::Persistent<v8::Value> selected_npn_proto_; #endif // OPENSSL_NPN_NEGOTIATED +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + v8::Persistent<v8::Value> sni_context_; +#endif + friend class SecureContext; }; @@ -264,7 +286,6 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap { ~Connection() override { #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sniObject_.Reset(); - sniContext_.Reset(); servername_.Reset(); #endif } @@ -279,7 +300,6 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap { #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB v8::Persistent<v8::Object> sniObject_; - v8::Persistent<v8::Value> sniContext_; v8::Persistent<v8::String> servername_; #endif diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 703bce8667..fd337d74a6 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -150,6 +150,8 @@ void TLSWrap::InitSSL() { InitNPN(sc_); + SSL_set_cert_cb(ssl_, SSLWrap<TLSWrap>::SSLCertCallback, this); + if (is_server()) { SSL_set_accept_state(ssl_); } else if (is_client()) { @@ -355,6 +357,7 @@ Local<Value> TLSWrap::GetSSLError(int status, int* err, const char** msg) { case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_X509_LOOKUP: break; case SSL_ERROR_ZERO_RETURN: return scope.Escape(env()->zero_return_string()); @@ -738,12 +741,6 @@ void TLSWrap::EnableSessionCallbacks( const FunctionCallbackInfo<Value>& args) { TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder()); wrap->enable_session_callbacks(); - EnableHelloParser(args); -} - - -void TLSWrap::EnableHelloParser(const FunctionCallbackInfo<Value>& args) { - TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder()); NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); wrap->hello_parser_.Start(SSLWrap<TLSWrap>::OnClientHello, OnClientHelloParseEnd, @@ -759,6 +756,12 @@ void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) { } +void TLSWrap::EnableCertCb(const FunctionCallbackInfo<Value>& args) { + TLSWrap* wrap = Unwrap<TLSWrap>(args.Holder()); + wrap->WaitForCertCb(OnClientHelloParseEnd, wrap); +} + + void TLSWrap::OnClientHelloParseEnd(void* arg) { TLSWrap* c = static_cast<TLSWrap*>(arg); c->Cycle(); @@ -857,8 +860,8 @@ void TLSWrap::Initialize(Handle<Object> target, env->SetProtoMethod(t, "start", Start); env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); - env->SetProtoMethod(t, "enableHelloParser", EnableHelloParser); env->SetProtoMethod(t, "destroySSL", DestroySSL); + env->SetProtoMethod(t, "enableCertCb", EnableCertCb); StreamBase::AddMethods<TLSWrap>(env, t, StreamBase::kFlagHasWritev); SSLWrap<TLSWrap>::AddMethods(env, t); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 25088d3026..a304475190 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -130,7 +130,7 @@ class TLSWrap : public crypto::SSLWrap<TLSWrap>, static void SetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args); static void EnableSessionCallbacks( const v8::FunctionCallbackInfo<v8::Value>& args); - static void EnableHelloParser( + static void EnableCertCb( const v8::FunctionCallbackInfo<v8::Value>& args); static void DestroySSL(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -159,10 +159,6 @@ class TLSWrap : public crypto::SSLWrap<TLSWrap>, // If true - delivered EOF to the js-land, either after `close_notify`, or // after the `UV_EOF` on socket. bool eof_; - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - v8::Persistent<v8::Value> sni_context_; -#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB }; } // namespace node |