From dac9e3811059ea72f16c448bf7cb927999eb64f4 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Mon, 26 Oct 2015 12:06:28 -0400 Subject: [PATCH] tls: copy client CAs and cert store on CertCb Copy client CA certs and cert store when asynchronously selecting `SecureContext` during `SNICallback`. We already copy private key, certificate, and certificate chain, but the client CA certs were missing. Fix: #2772 PR-URL: https://github.com/nodejs/node/pull/3537 Reviewed-By: Ben Noordhuis --- src/node_crypto.cc | 31 ++++++++++++++++++++++++++-- src/node_crypto.h | 2 ++ src/tls_wrap.cc | 3 +-- test/parallel/test-tls-sni-option.js | 31 +++++++++++++++++++++------- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index eec1eb9e63a5ec..06e24a53e68c0e 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -129,6 +129,8 @@ template class SSLWrap; template void SSLWrap::AddMethods(Environment* env, Local t); template void SSLWrap::InitNPN(SecureContext* sc); +template void SSLWrap::SetSNIContext(SecureContext* sc); +template int SSLWrap::SetCACerts(SecureContext* sc); template SSL_SESSION* SSLWrap::GetSessionCallback( SSL* s, unsigned char* key, @@ -2165,6 +2167,8 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { rv = SSL_use_PrivateKey(w->ssl_, pkey); if (rv && chain != nullptr) rv = SSL_set1_chain(w->ssl_, chain); + if (rv) + rv = w->SetCACerts(sc); if (!rv) { unsigned long err = ERR_get_error(); if (!err) @@ -2215,6 +2219,30 @@ void SSLWrap::DestroySSL() { } +template +void SSLWrap::SetSNIContext(SecureContext* sc) { + InitNPN(sc); + CHECK_EQ(SSL_set_SSL_CTX(ssl_, sc->ctx_), sc->ctx_); + + SetCACerts(sc); +} + + +template +int SSLWrap::SetCACerts(SecureContext* sc) { + int err = SSL_set1_verify_cert_store(ssl_, SSL_CTX_get_cert_store(sc->ctx_)); + if (err != 1) + return err; + + STACK_OF(X509_NAME)* list = SSL_dup_CA_list( + SSL_CTX_get_client_CA_list(sc->ctx_)); + + // NOTE: `SSL_set_client_CA_list` takes the ownership of `list` + SSL_set_client_CA_list(ssl_, list); + return 1; +} + + void Connection::OnClientHelloParseEnd(void* arg) { Connection* conn = static_cast(arg); @@ -2528,8 +2556,7 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { if (secure_context_constructor_template->HasInstance(ret)) { conn->sni_context_.Reset(env->isolate(), ret); SecureContext* sc = Unwrap(ret.As()); - InitNPN(sc); - SSL_set_SSL_CTX(s, sc->ctx_); + conn->SetSNIContext(sc); } else { return SSL_TLSEXT_ERR_NOACK; } diff --git a/src/node_crypto.h b/src/node_crypto.h index e009fc1da63b01..cb94650e0735d0 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -279,6 +279,8 @@ class SSLWrap { void DestroySSL(); void WaitForCertCb(CertCb cb, void* arg); + void SetSNIContext(SecureContext* sc); + int SetCACerts(SecureContext* sc); inline Environment* ssl_env() const { return env_; diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 1bdd4b7df947fe..d7bf4ed8bee784 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -867,8 +867,7 @@ int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { p->sni_context_.Reset(env->isolate(), ctx); SecureContext* sc = Unwrap(ctx.As()); - InitNPN(sc); - SSL_set_SSL_CTX(s, sc->ctx_); + p->SetSNIContext(sc); return SSL_TLSEXT_ERR_OK; } #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB diff --git a/test/parallel/test-tls-sni-option.js b/test/parallel/test-tls-sni-option.js index 5b0bd53f4e6343..510b929ae2a17b 100644 --- a/test/parallel/test-tls-sni-option.js +++ b/test/parallel/test-tls-sni-option.js @@ -26,6 +26,8 @@ function loadPEM(n) { var serverOptions = { key: loadPEM('agent2-key'), cert: loadPEM('agent2-cert'), + requestCert: true, + rejectUnauthorized: false, SNICallback: function(servername, callback) { var context = SNIContexts[servername]; @@ -46,7 +48,8 @@ var serverOptions = { var SNIContexts = { 'a.example.com': { key: loadPEM('agent1-key'), - cert: loadPEM('agent1-cert') + cert: loadPEM('agent1-cert'), + ca: [ loadPEM('ca2-cert') ] }, 'b.example.com': { key: loadPEM('agent3-key'), @@ -66,6 +69,13 @@ var clientsOptions = [{ ca: [loadPEM('ca1-cert')], servername: 'a.example.com', rejectUnauthorized: false +}, { + port: serverPort, + key: loadPEM('agent4-key'), + cert: loadPEM('agent4-cert'), + ca: [loadPEM('ca1-cert')], + servername: 'a.example.com', + rejectUnauthorized: false }, { port: serverPort, key: loadPEM('agent2-key'), @@ -97,7 +107,7 @@ let serverError; let clientError; var server = tls.createServer(serverOptions, function(c) { - serverResults.push(c.servername); + serverResults.push({ sni: c.servername, authorized: c.authorized }); }); server.on('clientError', function(err) { @@ -144,9 +154,16 @@ function startTest() { } process.on('exit', function() { - assert.deepEqual(serverResults, ['a.example.com', 'b.example.com', - 'c.wrong.com', null]); - assert.deepEqual(clientResults, [true, true, false, false]); - assert.deepEqual(clientErrors, [null, null, null, 'socket hang up']); - assert.deepEqual(serverErrors, [null, null, null, 'Invalid SNI context']); + assert.deepEqual(serverResults, [ + { sni: 'a.example.com', authorized: false }, + { sni: 'a.example.com', authorized: true }, + { sni: 'b.example.com', authorized: false }, + { sni: 'c.wrong.com', authorized: false }, + null + ]); + assert.deepEqual(clientResults, [true, true, true, false, false]); + assert.deepEqual(clientErrors, [null, null, null, null, 'socket hang up']); + assert.deepEqual(serverErrors, [ + null, null, null, null, 'Invalid SNI context' + ]); });