You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by sh...@apache.org on 2019/05/08 20:30:32 UTC

[trafficserver] branch master updated: Add API and fix logic for TS_SSL_VERIFY_*_HOOK.

This is an automated email from the ASF dual-hosted git repository.

shinrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new f2519d1  Add API and fix logic for TS_SSL_VERIFY_*_HOOK.
f2519d1 is described below

commit f2519d1e323c420d2460781f4c45ad9cac6e7dc8
Author: Susan Hinrichs <sh...@oath.com>
AuthorDate: Wed May 1 19:54:40 2019 +0000

    Add API and fix logic for TS_SSL_VERIFY_*_HOOK.
---
 .../api/functions/TSVConnSslVerifyCTXGet.en.rst    |  51 ++++++
 .../hooks-and-transactions/ssl-hooks.en.rst        |  18 +-
 include/ts/apidefs.h.in                            |   1 +
 include/ts/ts.h                                    |   2 +
 iocore/net/P_SSLNetVConnection.h                   |  16 ++
 iocore/net/SSLClientUtils.cc                       |   2 +
 iocore/net/SSLNetVConnection.cc                    |   2 +-
 iocore/net/SSLUtils.cc                             |  10 +-
 src/traffic_server/InkAPI.cc                       |  11 ++
 .../gold_tests/tls/tls_hooks_client_verify.test.py | 112 +++++++++++++
 tests/tools/plugins/ssl_client_verify_test.cc      | 183 +++++++++++++++++++++
 11 files changed, 399 insertions(+), 9 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSVConnSslVerifyCTXGet.en.rst b/doc/developer-guide/api/functions/TSVConnSslVerifyCTXGet.en.rst
new file mode 100644
index 0000000..f2dd6ea
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSVConnSslVerifyCTXGet.en.rst
@@ -0,0 +1,51 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed
+   with this work for additional information regarding copyright
+   ownership.  The ASF licenses this file to you under the Apache
+   License, Version 2.0 (the "License"); you may not use this file
+   except in compliance with the License.  You may obtain a copy of
+   the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied.  See the License for the specific language governing
+   permissions and limitations under the License.
+
+.. include:: ../../../common.defs
+
+.. default-domain:: c
+
+TSVConnSslVerifyCTXGet
+***********************
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn svc)
+
+Description
+===========
+
+Get the TSSslVerifyCTX object that corresponds to the certificates being verified for the SSL connection 
+corresponding to :arg:`svc`.
+
+This value is only meaningful during the peer certificate verification callbacks, specifically during callbacks
+invoked from the TS_SSL_VERIFY_SERVER_HOOK and TS_SSL_VERIFY_CLIENT_HOOK.
+
+Types
+=====
+
+.. type:: TSSslConnection
+
+	The SSL (per connection) object. This is an opaque type that can be cast to the
+	appropriate type (:code:`SSL *` for the OpenSSL library).
+
+.. type:: TSSslVerifyCTX
+
+        The SSL object that corresponds to the peer certificates being verified.  This is an
+        opaque type that can be cast to the appropriate implementation type (:code `X509_STORE_CTX *` for the OpenSSL library).
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
index 1b7d49c..d164cb2 100644
--- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
+++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
@@ -95,23 +95,27 @@ TS_SSL_VERIFY_CLIENT_HOOK
 
 This hook is called when a client connects to Traffic Server and presents a
 client certificate in the case of a mutual TLS handshake.  The callback can
-get the SSL object from the TSVConn argument and use that to access the client
-certificate and make any additional checks.
+use the TSVConn argument and fetch the TSSslVerifyCTX object using the :c:func:`TXVConnSslVerifyCTXGet()`
+method and fetch the peer's certificates to make any additional checks.
 
 Processing will continue regardless of whether the hook callback executes
 :c:func:`TSVConnReenable()` since the openssl implementation does not allow
-for pausing processing during the certificate verify callback.
+for pausing processing during the certificate verify callback.  The plugin can
+use the :c:func:`TSConnReenableEx()` function to pass in the TS_EVENT_ERROR and
+stop the TLS handshake.
 
 TS_SSL_VERIFY_SERVER_HOOK
 -------------------------
 
-This hooks is called when a Traffic Server connects to an origin and the origin
-presents a certificate.  The callback can get the SSL object from the TSVConn
-argument and use that to access the origin certificate and make any additional checks.
+This hook is called when a Traffic Server connects to an origin and the origin
+presents a certificate.  The callback can use the TSVConn argument and fetch the 
+TSSslVerifyCTX object using the :c:func:`TXVConnSslVerifyCTXGet()`
+method and fetch the peer's certificates to make any additional checks.
 
 Processing will continue regardless of whether the hook callback executes
 :c:func:`TSVConnReenable()` since the openssl implementation does not allow
-for pausing processing during the certificate verify callback.
+for pausing processing during the certificate verify callback.  The plugin can use
+the :c:func:`TSConnReenableEx()` function to pass in the TS_EVENT_ERROR and
 
 TS_VCONN_OUTBOUND_START_HOOK
 ----------------------------
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index 4841d56..cc34840 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -898,6 +898,7 @@ typedef struct tsapi_httpparser *TSHttpParser;
 typedef struct tsapi_cachekey *TSCacheKey;
 typedef struct tsapi_cachehttpinfo *TSCacheHttpInfo;
 typedef struct tsapi_cachetxn *TSCacheTxn;
+typedef struct tsapi_x509_store_ctx *TSSslVerifyCTX;
 
 typedef struct tsapi_port *TSPortDescriptor;
 typedef struct tsapi_vio *TSVIO;
diff --git a/include/ts/ts.h b/include/ts/ts.h
index cd4f46a..6552969 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1230,6 +1230,8 @@ tsapi void TSVConnReenableEx(TSVConn sslvcp, TSEvent event);
 tsapi TSReturnCode TSVConnTunnel(TSVConn sslp);
 /*  Return the SSL object associated with the connection */
 tsapi TSSslConnection TSVConnSSLConnectionGet(TSVConn sslp);
+/* Return the intermediate X509StoreCTX object that references the certificate being validated */
+tsapi TSSslVerifyCTX TSVConnSslVerifyCTXGet(TSVConn sslp);
 /*  Fetch a SSL context from the global lookup table */
 tsapi TSSslContext TSSslContextFindByName(const char *name);
 tsapi TSSslContext TSSslContextFindByAddr(struct sockaddr const *);
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index 7f35010..23733a2 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -383,6 +383,21 @@ public:
   bool protocol_mask_set = false;
   unsigned long protocol_mask;
 
+  // Only applies during the VERIFY certificate hooks (client and server side)
+  // Means to give the plugin access to the data structure passed in during the underlying
+  // openssl callback so the plugin can make more detailed decisions about the
+  // validity of the certificate in their cases
+  X509_STORE_CTX *
+  get_verify_cert()
+  {
+    return verify_cert;
+  }
+  void
+  set_verify_cert(X509_STORE_CTX *ctx)
+  {
+    verify_cert = ctx;
+  }
+
 private:
   std::string_view map_tls_protocol_to_tag(const char *proto_string) const;
   bool update_rbio(bool move_to_socket);
@@ -425,6 +440,7 @@ private:
   char *tunnel_host                = nullptr;
   in_port_t tunnel_port            = 0;
   bool tunnel_decrypt              = false;
+  X509_STORE_CTX *verify_cert      = nullptr;
 };
 
 typedef int (SSLNetVConnection::*SSLNetVConnHandler)(int, void *);
diff --git a/iocore/net/SSLClientUtils.cc b/iocore/net/SSLClientUtils.cc
index 41aa534..b60225c 100644
--- a/iocore/net/SSLClientUtils.cc
+++ b/iocore/net/SSLClientUtils.cc
@@ -117,7 +117,9 @@ verify_callback(int signature_ok, X509_STORE_CTX *ctx)
     }
   }
   // If the previous configured checks passed, give the hook a try
+  netvc->set_verify_cert(ctx);
   netvc->callHooks(TS_EVENT_SSL_VERIFY_SERVER);
+  netvc->set_verify_cert(nullptr);
   if (netvc->getSSLHandShakeComplete()) { // hook moved the handshake state to terminal
     unsigned char *sni_name;
     char buff[INET6_ADDRSTRLEN];
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index e4c9fbf..af450ed 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -1021,7 +1021,6 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
       SSLErrorVC(this, "failed to create SSL server session");
       return EVENT_ERROR;
     }
-
     return sslServerHandShakeEvent(err);
 
   case SSL_EVENT_CLIENT:
@@ -1540,6 +1539,7 @@ SSLNetVConnection::reenable(NetHandler *nh, int event)
     sslHandshakeHookState = HANDSHAKE_HOOKS_CERT;
     break;
   case HANDSHAKE_HOOKS_VERIFY_SERVER:
+  case HANDSHAKE_HOOKS_CLIENT_CERT:
     if (event == TS_EVENT_ERROR) {
       sslHandshakeStatus = SSL_HANDSHAKE_ERROR;
     }
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index efc3f3e..ca05cd9 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -352,7 +352,15 @@ ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx)
   auto *ssl                = static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
   SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
 
+  netvc->set_verify_cert(ctx);
   netvc->callHooks(TS_EVENT_SSL_VERIFY_CLIENT);
+  netvc->set_verify_cert(nullptr);
+
+  if (netvc->getSSLHandShakeComplete()) { // hook moved the handshake state to terminal
+    Warning("TS_EVENT_SSL_VERIFY_CLIENT plugin failed the client certificate check for %s.", netvc->options.sni_servername.get());
+    return false;
+  }
+
   return preverify_ok;
 }
 
@@ -1164,7 +1172,7 @@ setClientCertLevel(SSL *ssl, uint8_t certLevel)
   }
 
   Debug("ssl", "setting cert level to %d", server_verify_client);
-  SSL_set_verify(ssl, server_verify_client, nullptr);
+  SSL_set_verify(ssl, server_verify_client, ssl_verify_client_callback);
   SSL_set_verify_depth(ssl, params->verify_depth); // might want to make configurable at some point.
 }
 
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index f2a2bd5..0cd9c7d 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -8962,6 +8962,17 @@ TSVConnSSLConnectionGet(TSVConn sslp)
   return ssl;
 }
 
+tsapi TSSslVerifyCTX
+TSVConnSslVerifyCTXGet(TSVConn sslp)
+{
+  NetVConnection *vc        = reinterpret_cast<NetVConnection *>(sslp);
+  SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection *>(vc);
+  if (ssl_vc != nullptr) {
+    return reinterpret_cast<TSSslVerifyCTX>(ssl_vc->get_verify_cert());
+  }
+  return nullptr;
+}
+
 tsapi TSSslContext
 TSSslContextFindByName(const char *name)
 {
diff --git a/tests/gold_tests/tls/tls_hooks_client_verify.test.py b/tests/gold_tests/tls/tls_hooks_client_verify.test.py
new file mode 100644
index 0000000..5978ad2
--- /dev/null
+++ b/tests/gold_tests/tls/tls_hooks_client_verify.test.py
@@ -0,0 +1,112 @@
+'''
+Test SERVER_VERIFY_HOOK
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+import re
+
+Test.Summary = '''
+Test different combinations of TLS handshake hooks to ensure they are applied consistently.
+'''
+
+Test.SkipUnless(Condition.HasProgram("grep", "grep needs to be installed on system for this test to work"))
+
+ts = Test.MakeATSProcess("ts", select_ports=False)
+server = Test.MakeOriginServer("server", ssl=True)
+request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+# desired response form the origin server
+response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+ts.addSSLfile("ssl/signer.pem")
+
+ts.Variables.ssl_port = 4443
+ts.Disk.records_config.update({
+    # Test looks for debug output from the plugin
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'ssl_client_verify_test',
+    'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    # enable ssl port
+    'proxy.config.http.server_ports': '{0}:ssl'.format(ts.Variables.ssl_port),
+    'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2',
+    'proxy.config.exec_thread.autoconfig.scale': 1.0,
+    'proxy.config.ssl.CA.cert.filename': '{0}/signer.pem'.format(ts.Variables.SSLDir),
+    'proxy.config.url_remap.pristine_host_hdr': 1
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+ts.Disk.remap_config.AddLine(
+    'map https://foo.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port)
+)
+ts.Disk.remap_config.AddLine(
+    'map https://bar.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port)
+)
+ts.Disk.remap_config.AddLine(
+    'map https://random.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port)
+)
+
+ts.Disk.ssl_server_name_yaml.AddLines([
+    '- fqdn: bar.com',
+    '  verify_client: STRICT',
+    '- fqdn: foo.com',
+    '  verify_client: STRICT',
+])
+
+Test.PreparePlugin(os.path.join(Test.Variables.AtsTestToolsDir, 'plugins', 'ssl_client_verify_test.cc'), ts, '-count=2 -good=foo.com')
+
+tr = Test.AddTestRun("request good name")
+tr.Setup.Copy("ssl/signed-foo.pem")
+tr.Setup.Copy("ssl/signed-foo.key")
+tr.Setup.Copy("ssl/signed-bar.pem")
+tr.Setup.Copy("ssl/signed-bar.key")
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port))
+tr.StillRunningAfter = ts
+tr.StillRunningAfter = server
+tr.Processes.Default.Command = "curl --tls-max 1.2 -k --cert ./signed-foo.pem --key ./signed-foo.key --resolve 'foo.com:{0}:127.0.0.1' https://foo.com:{0}/case1".format(ts.Variables.ssl_port)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.all = Testers.ExcludesExpression("Could Not Connect", "Curl attempt should have succeeded")
+
+
+tr2 = Test.AddTestRun("request bad name")
+tr2.StillRunningAfter = ts
+tr2.StillRunningAfter = server
+tr2.Processes.Default.Command = "curl --tls-max 1.2 -k --cert ./signed-bar.pem --key ./signed-bar.key --resolve 'foo.com:{0}:127.0.0.1' https://foo.com:{0}/case1".format(ts.Variables.ssl_port)
+tr2.Processes.Default.ReturnCode = 35
+tr2.Processes.Default.Streams.all = Testers.ContainsExpression("error", "Curl attempt should have failed")
+
+tr3 = Test.AddTestRun("request badly signed cert")
+tr3.Setup.Copy("ssl/server.pem")
+tr3.Setup.Copy("ssl/server.key")
+tr3.StillRunningAfter = ts
+tr3.StillRunningAfter = server
+tr3.Processes.Default.Command = "curl --tls-max 1.2 -k --cert ./server.pem --key ./server.key --resolve 'foo.com:{0}:127.0.0.1' https://foo.com:{0}/case1".format(ts.Variables.ssl_port)
+tr3.Processes.Default.ReturnCode = 35
+tr3.Processes.Default.Streams.all = Testers.ContainsExpression("error", "Curl attempt should have failed")
+
+ts.Streams.All += Testers.ContainsExpression("Client verify callback 0 [\da-fx]+? - event is good good HS", "verify callback happens 2 times")
+ts.Streams.All += Testers.ContainsExpression("Client verify callback 1 [\da-fx]+? - event is good good HS", "verify callback happens 2 times")
+ts.Streams.All += Testers.ContainsExpression("Client verify callback 0 [\da-fx]+? - event is good error HS", "verify callback happens 2 times")
+ts.Streams.All += Testers.ContainsExpression("Client verify callback 1 [\da-fx]+? - event is good error HS", "verify callback happens 2 times")
+
diff --git a/tests/tools/plugins/ssl_client_verify_test.cc b/tests/tools/plugins/ssl_client_verify_test.cc
new file mode 100644
index 0000000..c6967f7
--- /dev/null
+++ b/tests/tools/plugins/ssl_client_verify_test.cc
@@ -0,0 +1,183 @@
+/** @file
+
+  SSL client certificate verification plugin
+  Checks for specificate names in the client provided certificate and
+  fails the handshake if none of the good names are present
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include <getopt.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/asn1.h>
+#include <strings.h>
+#include <string.h>
+#include <string>
+#include <map>
+
+#define PN "ssl_client_verify_test"
+#define PCP "[" PN " Plugin] "
+
+std::map<std::string, int> good_names;
+
+bool
+check_name(std::string name)
+{
+  auto entry = good_names.find(name);
+  return entry != good_names.end();
+}
+
+bool
+check_names(X509 *cert)
+{
+  bool retval = false;
+
+  // Check the common name
+  X509_NAME *subject = X509_get_subject_name(cert);
+  if (subject) {
+    int pos = -1;
+    for (; !retval;) {
+      pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos);
+      if (pos == -1) {
+        break;
+      }
+
+      X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, pos);
+      ASN1_STRING *cn    = X509_NAME_ENTRY_get_data(e);
+      char *subj_name    = strndup(reinterpret_cast<const char *>(ASN1_STRING_get0_data(cn)), ASN1_STRING_length(cn));
+      retval             = check_name(subj_name);
+      free(subj_name);
+    }
+  }
+  if (!retval) {
+    // Check the subjectAltNanes (if present)
+    GENERAL_NAMES *names = (GENERAL_NAMES *)X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr);
+    if (names) {
+      unsigned count = sk_GENERAL_NAME_num(names);
+      for (unsigned i = 0; i < count && !retval; ++i) {
+        GENERAL_NAME *name;
+
+        name = sk_GENERAL_NAME_value(names, i);
+        if (name->type == GEN_DNS) {
+          char *dns =
+            strndup(reinterpret_cast<const char *>(ASN1_STRING_get0_data(name->d.dNSName)), ASN1_STRING_length(name->d.dNSName));
+          retval = check_name(dns);
+          free(dns);
+        }
+      }
+      GENERAL_NAMES_free(names);
+    }
+  }
+  return retval;
+}
+
+int
+CB_client_verify(TSCont cont, TSEvent event, void *edata)
+{
+  TSVConn ssl_vc = reinterpret_cast<TSVConn>(edata);
+
+  int count = reinterpret_cast<intptr_t>(TSContDataGet(cont));
+
+  // Is this a good name or not?
+  TSEvent reenable_event = TS_EVENT_CONTINUE;
+  X509_STORE_CTX *ctx    = reinterpret_cast<X509_STORE_CTX *>(TSVConnSslVerifyCTXGet(ssl_vc));
+  if (ctx) {
+    STACK_OF(X509) *chain = X509_STORE_CTX_get1_chain(ctx);
+    // X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
+    bool retval = false;
+    for (int i = 0; i < sk_X509_num(chain) && !retval; i++) {
+      auto cert = sk_X509_value(chain, i);
+      retval    = check_names(cert);
+    }
+    if (!retval) {
+      reenable_event = TS_EVENT_ERROR;
+    }
+  } else {
+    reenable_event = TS_EVENT_ERROR;
+  }
+
+  TSDebug(PN, "Client verify callback %d %p - event is %s %s", count, ssl_vc, event == TS_EVENT_SSL_VERIFY_CLIENT ? "good" : "bad",
+          reenable_event == TS_EVENT_ERROR ? "error HS" : "good HS");
+
+  // All done, reactivate things
+  TSVConnReenableEx(ssl_vc, reenable_event);
+  return TS_SUCCESS;
+}
+
+void
+parse_callbacks(int argc, const char *argv[], int &count)
+{
+  int i = 0;
+  const char *ptr;
+  for (i = 0; i < argc; i++) {
+    if (argv[i][0] == '-') {
+      switch (argv[i][1]) {
+      case 'c':
+        ptr = index(argv[i], '=');
+        if (ptr) {
+          count = atoi(ptr + 1);
+        }
+        break;
+      case 'g':
+        ptr = index(argv[i], '=');
+        if (ptr) {
+          good_names.insert(std::pair<std::string, int>(std::string(ptr + 1), 1));
+        }
+        break;
+      }
+    }
+  }
+}
+
+void
+setup_callbacks(int count)
+{
+  TSCont cb = nullptr;
+  int i;
+
+  TSDebug(PN, "Setup callbacks count=%d", count);
+  for (i = 0; i < count; i++) {
+    cb = TSContCreate(&CB_client_verify, TSMutexCreate());
+    TSContDataSet(cb, (void *)(intptr_t)i);
+    TSHttpHookAdd(TS_SSL_VERIFY_CLIENT_HOOK, cb);
+  }
+  return;
+}
+
+// Called by ATS as our initialization point
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+  info.plugin_name   = const_cast<char *>("SSL verify server test");
+  info.vendor_name   = const_cast<char *>("apache");
+  info.support_email = const_cast<char *>("shinrich@apache.org");
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Plugin registration failed", PN);
+  }
+
+  int verify_count = 0;
+  parse_callbacks(argc, argv, verify_count);
+  setup_callbacks(verify_count);
+  return;
+}