summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFedor Indutny <fedor@indutny.com>2015-04-18 10:19:23 +0200
committerFedor Indutny <fedor@indutny.com>2015-05-01 16:56:55 +0200
commit550c2638c0885f9cbb1022f8f5234015e21836fe (patch)
tree5bc382b6cef0b84dc1851f719a33b7eb975339b6 /src
parent30b7349176da785cd7294fec8c31cfb9c5f791e8 (diff)
downloadandroid-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.h1
-rw-r--r--src/node_crypto.cc132
-rw-r--r--src/node_crypto.h26
-rw-r--r--src/tls_wrap.cc17
-rw-r--r--src/tls_wrap.h6
5 files changed, 164 insertions, 18 deletions
diff --git a/src/env.h b/src/env.h
index 9099b3b931..8a92634067 100644
--- a/src/env.h
+++ b/src/env.h
@@ -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