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

[trafficserver] branch master updated: Allow for override of ssl.client files

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 fb513ab  Allow for override of ssl.client files
fb513ab is described below

commit fb513abe68dfe95aaf3d972d7b4d20f40ef27382
Author: Susan Hinrichs <sh...@oath.com>
AuthorDate: Thu Dec 6 23:18:47 2018 +0000

    Allow for override of ssl.client files
---
 doc/admin-guide/files/records.config.en.rst        |   5 +
 .../api/functions/TSHttpOverridableConfig.en.rst   |   7 +-
 include/ts/apidefs.h.in                            |   3 +
 iocore/net/I_NetVConnection.h                      |  31 ++---
 iocore/net/P_SSLConfig.h                           |   8 +-
 iocore/net/P_SSLSNI.h                              |   2 +-
 iocore/net/P_UnixNetVConnection.h                  |   8 +-
 iocore/net/SSLClientUtils.cc                       |  37 ------
 iocore/net/SSLConfig.cc                            | 132 +++++++++++++------
 iocore/net/SSLNetVConnection.cc                    |  20 ++-
 iocore/net/SSLSNIConfig.cc                         |   5 +-
 iocore/net/SSLUtils.cc                             |   3 +-
 mgmt/RecordsConfig.cc                              |   4 +-
 plugins/lua/ts_lua_http_config.c                   |   4 +
 proxy/http/HttpConfig.h                            |  15 ++-
 proxy/http/HttpSM.cc                               |   3 +
 src/traffic_server/InkAPI.cc                       |  45 ++++---
 src/traffic_server/InkAPITest.cc                   |   4 +-
 .../tls/tls_client_cert_override.test.py           | 145 +++++++++++++++++++++
 .../gold_tests/tls/tls_verify_ca_override.test.py  | 137 +++++++++++++++++++
 20 files changed, 489 insertions(+), 129 deletions(-)

diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index 34d9e3a..e244384 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3508,6 +3508,7 @@ Client-Related Configuration
 
 .. ts:cv:: CONFIG proxy.config.ssl.client.cert.filename STRING NULL
    :reloadable:
+   :overridable:
 
    The filename of SSL client certificate installed on |TS|.
 
@@ -3519,6 +3520,7 @@ Client-Related Configuration
 
 .. ts:cv:: CONFIG proxy.config.ssl.client.private_key.filename STRING NULL
    :reloadable:
+   :overridable:
 
    The filename of the |TS| private key. Change this variable
    only if the private key is not located in the |TS| SSL
@@ -3532,11 +3534,14 @@ Client-Related Configuration
    file.
 
 .. ts:cv:: CONFIG proxy.config.ssl.client.CA.cert.filename STRING NULL
+   :reloadable:
+   :overridable:
 
    The filename of the certificate authority against which the origin
    server will be verified.
 
 .. ts:cv:: CONFIG proxy.config.ssl.client.CA.cert.path STRING NULL
+   :reloadable:
 
    Specifies the location of the certificate authority file against
    which the origin server will be verified.
diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
index 91eaaff..a38fd56 100644
--- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
@@ -152,7 +152,7 @@ TSOverridableConfigKey Value                                        Configuratio
 :c:macro:`TS_CONFIG_HTTP_POST_CHECK_CONTENT_LENGTH_ENABLED`         :ts:cv:`proxy.config.http.post.check.content_length.enabled`
 :c:macro:`TS_CONFIG_HTTP_POST_CONNECT_ATTEMPTS_TIMEOUT`             :ts:cv:`proxy.config.http.post_connect_attempts_timeout`
 :c:macro:`TS_CONFIG_HTTP_REDIRECT_USE_ORIG_CACHE_KEY`               :ts:cv:`proxy.config.http.redirect_use_orig_cache_key`
-TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED                               proxy.config.http.request_buffer_enabled
+:c:macro:`TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED`                    :ts:cv:`proxy.config.http.request_buffer_enabled`
 :c:macro:`TS_CONFIG_HTTP_REQUEST_HEADER_MAX_SIZE`                   :ts:cv:`proxy.config.http.request_header_max_size`
 :c:macro:`TS_CONFIG_HTTP_RESPONSE_HEADER_MAX_SIZE`                  :ts:cv:`proxy.config.http.response_header_max_size`
 :c:macro:`TS_CONFIG_HTTP_RESPONSE_SERVER_ENABLED`                   :ts:cv:`proxy.config.http.response_server_enabled`
@@ -184,6 +184,11 @@ TS_CONFIG_HTTP_REQUEST_BUFFER_ENABLED                               proxy.config
 :c:macro:`TS_CONFIG_URL_REMAP_PRISTINE_HOST_HDR`                    :ts:cv:`proxy.config.url_remap.pristine_host_hdr`
 :c:macro:`TS_CONFIG_WEBSOCKET_ACTIVE_TIMEOUT`                       :ts:cv:`proxy.config.websocket.active_timeout`
 :c:macro:`TS_CONFIG_WEBSOCKET_NO_ACTIVITY_TIMEOUT`                  :ts:cv:`proxy.config.websocket.no_activity_timeout`
+:c:macro:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY`                :ts:cv:`proxy.config.ssl.client.verify.server.policy`
+:c:macro:`TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES`            :ts:cv:`proxy.config.ssl.client.verify.server.properties`
+:c:macro:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME`                       :ts:cv:`proxy.config.ssl.client.cert.filename`
+:c:macro:`TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME`                :ts:cv:`proxy.config.ssl.client.private_key.filename`
+:c:macro:`TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME`                    :ts:cv:`proxy.config.ssl.client.CA.cert.filename`
 ==================================================================  ====================================================================
 
 Examples
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index bb3851f..5e7bf7c 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -789,6 +789,7 @@ typedef enum {
   TS_CONFIG_SRV_ENABLED,
   TS_CONFIG_HTTP_FORWARD_CONNECT_METHOD,
   TS_CONFIG_SSL_CERT_FILENAME,
+  TS_CONFIG_SSL_CLIENT_CERT_FILENAME = TS_CONFIG_SSL_CERT_FILENAME,
   TS_CONFIG_SSL_CERT_FILEPATH,
   TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB,
   TS_CONFIG_HTTP_CACHE_ENABLE_DEFAULT_VARY_HEADER,
@@ -814,6 +815,8 @@ typedef enum {
   TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY,
   TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES,
   TS_CONFIG_SSL_CLIENT_SNI_POLICY,
+  TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME,
+  TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME,
   TS_CONFIG_LAST_ENTRY
 } TSOverridableConfigKey;
 
diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h
index e9b0427..1a8d601 100644
--- a/iocore/net/I_NetVConnection.h
+++ b/iocore/net/I_NetVConnection.h
@@ -186,7 +186,20 @@ struct NetVCOptions {
   /**
    * Client certificate to use in response to OS's certificate request
    */
-  ats_scoped_str clientCertificate;
+  const char *ssl_client_cert_name = nullptr;
+  /*
+   * File containing private key matching certificate
+   */
+  const char *ssl_client_private_key_name = nullptr;
+  /*
+   * File containing CA certs for verifying origin's cert
+   */
+  const char *ssl_client_ca_cert_name = nullptr;
+  /*
+   * Directory containing CA certs for verifying origin's cert
+   */
+  const char *ssl_client_ca_cert_path = nullptr;
+
   /// Reset all values to defaults.
 
   /**
@@ -233,13 +246,6 @@ struct NetVCOptions {
     }
     return *this;
   }
-  self &
-  set_client_certname(const char *name)
-  {
-    clientCertificate = ats_strdup(name);
-    // clientCertificate = name;
-    return *this;
-  }
 
   self &
   operator=(self const &that)
@@ -255,9 +261,8 @@ struct NetVCOptions {
        * memcpy removes the extra reference to that's copy of the string
        * Removing the release will eventualy cause a double free crash
        */
-      sni_servername    = nullptr; // release any current name.
-      ssl_servername    = nullptr;
-      clientCertificate = nullptr;
+      sni_servername = nullptr; // release any current name.
+      ssl_servername = nullptr;
       memcpy(static_cast<void *>(this), &that, sizeof(self));
       if (that.sni_servername) {
         sni_servername.release(); // otherwise we'll free the source string.
@@ -267,10 +272,6 @@ struct NetVCOptions {
         ssl_servername.release(); // otherwise we'll free the source string.
         this->ssl_servername = ats_strdup(that.ssl_servername);
       }
-      if (that.clientCertificate) {
-        clientCertificate.release(); // otherwise we'll free the source string.
-        this->clientCertificate = ats_strdup(that.clientCertificate);
-      }
     }
     return *this;
   }
diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h
index 3bccf9f..5c93abb 100644
--- a/iocore/net/P_SSLConfig.h
+++ b/iocore/net/P_SSLConfig.h
@@ -80,7 +80,9 @@ struct SSLConfigParams : public ConfigInfo {
   int ssl_session_cache_auto_clear;
 
   char *clientCertPath;
+  char *clientCertPathOnly;
   char *clientKeyPath;
+  char *clientKeyPathOnly;
   char *clientCACertFilename;
   char *clientCACertPath;
   YamlSNIConfig::Policy verifyServerPolicy;
@@ -115,11 +117,15 @@ struct SSLConfigParams : public ConfigInfo {
 
   SSL_CTX *client_ctx;
 
+  // Making this mutable since this is a updatable
+  // cache on an otherwise immutable config object
+  // The ctx_map owns the client SSL_CTX objects and is responseible for cleaning them up
   mutable std::unordered_map<std::string, SSL_CTX *> ctx_map;
   mutable ink_mutex ctxMapLock;
 
   SSL_CTX *getClientSSL_CTX(void) const;
-  SSL_CTX *getNewCTX(const char *client_cert, const char *key_file) const;
+  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();
   void cleanup();
diff --git a/iocore/net/P_SSLSNI.h b/iocore/net/P_SSLSNI.h
index a1d7504..220de51 100644
--- a/iocore/net/P_SSLSNI.h
+++ b/iocore/net/P_SSLSNI.h
@@ -46,7 +46,7 @@ struct NextHopProperty {
   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
-  NextHopProperty();
+  NextHopProperty() {}
 };
 
 using actionVector = std::vector<std::unique_ptr<ActionItem>>;
diff --git a/iocore/net/P_UnixNetVConnection.h b/iocore/net/P_UnixNetVConnection.h
index 1516ddd..0a32031 100644
--- a/iocore/net/P_UnixNetVConnection.h
+++ b/iocore/net/P_UnixNetVConnection.h
@@ -66,9 +66,11 @@ NetVCOptions::reset()
 
   etype = ET_NET;
 
-  sni_servername    = nullptr;
-  ssl_servername    = nullptr;
-  clientCertificate = nullptr;
+  sni_servername              = nullptr;
+  ssl_servername              = nullptr;
+  ssl_client_cert_name        = nullptr;
+  ssl_client_private_key_name = nullptr;
+  ssl_client_ca_cert_name     = nullptr;
 }
 
 TS_INLINE void
diff --git a/iocore/net/SSLClientUtils.cc b/iocore/net/SSLClientUtils.cc
index 7f0ceb2..89842f5 100644
--- a/iocore/net/SSLClientUtils.cc
+++ b/iocore/net/SSLClientUtils.cc
@@ -143,7 +143,6 @@ SSLInitClientContext(const SSLConfigParams *params)
 {
   ink_ssl_method_t meth = nullptr;
   SSL_CTX *client_ctx   = nullptr;
-  char *clientKeyPtr    = nullptr;
 
   // Note that we do not call RAND_seed() explicitly here, we depend on OpenSSL
   // to do the seeding of the PRNG for us. This is the case for all platforms that
@@ -183,44 +182,8 @@ SSLInitClientContext(const SSLConfigParams *params)
   }
 #endif
 
-  // if no path is given for the client private key,
-  // assume it is contained in the client certificate file.
-  clientKeyPtr = params->clientKeyPath;
-  if (clientKeyPtr == nullptr) {
-    clientKeyPtr = params->clientCertPath;
-  }
-
-  if (params->clientCertPath != nullptr && params->clientCertPath[0] != '\0') {
-    if (!SSL_CTX_use_certificate_chain_file(client_ctx, params->clientCertPath)) {
-      SSLError("failed to load client certificate from %s", params->clientCertPath);
-      goto fail;
-    }
-
-    if (!SSL_CTX_use_PrivateKey_file(client_ctx, clientKeyPtr, SSL_FILETYPE_PEM)) {
-      SSLError("failed to load client private key file from %s", clientKeyPtr);
-      goto fail;
-    }
-
-    if (!SSL_CTX_check_private_key(client_ctx)) {
-      SSLError("client private key (%s) does not match the certificate public key (%s)", clientKeyPtr, params->clientCertPath);
-      goto fail;
-    }
-  }
-
   SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback);
   SSL_CTX_set_verify_depth(client_ctx, params->client_verify_depth);
-
-  if (params->clientCACertFilename != nullptr || params->clientCACertPath != nullptr) {
-    if (!SSL_CTX_load_verify_locations(client_ctx, params->clientCACertFilename, params->clientCACertPath)) {
-      SSLError("invalid client CA Certificate file (%s) or CA Certificate path (%s)", params->clientCACertFilename,
-               params->clientCACertPath);
-      goto fail;
-    }
-  } else if (!SSL_CTX_set_default_verify_paths(client_ctx)) {
-    SSLError("failed to set the default verify paths");
-    goto fail;
-  }
-
   if (SSLConfigParams::init_ssl_ctx_cb) {
     SSLConfigParams::init_ssl_ctx_cb(client_ctx, false);
   }
diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc
index a6dbed7..2cdeca0 100644
--- a/iocore/net/SSLConfig.cc
+++ b/iocore/net/SSLConfig.cc
@@ -89,12 +89,12 @@ SSLConfigParams::reset()
 {
   serverCertPathOnly = serverCertChainFilename = configFilePath = serverCACertFilename = serverCACertPath = clientCertPath =
     clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly =
-      nullptr;
-  server_tls13_cipher_suites = nullptr;
-  client_tls13_cipher_suites = nullptr;
-  server_groups_list         = nullptr;
-  client_groups_list         = nullptr;
-  client_ctx                 = nullptr;
+      clientKeyPathOnly = clientCertPathOnly = nullptr;
+  server_tls13_cipher_suites                 = nullptr;
+  client_tls13_cipher_suites                 = nullptr;
+  server_groups_list                         = nullptr;
+  client_groups_list                         = nullptr;
+  client_ctx                                 = nullptr;
   clientCertLevel = client_verify_depth = verify_depth = 0;
   verifyServerPolicy                                   = YamlSNIConfig::Policy::DISABLED;
   verifyServerProperties                               = YamlSNIConfig::Property::NONE;
@@ -116,7 +116,9 @@ SSLConfigParams::cleanup()
   serverCACertFilename    = (char *)ats_free_null(serverCACertFilename);
   serverCACertPath        = (char *)ats_free_null(serverCACertPath);
   clientCertPath          = (char *)ats_free_null(clientCertPath);
+  clientCertPathOnly      = (char *)ats_free_null(clientCertPathOnly);
   clientKeyPath           = (char *)ats_free_null(clientKeyPath);
+  clientKeyPathOnly       = (char *)ats_free_null(clientKeyPathOnly);
   clientCACertFilename    = (char *)ats_free_null(clientCACertFilename);
   clientCACertPath        = (char *)ats_free_null(clientCACertPath);
   configFilePath          = (char *)ats_free_null(configFilePath);
@@ -131,7 +133,7 @@ SSLConfigParams::cleanup()
   server_groups_list         = (char *)ats_free_null(server_groups_list);
   client_groups_list         = (char *)ats_free_null(client_groups_list);
 
-  SSLReleaseContext(client_ctx);
+  cleanupCTXTable();
   reset();
 }
 
@@ -410,14 +412,14 @@ SSLConfigParams::initialize()
   REC_ReadConfigStringAlloc(ssl_client_cert_filename, "proxy.config.ssl.client.cert.filename");
   REC_ReadConfigStringAlloc(ssl_client_cert_path, "proxy.config.ssl.client.cert.path");
   if (ssl_client_cert_filename && ssl_client_cert_path) {
-    set_paths_helper(ssl_client_cert_path, ssl_client_cert_filename, nullptr, &clientCertPath);
+    set_paths_helper(ssl_client_cert_path, ssl_client_cert_filename, &clientCertPathOnly, &clientCertPath);
   }
   ats_free_null(ssl_client_cert_filename);
   ats_free_null(ssl_client_cert_path);
 
   REC_ReadConfigStringAlloc(ssl_client_private_key_filename, "proxy.config.ssl.client.private_key.filename");
   REC_ReadConfigStringAlloc(ssl_client_private_key_path, "proxy.config.ssl.client.private_key.path");
-  set_paths_helper(ssl_client_private_key_path, ssl_client_private_key_filename, nullptr, &clientKeyPath);
+  set_paths_helper(ssl_client_private_key_path, ssl_client_private_key_filename, &clientKeyPathOnly, &clientKeyPath);
   ats_free_null(ssl_client_private_key_filename);
   ats_free_null(ssl_client_private_key_path);
 
@@ -434,41 +436,12 @@ SSLConfigParams::initialize()
   // Enable client regardless of config file settings as remap file
   // can cause HTTP layer to connect using SSL. But only if SSL
   // initialization hasn't failed already.
-  client_ctx = SSLInitClientContext(this);
+  client_ctx = this->getCTX(this->clientCertPath, this->clientKeyPath, this->clientCACertFilename, this->clientCACertPath);
   if (!client_ctx) {
     SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function");
   }
 }
 
-// creates a new context attaching the provided certificate
-SSL_CTX *
-SSLConfigParams::getNewCTX(const char *client_cert, const char *client_key) const
-{
-  SSL_CTX *nclient_ctx = nullptr;
-  nclient_ctx          = SSLInitClientContext(this);
-  if (!nclient_ctx) {
-    SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function");
-    return nullptr;
-  }
-  if (client_cert != nullptr && client_cert[0] != '\0') {
-    if (!SSL_CTX_use_certificate_chain_file(nclient_ctx, (const char *)client_cert)) {
-      SSLError("failed to load client certificate from %s", this->clientCertPath);
-      SSLReleaseContext(nclient_ctx);
-      return nullptr;
-    }
-  }
-  // If there is not private key specified, perhaps it is in the file with the cert
-  if (client_key == nullptr || client_key[0] == '\0') {
-    client_key = client_cert;
-  }
-  // Try loading the private key
-  if (client_key != nullptr && client_key[0] != '\0') {
-    // If it failed, then we are just going to use the previously set private key from records.config
-    SSL_CTX_use_PrivateKey_file(nclient_ctx, client_key, SSL_FILETYPE_PEM);
-  }
-  return nclient_ctx;
-}
-
 SSL_CTX *
 SSLConfigParams::getClientSSL_CTX() const
 {
@@ -682,3 +655,84 @@ SSLTicketParams::cleanup()
   ticket_block_free(default_global_keyblock);
   ticket_key_filename = (char *)ats_free_null(ticket_key_filename);
 }
+
+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;
+  std::string key;
+  ts::bwprint(key, "{}:{}:{}:{}", client_cert, key_file, ca_bundle_file, ca_bundle_path);
+
+  ink_mutex_acquire(&ctxMapLock);
+  auto iter = ctx_map.find(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);
+
+  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 (!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;
+    }
+  }
+
+  // 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);
+      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);
+  iter = ctx_map.find(key);
+  if (iter != ctx_map.end()) {
+    SSL_CTX_free(client_ctx);
+    client_ctx = iter->second;
+  } else {
+    ctx_map.insert(std::make_pair(key, client_ctx));
+  }
+  ink_mutex_release(&ctxMapLock);
+  return client_ctx;
+
+fail:
+  if (client_ctx) {
+    SSL_CTX_free(client_ctx);
+  }
+  return nullptr;
+}
+
+void
+SSLConfigParams::cleanupCTXTable()
+{
+  ink_mutex_acquire(&ctxMapLock);
+  auto iter = ctx_map.begin();
+  while (iter != ctx_map.end()) {
+    SSL_CTX_free(iter->second);
+    ++iter;
+  }
+  ctx_map.clear();
+  ink_mutex_release(&ctxMapLock);
+}
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 96221d4..1e5dccf 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -22,6 +22,7 @@
  */
 #include "tscore/ink_config.h"
 #include "tscore/EventNotify.h"
+#include "tscore/I_Layout.h"
 #include "records/I_RecHttp.h"
 #include "P_Net.h"
 #include "P_SSLNextProtocolSet.h"
@@ -970,7 +971,24 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
       auto nps           = sniParam->getPropertyConfig(serverKey);
       SSL_CTX *clientCTX = nullptr;
 
-      if (nps) {
+      // First Look to see if there are override parameters
+      if (options.ssl_client_cert_name) {
+        std::string certFilePath = Layout::get()->relative_to(params->clientCertPathOnly, options.ssl_client_cert_name);
+        std::string keyFilePath;
+        if (options.ssl_client_private_key_name) {
+          keyFilePath = Layout::get()->relative_to(params->clientKeyPathOnly, options.ssl_client_private_key_name);
+        }
+        std::string caCertFilePath;
+        if (options.ssl_client_ca_cert_name) {
+          caCertFilePath = Layout::get()->relative_to(params->clientCACertPath, options.ssl_client_ca_cert_name);
+        }
+        clientCTX =
+          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;
       } else { // Just stay with the values passed down from the SM for verify
         clientCTX = params->client_ctx;
diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc
index 524d2ab..470a544 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -41,7 +41,6 @@
 static ConfigUpdateHandler<SNIConfig> *sniConfigUpdate;
 struct NetAccept;
 std::unordered_map<int, SSLNextProtocolSet *> snpsMap;
-NextHopProperty::NextHopProperty() {}
 
 const NextHopProperty *
 SNIConfigParams::getPropertyConfig(const std::string &servername) const
@@ -83,8 +82,8 @@ SNIConfigParams::loadSNIConfig()
     auto clientCTX       = params->getClientSSL_CTX();
     const char *certFile = item.client_cert.data();
     const char *keyFile  = item.client_key.data();
-    if (certFile) {
-      clientCTX = params->getNewCTX(certFile, keyFile);
+    if (certFile && certFile[0] != '\0') {
+      clientCTX = params->getCTX(certFile, keyFile, params->clientCACertFilename, params->clientCACertPath);
     }
 
     auto nps = next_hop_list.emplace(next_hop_list.end());
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index 5e53b0b..d0ae283 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -2289,10 +2289,11 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
 }
 #endif /* HAVE_OPENSSL_SESSION_TICKETS */
 
+// Release SSL_CTX and the associated data. This works for both
+// client and server contexts and gracefully accepts nullptr.
 void
 SSLReleaseContext(SSL_CTX *ctx)
 {
-  // SSL_CTX_free() does nothing if ctx in nullptr, so there's no need to check.
   SSL_CTX_free(ctx);
 }
 
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index e9cb40f..7c329f1 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1150,9 +1150,9 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.ssl.client.private_key.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.ssl.client.CA.cert.filename", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_STR, "^[^[:space:]]*$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.client.CA.cert.filename", RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_STR, "^[^[:space:]]*$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.ssl.client.CA.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+  {RECT_CONFIG, "proxy.config.ssl.client.CA.cert.path", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.ssl.client.sni_policy", RECD_STRING, TS_BUILD_SYSCONFDIR, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
diff --git a/plugins/lua/ts_lua_http_config.c b/plugins/lua/ts_lua_http_config.c
index 74bc201..c7f46cb 100644
--- a/plugins/lua/ts_lua_http_config.c
+++ b/plugins/lua/ts_lua_http_config.c
@@ -138,6 +138,8 @@ typedef enum {
   TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY               = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY,
   TS_LUA_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES           = TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES,
   TS_LUA_CONFIG_SSL_CLIENT_SNI_POLICY                         = TS_CONFIG_SSL_CLIENT_SNI_POLICY,
+  TS_LUA_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME               = TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME,
+  TS_LUA_CONFIG_SSL_CLIENT_CA_CERT_FILENAME                   = TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME,
   TS_LUA_CONFIG_LAST_ENTRY                                    = TS_CONFIG_LAST_ENTRY,
 } TSLuaOverridableConfigKey;
 
@@ -266,6 +268,8 @@ ts_lua_var_item ts_lua_http_config_vars[] = {
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES),
   TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_SNI_POLICY),
+  TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME),
+  TS_LUA_MAKE_VAR_ITEM(TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_SERVER_CONNECTION_MAX),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_HTTP_PER_SERVER_CONNECTION_MATCH),
   TS_LUA_MAKE_VAR_ITEM(TS_LUA_CONFIG_LAST_ENTRY),
diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h
index f274168..59f0faf 100644
--- a/proxy/http/HttpConfig.h
+++ b/proxy/http/HttpConfig.h
@@ -552,8 +552,9 @@ struct OverridableHttpConfigParams {
       global_user_agent_header_size(0),
       cache_heuristic_lm_factor(0.10),
       background_fill_threshold(0.5),
-      client_cert_filename(nullptr),
-      client_cert_filepath(nullptr),
+      ssl_client_cert_filename(nullptr),
+      ssl_client_private_key_filename(nullptr),
+      ssl_client_ca_cert_filename(nullptr),
       cache_vary_default_text(nullptr),
       cache_vary_default_images(nullptr),
       cache_vary_default_other(nullptr)
@@ -798,8 +799,9 @@ struct OverridableHttpConfigParams {
   MgmtFloat background_fill_threshold;
 
   // Various strings, good place for them here ...
-  char *client_cert_filename;
-  char *client_cert_filepath;
+  char *ssl_client_cert_filename;
+  char *ssl_client_private_key_filename;
+  char *ssl_client_ca_cert_filename;
 
   char *cache_vary_default_text;
   char *cache_vary_default_images;
@@ -968,8 +970,9 @@ inline HttpConfigParams::~HttpConfigParams()
   ats_free(oride.body_factory_template_base);
   ats_free(oride.proxy_response_server_string);
   ats_free(oride.global_user_agent_header);
-  ats_free(oride.client_cert_filename);
-  ats_free(oride.client_cert_filepath);
+  ats_free(oride.ssl_client_cert_filename);
+  ats_free(oride.ssl_client_private_key_filename);
+  ats_free(oride.ssl_client_ca_cert_filename);
   ats_free(oride.cache_vary_default_text);
   ats_free(oride.cache_vary_default_images);
   ats_free(oride.cache_vary_default_other);
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index 75095d9..18dad8e 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -5044,6 +5044,9 @@ HttpSM::do_http_server_open(bool raw)
   if (scheme_to_use == URL_WKSIDX_HTTPS || HttpTransactHeaders::is_method_idempotent(t_state.method)) {
     opt.f_tcp_fastopen = (t_state.txn_conf->sock_option_flag_out & NetVCOptions::SOCK_OPT_TCP_FAST_OPEN);
   }
+  opt.ssl_client_cert_name        = t_state.txn_conf->ssl_client_cert_filename;
+  opt.ssl_client_private_key_name = t_state.txn_conf->ssl_client_private_key_filename;
+  opt.ssl_client_ca_cert_name     = t_state.txn_conf->ssl_client_ca_cert_filename;
 
   if (scheme_to_use == URL_WKSIDX_HTTPS) {
     SMDebug("http", "calling sslNetProcessor.connect_re");
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index c537e16..61de605 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -8198,18 +8198,16 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr
   case TS_CONFIG_HTTP_FORWARD_CONNECT_METHOD:
     ret = _memberp_to_generic(&overridableHttpConfig->forward_connect_method, conv);
     break;
-  case TS_CONFIG_SSL_CERT_FILENAME:
-    ret = _memberp_to_generic(&overridableHttpConfig->client_cert_filename, conv);
-    break;
-  case TS_CONFIG_SSL_CERT_FILEPATH:
-    ret = _memberp_to_generic(&overridableHttpConfig->client_cert_filepath, conv);
-    break;
   case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER:
     ret = _memberp_to_generic(&overridableHttpConfig->ssl_client_verify_server, conv);
     break;
   case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY:
   case TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES:
   case TS_CONFIG_SSL_CLIENT_SNI_POLICY:
+  case TS_CONFIG_SSL_CLIENT_CERT_FILENAME:
+  case TS_CONFIG_SSL_CERT_FILEPATH:
+  case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME:
+  case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME:
     // String, must be handled elsewhere
     break;
   case TS_CONFIG_PARENT_FAILURES_UPDATE_HOSTDB:
@@ -8395,16 +8393,6 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
       s->t_state.txn_conf->body_factory_template_base_len = 0;
     }
     break;
-  case TS_CONFIG_SSL_CERT_FILENAME:
-    if (value && length > 0) {
-      s->t_state.txn_conf->client_cert_filename = const_cast<char *>(value);
-    }
-    break;
-  case TS_CONFIG_SSL_CERT_FILEPATH:
-    if (value && length > 0) {
-      s->t_state.txn_conf->client_cert_filepath = const_cast<char *>(value);
-    }
-    break;
   case TS_CONFIG_HTTP_INSERT_FORWARDED:
     if (value && length > 0) {
       ts::LocalBufferWriter<1024> error;
@@ -8431,6 +8419,24 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, TSOverridableConfigKey conf, const char
       s->t_state.txn_conf->ssl_client_sni_policy = const_cast<char *>(value);
     }
     break;
+  case TS_CONFIG_SSL_CLIENT_CERT_FILENAME:
+    if (value && length > 0) {
+      s->t_state.txn_conf->ssl_client_cert_filename = const_cast<char *>(value);
+    }
+    break;
+  case TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME:
+    if (value && length > 0) {
+      s->t_state.txn_conf->ssl_client_private_key_filename = const_cast<char *>(value);
+    }
+    break;
+  case TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME:
+    if (value && length > 0) {
+      s->t_state.txn_conf->ssl_client_ca_cert_filename = const_cast<char *>(value);
+    }
+    break;
+  case TS_CONFIG_SSL_CERT_FILEPATH:
+    /* noop */
+    break;
   default: {
     MgmtConverter const *conv;
     void *dest = _conf_to_memberp(conf, s->t_state.txn_conf, conv);
@@ -8505,7 +8511,6 @@ static const std::unordered_map<std::string_view, std::tuple<const TSOverridable
    {"proxy.config.http.slow.log.threshold", {TS_CONFIG_HTTP_SLOW_LOG_THRESHOLD, TS_RECORDDATATYPE_INT}},
    {"proxy.config.http.cache.max_stale_age", {TS_CONFIG_HTTP_CACHE_MAX_STALE_AGE, TS_RECORDDATATYPE_INT}},
    {"proxy.config.http.default_buffer_size", {TS_CONFIG_HTTP_DEFAULT_BUFFER_SIZE, TS_RECORDDATATYPE_INT}},
-   {"proxy.config.ssl.client.cert.filename", {TS_CONFIG_SSL_CERT_FILENAME, TS_RECORDDATATYPE_STRING}},
    {"proxy.config.http.response_server_str", {TS_CONFIG_HTTP_RESPONSE_SERVER_STR, TS_RECORDDATATYPE_STRING}},
    {"proxy.config.http.keep_alive_post_out", {TS_CONFIG_HTTP_KEEP_ALIVE_POST_OUT, TS_RECORDDATATYPE_INT}},
    {"proxy.config.net.sock_option_flag_out", {TS_CONFIG_NET_SOCK_OPTION_FLAG_OUT, TS_RECORDDATATYPE_INT}},
@@ -8621,7 +8626,11 @@ static const std::unordered_map<std::string_view, std::tuple<const TSOverridable
    {"proxy.config.ssl.client.verify.server", {TS_CONFIG_SSL_CLIENT_VERIFY_SERVER, TS_RECORDDATATYPE_INT}},
    {"proxy.config.ssl.client.verify.server.policy", {TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_POLICY, TS_RECORDDATATYPE_STRING}},
    {"proxy.config.ssl.client.verify.server.properties", {TS_CONFIG_SSL_CLIENT_VERIFY_SERVER_PROPERTIES, TS_RECORDDATATYPE_STRING}},
-   {"proxy.config.ssl.client.sni_policy", {TS_CONFIG_SSL_CLIENT_SNI_POLICY, TS_RECORDDATATYPE_STRING}}});
+   {"proxy.config.ssl.client.sni_policy", {TS_CONFIG_SSL_CLIENT_SNI_POLICY, TS_RECORDDATATYPE_STRING}},
+   {"proxy.config.ssl.client.cert.filename", {TS_CONFIG_SSL_CLIENT_CERT_FILENAME, TS_RECORDDATATYPE_STRING}},
+   {"proxy.config.ssl.client.cert.path", {TS_CONFIG_SSL_CERT_FILEPATH, TS_RECORDDATATYPE_STRING}},
+   {"proxy.config.ssl.client.private_key.filename", {TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME, TS_RECORDDATATYPE_STRING}},
+   {"proxy.config.ssl.client.CA.cert.filename", {TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME, TS_RECORDDATATYPE_STRING}}});
 
 TSReturnCode
 TSHttpTxnConfigFind(const char *name, int length, TSOverridableConfigKey *conf, TSRecordDataType *type)
diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc
index 6d38500..811f1be 100644
--- a/src/traffic_server/InkAPITest.cc
+++ b/src/traffic_server/InkAPITest.cc
@@ -8693,7 +8693,9 @@ std::array<std::string_view, TS_CONFIG_LAST_ENTRY> SDK_Overridable_Configs = {
    "proxy.config.ssl.client.verify.server",
    "proxy.config.ssl.client.verify.server.policy",
    "proxy.config.ssl.client.verify.server.properties",
-   "proxy.config.ssl.client.sni_policy"}};
+   "proxy.config.ssl.client.sni_policy",
+   "proxy.config.ssl.client.private_key.filename",
+   "proxy.config.ssl.client.CA.cert.filename"}};
 
 REGRESSION_TEST(SDK_API_OVERRIDABLE_CONFIGS)(RegressionTest *test, int /* atype ATS_UNUSED */, int *pstatus)
 {
diff --git a/tests/gold_tests/tls/tls_client_cert_override.test.py b/tests/gold_tests/tls/tls_client_cert_override.test.py
new file mode 100644
index 0000000..e5d21b8
--- /dev/null
+++ b/tests/gold_tests/tls/tls_client_cert_override.test.py
@@ -0,0 +1,145 @@
+'''
+Test offering client cert to origin
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+import re
+
+Test.Summary = '''
+Test conf_remp to specify different client certificates to offer to the origin
+'''
+
+Test.SkipUnless(Condition.HasProgram("grep", "grep needs to be installed on system for this test to work"))
+
+ts = Test.MakeATSProcess("ts", command="traffic_manager", select_ports=False)
+cafile = "{0}/signer.pem".format(Test.RunDirectory)
+cafile2 = "{0}/signer2.pem".format(Test.RunDirectory)
+server = Test.MakeOriginServer("server", ssl=True, options = { "--clientCA": cafile, "--clientverify": "true"}, clientcert="{0}/signed-foo.pem".format(Test.RunDirectory), clientkey="{0}/signed-foo.key".format(Test.RunDirectory))
+server2 = Test.MakeOriginServer("server2", ssl=True, options = { "--clientCA": cafile2, "--clientverify": "true"}, clientcert="{0}/signed2-bar.pem".format(Test.RunDirectory), clientkey="{0}/signed-bar.key".format(Test.RunDirectory))
+server.Setup.Copy("ssl/signer.pem")
+server.Setup.Copy("ssl/signer2.pem")
+server.Setup.Copy("ssl/signed-foo.pem")
+server.Setup.Copy("ssl/signed-foo.key")
+server.Setup.Copy("ssl/signed2-foo.pem")
+server.Setup.Copy("ssl/signed2-bar.pem")
+server.Setup.Copy("ssl/signed-bar.key")
+server2.Setup.Copy("ssl/signer.pem")
+server2.Setup.Copy("ssl/signer2.pem")
+server2.Setup.Copy("ssl/signed-foo.pem")
+server2.Setup.Copy("ssl/signed-foo.key")
+server2.Setup.Copy("ssl/signed2-foo.pem")
+server2.Setup.Copy("ssl/signed2-bar.pem")
+server2.Setup.Copy("ssl/signed-bar.key")
+
+request_header = {"headers": "GET / HTTP/1.1\r\nHost: example.com\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)
+request_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\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)
+
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+ts.addSSLfile("ssl/signed-foo.pem")
+ts.addSSLfile("ssl/signed-foo.key")
+ts.addSSLfile("ssl/signed2-foo.pem")
+ts.addSSLfile("ssl/signed-bar.pem")
+ts.addSSLfile("ssl/signed2-bar.pem")
+ts.addSSLfile("ssl/signed-bar.key")
+
+ts.Variables.ssl_port = 4443
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 0,
+    'proxy.config.diags.debug.tags': 'ssl',
+    '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}'.format(ts.Variables.port),
+    'proxy.config.ssl.client.verify.server':  0,
+    'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2',
+    'proxy.config.ssl.client.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.client.cert.filename': 'signed-foo.pem',
+    'proxy.config.ssl.client.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.client.private_key.filename': 'signed-foo.key',
+    'proxy.config.url_remap.pristine_host_hdr' : 1,
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+ts.Disk.remap_config.AddLine(
+    'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'.format(server.Variables.Port, "signed-foo.pem", "signed-foo.key")
+)
+ts.Disk.remap_config.AddLine(
+    'map /badcase1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'.format(server.Variables.Port, "signed2-foo.pem", "signed-foo.key")
+)
+ts.Disk.remap_config.AddLine(
+    'map /case2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'.format(server2.Variables.Port, "signed2-foo.pem", "signed-foo.key")
+)
+ts.Disk.remap_config.AddLine(
+    'map /badcase2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'.format(server2.Variables.Port, "signed-foo.pem", "signed-foo.key")
+)
+
+# Should succeed
+tr = Test.AddTestRun("Connect with correct client cert to first server")
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.port))
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(server2)
+tr.StillRunningAfter = ts
+tr.StillRunningAfter = server
+tr.StillRunningAfter = server2
+tr.Processes.Default.Command = "curl -H host:example.com  http://127.0.0.1:{0}/case1".format(ts.Variables.port)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.TimeOut = 5
+tr.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response")
+tr.TimeOut = 5
+
+#Should fail
+trfail = Test.AddTestRun("Connect with bad client cert to first server")
+trfail.StillRunningAfter = ts
+trfail.StillRunningAfter = server
+trfail.StillRunningAfter = server2
+trfail.Processes.Default.Command = 'curl -H host:example.com  http://127.0.0.1:{0}/badcase1'.format(ts.Variables.port)
+trfail.Processes.Default.ReturnCode = 0
+trfail.Processes.Default.TimeOut = 5
+trfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response")
+trfail.TimeOut = 5
+
+# Should succeed
+trbar = Test.AddTestRun("Connect with correct client cert to second server")
+trbar.StillRunningAfter = ts
+trbar.StillRunningAfter = server
+trbar.StillRunningAfter = server2
+trbar.Processes.Default.Command = "curl -H host:bar.com  http://127.0.0.1:{0}/case2".format(ts.Variables.port)
+trbar.Processes.Default.ReturnCode = 0
+trbar.Processes.Default.TimeOut = 5
+trbar.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response")
+trbar.TimeOut = 5
+
+#Should fail
+trbarfail = Test.AddTestRun("Connect with bad client cert to second server")
+trbarfail.StillRunningAfter = ts
+trbarfail.StillRunningAfter = server
+trbarfail.StillRunningAfter = server2
+trbarfail.Processes.Default.Command = 'curl -H host:bar.com  http://127.0.0.1:{0}/badcase2'.format(ts.Variables.port)
+trbarfail.Processes.Default.ReturnCode = 0
+trbarfail.Processes.Default.TimeOut = 5
+trbarfail.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Check response")
+trbarfail.TimeOut = 5
+
+
diff --git a/tests/gold_tests/tls/tls_verify_ca_override.test.py b/tests/gold_tests/tls/tls_verify_ca_override.test.py
new file mode 100644
index 0000000..016ae99
--- /dev/null
+++ b/tests/gold_tests/tls/tls_verify_ca_override.test.py
@@ -0,0 +1,137 @@
+'''
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+Test.Summary = '''
+Test tls server  certificate verification options. Exercise conf_remap for ca bundle
+'''
+
+# need Curl
+Test.SkipUnless(
+    Condition.HasProgram("curl", "Curl need to be installed on system for this test to work")
+)
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", select_ports=False)
+server1 = Test.MakeOriginServer("server1", ssl=True, options = {"--key": "{0}/signed-foo.key".format(Test.RunDirectory), "--cert": "{0}/signed-foo.pem".format(Test.RunDirectory)})
+server2 = Test.MakeOriginServer("server2", ssl=True, options = {"--key": "{0}/signed-foo.key".format(Test.RunDirectory), "--cert": "{0}/signed2-foo.pem".format(Test.RunDirectory)})
+
+request_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: foo.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+request_bad_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: bad_foo.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+request_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+request_bad_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bad_bar.com\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": ""}
+server1.addResponse("sessionlog.json", request_foo_header, response_header)
+server1.addResponse("sessionlog.json", request_bad_foo_header, response_header)
+server2.addResponse("sessionlog.json", request_bar_header, response_header)
+server2.addResponse("sessionlog.json", request_bad_bar_header, response_header)
+
+# add ssl materials like key, certificates for the server
+ts.addSSLfile("ssl/signed-foo.pem")
+ts.addSSLfile("ssl/signed2-foo.pem")
+ts.addSSLfile("ssl/signed-foo.key")
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+ts.addSSLfile("ssl/signer.pem")
+ts.addSSLfile("ssl/signer.key")
+ts.addSSLfile("ssl/signer2.pem")
+ts.addSSLfile("ssl/signer2.key")
+
+ts.Variables.ssl_port = 4443
+ts.Disk.remap_config.AddLine(
+    'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(server1.Variables.Port, ts.Variables.SSLDir, "signer.pem")
+)
+ts.Disk.remap_config.AddLine(
+    'map /badcase1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(server1.Variables.Port, ts.Variables.SSLDir, "signer2.pem")
+)
+ts.Disk.remap_config.AddLine(
+    'map /case2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(server2.Variables.Port, ts.Variables.SSLDir, "signer2.pem")
+)
+ts.Disk.remap_config.AddLine(
+    'map /badcase2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(server2.Variables.Port, ts.Variables.SSLDir, "signer.pem")
+)
+
+ts.Disk.ssl_multicert_config.AddLine(
+    'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+# Case 1, global config policy=permissive properties=signature
+#         override for foo.com policy=enforced properties=all
+ts.Disk.records_config.update({
+    'proxy.config.diags.debug.enabled': 1,
+    'proxy.config.diags.debug.tags': 'http|ssl',
+    'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    # enable ssl port
+    'proxy.config.http.server_ports': '{0} {1}:proto=http2;http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port),
+    'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2',
+    # set global policy
+    'proxy.config.ssl.client.verify.server.policy': 'ENFORCED',
+    'proxy.config.ssl.client.verify.server.properties': 'SIGNATURE',
+    'proxy.config.ssl.client.CA.cert.path': '/tmp',
+    'proxy.config.ssl.client.CA.cert.filename': '{0}/signer.pem'.format(ts.Variables.SSLDir),
+    'proxy.config.url_remap.pristine_host_hdr': 1
+})
+
+# Should succeed 
+tr = Test.AddTestRun("Use corrcect ca bundle for server 1")
+tr.Processes.Default.Command = 'curl -k -H \"host: foo.com\"  http://127.0.0.1:{0}/case1'.format(ts.Variables.port)
+tr.ReturnCode = 0
+tr.Setup.Copy("ssl/signed-foo.key")
+tr.Setup.Copy("ssl/signed-foo.pem")
+tr.Setup.Copy("ssl/signed2-foo.pem")
+tr.Processes.Default.StartBefore(server1)
+tr.Processes.Default.StartBefore(server2)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.port))
+tr.StillRunningAfter = server1
+tr.StillRunningAfter = ts
+tr.Processes.Default.TimeOut = 5
+# Should succed.  No message
+tr.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Curl attempt should have succeeded")
+tr.TimeOut = 5
+
+tr2 = Test.AddTestRun("Use incorrect ca  bundle for server 1")
+tr2.Processes.Default.Command = "curl -k -H \"host: bar.com\"  http://127.0.0.1:{0}/badcase1".format(ts.Variables.port)
+tr2.ReturnCode = 0
+tr2.StillRunningAfter = server1
+tr2.Processes.Default.TimeOut = 5
+tr2.StillRunningAfter = ts
+# Should succeed, but will be message in log about name mismatch
+tr2.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Curl attempt should have succeeded")
+tr2.TimeOut = 5
+
+tr2 = Test.AddTestRun("Use currect ca bundle for server 2")
+tr2.Processes.Default.Command = "curl -k -H \"host: random.com\"  http://127.0.0.1:{0}/case2".format(ts.Variables.port)
+tr2.ReturnCode = 0
+tr2.StillRunningAfter = server2
+tr2.Processes.Default.TimeOut = 5
+tr2.StillRunningAfter = ts
+# Should succeed, but will be message in log about signature 
+tr2.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Curl attempt should have succeeded")
+tr2.TimeOut = 5
+
+tr3 = Test.AddTestRun("User incorrect ca bundle for server 2")
+tr3.Processes.Default.Command = "curl -k -H \"host: foo.com\"  http://127.0.0.1:{0}/badcase2".format(ts.Variables.port)
+tr3.ReturnCode = 0
+tr3.StillRunningAfter = server2
+tr3.StillRunningAfter = ts
+# Should succeed.  No error messages
+tr3.Processes.Default.Streams.stdout = Testers.ContainsExpression("Could Not Connect", "Curl attempt should have succeeded")
+tr3.Processes.Default.TimeOut = 5
+
+