Skip to content

Commit 16d2ac8

Browse files
indutnyShigeki Ohtsu
authored and
Shigeki Ohtsu
committed
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 nodejs#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: nodejs#1423
1 parent 77db7e1 commit 16d2ac8

23 files changed

+444
-102
lines changed

lib/_tls_wrap.js

+23-26
Original file line numberDiff line numberDiff line change
@@ -141,29 +141,23 @@ function onclienthello(hello) {
141141
if (err)
142142
return self.destroy(err);
143143

144-
// Servername came from SSL session
145-
// NOTE: TLS Session ticket doesn't include servername information
146-
//
147-
// Another note, From RFC3546:
148-
//
149-
// If, on the other hand, the older
150-
// session is resumed, then the server MUST ignore extensions appearing
151-
// in the client hello, and send a server hello containing no
152-
// extensions; in this case the extension functionality negotiated
153-
// during the original session initiation is applied to the resumed
154-
// session.
155-
//
156-
// Therefore we should account session loading when dealing with servername
157-
var servername = session && session.servername || hello.servername;
158-
loadSNI(self, servername, function(err, ctx) {
144+
self._handle.endParser();
145+
});
146+
}
147+
148+
149+
function oncertcb(info) {
150+
var self = this;
151+
var servername = info.servername;
152+
153+
loadSNI(self, servername, function(err, ctx) {
154+
if (err)
155+
return self.destroy(err);
156+
requestOCSP(self, info, ctx, function(err) {
159157
if (err)
160158
return self.destroy(err);
161-
requestOCSP(self, hello, ctx, function(err) {
162-
if (err)
163-
return self.destroy(err);
164159

165-
self._handle.endParser();
166-
});
160+
self._handle.certCbDone();
167161
});
168162
});
169163
}
@@ -325,15 +319,18 @@ TLSSocket.prototype._init = function(socket, wrap) {
325319
ssl.onhandshakestart = onhandshakestart.bind(this);
326320
ssl.onhandshakedone = onhandshakedone.bind(this);
327321
ssl.onclienthello = onclienthello.bind(this);
322+
ssl.oncertcb = oncertcb.bind(this);
328323
ssl.onnewsession = onnewsession.bind(this);
329324
ssl.lastHandshakeTime = 0;
330325
ssl.handshakes = 0;
331326

332-
if (this.server &&
333-
(listenerCount(this.server, 'resumeSession') > 0 ||
334-
listenerCount(this.server, 'newSession') > 0 ||
335-
listenerCount(this.server, 'OCSPRequest') > 0)) {
336-
ssl.enableSessionCallbacks();
327+
if (this.server) {
328+
if (listenerCount(this.server, 'resumeSession') > 0 ||
329+
listenerCount(this.server, 'newSession') > 0) {
330+
ssl.enableSessionCallbacks();
331+
}
332+
if (listenerCount(this.server, 'OCSPRequest') > 0)
333+
ssl.enableCertCb();
337334
}
338335
} else {
339336
ssl.onhandshakestart = function() {};
@@ -374,7 +371,7 @@ TLSSocket.prototype._init = function(socket, wrap) {
374371
options.server._contexts.length)) {
375372
assert(typeof options.SNICallback === 'function');
376373
this._SNICallback = options.SNICallback;
377-
ssl.enableHelloParser();
374+
ssl.enableCertCb();
378375
}
379376

380377
if (process.features.tls_npn && options.NPNProtocols)

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace node {
5555
V(bytes_parsed_string, "bytesParsed") \
5656
V(callback_string, "callback") \
5757
V(change_string, "change") \
58+
V(oncertcb_string, "oncertcb") \
5859
V(onclose_string, "_onclose") \
5960
V(code_string, "code") \
6061
V(compare_string, "compare") \

src/node_crypto.cc

+133-7
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ X509_STORE* root_cert_store;
104104
template class SSLWrap<TLSWrap>;
105105
template void SSLWrap<TLSWrap>::AddMethods(Environment* env,
106106
Handle<FunctionTemplate> t);
107-
template void SSLWrap<TLSWrap>::InitNPN(SecureContext* sc);
107+
template void SSLWrap<TLSWrap>::InitNPN(TLSWrap* w, SecureContext* sc);
108108
template SSL_SESSION* SSLWrap<TLSWrap>::GetSessionCallback(
109109
SSL* s,
110110
unsigned char* key,
@@ -131,6 +131,8 @@ template int SSLWrap<TLSWrap>::SelectNextProtoCallback(
131131
void* arg);
132132
#endif
133133
template int SSLWrap<TLSWrap>::TLSExtStatusCallback(SSL* s, void* arg);
134+
template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg);
135+
template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg);
134136

135137

136138
static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
@@ -509,7 +511,8 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
509511
}
510512

511513
while ((ca = PEM_read_bio_X509(in, nullptr, CryptoPemCallback, nullptr))) {
512-
r = SSL_CTX_add_extra_chain_cert(ctx, ca);
514+
// NOTE: Increments reference count on `ca`
515+
r = SSL_CTX_add1_chain_cert(ctx, ca);
513516

514517
if (!r) {
515518
X509_free(ca);
@@ -978,6 +981,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
978981
env->SetProtoMethod(t, "verifyError", VerifyError);
979982
env->SetProtoMethod(t, "getCurrentCipher", GetCurrentCipher);
980983
env->SetProtoMethod(t, "endParser", EndParser);
984+
env->SetProtoMethod(t, "certCbDone", CertCbDone);
981985
env->SetProtoMethod(t, "renegotiate", Renegotiate);
982986
env->SetProtoMethod(t, "shutdownSSL", Shutdown);
983987
env->SetProtoMethod(t, "getTLSTicket", GetTLSTicket);
@@ -1008,7 +1012,7 @@ void SSLWrap<Base>::AddMethods(Environment* env, Handle<FunctionTemplate> t) {
10081012

10091013

10101014
template <class Base>
1011-
void SSLWrap<Base>::InitNPN(SecureContext* sc) {
1015+
void SSLWrap<Base>::InitNPN(Base* w, SecureContext* sc) {
10121016
#ifdef OPENSSL_NPN_NEGOTIATED
10131017
// Server should advertise NPN protocols
10141018
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
@@ -1024,6 +1028,8 @@ void SSLWrap<Base>::InitNPN(SecureContext* sc) {
10241028
SSL_CTX_set_tlsext_status_cb(sc->ctx_, TLSExtStatusCallback);
10251029
SSL_CTX_set_tlsext_status_arg(sc->ctx_, nullptr);
10261030
#endif // NODE__HAVE_TLSEXT_STATUS_CB
1031+
1032+
SSL_set_cert_cb(w->ssl_, SSLWrap<Base>::SSLCertCallback, w);
10271033
}
10281034

10291035

@@ -1860,6 +1866,122 @@ int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
18601866
#endif // NODE__HAVE_TLSEXT_STATUS_CB
18611867

18621868

1869+
template <class Base>
1870+
void SSLWrap<Base>::WaitForCertCb(CertCb cb, void* arg) {
1871+
cert_cb_ = cb;
1872+
cert_cb_arg_ = arg;
1873+
}
1874+
1875+
1876+
template <class Base>
1877+
int SSLWrap<Base>::SSLCertCallback(SSL* s, void* arg) {
1878+
Base* w = static_cast<Base*>(SSL_get_app_data(s));
1879+
1880+
if (!w->is_server())
1881+
return 1;
1882+
1883+
if (!w->is_waiting_cert_cb())
1884+
return 1;
1885+
1886+
if (w->cert_cb_running_)
1887+
return -1;
1888+
1889+
Environment* env = w->env();
1890+
HandleScope handle_scope(env->isolate());
1891+
Context::Scope context_scope(env->context());
1892+
w->cert_cb_running_ = true;
1893+
1894+
Local<Object> info = Object::New(env->isolate());
1895+
1896+
SSL_SESSION* sess = SSL_get_session(s);
1897+
if (sess != nullptr) {
1898+
if (sess->tlsext_hostname == nullptr) {
1899+
info->Set(env->servername_string(), String::Empty(env->isolate()));
1900+
} else {
1901+
Local<String> servername = OneByteString(env->isolate(),
1902+
sess->tlsext_hostname,
1903+
strlen(sess->tlsext_hostname));
1904+
info->Set(env->servername_string(), servername);
1905+
}
1906+
info->Set(env->tls_ticket_string(),
1907+
Boolean::New(env->isolate(), sess->tlsext_ticklen != 0));
1908+
}
1909+
bool ocsp = s->tlsext_status_type == TLSEXT_STATUSTYPE_ocsp;
1910+
info->Set(env->ocsp_request_string(), Boolean::New(env->isolate(), ocsp));
1911+
1912+
Local<Value> argv[] = { info };
1913+
w->MakeCallback(env->oncertcb_string(), ARRAY_SIZE(argv), argv);
1914+
1915+
if (!w->cert_cb_running_)
1916+
return 1;
1917+
1918+
// Performing async action, wait...
1919+
return -1;
1920+
}
1921+
1922+
1923+
template <class Base>
1924+
void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) {
1925+
Base* w = Unwrap<Base>(args.Holder());
1926+
Environment* env = w->env();
1927+
1928+
CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_);
1929+
1930+
Local<Object> object = w->object();
1931+
Local<Value> ctx = object->Get(env->sni_context_string());
1932+
Local<FunctionTemplate> cons = env->secure_context_constructor_template();
1933+
1934+
// Not an object, probably undefined or null
1935+
if (!ctx->IsObject())
1936+
goto fire_cb;
1937+
1938+
if (cons->HasInstance(ctx)) {
1939+
SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
1940+
w->sni_context_.Reset();
1941+
w->sni_context_.Reset(env->isolate(), ctx);
1942+
1943+
int rv;
1944+
1945+
// NOTE: reference count is not increased by this API methods
1946+
X509* x509 = SSL_CTX_get0_certificate(sc->ctx_);
1947+
EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_);
1948+
STACK_OF(X509)* chain;
1949+
1950+
rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain);
1951+
if (rv)
1952+
rv = SSL_use_certificate(w->ssl_, x509);
1953+
if (rv)
1954+
rv = SSL_use_PrivateKey(w->ssl_, pkey);
1955+
if (rv && chain != nullptr)
1956+
rv = SSL_set1_chain(w->ssl_, chain);
1957+
if (!rv) {
1958+
unsigned long err = ERR_get_error();
1959+
if (!err)
1960+
return env->ThrowError("CertCbDone");
1961+
return ThrowCryptoError(env, err);
1962+
}
1963+
} else {
1964+
// Failure: incorrect SNI context object
1965+
Local<Value> err = Exception::TypeError(env->sni_context_err_string());
1966+
w->MakeCallback(env->onerror_string(), 1, &err);
1967+
return;
1968+
}
1969+
1970+
fire_cb:
1971+
CertCb cb;
1972+
void* arg;
1973+
1974+
cb = w->cert_cb_;
1975+
arg = w->cert_cb_arg_;
1976+
1977+
w->cert_cb_running_ = false;
1978+
w->cert_cb_ = nullptr;
1979+
w->cert_cb_arg_ = nullptr;
1980+
1981+
cb(arg);
1982+
}
1983+
1984+
18631985
template <class Base>
18641986
void SSLWrap<Base>::SSLGetter(Local<String> property,
18651987
const PropertyCallbackInfo<Value>& info) {
@@ -1955,6 +2077,10 @@ int Connection::HandleSSLError(const char* func,
19552077
DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func);
19562078
return 0;
19572079

2080+
} else if (err == SSL_ERROR_WANT_X509_LOOKUP) {
2081+
DEBUG_PRINT("[%p] SSL: %s want x509 lookup\n", ssl_, func);
2082+
return 0;
2083+
19582084
} else if (err == SSL_ERROR_ZERO_RETURN) {
19592085
HandleScope scope(ssl_env()->isolate());
19602086

@@ -2120,7 +2246,7 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
21202246

21212247
// Call the SNI callback and use its return value as context
21222248
if (!conn->sniObject_.IsEmpty()) {
2123-
conn->sniContext_.Reset();
2249+
conn->sni_context_.Reset();
21242250

21252251
Local<Value> arg = PersistentToLocal(env->isolate(), conn->servername_);
21262252
Local<Value> ret = conn->MakeCallback(env->onselect_string(), 1, &arg);
@@ -2129,9 +2255,9 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
21292255
Local<FunctionTemplate> secure_context_constructor_template =
21302256
env->secure_context_constructor_template();
21312257
if (secure_context_constructor_template->HasInstance(ret)) {
2132-
conn->sniContext_.Reset(env->isolate(), ret);
2258+
conn->sni_context_.Reset(env->isolate(), ret);
21332259
SecureContext* sc = Unwrap<SecureContext>(ret.As<Object>());
2134-
InitNPN(sc);
2260+
InitNPN(conn, sc);
21352261
SSL_set_SSL_CTX(s, sc->ctx_);
21362262
} else {
21372263
return SSL_TLSEXT_ERR_NOACK;
@@ -2166,7 +2292,7 @@ void Connection::New(const FunctionCallbackInfo<Value>& args) {
21662292
if (is_server)
21672293
SSL_set_info_callback(conn->ssl_, SSLInfoCallback);
21682294

2169-
InitNPN(sc);
2295+
InitNPN(conn, sc);
21702296

21712297
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
21722298
if (is_server) {

src/node_crypto.h

+25-4
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ class SSLWrap {
138138
kind_(kind),
139139
next_sess_(nullptr),
140140
session_callbacks_(false),
141-
new_session_wait_(false) {
141+
new_session_wait_(false),
142+
cert_cb_(nullptr),
143+
cert_cb_arg_(nullptr),
144+
cert_cb_running_(false) {
142145
ssl_ = SSL_new(sc->ctx_);
143146
CHECK_NE(ssl_, nullptr);
144147
}
@@ -157,6 +160,9 @@ class SSLWrap {
157160
npn_protos_.Reset();
158161
selected_npn_proto_.Reset();
159162
#endif
163+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
164+
sni_context_.Reset();
165+
#endif
160166
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
161167
ocsp_response_.Reset();
162168
#endif // NODE__HAVE_TLSEXT_STATUS_CB
@@ -167,9 +173,12 @@ class SSLWrap {
167173
inline bool is_server() const { return kind_ == kServer; }
168174
inline bool is_client() const { return kind_ == kClient; }
169175
inline bool is_waiting_new_session() const { return new_session_wait_; }
176+
inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; }
170177

171178
protected:
172-
static void InitNPN(SecureContext* sc);
179+
typedef void (*CertCb)(void* arg);
180+
181+
static void InitNPN(Base* w, SecureContext* sc);
173182
static void AddMethods(Environment* env, v8::Handle<v8::FunctionTemplate> t);
174183

175184
static SSL_SESSION* GetSessionCallback(SSL* s,
@@ -190,6 +199,7 @@ class SSLWrap {
190199
static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
191200
static void GetCurrentCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
192201
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
202+
static void CertCbDone(const v8::FunctionCallbackInfo<v8::Value>& args);
193203
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
194204
static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
195205
static void GetTLSTicket(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -218,9 +228,12 @@ class SSLWrap {
218228
void* arg);
219229
#endif // OPENSSL_NPN_NEGOTIATED
220230
static int TLSExtStatusCallback(SSL* s, void* arg);
231+
static int SSLCertCallback(SSL* s, void* arg);
221232
static void SSLGetter(v8::Local<v8::String> property,
222233
const v8::PropertyCallbackInfo<v8::Value>& info);
223234

235+
void WaitForCertCb(CertCb cb, void* arg);
236+
224237
inline Environment* ssl_env() const {
225238
return env_;
226239
}
@@ -231,6 +244,12 @@ class SSLWrap {
231244
SSL* ssl_;
232245
bool session_callbacks_;
233246
bool new_session_wait_;
247+
248+
// SSL_set_cert_cb
249+
CertCb cert_cb_;
250+
void* cert_cb_arg_;
251+
bool cert_cb_running_;
252+
234253
ClientHelloParser hello_parser_;
235254

236255
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
@@ -242,6 +261,10 @@ class SSLWrap {
242261
v8::Persistent<v8::Value> selected_npn_proto_;
243262
#endif // OPENSSL_NPN_NEGOTIATED
244263

264+
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
265+
v8::Persistent<v8::Value> sni_context_;
266+
#endif
267+
245268
friend class SecureContext;
246269
};
247270

@@ -253,7 +276,6 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
253276
~Connection() override {
254277
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
255278
sniObject_.Reset();
256-
sniContext_.Reset();
257279
servername_.Reset();
258280
#endif
259281
}
@@ -268,7 +290,6 @@ class Connection : public SSLWrap<Connection>, public AsyncWrap {
268290

269291
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
270292
v8::Persistent<v8::Object> sniObject_;
271-
v8::Persistent<v8::Value> sniContext_;
272293
v8::Persistent<v8::String> servername_;
273294
#endif
274295

0 commit comments

Comments
 (0)