trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From shinr...@apache.org
Subject [trafficserver] branch master updated: New APIs: Server/ClientCertUpdate Structure changes for SSLCertContext using share pointer and mutex
Date Wed, 12 Jun 2019 15:57:24 GMT
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 cf6ddb3  New APIs: Server/ClientCertUpdate Structure changes for SSLCertContext using share pointer and mutex
cf6ddb3 is described below

commit cf6ddb335cc761dd3851ddea0046e43f4e0d371b
Author: dyrock <zeyuany@gmail.com>
AuthorDate: Wed Feb 20 10:29:08 2019 -0600

    New APIs: Server/ClientCertUpdate
    Structure changes for SSLCertContext using share pointer and mutex
    
    Changed 1) getCTX() to read context mapping without acquiring locks and 2) nextHopProperty to include file path instead of context pointers. Now all client context request will go to the mappings in SSLConfigParams.
    
    Rebased with new API for client context retrieval
    
    Added SSL_CTX_up_ref for OpenSSL<1.1.0. Solves potential race condition. Cleanup SSLInternals.
    
    Added worker function in SSLClientUtils to hide cert/key loading details from InkAPI.cc
---
 .../api/functions/TSSslClientCertUpdate.en.rst     |  40 ++++++
 .../api/functions/TSSslClientContext.en.rst        |   3 +-
 .../api/functions/TSSslServerCertUpdate.en.rst     |  40 ++++++
 example/Makefile.am                                |   2 +
 example/cert_update/cert_update.cc                 |  86 ++++++++++++
 example/client_context_dump/client_context_dump.cc |   5 +-
 include/ts/ts.h                                    |   6 +-
 iocore/net/OCSPStapling.cc                         |  42 +++---
 iocore/net/P_SSLCertLookup.h                       |  85 +++++++++---
 iocore/net/P_SSLClientUtils.h                      |   2 +
 iocore/net/P_SSLConfig.h                           |  13 +-
 iocore/net/P_SSLSNI.h                              |   5 +-
 iocore/net/P_SSLUtils.h                            |  27 +---
 iocore/net/SSLCertLookup.cc                        |  69 +++++-----
 iocore/net/SSLClientUtils.cc                       |  47 +++++++
 iocore/net/SSLConfig.cc                            | 124 ++++++-----------
 iocore/net/SSLInternal.cc                          |  26 +++-
 iocore/net/{P_SSLClientUtils.h => SSLInternal.h}   |  23 ++--
 iocore/net/SSLNetVConnection.cc                    |  31 +++--
 iocore/net/SSLSNIConfig.cc                         |  15 ++-
 iocore/net/SSLSessionTicket.cc                     |   2 +-
 iocore/net/SSLStats.cc                             |  13 +-
 iocore/net/SSLUtils.cc                             | 124 +++++++++--------
 iocore/net/test_certlookup.cc                      |  36 ++---
 src/traffic_server/InkAPI.cc                       | 149 +++++++++++++++++++--
 tests/gold_tests/autest-site/conditions.test.ext   |  16 +++
 .../pluginTest/cert_update/cert_update.test.py     | 147 ++++++++++++++++++++
 .../cert_update/gold/client-cert-after.gold        |   1 +
 .../cert_update/gold/client-cert-pre.gold          |   1 +
 .../cert_update/gold/server-cert-after.gold        |   1 +
 .../cert_update/gold/server-cert-pre.gold          |   1 +
 .../pluginTest/cert_update/gold/update.gold        |   3 +
 .../pluginTest/cert_update/ssl/client1.pem         |  83 ++++++++++++
 .../pluginTest/cert_update/ssl/client2.pem         |  84 ++++++++++++
 .../pluginTest/cert_update/ssl/server1.pem         |  84 ++++++++++++
 .../pluginTest/cert_update/ssl/server2.pem         |  84 ++++++++++++
 tests/tools/plugins/cert_update.cc                 |  86 ++++++++++++
 37 files changed, 1296 insertions(+), 310 deletions(-)

diff --git a/doc/developer-guide/api/functions/TSSslClientCertUpdate.en.rst b/doc/developer-guide/api/functions/TSSslClientCertUpdate.en.rst
new file mode 100644
index 0000000..b39a71d
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSSslClientCertUpdate.en.rst
@@ -0,0 +1,40 @@
+.. 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
+
+TSSslClientCertUpdate
+************************
+
+Traffic Server TLS client cert update
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSReturnCode TSSslClientCertUpdate(const char *cert_path, const char *key_path)
+
+Description
+===========
+:func:`TSSslClientCertUpdate` updates existing client certificates configured in :file:`ssl_server_name.yaml` or `proxy.config.ssl.client.cert.filename`. :arg:`cert_path` should be exact match as provided in configurations.
+:func:`TSSslClientCertUpdate` returns `TS_SUCCESS` only if :arg:`cert_path` exists in configuration and reloaded to update the context.
+
+Type
+====
+.. type:: TSReturnCode
diff --git a/doc/developer-guide/api/functions/TSSslClientContext.en.rst b/doc/developer-guide/api/functions/TSSslClientContext.en.rst
index f6aa13b..62e6379 100644
--- a/doc/developer-guide/api/functions/TSSslClientContext.en.rst
+++ b/doc/developer-guide/api/functions/TSSslClientContext.en.rst
@@ -45,7 +45,8 @@ of lookup keys (2 for each context).
 key pairs. User should call :func:`TSSslClientContextsNamesGet` first to determine which lookup keys are 
 present before quering for the context. :arg:`ca_paths` should be the first key and :arg:`ck_paths` 
 should be the second. This function returns NULL if the client context mapping are changed and no valid 
-context exists for the key pair.
+context exists for the key pair. The caller is responsible for releasing the context returned by this 
+function with :func:`TSSslContextDestroy`.
 
 Examples
 ========
diff --git a/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst
new file mode 100644
index 0000000..7a68f7d
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst
@@ -0,0 +1,40 @@
+.. 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
+
+TSSslServerCertUpdate
+************************
+
+Traffic Server TLS server cert update
+
+Synopsis
+========
+
+`#include <ts/ts.h>`
+
+.. function:: TSReturnCode TSSslServerCertUpdate(const char *cert_path, const char *key_path)
+
+Description
+===========
+:func:`TSSslServerCertUpdate` updates existing server certificates configured in :file:`ssl_multicert.config` based on the common name in :arg:`cert_path`. if :arg:`key_path` is set to nullptr, the function will use :arg:`cert_path` for both certificate and private key.
+:func:`TSSslServerCertUpdate` returns `TS_SUCCESS` only if there exists such a mapping, :arg:`cert_path` is a valid cert, and the context is updated to use that cert.
+
+Type
+====
+.. type:: TSReturnCode
diff --git a/example/Makefile.am b/example/Makefile.am
index 062b749..875d808 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -32,6 +32,7 @@ example_Plugins = \
 	blacklist_0.la \
 	blacklist_1.la \
 	bnull_transform.la \
+	cert_update.la \
 	request_buffer.la \
 	cache_scan.la \
 	client_context_dump.la \
@@ -102,6 +103,7 @@ basic_auth_la_SOURCES = basic_auth/basic_auth.c
 blacklist_0_la_SOURCES = blacklist_0/blacklist_0.c
 blacklist_1_la_SOURCES = blacklist_1/blacklist_1.c
 bnull_transform_la_SOURCES = bnull_transform/bnull_transform.c
+cert_update_la_SOURCES = cert_update/cert_update.cc
 request_buffer_la_SOURCES = request_buffer/request_buffer.c
 cache_scan_la_SOURCES = cache_scan/cache_scan.cc
 client_context_dump_la_SOURCES = client_context_dump/client_context_dump.cc
diff --git a/example/cert_update/cert_update.cc b/example/cert_update/cert_update.cc
new file mode 100644
index 0000000..4c8d860
--- /dev/null
+++ b/example/cert_update/cert_update.cc
@@ -0,0 +1,86 @@
+/** @file
+
+  an example cert update plugin
+
+  @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 <stdio.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#include "ts/ts.h"
+#include "tscpp/util/TextView.h"
+
+#define PLUGIN_NAME "cert_update"
+
+// Plugin Message Continuation
+int
+CB_cert_update(TSCont, TSEvent, void *edata)
+{
+  TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
+  static constexpr std::string_view PLUGIN_PREFIX("cert_update."_sv);
+
+  std::string_view tag(msg->tag, strlen(msg->tag));
+  const char *server_cert_path = nullptr;
+  const char *client_cert_path = nullptr;
+  if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
+    tag.remove_prefix(PLUGIN_PREFIX.size());
+    if (tag == "server") {
+      server_cert_path = static_cast<const char *>(msg->data);
+      TSDebug(PLUGIN_NAME, "Received Msg to update server cert with %s", server_cert_path);
+    } else if (tag == "client") {
+      client_cert_path = static_cast<const char *>(msg->data);
+      TSDebug(PLUGIN_NAME, "Received Msg to update client cert with %s", client_cert_path);
+    }
+  }
+
+  if (server_cert_path) {
+    if (TS_SUCCESS == TSSslServerCertUpdate(server_cert_path, nullptr)) {
+      TSDebug(PLUGIN_NAME, "Successfully updated server cert with %s", server_cert_path);
+    } else {
+      TSDebug(PLUGIN_NAME, "Failed to update server cert with %s", server_cert_path);
+    }
+  }
+  if (client_cert_path) {
+    if (TS_SUCCESS == TSSslClientCertUpdate(client_cert_path, nullptr)) {
+      TSDebug(PLUGIN_NAME, "Successfully updated client cert with %s", client_cert_path);
+    } else {
+      TSDebug(PLUGIN_NAME, "Failed to update client cert with %s", client_cert_path);
+    }
+  }
+  return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PLUGIN_NAME;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+  }
+  TSDebug(PLUGIN_NAME, "Initialized.");
+  TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, TSContCreate(CB_cert_update, nullptr));
+}
diff --git a/example/client_context_dump/client_context_dump.cc b/example/client_context_dump/client_context_dump.cc
index ae19e98..919fa4f 100644
--- a/example/client_context_dump/client_context_dump.cc
+++ b/example/client_context_dump/client_context_dump.cc
@@ -51,9 +51,9 @@ asn1_string_extract(ASN1_STRING *s)
 void
 dump_context(const char *ca_path, const char *ck_path)
 {
-  SSL_CTX *ctx = reinterpret_cast<SSL_CTX *>(TSSslClientContextFindByName(ca_path, ck_path));
+  TSSslContext ctx = TSSslClientContextFindByName(ca_path, ck_path);
   if (ctx) {
-    SSL *s = SSL_new(ctx);
+    SSL *s = SSL_new(reinterpret_cast<SSL_CTX *>(ctx));
     if (s) {
       char *data  = nullptr;
       long length = 0;
@@ -138,6 +138,7 @@ dump_context(const char *ca_path, const char *ck_path)
       }
     }
     SSL_free(s);
+    TSSslContextDestroy(ctx);
   }
 }
 
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 250d258..6673000 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1239,7 +1239,11 @@ tsapi TSSslContext TSSslContextFindByAddr(struct sockaddr const *);
 tsapi TSReturnCode TSSslClientContextsNamesGet(int n, const char **result, int *actual);
 tsapi TSSslContext TSSslClientContextFindByName(const char *ca_paths, const char *ck_paths);
 
-/*  Create a new SSL context based on the settings in records.config */
+/* Update SSL certs in internal storage from given path */
+tsapi TSReturnCode TSSslClientCertUpdate(const char *cert_path, const char *key_path);
+tsapi TSReturnCode TSSslServerCertUpdate(const char *cert_path, const char *key_path);
+
+/* Create a new SSL context based on the settings in records.config */
 tsapi TSSslContext TSSslServerContextCreate(TSSslX509 cert, const char *certname, const char *rsp_file);
 tsapi void TSSslContextDestroy(TSSslContext ctx);
 tsapi void TSSslTicketKeyUpdate(char *ticketData, int ticketDataLen);
diff --git a/iocore/net/OCSPStapling.cc b/iocore/net/OCSPStapling.cc
index e6635ed..43f3ff9 100644
--- a/iocore/net/OCSPStapling.cc
+++ b/iocore/net/OCSPStapling.cc
@@ -479,7 +479,7 @@ done:
 void
 ocsp_update()
 {
-  SSL_CTX *ctx;
+  shared_SSL_CTX ctx;
   OCSP_RESPONSE *resp = nullptr;
   time_t current_time;
 
@@ -488,27 +488,29 @@ ocsp_update()
 
   for (unsigned i = 0; i < ctxCount; i++) {
     SSLCertContext *cc = certLookup->get(i);
-    if (cc && cc->ctx) {
-      ctx               = cc->ctx;
-      certinfo *cinf    = nullptr;
-      certinfo_map *map = stapling_get_cert_info(ctx);
-      if (map) {
-        // Walk over all certs associated with this CTX
-        for (certinfo_map::iterator iter = map->begin(); iter != map->end(); ++iter) {
-          cinf = iter->second;
-          ink_mutex_acquire(&cinf->stapling_mutex);
-          current_time = time(nullptr);
-          if ((cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) && !cinf->is_prefetched) {
-            ink_mutex_release(&cinf->stapling_mutex);
-            if (stapling_refresh_response(cinf, &resp)) {
-              Debug("Successfully refreshed OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
-              SSL_INCREMENT_DYN_STAT(ssl_ocsp_refreshed_cert_stat);
+    if (cc) {
+      ctx = cc->getCtx();
+      if (ctx) {
+        certinfo *cinf    = nullptr;
+        certinfo_map *map = stapling_get_cert_info(ctx.get());
+        if (map) {
+          // Walk over all certs associated with this CTX
+          for (certinfo_map::iterator iter = map->begin(); iter != map->end(); ++iter) {
+            cinf = iter->second;
+            ink_mutex_acquire(&cinf->stapling_mutex);
+            current_time = time(nullptr);
+            if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < current_time) {
+              ink_mutex_release(&cinf->stapling_mutex);
+              if (stapling_refresh_response(cinf, &resp)) {
+                Debug("Successfully refreshed OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
+                SSL_INCREMENT_DYN_STAT(ssl_ocsp_refreshed_cert_stat);
+              } else {
+                Error("Failed to refresh OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
+                SSL_INCREMENT_DYN_STAT(ssl_ocsp_refresh_cert_failure_stat);
+              }
             } else {
-              Error("Failed to refresh OCSP for %s certificate. url=%s", cinf->certname, cinf->uri);
-              SSL_INCREMENT_DYN_STAT(ssl_ocsp_refresh_cert_failure_stat);
+              ink_mutex_release(&cinf->stapling_mutex);
             }
-          } else {
-            ink_mutex_release(&cinf->stapling_mutex);
           }
         }
       }
diff --git a/iocore/net/P_SSLCertLookup.h b/iocore/net/P_SSLCertLookup.h
index 3a79340..b36daf8 100644
--- a/iocore/net/P_SSLCertLookup.h
+++ b/iocore/net/P_SSLCertLookup.h
@@ -30,6 +30,36 @@
 struct SSLConfigParams;
 struct SSLContextStorage;
 
+/** Special things to do instead of use a context.
+    In general an option will be associated with a @c nullptr context because
+    the context is not used.
+*/
+enum class SSLCertContextOption {
+  OPT_NONE,  ///< Nothing special. Implies valid context.
+  OPT_TUNNEL ///< Just tunnel, don't terminate.
+};
+
+/**
+   @brief Gather user provided settings from ssl_multicert.config in to this single struct
+ */
+struct SSLMultiCertConfigParams {
+  SSLMultiCertConfigParams() : opt(SSLCertContextOption::OPT_NONE)
+  {
+    REC_ReadConfigInt32(session_ticket_enabled, "proxy.config.ssl.server.session_ticket.enable");
+  }
+
+  int session_ticket_enabled;   ///< session ticket enabled
+  ats_scoped_str addr;          ///< IPv[64] address to match
+  ats_scoped_str cert;          ///< certificate
+  ats_scoped_str first_cert;    ///< the first certificate name when multiple cert files are in 'ssl_cert_name'
+  ats_scoped_str ca;            ///< CA public certificate
+  ats_scoped_str key;           ///< Private key
+  ats_scoped_str ocsp_response; ///< prefetched OCSP response
+  ats_scoped_str dialog;        ///< Private key dialog
+  ats_scoped_str servername;    ///< Destination server
+  SSLCertContextOption opt;     ///< SSLCertContext special handling option
+};
+
 struct ssl_ticket_key_t {
   unsigned char key_name[16];
   unsigned char hmac_secret[16];
@@ -40,6 +70,11 @@ struct ssl_ticket_key_block {
   unsigned num_keys;
   ssl_ticket_key_t keys[];
 };
+
+using shared_SSLMultiCertConfigParams = std::shared_ptr<SSLMultiCertConfigParams>;
+using shared_SSL_CTX                  = std::shared_ptr<SSL_CTX>;
+using shared_ssl_ticket_key_block     = std::shared_ptr<ssl_ticket_key_block>;
+
 /** A certificate context.
 
     This holds data about a certificate and how it is used by the SSL logic. Current this is mainly
@@ -52,30 +87,42 @@ struct ssl_ticket_key_block {
 
 */
 struct SSLCertContext {
-  /** Special things to do instead of use a context.
-      In general an option will be associated with a @c nullptr context because
-      the context is not used.
-  */
-  enum Option {
-    OPT_NONE,  ///< Nothing special. Implies valid context.
-    OPT_TUNNEL ///< Just tunnel, don't terminate.
-  };
-
-  SSLCertContext() {}
-  explicit SSLCertContext(SSL_CTX *c) : ctx(c) {}
-  SSLCertContext(SSL_CTX *c, Option o) : ctx(c), opt(o) {}
-  SSLCertContext(SSL_CTX *c, Option o, ssl_ticket_key_block *kb) : ctx(c), opt(o), keyblock(kb) {}
+private:
+  mutable std::mutex ctx_mutex;
+  shared_SSL_CTX ctx;
+
+public:
+  SSLCertContext() : ctx_mutex(), ctx(nullptr), opt(SSLCertContextOption::OPT_NONE), userconfig(nullptr), keyblock(nullptr) {}
+  explicit SSLCertContext(SSL_CTX *c)
+    : ctx_mutex(), ctx(c, SSL_CTX_free), opt(SSLCertContextOption::OPT_NONE), userconfig(nullptr), keyblock(nullptr)
+  {
+  }
+  SSLCertContext(shared_SSL_CTX sc, shared_SSLMultiCertConfigParams u)
+    : ctx_mutex(), ctx(sc), opt(u->opt), userconfig(nullptr), keyblock(nullptr)
+  {
+  }
+  SSLCertContext(shared_SSL_CTX sc, shared_SSLMultiCertConfigParams u, shared_ssl_ticket_key_block kb)
+    : ctx_mutex(), ctx(sc), opt(u->opt), userconfig(u), keyblock(kb)
+  {
+  }
+  SSLCertContext(SSLCertContext const &other);
+  SSLCertContext &operator=(SSLCertContext const &other);
+  ~SSLCertContext() {}
+
+  /// Threadsafe Functions to get and set shared SSL_CTX pointer
+  shared_SSL_CTX getCtx();
+  void setCtx(shared_SSL_CTX sc);
   void release();
 
-  SSL_CTX *ctx                   = nullptr;  ///< openSSL context.
-  Option opt                     = OPT_NONE; ///< Special handling option.
-  ssl_ticket_key_block *keyblock = nullptr;  ///< session keys associated with this address
+  SSLCertContextOption opt                   = SSLCertContextOption::OPT_NONE; ///< Special handling option.
+  shared_SSLMultiCertConfigParams userconfig = nullptr;                        ///< User provided settings
+  shared_ssl_ticket_key_block keyblock       = nullptr;                        ///< session keys associated with this address
 };
 
 struct SSLCertLookup : public ConfigInfo {
   SSLContextStorage *ssl_storage;
-  SSL_CTX *ssl_default = nullptr;
-  bool is_valid        = true;
+  shared_SSL_CTX ssl_default;
+  bool is_valid = true;
 
   int insert(const char *name, SSLCertContext const &cc);
   int insert(const IpEndpoint &address, SSLCertContext const &cc);
@@ -97,7 +144,7 @@ struct SSLCertLookup : public ConfigInfo {
   SSL_CTX *
   defaultContext() const
   {
-    return ssl_default;
+    return ssl_default.get();
   }
 
   unsigned count() const;
diff --git a/iocore/net/P_SSLClientUtils.h b/iocore/net/P_SSLClientUtils.h
index ff99733..8d4b11a 100644
--- a/iocore/net/P_SSLClientUtils.h
+++ b/iocore/net/P_SSLClientUtils.h
@@ -35,5 +35,7 @@
 
 // Create and initialize a SSL client context.
 SSL_CTX *SSLInitClientContext(const struct SSLConfigParams *param);
+SSL_CTX *SSLCreateClientContext(const struct SSLConfigParams *params, const char *ca_bundle_file, const char *ca_bundle_path,
+                                const char *cert_path, const char *key_path);
 
 int verify_callback(int preverify_ok, X509_STORE_CTX *ctx);
diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h
index 3749a76..a79b965 100644
--- a/iocore/net/P_SSLConfig.h
+++ b/iocore/net/P_SSLConfig.h
@@ -42,6 +42,7 @@
 
 struct SSLCertLookup;
 struct ssl_ticket_key_block;
+
 /////////////////////////////////////////////////////////////
 //
 // struct SSLConfigParams
@@ -121,18 +122,20 @@ struct SSLConfigParams : public ConfigInfo {
   static int async_handshake_enabled;
   static char *engine_conf_file;
 
-  SSL_CTX *client_ctx;
+  shared_SSL_CTX client_ctx;
 
   // Client contexts are held by 2-level map:
   // The first level maps from CA bundle file&path to next level map;
   // The second level maps from cert&key to actual SSL_CTX;
   // The second level map owns the client SSL_CTX objects and is responsible for cleaning them up
-  using CTX_MAP = std::unordered_map<std::string, SSL_CTX *>;
-  mutable std::unordered_map<std::string, CTX_MAP *> top_level_ctx_map;
+  using CTX_MAP = std::unordered_map<std::string, shared_SSL_CTX>;
+  mutable std::unordered_map<std::string, CTX_MAP> top_level_ctx_map;
   mutable ink_mutex ctxMapLock;
 
-  SSL_CTX *getClientSSL_CTX() const;
-  SSL_CTX *getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file, const char *ca_bundle_path) const;
+  shared_SSL_CTX getClientSSL_CTX() const;
+  shared_SSL_CTX getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file,
+                        const char *ca_bundle_path) const;
+
   void cleanupCTXTable();
 
   void initialize();
diff --git a/iocore/net/P_SSLSNI.h b/iocore/net/P_SSLSNI.h
index 14c2394..970c53a 100644
--- a/iocore/net/P_SSLSNI.h
+++ b/iocore/net/P_SSLSNI.h
@@ -43,9 +43,12 @@
 // Properties for the next hop server
 struct NextHopProperty {
   std::string name;                                                                // name of the server
+  std::string client_cert_file;                                                    // full path to client cert file for lookup
+  std::string client_key_file;                                                     // full path to client key file for lookup
   YamlSNIConfig::Policy verifyServerPolicy       = YamlSNIConfig::Policy::UNSET;   // whether to verify the next hop
   YamlSNIConfig::Property verifyServerProperties = YamlSNIConfig::Property::UNSET; // what to verify on the next hop
-  SSL_CTX *ctx                                   = nullptr; // ctx generated off the certificate to present to this server
+
+  SSL_CTX *ctx = nullptr; // ctx generated off the certificate to present to this server
   NextHopProperty() {}
 };
 
diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h
index 932a8c3..19f6bc1 100644
--- a/iocore/net/P_SSLUtils.h
+++ b/iocore/net/P_SSLUtils.h
@@ -40,24 +40,6 @@ class SSLNetVConnection;
 typedef int ssl_error_t;
 
 /**
-   @brief Gather user provided settings from ssl_multicert.config in to this single struct
- */
-struct SSLMultiCertConfigParams {
-  SSLMultiCertConfigParams() { REC_ReadConfigInt32(session_ticket_enabled, "proxy.config.ssl.server.session_ticket.enable"); }
-
-  int session_ticket_enabled;   ///< session ticket enabled
-  ats_scoped_str addr;          ///< IPv[64] address to match
-  ats_scoped_str cert;          ///< certificate
-  ats_scoped_str first_cert;    ///< the first certificate name when multiple cert files are in 'ssl_cert_name'
-  ats_scoped_str ca;            ///< CA public certificate
-  ats_scoped_str key;           ///< Private key
-  ats_scoped_str ocsp_response; ///< prefetched OCSP response
-  ats_scoped_str dialog;        ///< Private key dialog
-  ats_scoped_str servername;    ///< Destination server
-  SSLCertContext::Option opt = SSLCertContext::OPT_NONE; ///< SSLCertContext special handling option
-};
-
-/**
     @brief Load SSL certificates from ssl_multicert.config and setup SSLCertLookup for SSLCertificateConfig
  */
 class SSLMultiCertConfigLoader
@@ -84,13 +66,14 @@ protected:
   const SSLConfigParams *_params;
 
 private:
-  virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCertConfigParams *ssl_multi_cert_params);
+  virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams ssl_multi_cert_params);
   virtual void _set_handshake_callbacks(SSL_CTX *ctx);
 };
 
-// Create a new SSL server context fully configured.
-// Used by TS API (TSSslServerContextCreate)
-SSL_CTX *SSLCreateServerContext(const SSLConfigParams *params);
+// Create a new SSL server context fully configured (cert and keys are optional).
+// Used by TS API (TSSslServerContextCreate and TSSslServerCertUpdate)
+SSL_CTX *SSLCreateServerContext(const SSLConfigParams *params, const SSLMultiCertConfigParams *sslMultiCertSettings,
+                                const char *cert_path = nullptr, const char *key_path = nullptr);
 
 // Release SSL_CTX and the associated data. This works for both
 // client and server contexts and gracefully accepts nullptr.
diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc
index 4c4960c..9d2da3c 100644
--- a/iocore/net/SSLCertLookup.cc
+++ b/iocore/net/SSLCertLookup.cc
@@ -245,19 +245,44 @@ fail:
   return nullptr;
 #endif /* TS_HAVE_OPENSSL_SESSION_TICKETS */
 }
-void
-SSLCertContext::release()
+
+SSLCertContext::SSLCertContext(SSLCertContext const &other)
+{
+  opt        = other.opt;
+  userconfig = other.userconfig;
+  keyblock   = other.keyblock;
+  std::lock_guard<std::mutex> lock(other.ctx_mutex);
+  ctx = other.ctx;
+}
+
+SSLCertContext &
+SSLCertContext::operator=(SSLCertContext const &other)
 {
-  if (keyblock) {
-    ticket_block_free(keyblock);
-    keyblock = nullptr;
+  if (&other != this) {
+    this->opt        = other.opt;
+    this->userconfig = other.userconfig;
+    this->keyblock   = other.keyblock;
+    std::lock_guard<std::mutex> lock(other.ctx_mutex);
+    this->ctx = other.ctx;
   }
+  return *this;
+}
 
-  SSLReleaseContext(ctx);
-  ctx = nullptr;
+shared_SSL_CTX
+SSLCertContext::getCtx()
+{
+  std::lock_guard<std::mutex> lock(ctx_mutex);
+  return ctx;
 }
 
-SSLCertLookup::SSLCertLookup() : ssl_storage(new SSLContextStorage()) {}
+void
+SSLCertContext::setCtx(shared_SSL_CTX sc)
+{
+  std::lock_guard<std::mutex> lock(ctx_mutex);
+  ctx = sc;
+}
+
+SSLCertLookup::SSLCertLookup() : ssl_storage(new SSLContextStorage()), ssl_default(nullptr), is_valid(true) {}
 
 SSLCertLookup::~SSLCertLookup()
 {
@@ -317,27 +342,7 @@ SSLCertLookup::get(unsigned i) const
 
 SSLContextStorage::SSLContextStorage() {}
 
-bool
-SSLCtxCompare(SSLCertContext const &cc1, SSLCertContext const &cc2)
-{
-  // Either they are both real ctx pointers and cc1 has the smaller pointer
-  // Or only cc2 has a non-null pointer
-  return cc1.ctx < cc2.ctx;
-}
-
-SSLContextStorage::~SSLContextStorage()
-{
-  // First sort the array so we can efficiently detect duplicates
-  // and avoid the double free
-  std::sort(ctx_store.begin(), ctx_store.end(), SSLCtxCompare);
-  SSL_CTX *last_ctx = nullptr;
-  for (auto &&it : this->ctx_store) {
-    if (it.ctx != last_ctx) {
-      last_ctx = it.ctx;
-      it.release();
-    }
-  }
-}
+SSLContextStorage::~SSLContextStorage() {}
 
 int
 SSLContextStorage::store(SSLCertContext const &cc)
@@ -363,6 +368,8 @@ SSLContextStorage::insert(const char *name, int idx)
   ats_wildcard_matcher wildcard;
   char lower_case_name[TS_MAX_HOST_NAME_LEN + 1];
   transform_lower(name, lower_case_name);
+
+  shared_SSL_CTX ctx = this->ctx_store[idx].getCtx();
   if (wildcard.match(lower_case_name)) {
     // Strip the wildcard and store the subdomain
     const char *subdomain = index(lower_case_name, '*');
@@ -378,7 +385,7 @@ SSLContextStorage::insert(const char *name, int idx)
         idx = -1;
       } else {
         this->wilddomains.emplace(subdomain, idx);
-        Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]", lower_case_name, this->ctx_store[idx].ctx, idx);
+        Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]", lower_case_name, ctx.get(), idx);
       }
     }
   } else {
@@ -388,7 +395,7 @@ SSLContextStorage::insert(const char *name, int idx)
       idx = -1;
     } else {
       this->hostnames.emplace(lower_case_name, idx);
-      Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]", lower_case_name, this->ctx_store[idx].ctx, idx);
+      Debug("ssl", "indexed '%s' with SSL_CTX %p [%d]", lower_case_name, ctx.get(), idx);
     }
   }
   return idx;
diff --git a/iocore/net/SSLClientUtils.cc b/iocore/net/SSLClientUtils.cc
index b60225c..59f2bce 100644
--- a/iocore/net/SSLClientUtils.cc
+++ b/iocore/net/SSLClientUtils.cc
@@ -197,3 +197,50 @@ fail:
   SSLReleaseContext(client_ctx);
   ::exit(1);
 }
+
+SSL_CTX *
+SSLCreateClientContext(const struct SSLConfigParams *params, const char *ca_bundle_path, const char *ca_bundle_file,
+                       const char *cert_path, const char *key_path)
+{
+  std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx(nullptr, &SSL_CTX_free);
+
+  if (nullptr == params || nullptr == cert_path) {
+    return nullptr;
+  }
+
+  ctx.reset(SSLInitClientContext(params));
+
+  if (!ctx) {
+    return nullptr;
+  }
+
+  if (!SSL_CTX_use_certificate_chain_file(ctx.get(), cert_path)) {
+    SSLError("SSLCreateClientContext(): failed to load client certificate.");
+    return nullptr;
+  }
+
+  if (!key_path || key_path[0] == '\0') {
+    key_path = cert_path;
+  }
+
+  if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key_path, SSL_FILETYPE_PEM)) {
+    SSLError("SSLCreateClientContext(): failed to load client private key.");
+    return nullptr;
+  }
+
+  if (!SSL_CTX_check_private_key(ctx.get())) {
+    SSLError("SSLCreateClientContext(): client private key does not match client certificate.");
+    return nullptr;
+  }
+
+  if (ca_bundle_file || ca_bundle_path) {
+    if (!SSL_CTX_load_verify_locations(ctx.get(), ca_bundle_file, ca_bundle_path)) {
+      SSLError("SSLCreateClientContext(): Invalid client CA cert file/CA path.");
+      return nullptr;
+    }
+  } else if (!SSL_CTX_set_default_verify_paths(ctx.get())) {
+    SSLError("SSLCreateClientContext(): failed to set the default verify paths.");
+    return nullptr;
+  }
+  return ctx.release();
+}
diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc
index 0183800..86694a8 100644
--- a/iocore/net/SSLConfig.cc
+++ b/iocore/net/SSLConfig.cc
@@ -448,7 +448,7 @@ SSLConfigParams::initialize()
   }
 }
 
-SSL_CTX *
+shared_SSL_CTX
 SSLConfigParams::getClientSSL_CTX() const
 {
   return client_ctx;
@@ -673,95 +673,70 @@ SSLTicketParams::cleanup()
   ticket_key_filename = (char *)ats_free_null(ticket_key_filename);
 }
 
-SSL_CTX *
+shared_SSL_CTX
 SSLConfigParams::getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file, const char *ca_bundle_path) const
 {
-  SSL_CTX *client_ctx = nullptr;
-  CTX_MAP *ctx_map    = nullptr;
+  shared_SSL_CTX client_ctx = nullptr;
   std::string top_level_key, ctx_key;
   ts::bwprint(top_level_key, "{}:{}", ca_bundle_file, ca_bundle_path);
   ts::bwprint(ctx_key, "{}:{}", client_cert, key_file);
 
-  ink_mutex_acquire(&ctxMapLock);
-  // Do first level searching and create new CTX_MAP as second level if not exists.
-  auto top_iter = top_level_ctx_map.find(top_level_key);
-  if (top_iter != top_level_ctx_map.end()) {
-    if (top_iter->second == nullptr) {
-      top_iter->second = new CTX_MAP;
+  auto ctx_map_iter = top_level_ctx_map.find(top_level_key);
+  if (ctx_map_iter != top_level_ctx_map.end()) {
+    auto ctx_iter = ctx_map_iter->second.find(ctx_key);
+    if (ctx_iter != ctx_map_iter->second.end()) {
+      client_ctx = ctx_iter->second;
     }
-    ctx_map = top_iter->second;
-  } else {
-    ctx_map = new CTX_MAP;
-    top_level_ctx_map.insert(std::make_pair(top_level_key, ctx_map));
-  }
-  // Do second level searching and return client ctx if found
-  auto iter = ctx_map->find(ctx_key);
-  if (iter != ctx_map->end()) {
-    client_ctx = iter->second;
-    ink_mutex_release(&ctxMapLock);
-    return client_ctx;
   }
-  ink_mutex_release(&ctxMapLock);
 
-  // Not yet in the table.  Make the cert and add it to the table
-  client_ctx = SSLInitClientContext(this);
+  // Create context if doesn't exists
+  if (!client_ctx) {
+    client_ctx = shared_SSL_CTX(SSLInitClientContext(this), SSLReleaseContext);
 
-  if (client_cert) {
-    // Set public and private keys
-    if (!SSL_CTX_use_certificate_chain_file(client_ctx, client_cert)) {
-      SSLError("failed to load client certificate from %s", client_cert);
-      goto fail;
-    }
-    if (!key_file || key_file[0] == '\0') {
-      key_file = client_cert;
-    }
-    if (!SSL_CTX_use_PrivateKey_file(client_ctx, key_file, SSL_FILETYPE_PEM)) {
-      SSLError("failed to load client private key file from %s", key_file);
-      goto fail;
-    }
+    if (client_cert) {
+      // Set public and private keys
+      if (!SSL_CTX_use_certificate_chain_file(client_ctx.get(), client_cert)) {
+        SSLError("failed to load client certificate from %s", client_cert);
+        goto fail;
+      }
+      if (!key_file || key_file[0] == '\0') {
+        key_file = client_cert;
+      }
+      if (!SSL_CTX_use_PrivateKey_file(client_ctx.get(), key_file, SSL_FILETYPE_PEM)) {
+        SSLError("failed to load client private key file from %s", key_file);
+        goto fail;
+      }
 
-    if (!SSL_CTX_check_private_key(client_ctx)) {
-      SSLError("client private key (%s) does not match the certificate public key (%s)", key_file, client_cert);
-      goto fail;
+      if (!SSL_CTX_check_private_key(client_ctx.get())) {
+        SSLError("client private key (%s) does not match the certificate public key (%s)", key_file, client_cert);
+        goto fail;
+      }
     }
-  }
 
-  // Set CA information for verifying peer cert
-  if (ca_bundle_file != nullptr || ca_bundle_path != nullptr) {
-    if (!SSL_CTX_load_verify_locations(client_ctx, ca_bundle_file, ca_bundle_path)) {
-      SSLError("invalid client CA Certificate file (%s) or CA Certificate path (%s)", ca_bundle_file, ca_bundle_path);
+    // Set CA information for verifying peer cert
+    if (ca_bundle_file != nullptr || ca_bundle_path != nullptr) {
+      if (!SSL_CTX_load_verify_locations(client_ctx.get(), ca_bundle_file, ca_bundle_path)) {
+        SSLError("invalid client CA Certificate file (%s) or CA Certificate path (%s)", ca_bundle_file, ca_bundle_path);
+        goto fail;
+      }
+    } else if (!SSL_CTX_set_default_verify_paths(client_ctx.get())) {
+      SSLError("failed to set the default verify paths");
       goto fail;
     }
-  } else if (!SSL_CTX_set_default_verify_paths(client_ctx)) {
-    SSLError("failed to set the default verify paths");
-    goto fail;
-  }
 
-  ink_mutex_acquire(&ctxMapLock);
-  top_iter = top_level_ctx_map.find(top_level_key);
-  if (top_iter != top_level_ctx_map.end()) {
-    if (top_iter->second == nullptr) {
-      top_iter->second = new CTX_MAP;
+    // Try to update the context in mapping with lock acquired. If a valid context exists, return it without changing the structure.
+    ink_mutex_acquire(&ctxMapLock);
+    auto ctx_iter = top_level_ctx_map[top_level_key].find(ctx_key);
+    if (ctx_iter == top_level_ctx_map[top_level_key].end() || ctx_iter->second == nullptr) {
+      top_level_ctx_map[top_level_key][ctx_key] = client_ctx;
+    } else {
+      client_ctx = ctx_iter->second;
     }
-    ctx_map = top_iter->second;
-  } else {
-    ctx_map = new CTX_MAP;
-    top_level_ctx_map.insert(std::make_pair(top_level_key, ctx_map));
-  }
-  iter = ctx_map->find(ctx_key);
-  if (iter != ctx_map->end()) {
-    SSL_CTX_free(client_ctx);
-    client_ctx = iter->second;
-  } else {
-    ctx_map->insert(std::make_pair(ctx_key, client_ctx));
+    ink_mutex_release(&ctxMapLock);
   }
-  ink_mutex_release(&ctxMapLock);
   return client_ctx;
 
 fail:
-  if (client_ctx) {
-    SSL_CTX_free(client_ctx);
-  }
   return nullptr;
 }
 
@@ -769,17 +744,6 @@ void
 SSLConfigParams::cleanupCTXTable()
 {
   ink_mutex_acquire(&ctxMapLock);
-  CTX_MAP *ctx_map = nullptr;
-  for (auto &top_pair : top_level_ctx_map) {
-    ctx_map = top_pair.second;
-    if (ctx_map) {
-      for (auto &pair : (*ctx_map)) {
-        SSL_CTX_free(pair.second);
-      }
-      ctx_map->clear();
-      delete ctx_map;
-    }
-  }
   top_level_ctx_map.clear();
   ink_mutex_release(&ctxMapLock);
 }
diff --git a/iocore/net/SSLInternal.cc b/iocore/net/SSLInternal.cc
index 7e9b475..7d92073 100644
--- a/iocore/net/SSLInternal.cc
+++ b/iocore/net/SSLInternal.cc
@@ -22,8 +22,10 @@
   limitations under the License.
  */
 #include "tscore/ink_config.h"
-#if TS_USE_SET_RBIO
-// No need to do anything, this version of openssl provides the SSL_set0_rbio function
+#include <openssl/opensslv.h>
+
+#if TS_USE_SET_RBIO && OPENSSL_VERSION_NUMBER >= 0x10100000L
+// No need to do anything, this version of openssl provides the SSL_set0_rbio and SSL_CTX_up_ref.
 #else
 
 #ifdef OPENSSL_NO_SSL_INTERN
@@ -31,9 +33,24 @@
 #endif
 
 #include <openssl/ssl.h>
-#include "P_Net.h"
-#include "P_SSLNetVConnection.h"
 
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#include <atomic>
+
+static_assert(sizeof(std::atomic_int) == sizeof(int));
+static_assert(alignof(std::atomic_int) == alignof(int));
+
+int
+SSL_CTX_up_ref(SSL_CTX *ctx)
+{
+  int i;
+  i = atomic_fetch_add_explicit(reinterpret_cast<std::atomic_int *>(&ctx->references), 1, std::memory_order::memory_order_relaxed) +
+      1;
+  return ((i > 1) ? 1 : 0);
+}
+#endif
+
+#if !TS_USE_SET_RBIO
 void
 SSL_set0_rbio(SSL *ssl, BIO *rbio)
 {
@@ -42,5 +59,6 @@ SSL_set0_rbio(SSL *ssl, BIO *rbio)
   }
   ssl->rbio = rbio;
 }
+#endif
 
 #endif
diff --git a/iocore/net/P_SSLClientUtils.h b/iocore/net/SSLInternal.h
similarity index 69%
copy from iocore/net/P_SSLClientUtils.h
copy to iocore/net/SSLInternal.h
index ff99733..0ea83f3 100644
--- a/iocore/net/P_SSLClientUtils.h
+++ b/iocore/net/SSLInternal.h
@@ -1,5 +1,7 @@
 /** @file
 
+  Function prototypes that break the no internal pact with openssl.
+
   @section license License
 
   Licensed to the Apache Software Foundation (ASF) under one
@@ -20,20 +22,15 @@
  */
 
 #pragma once
-
 #include "tscore/ink_config.h"
-#include "tscore/Diags.h"
-#include "P_SSLUtils.h"
-#include "P_SSLConfig.h"
-
-#include <openssl/ssl.h>
+#include <openssl/opensslv.h>
 
-// BoringSSL does not have this include file
-#ifndef OPENSSL_IS_BORINGSSL
-#include <openssl/opensslconf.h>
+#if !TS_USE_SET_RBIO
+// Defined in SSLInternal.c, should probably make a separate include
+// file for this at some point
+void SSL_set0_rbio(SSL *ssl, BIO *rbio);
 #endif
 
-// Create and initialize a SSL client context.
-SSL_CTX *SSLInitClientContext(const struct SSLConfigParams *param);
-
-int verify_callback(int preverify_ok, X509_STORE_CTX *ctx);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+int SSL_CTX_up_ref(SSL_CTX *ctx);
+#endif
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 18e7820..16d8e1b 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -41,6 +41,7 @@
 #include "P_SSLSNI.h"
 #include "BIO_fastopen.h"
 #include "SSLStats.h"
+#include "SSLInternal.h"
 
 #include <climits>
 #include <string>
@@ -51,12 +52,6 @@ using namespace std::literals;
 #include <openssl/async.h>
 #endif
 
-#if !TS_USE_SET_RBIO
-// Defined in SSLInternal.c, should probably make a separate include
-// file for this at some point
-void SSL_set0_rbio(SSL *ssl, BIO *rbio);
-#endif
-
 // This is missing from BoringSSL
 #ifndef BIO_eof
 #define BIO_eof(b) (int)BIO_ctrl(b, BIO_CTRL_EOF, 0, nullptr)
@@ -1000,7 +995,7 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
       // No data has been read at this point, so we can go
       // directly into blind tunnel mode
 
-      if (cc && SSLCertContext::OPT_TUNNEL == cc->opt) {
+      if (cc && SSLCertContextOption::OPT_TUNNEL == cc->opt) {
         if (this->is_transparent) {
           this->attributes   = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
           sslHandshakeStatus = SSL_HANDSHAKE_DONE;
@@ -1037,8 +1032,9 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
         ats_ip_ntop(this->get_remote_addr(), buff, INET6_ADDRSTRLEN);
         serverKey = buff;
       }
-      auto nps           = sniParam->getPropertyConfig(serverKey);
-      SSL_CTX *clientCTX = nullptr;
+      auto nps                 = sniParam->getPropertyConfig(serverKey);
+      shared_SSL_CTX sharedCTX = nullptr;
+      SSL_CTX *clientCTX       = nullptr;
 
       // First Look to see if there are override parameters
       if (options.ssl_client_cert_name) {
@@ -1051,16 +1047,23 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
         if (options.ssl_client_ca_cert_name) {
           caCertFilePath = Layout::get()->relative_to(params->clientCACertPath, options.ssl_client_ca_cert_name);
         }
-        clientCTX =
+        sharedCTX =
           params->getCTX(certFilePath.c_str(), keyFilePath.empty() ? nullptr : keyFilePath.c_str(),
                          caCertFilePath.empty() ? params->clientCACertFilename : caCertFilePath.c_str(), params->clientCACertPath);
       } else if (options.ssl_client_ca_cert_name) {
         std::string caCertFilePath = Layout::get()->relative_to(params->clientCACertPath, options.ssl_client_ca_cert_name);
-        clientCTX = params->getCTX(params->clientCertPath, params->clientKeyPath, caCertFilePath.c_str(), params->clientCACertPath);
-      } else if (nps) {
-        clientCTX = nps->ctx;
+        sharedCTX = params->getCTX(params->clientCertPath, params->clientKeyPath, caCertFilePath.c_str(), params->clientCACertPath);
+      } else if (nps && !nps->client_cert_file.empty()) {
+        // If no overrides available, try the available nextHopProperty by reading from context mappings
+        sharedCTX =
+          params->getCTX(nps->client_cert_file.c_str(), nps->client_key_file.empty() ? nullptr : nps->client_key_file.c_str(),
+                         params->clientCACertFilename, params->clientCACertPath);
       } else { // Just stay with the values passed down from the SM for verify
-        clientCTX = params->client_ctx;
+        clientCTX = params->client_ctx.get();
+      }
+
+      if (sharedCTX) {
+        clientCTX = sharedCTX.get();
       }
 
       if (options.verifyServerPolicy != YamlSNIConfig::Policy::UNSET) {
diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc
index ea090c2..274a82e 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -82,23 +82,24 @@ SNIConfigParams::loadSNIConfig()
     ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));
 
     // set the next hop properties
+    auto nps = next_hop_list.emplace(next_hop_list.end());
+
     SSLConfig::scoped_config params;
-    auto clientCTX = params->getClientSSL_CTX();
     // Load if we have at least specified the client certificate
     if (!item.client_cert.empty()) {
-      std::string certFilePath = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data());
-      std::string keyFilePath;
+      nps->prop.client_cert_file = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data());
       if (!item.client_key.empty()) {
-        keyFilePath = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data());
+        nps->prop.client_key_file = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data());
       }
-      clientCTX = params->getCTX(certFilePath.c_str(), keyFilePath.c_str(), params->clientCACertFilename, params->clientCACertPath);
+
+      params->getCTX(nps->prop.client_cert_file.c_str(),
+                     nps->prop.client_key_file.empty() ? nullptr : nps->prop.client_key_file.c_str(), params->clientCACertFilename,
+                     params->clientCACertPath);
     }
 
-    auto nps = next_hop_list.emplace(next_hop_list.end());
     nps->setGlobName(item.fqdn);
     nps->prop.verifyServerPolicy     = item.verify_server_policy;
     nps->prop.verifyServerProperties = item.verify_server_properties;
-    nps->prop.ctx                    = clientCTX;
   } // end for
 }
 
diff --git a/iocore/net/SSLSessionTicket.cc b/iocore/net/SSLSessionTicket.cc
index 9346b29..1eb1a08 100644
--- a/iocore/net/SSLSessionTicket.cc
+++ b/iocore/net/SSLSessionTicket.cc
@@ -71,7 +71,7 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
     // Try the default
     keyblock = params->default_global_keyblock;
   } else {
-    keyblock = cc->keyblock;
+    keyblock = cc->keyblock.get();
   }
   ink_release_assert(keyblock != nullptr && keyblock->num_keys > 0);
 
diff --git a/iocore/net/SSLStats.cc b/iocore/net/SSLStats.cc
index b15e40d..b15f5d6 100644
--- a/iocore/net/SSLStats.cc
+++ b/iocore/net/SSLStats.cc
@@ -47,11 +47,14 @@ SSLRecRawStatSyncCount(const char *name, RecDataT data_type, RecData *data, RecR
     const unsigned ctxCount = certLookup->count();
     for (size_t i = 0; i < ctxCount; i++) {
       SSLCertContext *cc = certLookup->get(i);
-      if (cc && cc->ctx) {
-        sessions += SSL_CTX_sess_accept_good(cc->ctx);
-        hits += SSL_CTX_sess_hits(cc->ctx);
-        misses += SSL_CTX_sess_misses(cc->ctx);
-        timeouts += SSL_CTX_sess_timeouts(cc->ctx);
+      if (cc) {
+        shared_SSL_CTX ctx = cc->getCtx();
+        if (ctx) {
+          sessions += SSL_CTX_sess_accept_good(ctx.get());
+          hits += SSL_CTX_sess_hits(ctx.get());
+          misses += SSL_CTX_sess_misses(ctx.get());
+          timeouts += SSL_CTX_sess_timeouts(ctx.get());
+        }
       }
     }
   }
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 64d093b..1958ed7 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -277,8 +277,9 @@ ssl_rm_cached_session(SSL_CTX *ctx, SSL_SESSION *sess)
 static int
 set_context_cert(SSL *ssl)
 {
-  SSL_CTX *ctx       = nullptr;
-  SSLCertContext *cc = nullptr;
+  shared_SSL_CTX ctx  = nullptr;
+  SSL_CTX *verify_ctx = nullptr;
+  SSLCertContext *cc  = nullptr;
   SSLCertificateConfig::scoped_config lookup;
   const char *servername   = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
   SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
@@ -298,11 +299,11 @@ set_context_cert(SSL *ssl)
   // don't find a name-based match at this point, we *do not* want to mess with the context because we've
   // already made a best effort to find the best match.
   if (likely(servername)) {
-    cc = lookup->find((char *)servername);
-    if (cc && cc->ctx) {
-      ctx = cc->ctx;
+    cc = lookup->find(const_cast<char *>(servername));
+    if (cc) {
+      ctx = cc->getCtx();
     }
-    if (cc && SSLCertContext::OPT_TUNNEL == cc->opt && netvc->get_is_transparent()) {
+    if (cc && ctx && SSLCertContextOption::OPT_TUNNEL == cc->opt && netvc->get_is_transparent()) {
       netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
       netvc->setSSLHandShakeComplete(SSL_HANDSHAKE_DONE);
       retval = -1;
@@ -318,25 +319,25 @@ set_context_cert(SSL *ssl)
     if (0 == safe_getsockname(netvc->get_socket(), &ip.sa, &namelen)) {
       cc = lookup->find(ip);
     }
-    if (cc && cc->ctx) {
-      ctx = cc->ctx;
+    if (cc) {
+      ctx = cc->getCtx();
     }
   }
 
   if (ctx != nullptr) {
-    SSL_set_SSL_CTX(ssl, ctx);
+    SSL_set_SSL_CTX(ssl, ctx.get());
 #if TS_HAVE_OPENSSL_SESSION_TICKETS
     // Reset the ticket callback if needed
-    SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
+    SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), ssl_callback_session_ticket);
 #endif
   } else {
     found = false;
   }
 
-  ctx = SSL_get_SSL_CTX(ssl);
-  Debug("ssl", "ssl_cert_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", ctx, servername);
+  verify_ctx = SSL_get_SSL_CTX(ssl);
+  Debug("ssl", "ssl_cert_callback %s SSL context %p for requested name '%s'", found ? "found" : "using", verify_ctx, servername);
 
-  if (ctx == nullptr) {
+  if (verify_ctx == nullptr) {
     retval = 0;
     goto done;
   }
@@ -1391,15 +1392,34 @@ fail:
 }
 
 SSL_CTX *
-SSLCreateServerContext(const SSLConfigParams *params)
+SSLCreateServerContext(const SSLConfigParams *params, const SSLMultiCertConfigParams *sslMultiCertSettings, const char *cert_path,
+                       const char *key_path)
 {
   SSLMultiCertConfigLoader loader(params);
   std::vector<X509 *> cert_list;
-
-  SSL_CTX *ctx = loader.init_server_ssl_ctx(cert_list, nullptr);
+  std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ctx(nullptr, &SSL_CTX_free);
+  ctx.reset(loader.init_server_ssl_ctx(cert_list, sslMultiCertSettings));
   ink_assert(cert_list.empty());
 
-  return ctx;
+  if (cert_path) {
+    if (!SSL_CTX_use_certificate_file(ctx.get(), cert_path, SSL_FILETYPE_PEM)) {
+      SSLError("SSLCreateServerContext(): failed to load server certificate.");
+      return nullptr;
+    }
+    if (!key_path || key_path[0] == '\0') {
+      key_path = cert_path;
+    }
+    if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key_path, SSL_FILETYPE_PEM)) {
+      SSLError("SSLCreateServerContext(): failed to load server private key.");
+      return nullptr;
+    }
+    if (!SSL_CTX_check_private_key(ctx.get())) {
+      SSLError("SSLCreateServerContext(): server private key does not match server certificate.");
+      return nullptr;
+    }
+  }
+
+  return ctx.release();
 }
 
 /**
@@ -1407,12 +1427,12 @@ SSLCreateServerContext(const SSLConfigParams *params)
    Do NOT call SSL_CTX_set_* functions from here. SSL_CTX should be set up by SSLMultiCertConfigLoader::init_server_ssl_ctx().
  */
 SSL_CTX *
-SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCertConfigParams *sslMultCertSettings)
+SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams sslMultCertSettings)
 {
   std::vector<X509 *> cert_list;
-  SSL_CTX *ctx                   = this->init_server_ssl_ctx(cert_list, sslMultCertSettings);
-  ssl_ticket_key_block *keyblock = nullptr;
-  bool inserted                  = false;
+  shared_ssl_ticket_key_block keyblock = nullptr;
+  bool inserted                        = false;
+  shared_SSL_CTX ctx(this->init_server_ssl_ctx(cert_list, sslMultCertSettings.get()), SSL_CTX_free);
 
   if (!ctx || !sslMultCertSettings) {
     lookup->is_valid = false;
@@ -1431,23 +1451,23 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCe
 
   // Load the session ticket key if session tickets are not disabled
   if (sslMultCertSettings->session_ticket_enabled != 0) {
-    keyblock = ssl_context_enable_tickets(ctx, nullptr);
+    keyblock = shared_ssl_ticket_key_block(ssl_context_enable_tickets(ctx.get(), nullptr), ticket_block_free);
   }
 
   // Index this certificate by the specified IP(v6) address. If the address is "*", make it the default context.
   if (sslMultCertSettings->addr) {
     if (strcmp(sslMultCertSettings->addr, "*") == 0) {
-      if (lookup->insert(sslMultCertSettings->addr, SSLCertContext(ctx, sslMultCertSettings->opt, keyblock)) >= 0) {
+      if (lookup->insert(sslMultCertSettings->addr, SSLCertContext(ctx, sslMultCertSettings, keyblock)) >= 0) {
         inserted            = true;
         lookup->ssl_default = ctx;
-        this->_set_handshake_callbacks(ctx);
+        this->_set_handshake_callbacks(ctx.get());
       }
     } else {
       IpEndpoint ep;
 
       if (ats_ip_pton(sslMultCertSettings->addr, &ep) == 0) {
         Debug("ssl", "mapping '%s' to certificate %s", (const char *)sslMultCertSettings->addr, (const char *)certname);
-        if (lookup->insert(ep, SSLCertContext(ctx, sslMultCertSettings->opt, keyblock)) >= 0) {
+        if (lookup->insert(ep, SSLCertContext(ctx, sslMultCertSettings, keyblock)) >= 0) {
           inserted = true;
         }
       } else {
@@ -1456,32 +1476,24 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCe
       }
     }
   }
-  if (!inserted) {
-#if TS_HAVE_OPENSSL_SESSION_TICKETS
-    if (keyblock != nullptr) {
-      ticket_block_free(keyblock);
-    }
-#endif
-  }
 
   // Insert additional mappings. Note that this maps multiple keys to the same value, so when
   // this code is updated to reconfigure the SSL certificates, it will need some sort of
   // refcounting or alternate way of avoiding double frees.
   Debug("ssl", "importing SNI names from %s", (const char *)certname);
   for (auto cert : cert_list) {
-    if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, sslMultCertSettings->opt), cert, certname)) {
+    if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, sslMultCertSettings), cert, certname)) {
       inserted = true;
     }
   }
 
   if (inserted) {
     if (SSLConfigParams::init_ssl_ctx_cb) {
-      SSLConfigParams::init_ssl_ctx_cb(ctx, true);
+      SSLConfigParams::init_ssl_ctx_cb(ctx.get(), true);
     }
   }
 
   if (!inserted) {
-    SSLReleaseContext(ctx);
     ctx = nullptr;
   }
 
@@ -1489,11 +1501,11 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const SSLMultiCe
     X509_free(i);
   }
 
-  return ctx;
+  return ctx.get();
 }
 
 static bool
-ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams &sslMultCertSettings)
+ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams *sslMultCertSettings)
 {
   for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) {
     const char *label;
@@ -1507,40 +1519,40 @@ ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams
     }
 
     if (strcasecmp(label, SSL_IP_TAG) == 0) {
-      sslMultCertSettings.addr = ats_strdup(value);
+      sslMultCertSettings->addr = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_CERT_TAG) == 0) {
-      sslMultCertSettings.cert = ats_strdup(value);
+      sslMultCertSettings->cert = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_CA_TAG) == 0) {
-      sslMultCertSettings.ca = ats_strdup(value);
+      sslMultCertSettings->ca = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_PRIVATE_KEY_TAG) == 0) {
-      sslMultCertSettings.key = ats_strdup(value);
+      sslMultCertSettings->key = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_OCSP_RESPONSE_TAG) == 0) {
-      sslMultCertSettings.ocsp_response = ats_strdup(value);
+      sslMultCertSettings->ocsp_response = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_SESSION_TICKET_ENABLED) == 0) {
-      sslMultCertSettings.session_ticket_enabled = atoi(value);
+      sslMultCertSettings->session_ticket_enabled = atoi(value);
     }
 
     if (strcasecmp(label, SSL_KEY_DIALOG) == 0) {
-      sslMultCertSettings.dialog = ats_strdup(value);
+      sslMultCertSettings->dialog = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_SERVERNAME) == 0) {
-      sslMultCertSettings.servername = ats_strdup(value);
+      sslMultCertSettings->servername = ats_strdup(value);
     }
 
     if (strcasecmp(label, SSL_ACTION_TAG) == 0) {
       if (strcasecmp(SSL_ACTION_TUNNEL_TAG, value) == 0) {
-        sslMultCertSettings.opt = SSLCertContext::OPT_TUNNEL;
+        sslMultCertSettings->opt = SSLCertContextOption::OPT_TUNNEL;
       } else {
         Error("Unrecognized action for %s", SSL_ACTION_TAG.data());
         return false;
@@ -1548,11 +1560,11 @@ ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams
     }
   }
   // TS-4679:  It is ok to be missing the cert.  At least if the action is set to tunnel
-  if (sslMultCertSettings.cert) {
-    SimpleTokenizer cert_tok(sslMultCertSettings.cert, SSL_CERT_SEPARATE_DELIM);
+  if (sslMultCertSettings->cert) {
+    SimpleTokenizer cert_tok(sslMultCertSettings->cert, SSL_CERT_SEPARATE_DELIM);
     const char *first_cert = cert_tok.getNext();
     if (first_cert) {
-      sslMultCertSettings.first_cert = ats_strdup(first_cert);
+      sslMultCertSettings->first_cert = ats_strdup(first_cert);
     }
   }
 
@@ -1599,7 +1611,7 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
     }
 
     if (*line != '\0' && *line != '#') {
-      SSLMultiCertConfigParams sslMultiCertSettings;
+      shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
       const char *errPtr;
 
       errPtr = parseConfigLine(line, &line_info, &sslCertTags);
@@ -1608,10 +1620,10 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
         RecSignalWarning(REC_SIGNAL_CONFIG_ERROR, "%s: discarding %s entry at line %d: %s", __func__, params->configFilePath,
                          line_num, errPtr);
       } else {
-        if (ssl_extract_certificate(&line_info, sslMultiCertSettings)) {
+        if (ssl_extract_certificate(&line_info, sslMultiCertSettings.get())) {
           // There must be a certificate specified unless the tunnel action is set
-          if (sslMultiCertSettings.cert || sslMultiCertSettings.opt != SSLCertContext::OPT_TUNNEL) {
-            this->_store_ssl_ctx(lookup, &sslMultiCertSettings);
+          if (sslMultiCertSettings->cert || sslMultiCertSettings->opt != SSLCertContextOption::OPT_TUNNEL) {
+            this->_store_ssl_ctx(lookup, sslMultiCertSettings);
           } else {
             Warning("No ssl_cert_name specified and no tunnel action set");
           }
@@ -1626,9 +1638,9 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
   // bootstrap the SSL handshake so that we can subsequently do the SNI lookup to switch to the real
   // context.
   if (lookup->ssl_default == nullptr) {
-    SSLMultiCertConfigParams sslMultiCertSettings;
-    sslMultiCertSettings.addr = ats_strdup("*");
-    if (this->_store_ssl_ctx(lookup, &sslMultiCertSettings) == nullptr) {
+    shared_SSLMultiCertConfigParams sslMultiCertSettings(new SSLMultiCertConfigParams);
+    sslMultiCertSettings->addr = ats_strdup("*");
+    if (this->_store_ssl_ctx(lookup, sslMultiCertSettings) == nullptr) {
       Error("failed set default context");
       return false;
     }
diff --git a/iocore/net/test_certlookup.cc b/iocore/net/test_certlookup.cc
index 96a8f36..4344ad8 100644
--- a/iocore/net/test_certlookup.cc
+++ b/iocore/net/test_certlookup.cc
@@ -77,26 +77,26 @@ REGRESSION_TEST(SSLCertificateLookup)(RegressionTest *t, int /* atype ATS_UNUSED
   box.check(lookup.insert("www.foo.com", all_com_cc) < 0, "insert wildcard context duplicate");
 
   // Basic wildcard cases.
-  box.check(lookup.find("a.wild.com")->ctx == wild, "wildcard lookup for a.wild.com");
-  box.check(lookup.find("b.wild.com")->ctx == wild, "wildcard lookup for b.wild.com");
+  box.check(lookup.find("a.wild.com")->getCtx().get() == wild, "wildcard lookup for a.wild.com");
+  box.check(lookup.find("b.wild.com")->getCtx().get() == wild, "wildcard lookup for b.wild.com");
   box.check(lookup.insert("www.foo.com", all_com_cc) < 0, "insert wildcard context duplicate");
 
   // Verify that wildcard does longest match.
-  box.check(lookup.find("a.notwild.com")->ctx == notwild, "wildcard lookup for a.notwild.com");
-  box.check(lookup.find("notwild.com")->ctx == all_com, "wildcard lookup for notwild.com");
-  box.check(lookup.find("c.b.notwild.com")->ctx == b_notwild, "wildcard lookup for c.b.notwild.com");
+  box.check(lookup.find("a.notwild.com")->getCtx().get() == notwild, "wildcard lookup for a.notwild.com");
+  box.check(lookup.find("notwild.com")->getCtx().get() == all_com, "wildcard lookup for notwild.com");
+  box.check(lookup.find("c.b.notwild.com")->getCtx().get() == b_notwild, "wildcard lookup for c.b.notwild.com");
 
   // Basic hostname cases.
-  box.check(lookup.find("www.foo.com")->ctx == foo, "host lookup for www.foo.com");
+  box.check(lookup.find("www.foo.com")->getCtx().get() == foo, "host lookup for www.foo.com");
   box.check(lookup.find("www.bar.com") == nullptr, "www.bar.com won't match *.com because we only match one level");
   box.check(lookup.find("www.bar.net") == nullptr, "host lookup for www.bar.net");
 
   // Make sure cases are lowered
-  box.check(lookup.find("WWW.foo.com")->ctx == foo, "mixed case lookup for www.foo.com");
+  box.check(lookup.find("WWW.foo.com")->getCtx().get() == foo, "mixed case lookup for www.foo.com");
   box.check(lookup.insert("Mixed.Case.Com", foo_cc) >= 0, "mixed case insert for Mixed.Case.Com");
-  box.check(lookup.find("Mixed.CASE.Com")->ctx == foo, "mixed case lookup 1 for Mixed.Case.Com");
-  box.check(lookup.find("Mixed.Case.Com")->ctx == foo, "mixed case lookup 2 for Mixed.Case.Com");
-  box.check(lookup.find("mixed.case.com")->ctx == foo, "lower case lookup for Mixed.Case.Com");
+  box.check(lookup.find("Mixed.CASE.Com")->getCtx().get() == foo, "mixed case lookup 1 for Mixed.Case.Com");
+  box.check(lookup.find("Mixed.Case.Com")->getCtx().get() == foo, "mixed case lookup 2 for Mixed.Case.Com");
+  box.check(lookup.find("mixed.case.com")->getCtx().get() == foo, "lower case lookup for Mixed.Case.Com");
 }
 
 REGRESSION_TEST(SSLAddressLookup)(RegressionTest *t, int /* atype ATS_UNUSED */, int *pstatus)
@@ -138,20 +138,20 @@ REGRESSION_TEST(SSLAddressLookup)(RegressionTest *t, int /* atype ATS_UNUSED */,
   // the most specific match (ie. find the context with the port if it is available) ...
 
   box.check(lookup.insert(endpoint.ip6, ip6_cc) >= 0, "insert IPv6 address");
-  box.check(lookup.find(endpoint.ip6)->ctx == context.ip6, "IPv6 exact match lookup");
-  box.check(lookup.find(endpoint.ip6p)->ctx == context.ip6, "IPv6 exact match lookup w/ port");
+  box.check(lookup.find(endpoint.ip6)->getCtx().get() == context.ip6, "IPv6 exact match lookup");
+  box.check(lookup.find(endpoint.ip6p)->getCtx().get() == context.ip6, "IPv6 exact match lookup w/ port");
 
   box.check(lookup.insert(endpoint.ip6p, ip6p_cc) >= 0, "insert IPv6 address w/ port");
-  box.check(lookup.find(endpoint.ip6)->ctx == context.ip6, "IPv6 longest match lookup");
-  box.check(lookup.find(endpoint.ip6p)->ctx == context.ip6p, "IPv6 longest match lookup w/ port");
+  box.check(lookup.find(endpoint.ip6)->getCtx().get() == context.ip6, "IPv6 longest match lookup");
+  box.check(lookup.find(endpoint.ip6p)->getCtx().get() == context.ip6p, "IPv6 longest match lookup w/ port");
 
   box.check(lookup.insert(endpoint.ip4, ip4_cc) >= 0, "insert IPv4 address");
-  box.check(lookup.find(endpoint.ip4)->ctx == context.ip4, "IPv4 exact match lookup");
-  box.check(lookup.find(endpoint.ip4p)->ctx == context.ip4, "IPv4 exact match lookup w/ port");
+  box.check(lookup.find(endpoint.ip4)->getCtx().get() == context.ip4, "IPv4 exact match lookup");
+  box.check(lookup.find(endpoint.ip4p)->getCtx().get() == context.ip4, "IPv4 exact match lookup w/ port");
 
   box.check(lookup.insert(endpoint.ip4p, ip4p_cc) >= 0, "insert IPv4 address w/ port");
-  box.check(lookup.find(endpoint.ip4)->ctx == context.ip4, "IPv4 longest match lookup");
-  box.check(lookup.find(endpoint.ip4p)->ctx == context.ip4p, "IPv4 longest match lookup w/ port");
+  box.check(lookup.find(endpoint.ip4)->getCtx().get() == context.ip4, "IPv4 longest match lookup");
+  box.check(lookup.find(endpoint.ip4p)->getCtx().get() == context.ip4p, "IPv4 longest match lookup w/ port");
 }
 
 static unsigned
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index c5f1c1e..7597291 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -49,6 +49,9 @@
 #include "P_Cache.h"
 #include "records/I_RecCore.h"
 #include "P_SSLConfig.h"
+#include "P_SSLClientUtils.h"
+#include "SSLDiags.h"
+#include "SSLInternal.h"
 #include "ProxyConfig.h"
 #include "Plugin.h"
 #include "LogObject.h"
@@ -8980,8 +8983,11 @@ TSSslContextFindByName(const char *name)
   SSLCertLookup *lookup = SSLCertificateConfig::acquire();
   if (lookup != nullptr) {
     SSLCertContext *cc = lookup->find(name);
-    if (cc && cc->ctx) {
-      ret = reinterpret_cast<TSSslContext>(cc->ctx);
+    if (cc) {
+      shared_SSL_CTX ctx = cc->getCtx();
+      if (ctx) {
+        ret = reinterpret_cast<TSSslContext>(ctx.get());
+      }
     }
     SSLCertificateConfig::release(lookup);
   }
@@ -8996,8 +9002,11 @@ TSSslContextFindByAddr(struct sockaddr const *addr)
     IpEndpoint ip;
     ip.assign(addr);
     SSLCertContext *cc = lookup->find(ip);
-    if (cc && cc->ctx) {
-      ret = reinterpret_cast<TSSslContext>(cc->ctx);
+    if (cc) {
+      shared_SSL_CTX ctx = cc->getCtx();
+      if (ctx) {
+        ret = reinterpret_cast<TSSslContext>(ctx.get());
+      }
     }
     SSLCertificateConfig::release(lookup);
   }
@@ -9026,7 +9035,7 @@ TSSslClientContextsNamesGet(int n, const char **result, int *actual)
     ink_mutex_acquire(&ctx_map_lock);
     for (auto &ca_pair : ca_map) {
       // Populate mem array with 2 strings each time
-      for (auto &ctx_pair : *ca_pair.second) {
+      for (auto &ctx_pair : ca_pair.second) {
         if (idx + 1 < n) {
           mem[idx++] = ca_pair.first;
           mem[idx++] = ctx_pair.first;
@@ -9049,7 +9058,8 @@ TSSslClientContextsNamesGet(int n, const char **result, int *actual)
 /**
  * This function returns the client context corresponding to the lookup keys provided.
  * User should call TSSslClientContextsGet() first to determine which lookup keys are
- * present before querying for them.
+ * present before querying for them. User will need to release the context returned
+ * from this function.
  * Returns valid TSSslContext on success and nullptr on failure.
  * @param first_key Key string for the top level.
  * @param second_key Key string for the second level.
@@ -9066,9 +9076,10 @@ TSSslClientContextFindByName(const char *ca_paths, const char *ck_paths)
     ink_mutex_acquire(&params->ctxMapLock);
     auto ca_iter = params->top_level_ctx_map.find(ca_paths);
     if (ca_iter != params->top_level_ctx_map.end()) {
-      auto ctx_iter = ca_iter->second->find(ck_paths);
-      if (ctx_iter != ca_iter->second->end()) {
-        retval = reinterpret_cast<TSSslContext>(ctx_iter->second);
+      auto ctx_iter = ca_iter->second.find(ck_paths);
+      if (ctx_iter != ca_iter->second.end()) {
+        SSL_CTX_up_ref(ctx_iter->second.get());
+        retval = reinterpret_cast<TSSslContext>(ctx_iter->second.get());
       }
     }
     ink_mutex_release(&params->ctxMapLock);
@@ -9083,7 +9094,7 @@ TSSslServerContextCreate(TSSslX509 cert, const char *certname, const char *rsp_f
   TSSslContext ret        = nullptr;
   SSLConfigParams *config = SSLConfig::acquire();
   if (config != nullptr) {
-    ret = reinterpret_cast<TSSslContext>(SSLCreateServerContext(config));
+    ret = reinterpret_cast<TSSslContext>(SSLCreateServerContext(config, nullptr));
 #if TS_USE_TLS_OCSP
     if (ret && SSLConfigParams::ssl_ocsp_enabled && cert && certname) {
       if (SSL_CTX_set_tlsext_status_cb(reinterpret_cast<SSL_CTX *>(ret), ssl_callback_ocsp_stapling)) {
@@ -9104,6 +9115,124 @@ TSSslContextDestroy(TSSslContext ctx)
   SSLReleaseContext(reinterpret_cast<SSL_CTX *>(ctx));
 }
 
+TSReturnCode
+TSSslClientCertUpdate(const char *cert_path, const char *key_path)
+{
+  if (nullptr == cert_path) {
+    return TS_ERROR;
+  }
+
+  std::string key;
+  shared_SSL_CTX client_ctx = nullptr;
+  SSLConfigParams *params   = SSLConfig::acquire();
+
+  // Generate second level key for client context lookup
+  ts::bwprint(key, "{}:{}", cert_path, key_path);
+  Debug("ssl.cert_update", "TSSslClientCertUpdate(): Use %.*s as key for lookup", static_cast<int>(key.size()), key.data());
+
+  if (nullptr != params) {
+    // Try to update client contexts maps
+    auto &ca_paths_map = params->top_level_ctx_map;
+    auto &map_lock     = params->ctxMapLock;
+    std::string ca_paths_key;
+    // First try to locate the client context and its CA path (by top level)
+    ink_mutex_acquire(&map_lock);
+    for (auto &ca_paths_pair : ca_paths_map) {
+      auto &ctx_map = ca_paths_pair.second;
+      auto iter     = ctx_map.find(key);
+      if (iter != ctx_map.end() && iter->second != nullptr) {
+        ca_paths_key = ca_paths_pair.first;
+        break;
+      }
+    }
+    ink_mutex_release(&map_lock);
+
+    // Only update on existing
+    if (ca_paths_key.empty()) {
+      return TS_ERROR;
+    }
+
+    // Extract CA related paths
+    size_t sep                 = ca_paths_key.find(':');
+    std::string ca_bundle_file = ca_paths_key.substr(0, sep);
+    std::string ca_bundle_path = ca_paths_key.substr(sep + 1);
+
+    // Build new client context
+    client_ctx =
+      shared_SSL_CTX(SSLCreateClientContext(params, ca_bundle_path.empty() ? nullptr : ca_bundle_path.c_str(),
+                                            ca_bundle_file.empty() ? nullptr : ca_bundle_file.c_str(), cert_path, key_path),
+                     SSL_CTX_free);
+
+    // Successfully generates a client context, update in the map
+    ink_mutex_acquire(&map_lock);
+    auto iter = ca_paths_map.find(ca_paths_key);
+    if (iter != ca_paths_map.end() && iter->second.count(key)) {
+      iter->second[key] = client_ctx;
+    } else {
+      client_ctx = nullptr;
+    }
+    ink_mutex_release(&map_lock);
+  }
+
+  return client_ctx ? TS_SUCCESS : TS_ERROR;
+}
+
+TSReturnCode
+TSSslServerCertUpdate(const char *cert_path, const char *key_path)
+{
+  if (nullptr == cert_path) {
+    return TS_ERROR;
+  }
+
+  if (!key_path || key_path[0] == '\0') {
+    key_path = cert_path;
+  }
+
+  SSLCertContext *cc         = nullptr;
+  shared_SSL_CTX test_ctx    = nullptr;
+  std::shared_ptr<X509> cert = nullptr;
+
+  SSLConfig::scoped_config config;
+  SSLCertificateConfig::scoped_config lookup;
+
+  if (lookup && config) {
+    // Read cert from path to extract lookup key (common name)
+    scoped_BIO bio(BIO_new_file(cert_path, "r"));
+    if (bio) {
+      cert = std::shared_ptr<X509>(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr), X509_free);
+    }
+    if (!bio || !cert) {
+      SSLError("Failed to load certificate/key from %s", cert_path);
+      return TS_ERROR;
+    }
+
+    // Extract common name
+    int pos                       = X509_NAME_get_index_by_NID(X509_get_subject_name(cert.get()), NID_commonName, -1);
+    X509_NAME_ENTRY *common_name  = X509_NAME_get_entry(X509_get_subject_name(cert.get()), pos);
+    ASN1_STRING *common_name_asn1 = X509_NAME_ENTRY_get_data(common_name);
+    char *common_name_str         = reinterpret_cast<char *>(const_cast<unsigned char *>(ASN1_STRING_get0_data(common_name_asn1)));
+    if (ASN1_STRING_length(common_name_asn1) != static_cast<int>(strlen(common_name_str))) {
+      // Embedded NULL char
+      return TS_ERROR;
+    }
+    Debug("ssl.cert_update", "Updating from %s with common name %s", cert_path, common_name_str);
+
+    // Update context to use cert
+    cc = lookup->find(common_name_str);
+    if (cc && cc->getCtx()) {
+      test_ctx = shared_SSL_CTX(SSLCreateServerContext(config, cc->userconfig.get(), cert_path, key_path), SSLReleaseContext);
+      if (!test_ctx) {
+        return TS_ERROR;
+      }
+      // Atomic Swap
+      cc->setCtx(test_ctx);
+      return TS_SUCCESS;
+    }
+  }
+
+  return TS_ERROR;
+}
+
 tsapi void
 TSSslTicketKeyUpdate(char *ticketData, int ticketDataLen)
 {
diff --git a/tests/gold_tests/autest-site/conditions.test.ext b/tests/gold_tests/autest-site/conditions.test.ext
index f2b0587..a8a91c6 100644
--- a/tests/gold_tests/autest-site/conditions.test.ext
+++ b/tests/gold_tests/autest-site/conditions.test.ext
@@ -58,7 +58,22 @@ def HasCurlFeature(self, feature):
         default,
         "Curl needs to support feature: {feature}".format(feature=feature)
     )
+def HasCurlOption(self, option):
+    def default(output):
+        tag = option.lower()
+        for line in output.splitlines():
+            line = line.lower()
+            tokens = line.split()
+            for t in tokens:
+                if t == tag:
+                    return True
+        return False
 
+    return self.CheckOutput(
+        ['curl', '--help'],
+        default,
+        "Curl needs to support option: {option}".format(option=option)
+    )
 
 def HasATSFeature(self, feature):
 
@@ -80,5 +95,6 @@ ExtendCondition(HasOpenSSLVersion)
 ExtendCondition(HasATSFeature)
 ExtendCondition(HasCurlVersion)
 ExtendCondition(HasCurlFeature)
+ExtendCondition(HasCurlOption)
 ExtendCondition(PluginExists)
 
diff --git a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
new file mode 100644
index 0000000..24bd004
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
@@ -0,0 +1,147 @@
+'''
+Test the cert_update plugin.
+'''
+#  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.
+
+Test.Summary = '''
+Test cert_update plugin.
+'''
+
+# Check if curl --resolve and openssl exist
+Test.SkipUnless(
+    Condition.HasProgram("curl","Curl need to be installed on system for this test to work"),
+    Condition.HasCurlOption("--resolve")
+    )
+Test.SkipUnless(
+    Condition.HasProgram("openssl","Openssl need to be installed on system for this test to work")
+    )
+
+# Set up origin server 
+server = Test.MakeOriginServer("server")
+request_header = {
+    "headers": "GET / HTTP/1.1\r\nHost: doesnotmatter\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+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)
+
+# Set up ATS
+ts = Test.MakeATSProcess("ts", command="traffic_manager", select_ports=False)
+
+# Set up ssl files
+ts.addSSLfile("ssl/server1.pem")
+ts.addSSLfile("ssl/server2.pem")
+ts.addSSLfile("ssl/client1.pem")
+ts.addSSLfile("ssl/client2.pem")
+
+# Setup ssl ports
+ts.Variables.ssl_port = 4443
+s_server_port = 12345
+
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'cert_update',
+    'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.http.server_ports': (
+        '{0} {1}:proto=http2;http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port)),
+    'proxy.config.ssl.client.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.client.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.url_remap.pristine_host_hdr': 1
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=server1.pem ssl_key_name=server1.pem'
+)
+
+ts.Disk.remap_config.AddLines([
+    'map https://bar.com http://127.0.0.1:{0}'.format(server.Variables.Port),
+    'map https://foo.com https://127.0.0.1:{0}'.format(s_server_port)
+])
+
+ts.Disk.ssl_server_name_yaml.AddLines([
+    '- fqdn: "*foo.com"',
+    '  client_cert: "client1.pem"',
+])
+
+# Set up plugin
+Test.PreparePlugin(Test.Variables.AtsExampleDir + '/cert_update/cert_update.cc', ts)
+
+ts.Disk.plugin_config.AddLine(
+    'cert_update.so'
+)
+
+# Server-Cert-Pre
+# curl should see that Traffic Server presents bar.com cert from alice
+tr = Test.AddTestRun("Server-Cert-Pre")
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port))
+tr.Processes.Default.Command = (
+    'curl --verbose --insecure --resolve bar.com:{0}:127.0.0.1 https://bar.com:{0}'.format(ts.Variables.ssl_port)
+)
+tr.Processes.Default.Streams.stderr = "gold/server-cert-pre.gold"
+tr.Processes.Default.ReturnCode = 0
+tr.StillRunningAfter = server
+
+# Server-Cert-Update
+tr = Test.AddTestRun("Server-Cert-Update")
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = (
+    '{0}/traffic_ctl plugin msg cert_update.server {1}/server2.pem'.format(ts.Variables.BINDIR, ts.Variables.SSLDir)
+)
+ts.Streams.all = "gold/update.gold"
+ts.StillRunningAfter = server
+
+# Server-Cert-After
+# after use traffic_ctl to update server cert, curl should see bar.com cert from bob
+tr = Test.AddTestRun("Server-Cert-After")
+tr.Processes.Default.Env = ts.Env
+tr.Command = 'curl --verbose --insecure --resolve bar.com:{0}:127.0.0.1 https://bar.com:{0}'.format(ts.Variables.ssl_port)
+tr.Processes.Default.Streams.stderr = "gold/server-cert-after.gold"
+tr.Processes.Default.ReturnCode = 0
+ts.StillRunningAfter = server
+
+# Client-Cert-Pre
+# s_server should see client (Traffic Server) as alice.com
+tr = Test.AddTestRun("Client-Cert-Pre")
+s_server = tr.Processes.Process("s_server", "openssl s_server -www -key {0}/server1.pem -cert {0}/server1.pem -accept 12345 -Verify 1 -msg".format(ts.Variables.SSLDir))
+s_server.Ready = When.PortReady(12345)
+tr.Command = 'curl --verbose --insecure --header "Host: foo.com" https://localhost:{}'.format(ts.Variables.ssl_port)
+tr.Processes.Default.StartBefore(s_server)
+s_server.Streams.all = "gold/client-cert-pre.gold"
+tr.Processes.Default.ReturnCode = 0
+ts.StillRunningAfter = server
+
+# Client-Cert-Update
+tr = Test.AddTestRun("Client-Cert-Update")
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.Command = (
+    'mv {0}/client2.pem {0}/client1.pem && {1}/traffic_ctl plugin msg cert_update.client {0}/client1.pem'.format(ts.Variables.SSLDir, ts.Variables.BINDIR)
+)
+ts.Streams.all = "gold/update.gold"
+ts.StillRunningAfter = server
+
+# Client-Cert-After
+# after use traffic_ctl to update client cert, s_server should see client (Traffic Server) as bob.com
+tr = Test.AddTestRun("Client-Cert-After")
+s_server = tr.Processes.Process("s_server", "openssl s_server -www -key {0}/server1.pem -cert {0}/server1.pem -accept 12345 -Verify 1 -msg".format(ts.Variables.SSLDir))
+s_server.Ready = When.PortReady(12345)
+tr.Processes.Default.Env = ts.Env
+# Move client2.pem to replace client1.pem since cert path matters in client context mapping
+tr.Command = 'curl --verbose --insecure --header "Host: foo.com" https://localhost:{0}'.format(ts.Variables.ssl_port)
+tr.Processes.Default.StartBefore(s_server)
+s_server.Streams.all = "gold/client-cert-after.gold"
+tr.Processes.Default.ReturnCode = 0
+ts.StillRunningAfter = server
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/gold/client-cert-after.gold b/tests/gold_tests/pluginTest/cert_update/gold/client-cert-after.gold
new file mode 100644
index 0000000..fef60f6
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/gold/client-cert-after.gold
@@ -0,0 +1 @@
+``bob.com``
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/gold/client-cert-pre.gold b/tests/gold_tests/pluginTest/cert_update/gold/client-cert-pre.gold
new file mode 100644
index 0000000..6a94425
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/gold/client-cert-pre.gold
@@ -0,0 +1 @@
+``alice.com``
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/gold/server-cert-after.gold b/tests/gold_tests/pluginTest/cert_update/gold/server-cert-after.gold
new file mode 100644
index 0000000..e8ee06a
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/gold/server-cert-after.gold
@@ -0,0 +1 @@
+``bob@bar.com``
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/gold/server-cert-pre.gold b/tests/gold_tests/pluginTest/cert_update/gold/server-cert-pre.gold
new file mode 100644
index 0000000..6f535a8
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/gold/server-cert-pre.gold
@@ -0,0 +1 @@
+``alice@bar.com``
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/gold/update.gold b/tests/gold_tests/pluginTest/cert_update/gold/update.gold
new file mode 100644
index 0000000..4160bb7
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/gold/update.gold
@@ -0,0 +1,3 @@
+``
+``Successfully updated``
+``
\ No newline at end of file
diff --git a/tests/gold_tests/pluginTest/cert_update/ssl/client1.pem b/tests/gold_tests/pluginTest/cert_update/ssl/client1.pem
new file mode 100644
index 0000000..5fda0b3
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/ssl/client1.pem
@@ -0,0 +1,83 @@
+-----BEGIN CERTIFICATE-----
+MIIFXzCCA0egAwIBAgIUQW43tEf3dK2EAUi219sjNioWa8QwDQYJKoZIhvcNAQEL
+BQAwPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUx
+EjAQBgNVBAMMCWFsaWNlLmNvbTAeFw0xOTA1MDMyMTEzNDdaFw0yMDA1MDIyMTEz
+NDdaMD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJJTDEPMA0GA1UECgwGQXBhY2hl
+MRIwEAYDVQQDDAlhbGljZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQDEevfeMGJBziVeir3teXonOevM6E/4sZUxX2jDDgYvsYDkx8ConcufuO1B
+svu9Pg+Odi0/zVjoJbIdF4+KqucUs+unDC2a1IHwHbUEQadlkPBmQq1Uxxc3Rb0c
+RCjHzasn0p6vicq4bRmTtXSekezqTvwahDq0++Ci0fQwlbGewRBiJrrM+hbTOZdP
+MR+SgIWsGRcRRTm8qtwMX/02UzE3cfY0liU6x/jMs76shhjt54hNDQJCRsNenh6m
+rf5qD0i+GbQAjj6WN2LO+xuC0IoY8kbNjQTyKsOCkfhDADibyN70d/NUA3PhgqXD
+4r119vhkLFR3FuVCW4mDgJ3Xo5XdiQVJ7cbrPS3Ds1lPESiJUssNPBSO3h2WM5HI
+J9E9xDvQo4mrVc6phoDVFs3rtjjU9E5IMpCp8KxTQ5VpEk0/sHjijX5Q3OWWlBxX
+bXrQSExj7aDvjL5SliwgobQsAX1zadtm/KBtDJTFB8emYIZfdNxSzM2a37ndZ/yZ
+82NfUGY8FeY9kEMGGgLGU5KsIGgeVyFsbUHp/lGE+biup7NvlxJwV1BrujKjhpSn
+RtLNeJTPwhrDgOKtdxV8zXZ3OW/nRibPciD7qno9HPmJgiknVroGtZ7ZvsNd91MD
+BwyeIWC4+/39OQHHWM9tVyaKndNfKX40ewgL/FiOYkQ6m3i24wIDAQABo1MwUTAd
+BgNVHQ4EFgQUJ44G/cXLEv+XvhBqRAzt4GsQMdkwHwYDVR0jBBgwFoAUJ44G/cXL
+Ev+XvhBqRAzt4GsQMdkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AgEAiTnXOLgdVYKlrcmNQ6H7F6hDiZ6UiXEHyxBP+TVPF6zTIM9Zdcfpo8mfN+uC
+lT9vBxzzKCiVwL6bO71jAHxukS3IIyM7FCYQYE+a3lnQBV+/LLrFhfdEgK87V5j6
+T74y++0+ssHGa96jlw4y45Rx9lvYLxpTjSEe2kYiQMhZXLKiGOw2JgRcPOtImUeQ
+NHgss5d+CxGiq7qnl0OJLmInQqh3zHE3/sRu/+jJigZcn+UJHeszoJfZiKQEbgyv
+w8VtwkAEBCoiTEiRDhhoovfFUWAcoaD7HkkPt8+yW9hvIgbtmMEC0tvAqr8ddPBc
+UJ291FD4+03gEgk2nq3mmo0OWqzOsi8rGoJ6YvVgKLvUpirJhUhFWP/VJTVt6dA6
+C+bBd5/+J712gS0p0fkmUUPXXp5X7gTbVZxnO8WvgAPSx3lDjqF3fjzWot9ZqfOA
+jskwyvTC4AoXhn9ea7ipJKDUy0LN64OHt68fYI2wq6H8Qy8dL3FhRKeCV0jCKlKW
+EaYfNfK0H9EXCCNzBqUz7Y2i9dBlR6RcUR7A5L0DeOF/SVUjK6V0XOiQ32BIpOcD
+82YsuACZyGvZ1xxhy25YxDoymNBXxrO+1yYxW+Ni7mmpdoUg41rzD0uIOOQcIgh1
+Cs7tSiybuRdUCA7qVThgJW+4333WHbaUo51WJD60tM1Tlms=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+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
+NjZeZp1bX/xbzu2x1h8Wjz54bWDMppY=
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/pluginTest/cert_update/ssl/client2.pem b/tests/gold_tests/pluginTest/cert_update/ssl/client2.pem
new file mode 100644
index 0000000..d57294e
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/ssl/client2.pem
@@ -0,0 +1,84 @@
+-----BEGIN CERTIFICATE-----
+MIIFkzCCA3ugAwIBAgIUCTwgIdqokJhy+6RByaaL9Qbcr0IwDQYJKoZIhvcNAQEL
+BQAwWTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUx
+EDAOBgNVBAMMB2JvYi5jb20xGjAYBgkqhkiG9w0BCQEWC2JvYkBib2IuY29tMB4X
+DTE5MDUwMzIxNDExN1oXDTIwMDUwMjIxNDExN1owWTELMAkGA1UEBhMCVVMxCzAJ
+BgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUxEDAOBgNVBAMMB2JvYi5jb20xGjAY
+BgkqhkiG9w0BCQEWC2JvYkBib2IuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAznHm20mKyPXTscvtexos8cYq0ccHKSZFKjgkWjGm7Feahv67bTuD
+Sb4k4pS6inkHDkXMrcibuELBQAbSLS54X29sXwo2kkAAdAjvM2cwJWjNsTHzaRov
+l5tNfQgbQv8q1zW+ZJpnllPx7O1rKc5JBFBH0/HIr8GEFAiYOTGz9GZ6zuK1zdc8
++X9p7UGDkcBZgCYagdPKK3zrH1x3DM2LMcVn2XuZdaB2aT/dOQrXFET3czazA9pz
+0rLvk/11JXPokC/6cWa0dm6Ns5hfQ6SnUmz6wBmsloloIGIGG7WLHw4GN0mfFfid
+WLaA726QuCJTDDb1Ys62aXCqhzLQbRj+zMRDIFBZi9T0jri4tnTeAZbIr64tnlC6
+GhuLJ+8j3Y0r8Y2fKJ20RlPe1gbyU909bgkUEZNdvcciDsRPDQvUERa8JqB7w1D5
+xMOr+5Fh4dYGMHQsulL52KMt/vvE8rhUg4ketaU/Qwnww2TtEFqt1RCg3+H6slAZ
+MiZFmdSlVcFTG6PHUf2xVelGEFf5zDhy5lcKxiEDNwAphSyaMAH/yjRRz19pE56M
+WKaFvaxqdGcSmsxBLBSOVSv1zGZEGvekZTbZT8mxoqyQXR37YJIHWhpl0lUmprG9
+6MoiB4I6+qagEDU6dSMXtE1kRHfBLdG95Vg06iP2kx5MFz0YBAw0v0cCAwEAAaNT
+MFEwHQYDVR0OBBYEFM7VrHywWs66Hzjb4eVFnvyimF66MB8GA1UdIwQYMBaAFM7V
+rHywWs66Hzjb4eVFnvyimF66MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAIDsmn9uPb/cpIpludUc+9zMtvNkUjAu+Pu3N1p4Z3Ll1sWxWn9Wv3Sb
+4+XNKg47G6MGrJBwXkPYtCtclIUQXBiAf0A3vTRP1vafucy4Yr2EFVEFMHUq39fb
+fXuxsgyUbv6M+iFj9y8s2ebPdujCBsf68jH6tEBWgCxeocs+JG1a16r45ZgJFU71
+Anyw3DbBwLugVRUXVYp+W5vPQQFHwNSer2QvoUuESruakMWZopf0kiNrkmgRj5o/
+67KIpctsQTxawGr5db7/O2KNyXHdxVSknKrpqWwuvDZag1QE/5Le0dfT91lYC2v1
+597wqgigTb/cwVmVPMCHj0tywVGRVw9Mvq5eJKzawl5k/wvDgktTiJ5stQEA8Hoz
+DiRF2VOdlZ7LI1UunaVsez4Wk3JmA7ltIfkiet47qWrxXaVx3UD0nGnaQ+M2XJEL
+xlA9X9jqAJMbAcl0Jt+4jQ4ffvm+7RyegLLsVxOw/EI7jyoYb+dtV6EeEM1Hd8Yx
+903Tbi5zLX0UlvVTxYPcOqYSi8nj314d8gseeC85jtixm4dVe9yfNKsa84gMPjFU
+SordVG2iqSQQhWC2TZD2f8cMvZti/U/CqDzYiRPnaSPSkJcOLDGSGK1dPJDsph9c
+Urw5UZOEJK31mIDbq2jQLrmcyj6ytBqoFMSidNutzA8DcENzQUL5
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDOcebbSYrI9dOx
+y+17GizxxirRxwcpJkUqOCRaMabsV5qG/rttO4NJviTilLqKeQcORcytyJu4QsFA
+BtItLnhfb2xfCjaSQAB0CO8zZzAlaM2xMfNpGi+Xm019CBtC/yrXNb5kmmeWU/Hs
+7WspzkkEUEfT8civwYQUCJg5MbP0ZnrO4rXN1zz5f2ntQYORwFmAJhqB08orfOsf
+XHcMzYsxxWfZe5l1oHZpP905CtcURPdzNrMD2nPSsu+T/XUlc+iQL/pxZrR2bo2z
+mF9DpKdSbPrAGayWiWggYgYbtYsfDgY3SZ8V+J1YtoDvbpC4IlMMNvVizrZpcKqH
+MtBtGP7MxEMgUFmL1PSOuLi2dN4Blsivri2eULoaG4sn7yPdjSvxjZ8onbRGU97W
+BvJT3T1uCRQRk129xyIOxE8NC9QRFrwmoHvDUPnEw6v7kWHh1gYwdCy6UvnYoy3+
++8TyuFSDiR61pT9DCfDDZO0QWq3VEKDf4fqyUBkyJkWZ1KVVwVMbo8dR/bFV6UYQ
+V/nMOHLmVwrGIQM3ACmFLJowAf/KNFHPX2kTnoxYpoW9rGp0ZxKazEEsFI5VK/XM
+ZkQa96RlNtlPybGirJBdHftgkgdaGmXSVSamsb3oyiIHgjr6pqAQNTp1Ixe0TWRE
+d8Et0b3lWDTqI/aTHkwXPRgEDDS/RwIDAQABAoICAA3ZzIBX7czP2XUrVnKU7gEG
+p6bNcKiNHcTYYW//ttBSjFaUTqTkgkl3TWg6TE2wEw4dFr9uHyx8phSSoJVRcdgN
+VLsHp2OmkaE3XD0ZpjxMTMifrlIV5K2KSOejnJihIBGyVGTRizOlzCx2PWNfqLx2
+WmtY5HsOQ7tIXFYyBH3YRlMNGN+Hmlqu9r9pTtd/jUGwzMR7HixOxEOlY4NjYvxi
+3zXTOhePPYKQO1pxZL7CZcvAFsCibnTrdnF6ZtqtbSQHZAkpt9/eSPmMto+GQ4ef
+Mi+jSVYMQAU0Lm67fmsF+NrKwLarqHrkO8BUrbb87o96PHp1nf1zBq3tddhr/eRG
+R0+e72Dec02XMsof6d7AmBRsEJwvz7Nt7MGwszVxw3O1nzeAlpCuq/7eJrNPCfT/
+Lt16yizf5f36KGVQsNaC6saVWKUSf3I7OwZJrZpN5mbkyszWGuYEZL1fjUVYBYdM
+gXOmCmHpFUeQRENJ40PuJHFgYEhJ5ZT1Mp+p3YBe4aGtix5GntP3uzpp4AUKJ3oB
+f4C41a6vOieag1ivKBjusVv/j/obfn41x/BS258X4uzPCm833c6iY+jofIESyclp
+kqNoAoFTGmT7sIqbQRN7Jo1H6ZbuOC1Cp1m3GmJWfcjZwif1AXr+JnzdgmrS/Rl9
+mbBX7cFA3hyoQ4GpU62pAoIBAQDrcx30eBzeEsdYbSV4F6vu11yfoVbMTVMY2fA9
+Vp5vtDG41N4epw7nbSkSuQ0ZRpMCu6aio3vEWIZfk35Xm4oP7BHZ0fglnH8NJouz
+dO1QGK6t3wX6g0rB9mP4IscWIU7B7S6WNbEW32X3B5vbJl3BpMiURwdMK+O71zYB
+Ip1uQeCeJrI453BzpwLCcP4So4Ib3JkdFw94BI+pHhH0npNu+5yN8S9LbsLpKBUJ
+OuwarVBsaekxuNQCd8vqNQNVBI/wjRK/9kguOTqW0SiWdNmf1LyRqVIL7YqRAVKN
+xOIAjkH5BfYeEfWIQtEdV3bZu/HWbN2Cj39XI3BqgkxOCVKVAoIBAQDgdrQJYXlW
+eL6sBOzDBPEyZzjZRf3IyJQljl2DDkWFZjJ3Ds98T7CRm64cIW/iksVMzSvoEpl0
+j1GKQ4kNLKNz9iD+1W92Us7m1IejekKCviBqvSl+JdoiCCsnWA9+3fOCgbCKwZx1
+4Wk3aTx69KRZJtqH4o5NPENd0k5fSnbceE7sbex2Lv4ryPbQQTKgvnCTWzn6pEdi
+XjNdPHhELcyWRtiUVlE6zV92GBPzl8Bz3mi1tBSVG1dY+CIdE149rSd2YsV4tQvN
+A5T8kTH/bvGl8Jn1KPoQ724vizsSJgQakE0ReFc+scoz9NodQ49pqRPHdk9CFprB
+5chlAJ3m9o9rAoIBAEIqUoOt8WbS3iRSX9I0zMNM0CGn5E17eVuleyaxncqEV+i6
+IUV56u1MNtulFzJAK/X7p+NSj+hofDKFr16NPiolTArrP5HKPcYDTAT9WedFWGlS
+IEr69Fo3lHZZx5rHd2t17L6XjhGAbBYUlE7spDJTzW4l274jI1dZLjr5cEZYyveG
+plTpbSeDCnp76FpyipCr2HddUKKInZqH8cHNgl8Q5DjbS1Amay28btTuMwV4KP6e
+cMLhTur2oV5K0Ynlw1F1Q4ygeD5NJNLXKlHFupZ44RkJ/R2O/n6rYXinmF9RmuaR
+L03Z/Cbzp/JX7vVXJKn+Y+1ZyA5DzkaQIUNYyVUCggEAQ4tUx9HeGmhBMDBXMqQl
+FH69O5x1Lts0wUxi1VIRF4BWRT9erlComFhZfzuMmIiD+IVw5efa55lM9yc1cZJy
+KS3yZdzCKr/mZM2ld0sOApvF03jSqJQpXL5Khg9YsluFsEroXgi+1TYcXEE9ot5F
+KlKnxeYl3hX5S51CWihlNhi53ymA01t2vqQ9qRNFcdt8ssrr2oFevboNCMxugE2r
+17i/6XtD/EbaqAW80zth/Tv7FFp5KxlMIoigc1FltXeKfXRhad5JC8s9JPdoLS4s
+ZzvMiFppTXlPFd12zBJGf9vWZSBqWIJVj2bpz46J9EidnBL87K0yqpBDyijyWxLs
+uwKCAQB5mpi+DSCvsVRu5vpR+curP3rJD4drIne3I3WKOKdDhGfP/eN6Zxh/F+bG
+0oFPoW7bwbjmFpMXWSqt96SZY1tvfuAbFN6VtTzLhjiC06gD88KRyM8UPAjCuzl5
+hYST/Qgd7zLSjReIqY0/TCR0/tb+GXQKGSBtfX6+avWUqE4n/alq5p4hY5RPTIoz
+SAnV0Jpz2IxjwYMsM9GeuD1V/7Ad9wliZ7Xh0HpBykOlZRxAC7WMKKqQJKCquGiw
+U7P1zvWqTQKmxGEe6aMTL0aOv3Vqr4wda8sQGqts0Hgs6/2LB9c210MB86FE+NTm
+7fEmsFnjM6wdpL55qregGgcYdPuz
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/pluginTest/cert_update/ssl/server1.pem b/tests/gold_tests/pluginTest/cert_update/ssl/server1.pem
new file mode 100644
index 0000000..528df54
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/ssl/server1.pem
@@ -0,0 +1,84 @@
+-----BEGIN CERTIFICATE-----
+MIIFlzCCA3+gAwIBAgIUdIFP75TySC7M5O7eQsGXeUtIceYwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUx
+EDAOBgNVBAMMB2Jhci5jb20xHDAaBgkqhkiG9w0BCQEWDWFsaWNlQGJhci5jb20w
+HhcNMTkwNTAzMjE0ODU0WhcNMjAwNTAyMjE0ODU0WjBbMQswCQYDVQQGEwJVUzEL
+MAkGA1UECAwCSUwxDzANBgNVBAoMBkFwYWNoZTEQMA4GA1UEAwwHYmFyLmNvbTEc
+MBoGCSqGSIb3DQEJARYNYWxpY2VAYmFyLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMyCp3PLf8wP3cXhvrRuuKlRod6R4n3gkoPtwfvlsf+TXwFk
+mOVCkgBiXEQMOaDXHnCJdYy/I+PEjR11ipigwU13b9QnWW8DzSwaGRO9lXlA2E6J
+cgUe2Wh0TD4iwpUnSl3XFObeqU5+Rp86qt1zDhw0Yb6KS3EpUXf7HZEHAmfjT5xR
+Qsttk7J9F5ldi1gdxoLEh7LuLG+yGmjvdNWXEoLAloTOJdyjqmVigyzDRWXMZCsN
+ykbMB5Esa4RrhCxtm1raLMDADn3qKwdlPVh2ZPlwXo/5i2KenMMBDCjYZbYkVjYT
+vKQvFkFPzr1DNbaCwRuOAkPjsnKn+nz5kKROFgszIgKyw3GKWpteG2IIP09s+yHC
+LIe45vkV/VyJh1/EIn8YV+ahObCTRLoY1c+PQKH5FwXe0329ooT8WDr8RwM6szXB
+0FByNoLGTm+A4JA55H57g+ygbn65bYQltWPGiE+oXI0fd3DpSoxm3yjYYhD1/7Sz
+Cm389gaHoTS6pm/NOXXwk+bN1Qw1DjCOSrSCjiO2rAABMvqZakd32p9FhQKr7ujd
+ke1GZpRAEY+XL9RUby2Z/BhMC0oZlSGwSwKOpyGdpFBJFfE306qWsiK2ktzaQorP
+R265ozBbLu+tKln62T9GnuSGt2rW+tgZ/tiSEP+WP4uZhA15JudKTL/t6/eFAgMB
+AAGjUzBRMB0GA1UdDgQWBBSBfLDCTWPHivDuRZwKnvuddfAwEDAfBgNVHSMEGDAW
+gBSBfLDCTWPHivDuRZwKnvuddfAwEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4ICAQAh+3OX/mxs0oyZhWge3IDGNoRyjU7ymYeOAnO33JKMmccp7umQ
+zGrB4MrvCVnnazz3MMeEOVTei5Eg7jWtn/B2+1X0shqwq968ZE/T4I1b5Vc8xEKF
+b6nRfuHlVSE0f8RQjzqMiHOelkLPcH3PPPh5shQqwSpAR4zaGt3E6f/QrlU9FPet
+jORYKgIUGkQNjBZNpcm7OVlr56rvP1e9MZzrEZkPXiqwR9V3ocDu4V6T9uXsBM+8
+5ybREA1lKrDbOi13XU2+Lr4VlJg4CpEfomhhg2knpO6Iko31PT4QnIH0L7WrX+Hv
+ww4ga98ansylCTFqT8Gy8ZvK5GStkh6+MkGBtGk4/5QTyc7mZcrhZmiNnaBG5BJB
+SRrTWmARgUc+S1M7FODWYdH/abaHkRJJasS0wdOQXnufuuswV5DVaiT1eLpxKXYA
+8N5QiLGVz5BYkWlccRu8o70I3kXzeNcaTjHXpFhtZpFAatDcD3A/7BHicgZxpXH4
+1cFX+1FCyLZEhBwkAFZ+Wy938HKaRlZKT89WGHtcJo32QNDgS8kiIWjylFMBaSR6
+MLimU0iSJKrUr8taWF+zxnq7NLDY7Nm2nFSVrlvzw8faF+cHUvxm4x8BSdnXD57c
+5FNWfVi8yPZD13knRz0MH/vsoD1o1nHEKPFyw/gW+uKA5jMqng5oK+GOfA==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+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
+JoiOSTTYKkbWxl6ZNUfJPeeFcPsy
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/pluginTest/cert_update/ssl/server2.pem b/tests/gold_tests/pluginTest/cert_update/ssl/server2.pem
new file mode 100644
index 0000000..7f47929
--- /dev/null
+++ b/tests/gold_tests/pluginTest/cert_update/ssl/server2.pem
@@ -0,0 +1,84 @@
+-----BEGIN CERTIFICATE-----
+MIIFkzCCA3ugAwIBAgIUKvuCPWhK1Tgjycp6EyPW2Z04HSUwDQYJKoZIhvcNAQEL
+BQAwWTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUx
+EDAOBgNVBAMMB2Jhci5jb20xGjAYBgkqhkiG9w0BCQEWC2JvYkBiYXIuY29tMB4X
+DTE5MDUwMzIxNDgxN1oXDTIwMDUwMjIxNDgxN1owWTELMAkGA1UEBhMCVVMxCzAJ
+BgNVBAgMAklMMQ8wDQYDVQQKDAZBcGFjaGUxEDAOBgNVBAMMB2Jhci5jb20xGjAY
+BgkqhkiG9w0BCQEWC2JvYkBiYXIuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEArCvDw1lqneNGqUhsjDE2flec0PC6bzxqHcs8lgsZo2qj3NBVmCmh
+PEfnYTi5pccvPejDf7IcNajPnT6kTdmi3KQyuXeSVvidr+hk0BH1D5tX1Fojbhou
+i2InGudDk+tuxmd8I8eWANLoIh+/8y14b+JPe19E/vveaxlZMPw/nhTOg1Esd4bJ
+N5ajKUT4bR7ROEkyHHqM0HPTKdk1EF5wNmlHzGpMvkO8XXDj3DWM2S9LFTWDA6VI
+JJAMUehtY4c90PWOuzrICwZgRZkQRStg7NfUr8160lXLLOgvoLYnRCHh8DO7oDyC
+KaUKRkVYlUwxW0B20byAgwfEbRXm8CXSNM7ydrQjWxGe5Lxzikabp6lJqDyGTGHm
+EO7UWP1AevQz/9HQtj4zXcEBNB3XCOAocaI4+xDj4/6aK4wqqDpK0fjV63LKUqni
+ymy5uibj6u9FcqewDXOvaXylDSadAz5hRUbuk3ZYJPGQkhR6kE0OoopU+uSynlGM
+7EaeB4EppocFOgvOnBCOYfwjYZp8flxFKalGhMMkXGRpGcHdTo308WBRiZeHQJky
+Il8cudUWfFlaiCP0Twg0f8BjpYtfE5DidMpF9YWo4U6u1ffH+EaZ1YVbZ7QN8QH5
+DZ9krRcI7WgsBNZ9kwmFUcwy4vq8xAp6leHDG2FfNiAovI9y6GepnI8CAwEAAaNT
+MFEwHQYDVR0OBBYEFMNOR5+SdHCtxJVW6waYADtnKt0nMB8GA1UdIwQYMBaAFMNO
+R5+SdHCtxJVW6waYADtnKt0nMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAKuykeniFfjTxWjN03M6pIBpvO868XYhY1Q4ng1vmEJTTUa7gz0Rh6nO
+h6QAFFmnMTtXBuP79a4UznHb2o+/BZKMnTDRmOv0ArgUJrYYopjTmorflWFoS/yB
+n26Fhvrc9HW3469AYZ2oc0lbpMR7eshnnVhzYWTS/sLdDqk1Kys8Byf+aeZv1Zgw
+sovoXb89TK/vMoVgbkU/nGNf3GyhFHPsB44z6Wod2gDNdWT7HMABFAzmnAOKb2oI
+bhwA250AxgPWXPtWARoH6xFj7VKz1laRcADQAdQjKVbPsCPhp3KnMO+fLUs1vuX3
+7QLBhDGp3BQ2Ni+EKrcqcAfpZOJv2zgqYTjdCvdIq5QPd/tXJmAg886hnlfGlPuW
+bpTSDbMzdT/LEoTyj5vUVp8v7bNHcLhyP5WKYc5bPdGMBc5pqo5YKGis1qBCFsld
+vtl2xCRYqNfgz8fJFCbQYgdkHdIGpxtQ7X9DmM69vNUSOCMz2iYrk4L2ejUSSM7b
+2qwV1vghnmoM4cx7j9T4B29k+JGT1y8xd5vd7sQCVKr2Mnj4uUzkeEjjnLnNrwdv
+tj4/qV+Rc6anseARxYZvUwg2gaQfyI+qvgf8YX6ulW7KL8e9jkLEy/DlKlxnDsrx
+IZrFE973EHcm/g7qZEA650OTHQbXnJYq75fYXDf97KsmDq/g6h9h
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+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
+qxix3I9z/SzxQmb10gFNvEzR/fQDDiA=
+-----END PRIVATE KEY-----
diff --git a/tests/tools/plugins/cert_update.cc b/tests/tools/plugins/cert_update.cc
new file mode 100644
index 0000000..1bb18eb
--- /dev/null
+++ b/tests/tools/plugins/cert_update.cc
@@ -0,0 +1,86 @@
+/** @file
+
+  an example cert update plugin
+
+  @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 <stdio.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#include "ts/ts.h"
+#include "tscpp/util/TextView.h"
+
+#define PLUGIN_NAME "cert_update"
+
+// Plugin Message Continuation
+int
+CB_cert_update(TSCont, TSEvent, void *edata)
+{
+  TSPluginMsg *msg = static_cast<TSPluginMsg *>(edata);
+  static constexpr std::string_view PLUGIN_PREFIX("cert_update."_sv);
+
+  std::string_view tag(msg->tag, strlen(msg->tag));
+  const char *server_cert_path = nullptr;
+  const char *client_cert_path = nullptr;
+  if (tag.substr(0, PLUGIN_PREFIX.size()) == PLUGIN_PREFIX) {
+    tag.remove_prefix(PLUGIN_PREFIX.size());
+    if (tag == "server") {
+      server_cert_path = static_cast<const char *>(msg->data);
+      TSDebug(PLUGIN_NAME, "Received Msg to update server cert with %s", server_cert_path);
+    } else if (tag == "client") {
+      client_cert_path = static_cast<const char *>(msg->data);
+      TSDebug(PLUGIN_NAME, "Received Msg to update client cert with %s", client_cert_path);
+    }
+  }
+
+  if (server_cert_path) {
+    if (TS_SUCCESS == TSSslServerCertUpdate(server_cert_path, nullptr)) {
+      TSDebug(PLUGIN_NAME, "Successfully updated server cert with %s", server_cert_path);
+    } else {
+      TSDebug(PLUGIN_NAME, "Failed to update server cert with %s", server_cert_path);
+    }
+  }
+  if (client_cert_path) {
+    if (TS_SUCCESS == TSSslClientCertUpdate(client_cert_path, nullptr)) {
+      TSDebug(PLUGIN_NAME, "Successfully updated client cert with %s", client_cert_path);
+    } else {
+      TSDebug(PLUGIN_NAME, "Failed to update client cert with %s", client_cert_path);
+    }
+  }
+  return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PLUGIN_NAME;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "dev@trafficserver.apache.org";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[%s] Plugin registration failed", PLUGIN_NAME);
+  }
+  TSDebug(PLUGIN_NAME, "Initialized.");
+  TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, TSContCreate(CB_cert_update, nullptr));
+}
\ No newline at end of file


Mime
View raw message