tls: use `SSL_set_cert_cb` for async SNI/OCSP
authorFedor Indutny <fedor@indutny.com>
Sat, 18 Apr 2015 08:19:23 +0000 (10:19 +0200)
committerFedor Indutny <fedor@indutny.com>
Fri, 1 May 2015 14:56:55 +0000 (16:56 +0200)
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>
23 files changed:
lib/_tls_wrap.js
src/env.h
src/node_crypto.cc
src/node_crypto.h
src/tls_wrap.cc
src/tls_wrap.h
test/fixtures/keys/Makefile
test/fixtures/keys/agent1-cert.pem
test/fixtures/keys/agent6-cert.pem [new file with mode: 0644]
test/fixtures/keys/agent6-csr.pem [new file with mode: 0644]
test/fixtures/keys/agent6-key.pem [new file with mode: 0644]
test/fixtures/keys/agent6.cnf [new file with mode: 0644]
test/fixtures/keys/ca1-cert.pem
test/fixtures/keys/ca1-cert.srl
test/fixtures/keys/ca1-key.pem
test/fixtures/keys/ca1.cnf
test/fixtures/keys/ca3-cert.pem [new file with mode: 0644]
test/fixtures/keys/ca3-cert.srl [new file with mode: 0644]
test/fixtures/keys/ca3-csr.pem [new file with mode: 0644]
test/fixtures/keys/ca3-key.pem [new file with mode: 0644]
test/fixtures/keys/ca3.cnf [new file with mode: 0644]
test/parallel/test-tls-peer-certificate.js
test/parallel/test-tls-sni-server-client.js

index 122c704..95df8f5 100644 (file)
@@ -141,29 +141,23 @@ function onclienthello(hello) {
     if (err)
       return self.destroy(err);
 
-    // Servername came from SSL session
-    // NOTE: TLS Session ticket doesn't include servername information
-    //
-    // Another note, From RFC3546:
-    //
-    //   If, on the other hand, the older
-    //   session is resumed, then the server MUST ignore extensions appearing
-    //   in the client hello, and send a server hello containing no
-    //   extensions; in this case the extension functionality negotiated
-    //   during the original session initiation is applied to the resumed
-    //   session.
-    //
-    // Therefore we should account session loading when dealing with servername
-    var servername = session && session.servername || hello.servername;
-    loadSNI(self, servername, function(err, ctx) {
+    self._handle.endParser();
+  });
+}
+
+
+function oncertcb(info) {
+  var self = this;
+  var servername = info.servername;
+
+  loadSNI(self, servername, function(err, ctx) {
+    if (err)
+      return self.destroy(err);
+    requestOCSP(self, info, ctx, function(err) {
       if (err)
         return self.destroy(err);
-      requestOCSP(self, hello, ctx, function(err) {
-        if (err)
-          return self.destroy(err);
 
-        self._handle.endParser();
-      });
+      self._handle.certCbDone();
     });
   });
 }
@@ -333,15 +327,18 @@ TLSSocket.prototype._init = function(socket, wrap) {
     ssl.onhandshakestart = onhandshakestart.bind(this);
     ssl.onhandshakedone = onhandshakedone.bind(this);
     ssl.onclienthello = onclienthello.bind(this);
+    ssl.oncertcb = oncertcb.bind(this);
     ssl.onnewsession = onnewsession.bind(this);
     ssl.lastHandshakeTime = 0;
     ssl.handshakes = 0;
 
-    if (this.server &&
-        (listenerCount(this.server, 'resumeSession') > 0 ||
-         listenerCount(this.server, 'newSession') > 0 ||
-         listenerCount(this.server, 'OCSPRequest') > 0)) {
-      ssl.enableSessionCallbacks();
+    if (this.server) {
+      if (listenerCount(this.server, 'resumeSession') > 0 ||
+          listenerCount(this.server, 'newSession') > 0) {
+        ssl.enableSessionCallbacks();
+      }
+      if (listenerCount(this.server, 'OCSPRequest') > 0)
+        ssl.enableCertCb();
     }
   } else {
     ssl.onhandshakestart = function() {};
@@ -382,7 +379,7 @@ TLSSocket.prototype._init = function(socket, wrap) {
        options.server._contexts.length)) {
     assert(typeof options.SNICallback === 'function');
     this._SNICallback = options.SNICallback;
-    ssl.enableHelloParser();
+    ssl.enableCertCb();
   }
 
   if (process.features.tls_npn && options.NPNProtocols)
index 9099b3b..8a92634 100644 (file)
--- 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")                                                \
index 97a1058..e495458 100644 (file)
@@ -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_);
index f6069f8..179543b 100644 (file)
@@ -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
 
index 703bce8..fd337d7 100644 (file)
@@ -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);
index 25088d3..a304475 100644 (file)
@@ -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
index a096431..1e3d40e 100644 (file)
@@ -17,6 +17,28 @@ ca2-cert.pem: ca2.cnf
        echo '01' > ca2-serial
        touch ca2-database.txt
 
+#
+# Create Subordinate Certificate Authority: ca3
+# ('password' is used for the CA password.)
+#
+ca3-key.pem:
+       openssl genrsa -out ca3-key.pem 1024
+
+ca3-csr.pem: ca3.cnf ca3-key.pem
+       openssl req -new \
+               -extensions v3_ca -config ca3.cnf -key ca3-key.pem -out ca3-csr.pem
+
+ca3-cert.pem: ca3-csr.pem ca3-key.pem ca3.cnf ca1-cert.pem ca1-key.pem
+       openssl x509 -req \
+               -extfile ca3.cnf \
+               -extensions v3_ca \
+               -days 9999 \
+               -passin "pass:password" \
+               -in ca3-csr.pem \
+               -CA ca1-cert.pem \
+               -CAkey ca1-key.pem \
+               -CAcreateserial \
+               -out ca3-cert.pem
 
 #
 # agent1 is signed by ca1.
@@ -157,6 +179,31 @@ agent5-cert.pem: agent5-csr.pem ca2-cert.pem ca2-key.pem
 agent5-verify: agent5-cert.pem ca2-cert.pem
        openssl verify -CAfile ca2-cert.pem agent5-cert.pem
 
+#
+# agent6 is signed by ca3
+#
+
+agent6-key.pem:
+       openssl genrsa -out agent6-key.pem 1024
+
+agent6-csr.pem: agent6.cnf agent6-key.pem
+       openssl req -new -config agent6.cnf -key agent6-key.pem -out agent6-csr.pem
+
+agent6-cert.pem: agent6-csr.pem ca3-cert.pem ca3-key.pem
+       openssl x509 -req \
+               -days 9999 \
+               -passin "pass:password" \
+               -in agent6-csr.pem \
+               -CA ca3-cert.pem \
+               -CAkey ca3-key.pem \
+               -CAcreateserial \
+               -extfile agent6.cnf \
+               -out agent6-cert.pem
+       cat ca3-cert.pem >> agent6-cert.pem
+
+agent6-verify: agent6-cert.pem ca3-cert.pem
+       openssl verify -CAfile ca3-cert.pem agent6-cert.pem
+
 ec-key.pem:
        openssl ecparam -genkey -out ec-key.pem -name prime256v1
 
index c5bfb18..9c5c2ca 100644 (file)
@@ -1,9 +1,9 @@
 -----BEGIN CERTIFICATE-----
-MIIC1jCCAj+gAwIBAgIJAJqEq8+4pyq+MA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV
-BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBkpveWVu
-dDEQMA4GA1UECxMHTm9kZS5qczEMMAoGA1UEAxMDY2ExMSAwHgYJKoZIhvcNAQkB
-FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNDA0MTUyMTMxMzFaFw00MTA4MzAyMTMx
-MzFaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzAN
+MIIC1jCCAj+gAwIBAgIJAJqEq8+4pyrAMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu
+dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB
+FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNTA0MTgxMzI5MDhaFw00MjA5MDIxMzI5
+MDhaMH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzAN
 BgNVBAoTBkpveWVudDEQMA4GA1UECxMHTm9kZS5qczEPMA0GA1UEAxMGYWdlbnQx
 MSAwHgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0B
 AQEFAAOBjQAwgYkCgYEAuOs3hW8rF+7xx5iB9wjmIgd+HTqRFUeKxG+mWV35Hl6A
@@ -11,8 +11,8 @@ AQEFAAOBjQAwgYkCgYEAuOs3hW8rF+7xx5iB9wjmIgd+HTqRFUeKxG+mWV35Hl6A
 lRxqJGXTjx+vG/0nDCXLBhoDKO00zEccdjGS8xEjjieQQr+KeASmIm0kQmuN5YcC
 AwEAAaNhMF8wXQYIKwYBBQUHAQEEUTBPMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
 cC5ub2RlanMub3JnLzAoBggrBgEFBQcwAoYcaHR0cDovL2NhLm5vZGVqcy5vcmcv
-Y2EuY2VydDANBgkqhkiG9w0BAQUFAAOBgQAx6rhnYbPygJwIm6nidyx+ydJQC4Gk
-JD+pzbdJkTS+01r+xjVY/Wckn4JAsIlo/MMn055rs2cfdjoQtlj6yjEU6AP/7bfr
-Mju4lBxDLACJ2y5/rfj3wO4q4Knd4Q4mPWjlS2SwmkHZ21QOqJ6Ig9ps6HPM7syw
-ZYQ3WQ1LOPAxMg==
+Y2EuY2VydDANBgkqhkiG9w0BAQsFAAOBgQA45MmH28Gns+1yu9w9MR/oR8hKDMnG
+E4yDZ+9SofWdqRsGe5MNeMbp9c+FxIxODcNmdhV5Ao6+ZCRX4N9GjLqUL1jQoFAs
+pT/U80ZU+4bz2EwGMBQt7CJZb/u+j8/vXheyGFZkCWEQj6AgZQFTniRRQJLwbiy5
+uDktGqnhvamyrg==
 -----END CERTIFICATE-----
diff --git a/test/fixtures/keys/agent6-cert.pem b/test/fixtures/keys/agent6-cert.pem
new file mode 100644 (file)
index 0000000..b6c0399
--- /dev/null
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIICajCCAdOgAwIBAgIJAMTNiT75p13MMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu
+dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EzMSAwHgYJKoZIhvcNAQkB
+FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNTA0MTgxMzI4NDFaFw00MjA5MDIxMzI4
+NDFaMHQxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDERMA8GA1UECgwI
+VHJlc29yaXQxFjAUBgNVBAMMDcOBZMOhbSBMaXBwYWkxJzAlBgkqhkiG9w0BCQEW
+GGFkYW0ubGlwcGFpQHRyZXNvcml0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEA3Iwmwd6gdWH1AlSFeuVsEY/2MQm3XluOyHR9HNtXkWqwcQqVL8FX3NHt
+//1jaSTMJjkR4FhC9R0hX6wyUuBp11J4GzoDqd02JUkCeUISq/3/2G+ynaZCx5Eo
+GNHhcN0gALTCET/1QMD9h4aBjRbij3iHUghcbgverfkasp59WWcCAwEAATANBgkq
+hkiG9w0BAQsFAAOBgQAmfrCJY+FPeOraPTUQTYf9rXqfVRQEVc/yyVygPbtg3gtA
+yST0wI/g6sBjQ6Mm39yMf4rkWmwOKGtrKcqs9o9NdM5g5QQSWeg925Ex6aB+REgz
+qjaAsLM88BJ0QU76VPi6K0hDSpeuQ6Zrcp93VkdGdVZzna3FSCMTNRnSq/GuMQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIJAJqEq8+4pyq/MA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu
+dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB
+FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNTA0MTgxMzI4NDFaFw00MjA5MDIxMzI4
+NDFaMHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzAN
+BgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EzMSAw
+HgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAqs4MKn9saUIu/9EfHQPouC3kL9Mo5sd1WR6RBeSd8cqeFxXW
+EWEq/P0hUeAH1sY0u8RFOccJmSJg8KTyRGc+VZzWimopz17mTuQY4hPW4bFzqmQm
+7STfJz5eHzynBTU8jk5omi8hjbnRA38jOm4D7rN/vqtB+RG+vEhxONnq4DMCAwEA
+AaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBo8rX1uZWHvKHG
+gWw+LXrY24Pkg8NdDRmfqEVyuaR4GoGGOXCqlVaFa6x+4/eqOUzHoC9uGfPtjrvW
+BYQ1o/l0JZWW4KZYuXoVuMUSj+sel82mf9zLDeq5WYTPECgJDMfgVpXOmhHfyezn
+SkUTX7XJUohjET+X5BqTFlqRT/RfIw==
+-----END CERTIFICATE-----
diff --git a/test/fixtures/keys/agent6-csr.pem b/test/fixtures/keys/agent6-csr.pem
new file mode 100644 (file)
index 0000000..9d19256
--- /dev/null
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIB2TCCAUICAQAwdDELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MREw
+DwYDVQQKDAhUcmVzb3JpdDEWMBQGA1UEAwwNw4Fkw6FtIExpcHBhaTEnMCUGCSqG
+SIb3DQEJARYYYWRhbS5saXBwYWlAdHJlc29yaXQuY29tMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDcjCbB3qB1YfUCVIV65WwRj/YxCbdeW47IdH0c21eRarBx
+CpUvwVfc0e3//WNpJMwmORHgWEL1HSFfrDJS4GnXUngbOgOp3TYlSQJ5QhKr/f/Y
+b7KdpkLHkSgY0eFw3SAAtMIRP/VAwP2HhoGNFuKPeIdSCFxuC96t+Rqynn1ZZwID
+AQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3JkMA0GCSqG
+SIb3DQEBCwUAA4GBAEU4gmRyeeh5TMYG3bI0biXr+9CvkYBaHwZD5o4TUo8AenIR
+NTrJdy9Pg9B23eOnEnCDB+KMfl08UuaPxbKRXRtYm1rTC8v5wmJEpZdWxum4c3hL
+3o7J8/LmjRGQImr5vnS5zmsVrBLtjW+jVpSg5xnXFKQmpXPfgRwhvbu0lXf7
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/keys/agent6-key.pem b/test/fixtures/keys/agent6-key.pem
new file mode 100644 (file)
index 0000000..e42fa2d
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDcjCbB3qB1YfUCVIV65WwRj/YxCbdeW47IdH0c21eRarBxCpUv
+wVfc0e3//WNpJMwmORHgWEL1HSFfrDJS4GnXUngbOgOp3TYlSQJ5QhKr/f/Yb7Kd
+pkLHkSgY0eFw3SAAtMIRP/VAwP2HhoGNFuKPeIdSCFxuC96t+Rqynn1ZZwIDAQAB
+AoGBAIL3AsjbL8OksL56fG0XMY5YQ6SpFWeFzQsCCY2KPrzOcwodc6vRDyDE1KTP
+zimQvV3xQ8lKADDX5IqQka2fL5mgF+LighVvGHDm6M4ILJb46SDbuINwnqqvVuye
++OwjHBGEmKu18K+eL/YoCh3+sFTKP/18F7c7DGskCyzyub5RAkEA+Fs1ROx5w8AH
+cbIH4fMU/QBGQVnuKgNXGSPcT6NHqFLbrhvNn5HwoF1SiJKkML1h3gVpj3T8kquw
+Y1FcTVB9eQJBAONV1qXFo7i5gl2FyPuXvpgdzIXxzzr6q3seDkCR7q/vfBo+kKAx
+zyG2xjJrCc9+ox4Vh257qK9b57W6R6sWNd8CQQCeAHjNVpzI2nxh6t908k8h/nCz
+1uDcPa/FwLjCuaA3CC/Wfr28jP5HJ9gAJzrp/zIqK8tShxzAuxXGudY9Ib4RAkEA
+v+3elIIx4WktOQwUTOUmEoNGAufOD3tGf2E2oykRnRPRcM7Vh4nF2C7ZUgOweq/t
+wx5mAs7/8VzkWTb1/ul3fQJACLBXTChgyA77i5C/035tLwQbeLOjexLblEI0dgkW
+HIa8q4ZL0M7L+/oziQ8zIT0bTAqEG1Q00PgFLl3m8gDuNg==
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/keys/agent6.cnf b/test/fixtures/keys/agent6.cnf
new file mode 100644 (file)
index 0000000..1b66c9b
--- /dev/null
@@ -0,0 +1,18 @@
+[ req ]
+string_mask            = utf8only
+utf8                   = yes
+default_bits           = 1024
+days                   = 999
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+prompt                 = no
+
+[ req_distinguished_name ]
+C                      = HU
+L                      = Budapest
+O                      = Tresorit
+CN                     = Ádám Lippai
+emailAddress           = adam.lippai@tresorit.com
+
+[ req_attributes ]
+challengePassword              = A challenge password
index 638c803..8e45f88 100644 (file)
@@ -1,15 +1,16 @@
 -----BEGIN CERTIFICATE-----
-MIICazCCAdQCCQC1CQyJn8L/kzANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
-UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO
-BgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlA
-dGlueWNsb3Vkcy5vcmcwHhcNMTQwNDE1MjEzMTMxWhcNNDEwODMwMjEzMTMxWjB6
-MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK
-EwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDDAKBgNVBAMTA2NhMTEgMB4GCSqG
-SIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
-MIGJAoGBALBXMk/xFR2GN2v/wKreZKyIitGphxYGoJ2d//s/wM6qqyIW94aiq3sm
-1zpmOTPTorT9Pk32A7uKKHfrafB+yA07QXgCYXgzcn17nfFInncDyGdggNFGAO13
-5JuC3JC8pRJpEokkMszpHJxPdR6gKXIT05blUnpwGT/AmYJ8S59lAgMBAAEwDQYJ
-KoZIhvcNAQEFBQADgYEAAb+Pye0I+k927Qi2+cUowLS5MtmrEosUbTYwI4rqYSR2
-aiibqmC3Z55N72ktQ2pJKP8I1t3Rk+j8/yIKWzSn5Jd2GT4ZzqbANrdLKeAsfVDK
-pnsUR1IV/sdIvuELm+P4kyK5wafJytUjD+A4SH2oWN4EozDR1OidAhJrraXQksw=
+MIICgjCCAeugAwIBAgIJAI3yHAFGivOTMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu
+dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB
+FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNTA0MTgxMzI4NDFaFw00MjA5MDIxMzI4
+NDFaMHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzAN
+BgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAw
+HgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAwbF7gKfk7nGLcH0lbok1UJEBpMiQ49YxUqT/oIfXBaRjMODX
+RknQxpARWw4R8qj+Zeu9zZZ8Hzv3dAxtcpnMTgeoPUL3HCStk0bK8QrFdkFrBxQD
+mF92r9Mgr/fz+x7rSZuCIKhATwB5iJOz63fRctTL5tBmzG15JUG1GN5HPZECAwEA
+AaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAnAHtchz5FGqod
+8twiFF3yQdGN3WE3VC3A6VrcmjKUp+M7f0uRDYw4uKUhadyZdYhMn39fe9DVN6rC
+6wUUoe4hSs+0SWi6Ora7DFpCbm6fNpooSr0K0OUMZ2opwDmAEPdVOSPRhzQJ/cNp
+s3mxIrkAQ5kgJSSGlPETMEumQmXDfA==
 -----END CERTIFICATE-----
index a253989..3a619fb 100644 (file)
@@ -1,17 +1,17 @@
 -----BEGIN ENCRYPTED PRIVATE KEY-----
-MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIJ36Z9tFiv2oCAggA
-MBQGCCqGSIb3DQMHBAjCAyUwdFh+7gSCAoD9Sv9009MmLb8+wHhYhxmrPrpeYZOt
-W8xfELfal/2XWFe41WV41Qsa2HyN03VeyQjb+cnKvZX6HUCe5zVMUsf+B91yQBqR
-jOZgsgoflEC3tFrhW5ogmRnjuok3CbTYLx0f8GMucSRNa9ZPhpfUjrVcCUrKKRJ+
-owk0VxWQkRqMF/8WJJgHxnm8/jLbNKuHbwY8SSQO/pStyKxqx5rBXAA/YCrAx7EZ
-aDvc2q9EwRHX+hWIhpIkYna7PwnNAX03Ghv4iiwUlwHoomvgxExdUc9T8HBQu/xP
-2pmUqwTglAdRb0tNiYrF+yIesAlqBc1qoi7tv2SM3gTowdw2PeHq5MA/ShV21oC8
-51bc0OxRkEFsHGLIs1v5qzs21JhX0IKvb5LDHAaze2RwODfnCTcQb8jYaFZNmwpt
-ZrHgBQBwOHSvZ9CCqInDDtGBhYRlQIQRGgj9Ju5fruRYycn8vE58OcIfF52dwLKB
-7sjTFX0O0b58wpiQSJzbHGbjNlQNRXxdk9v1qfP8vx8rFHLoFSScaZomavp4uijU
-yLKVwquzEOVOMP8cC+yRk+zkcb9EE2pE1CqG0Mj6MHH1Lha98kakTIAS/VG59/Mp
-EqZoRtJ6n/DzscrRpHIm+170ufLGmivmTeOXcMyHv1pGp35c5VbmUTZCxcE7A/0f
-WPLVB3lBPgsvR/4NVx9A6mvXbkp2yngdfbvQgzRDG6pfrE1xeiUAyRCts7dR3Q9/
-dNHIM2wsiO3A/8Up1vrY6d/dcJt7cwjHRkx2eQFbpeE+a4CCNZ7gXFcwHLigBH8G
-uTTVZf4HJavjXiYlkY3OFnPuz4KzanJzuqluKeIdJwSEkp9Kra/Id0eO
+MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI4tZeEUDNwLoCAggA
+MBQGCCqGSIb3DQMHBAglCttpxF+UrASCAoCOiXoMG1+CJFtRnovzp0wSCeEtieI3
+jntJkCBF+NNUZrbWaQqWO+Id8KjXNFQdUo7lYT0d0w5lf52OHTyswjN0ILCOp/WN
+eVMDfgmaJXtaah2l++hDFhcbGsOdRcwM0+yComF02Qxr13ftYnIPzUH4+Ix1i1Tr
+6eBGDBJv0eXDZj/dLrploA7/pepKlytRw2stIL99TSICR54UQ7fyZ/oS6NvoQU7U
+iBCVsrkjyE6jHJZ6vNe/ZjM8ZdLM9K2inoldpFaavDTh6GwHC6e3e9FJuJk6X30e
+s2lhrVLOmxLuFS168iApyA8XvVTg/RG08DvkzUUFv8+HNETH0Qkb5kpJM+pPzwyu
+c5et2fX8YuQRc8SdIdd+Z0lZJga1IciEXlsfzGeUtcZKUjBZ5yPTx+1InfcNFDKR
+wCU2p0Haj9OCcvSBzU53I57RYXkENMEHQ46/FytGULXHimIoIR/SEbrqbKX0xDmT
+rc/4c4vI5+tQPYMjo0rqydLAb5YjWCivrXDVXVHrG0YFsAoJkvTBLXlPiqTcnkea
+KACuq7B+ymdVmpjV891OLuN3Yah+HgrvTkMlNexsFWvGpis03UYwpI6bppe4dHtt
+rLBlgtyxFLJ9hu/YnkR6HcrjKaW5kGFDX06elAZBdPD/6foghzTb4jw3OK6a4ue0
+PE/zF6d7QWW+w5xKtNO62WWKABi9OtLhdUNVAFyBlaA43zYbnXkyfmv0l7LXPZ9t
+Ps+BmG4r/gxO6GmZZ40sRXFjpkj230bTjbN6sUrU3WgOszMy0uFAph+zBUIMZWSP
+wqZWmQi8MHQ4+Qm8N5GdXUTXw32eZ60bj82QGPso/NNxzDQsk5wd+bR3
 -----END ENCRYPTED PRIVATE KEY-----
index ea43127..afd3066 100644 (file)
@@ -5,6 +5,7 @@ distinguished_name     = req_distinguished_name
 attributes             = req_attributes
 prompt                 = no
 output_password        = password
+x509_extensions        = v3_ca
 
 [ req_distinguished_name ]
 C                      = US
@@ -18,3 +19,5 @@ emailAddress           = ry@tinyclouds.org
 [ req_attributes ]
 challengePassword              = A challenge password
 
+[ v3_ca ]
+basicConstraints = CA:TRUE
diff --git a/test/fixtures/keys/ca3-cert.pem b/test/fixtures/keys/ca3-cert.pem
new file mode 100644 (file)
index 0000000..2c1cec8
--- /dev/null
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIJAJqEq8+4pyq/MA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzANBgNVBAoMBkpveWVu
+dDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2ExMSAwHgYJKoZIhvcNAQkB
+FhFyeUB0aW55Y2xvdWRzLm9yZzAeFw0xNTA0MTgxMzI4NDFaFw00MjA5MDIxMzI4
+NDFaMHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCU0YxDzAN
+BgNVBAoMBkpveWVudDEQMA4GA1UECwwHTm9kZS5qczEMMAoGA1UEAwwDY2EzMSAw
+HgYJKoZIhvcNAQkBFhFyeUB0aW55Y2xvdWRzLm9yZzCBnzANBgkqhkiG9w0BAQEF
+AAOBjQAwgYkCgYEAqs4MKn9saUIu/9EfHQPouC3kL9Mo5sd1WR6RBeSd8cqeFxXW
+EWEq/P0hUeAH1sY0u8RFOccJmSJg8KTyRGc+VZzWimopz17mTuQY4hPW4bFzqmQm
+7STfJz5eHzynBTU8jk5omi8hjbnRA38jOm4D7rN/vqtB+RG+vEhxONnq4DMCAwEA
+AaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBo8rX1uZWHvKHG
+gWw+LXrY24Pkg8NdDRmfqEVyuaR4GoGGOXCqlVaFa6x+4/eqOUzHoC9uGfPtjrvW
+BYQ1o/l0JZWW4KZYuXoVuMUSj+sel82mf9zLDeq5WYTPECgJDMfgVpXOmhHfyezn
+SkUTX7XJUohjET+X5BqTFlqRT/RfIw==
+-----END CERTIFICATE-----
diff --git a/test/fixtures/keys/ca3-cert.srl b/test/fixtures/keys/ca3-cert.srl
new file mode 100644 (file)
index 0000000..ecab728
--- /dev/null
@@ -0,0 +1 @@
+C4CD893EF9A75DCC
diff --git a/test/fixtures/keys/ca3-csr.pem b/test/fixtures/keys/ca3-csr.pem
new file mode 100644 (file)
index 0000000..4daa230
--- /dev/null
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIB3zCCAUgCAQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH
+DAJTRjEPMA0GA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQD
+DANjYTMxIDAeBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQCqzgwqf2xpQi7/0R8dA+i4LeQv0yjmx3VZHpEF
+5J3xyp4XFdYRYSr8/SFR4AfWxjS7xEU5xwmZImDwpPJEZz5VnNaKainPXuZO5Bji
+E9bhsXOqZCbtJN8nPl4fPKcFNTyOTmiaLyGNudEDfyM6bgPus3++q0H5Eb68SHE4
+2ergMwIDAQABoCUwIwYJKoZIhvcNAQkHMRYMFEEgY2hhbGxlbmdlIHBhc3N3b3Jk
+MA0GCSqGSIb3DQEBCwUAA4GBABMaKC7NVVdfoQeKwIy5lYo17mOr4WcWHPNRcoIy
+rAHLcAzFOp0RCSZ7ROVRR6O/QIBYapUmPmdYRhKfz1g35xCX3+T28cWXngALV5v0
+XzMYJiew+97/LlNnBwoTRafAorviugdbFgJeMpYHRkG7/zXQsBz+hwgymKZnHW9D
+Dl4h
+-----END CERTIFICATE REQUEST-----
diff --git a/test/fixtures/keys/ca3-key.pem b/test/fixtures/keys/ca3-key.pem
new file mode 100644 (file)
index 0000000..6c2b067
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCqzgwqf2xpQi7/0R8dA+i4LeQv0yjmx3VZHpEF5J3xyp4XFdYR
+YSr8/SFR4AfWxjS7xEU5xwmZImDwpPJEZz5VnNaKainPXuZO5BjiE9bhsXOqZCbt
+JN8nPl4fPKcFNTyOTmiaLyGNudEDfyM6bgPus3++q0H5Eb68SHE42ergMwIDAQAB
+AoGBAJkcc5N0/j2s8mynjXh5FJhlqvOkGjol+m+VEvNxaJRiySxwiqCxtdNrJf87
+EEvbCVJ4MoYEgfof8z5E3lerJRgqrhY2RSfiQrSUA89Lw9uYzcx28zhWpwwmuLHY
+5gjz+LCDDS5okLsXnl2awHXADEmcx29sZnRS6dGRFcf8F0FhAkEA1c7HrW8Vghu2
+FlRaY6LOuoFNAHM++ugoWrC85/moYevLG8wAJCuSIp/RuWrx1FdJoa7rfhyS649v
+cMGN0m1yHwJBAMyC1S1QoqXSdoqN8OrXyHJmaSbWG8IMLcT2FXA8Mk3Tk0zWSjiz
+sk/O85NsmUQQnkRgbtSS+w0Kc0OMWXbfl20CQH+igFsNjEZuaoXr90WxhD2cQK57
+HebEvopdJXhJ9nX2P/qpDpCJHiTjSVyp9hFvxjnp5RUU07QhnUIvmY073rsCQFMN
+ovNHNvZutVNpd3h372B+NJ/f/d/dQE0nvucYmzk9/ikLMZM7buO4YPTy+n9I3G1a
+WEgd9LSEFPFOsxpyjTUCQGn9XTyeSo1EoVuV21DE0Cnx30YsnPKMT1YRS7QgjDPK
+RA3fSsvnhtTzT53kfJ/ZurBV+RKbePL1JVqDtGvJVeE=
+-----END RSA PRIVATE KEY-----
diff --git a/test/fixtures/keys/ca3.cnf b/test/fixtures/keys/ca3.cnf
new file mode 100644 (file)
index 0000000..53855e1
--- /dev/null
@@ -0,0 +1,23 @@
+[ req ]
+default_bits           = 1024
+days                   = 999
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+prompt                 = no
+output_password        = password
+x509_extensions        = v3_ca
+
+[ req_distinguished_name ]
+C                      = US
+ST                     = CA
+L                      = SF
+O                      = Joyent
+OU                     = Node.js
+CN                     = ca3
+emailAddress           = ry@tinyclouds.org
+
+[ req_attributes ]
+challengePassword              = A challenge password
+
+[ v3_ca ]
+basicConstraints = CA:TRUE
index a1a19d7..afd8853 100644 (file)
@@ -36,13 +36,13 @@ server.listen(common.PORT, function() {
 
     common.debug(util.inspect(peerCert));
     assert.equal(peerCert.subject.emailAddress, 'ry@tinyclouds.org');
-    assert.equal(peerCert.serialNumber, '9A84ABCFB8A72ABE');
+    assert.equal(peerCert.serialNumber, '9A84ABCFB8A72AC0');
     assert.deepEqual(peerCert.infoAccess['OCSP - URI'],
                      [ 'http://ocsp.nodejs.org/' ]);
 
     var issuer = peerCert.issuerCertificate;
     assert.ok(issuer.issuerCertificate === issuer);
-    assert.equal(issuer.serialNumber, 'B5090C899FC2FF93');
+    assert.equal(issuer.serialNumber, '8DF21C01468AF393');
     verified = true;
     server.close();
   });
index 7a1d09b..a1d8672 100644 (file)
@@ -35,6 +35,11 @@ var SNIContexts = {
   'asterisk.test.com': {
     key: loadPEM('agent3-key'),
     cert: loadPEM('agent3-cert')
+  },
+  'chain.example.com': {
+    key: loadPEM('agent6-key'),
+    // NOTE: Contains ca3 chain cert
+    cert: loadPEM('agent6-cert')
   }
 };
 
@@ -42,32 +47,29 @@ var serverPort = common.PORT;
 
 var clientsOptions = [{
   port: serverPort,
-  key: loadPEM('agent1-key'),
-  cert: loadPEM('agent1-cert'),
   ca: [loadPEM('ca1-cert')],
   servername: 'a.example.com',
   rejectUnauthorized: false
 }, {
   port: serverPort,
-  key: loadPEM('agent2-key'),
-  cert: loadPEM('agent2-cert'),
   ca: [loadPEM('ca2-cert')],
   servername: 'b.test.com',
   rejectUnauthorized: false
 }, {
   port: serverPort,
-  key: loadPEM('agent2-key'),
-  cert: loadPEM('agent2-cert'),
   ca: [loadPEM('ca2-cert')],
   servername: 'a.b.test.com',
   rejectUnauthorized: false
 }, {
   port: serverPort,
-  key: loadPEM('agent3-key'),
-  cert: loadPEM('agent3-cert'),
   ca: [loadPEM('ca1-cert')],
   servername: 'c.wrong.com',
   rejectUnauthorized: false
+}, {
+  port: serverPort,
+  ca: [loadPEM('ca1-cert')],
+  servername: 'chain.example.com',
+  rejectUnauthorized: false
 }];
 
 var serverResults = [],
@@ -79,6 +81,7 @@ var server = tls.createServer(serverOptions, function(c) {
 
 server.addContext('a.example.com', SNIContexts['a.example.com']);
 server.addContext('*.test.com', SNIContexts['asterisk.test.com']);
+server.addContext('chain.example.com', SNIContexts['chain.example.com']);
 
 server.listen(serverPort, startTest);
 
@@ -105,7 +108,9 @@ function startTest() {
 }
 
 process.on('exit', function() {
-  assert.deepEqual(serverResults, ['a.example.com', 'b.test.com',
-                                   'a.b.test.com', 'c.wrong.com']);
-  assert.deepEqual(clientResults, [true, true, false, false]);
+  assert.deepEqual(serverResults, [
+    'a.example.com', 'b.test.com', 'a.b.test.com', 'c.wrong.com',
+    'chain.example.com'
+  ]);
+  assert.deepEqual(clientResults, [true, true, false, false, true]);
 });