You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/05/20 16:45:36 UTC

[trafficserver] 01/06: Rework server side SSL_CTX creation to better handle dual_cert mismatches (#6483)

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

zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit c986f64b8c67f38993d208b11666b4bae070cc8d
Author: Susan Hinrichs <sh...@yahoo-inc.com>
AuthorDate: Thu Mar 12 08:41:38 2020 -0500

    Rework server side SSL_CTX creation to better handle dual_cert mismatches (#6483)
    
    Rework server side SSL_CTX creation to better handle dual_cert name mismatches
    
    (cherry picked from commit f729c9dc41ff1635132f4bdc6331ce826f3bc2fe)
---
 iocore/net/P_SSLCertLookup.h                       |   2 +-
 iocore/net/P_SSLUtils.h                            |  26 +-
 iocore/net/QUICMultiCertConfigLoader.cc            | 112 ++-----
 iocore/net/QUICMultiCertConfigLoader.h             |   6 +-
 iocore/net/SSLUtils.cc                             | 348 ++++++++++++++-------
 tests/gold_tests/tls/ssl/signed-foo-ec.key         |   8 +
 tests/gold_tests/tls/ssl/signed-foo-ec.pem         |  14 +
 tests/gold_tests/tls/ssl/signed-san-ec.key         |   5 +
 tests/gold_tests/tls/ssl/signed-san-ec.pem         |  15 +
 tests/gold_tests/tls/ssl/signed-san.key            |  28 ++
 tests/gold_tests/tls/ssl/signed-san.pem            |  19 ++
 tests/gold_tests/tls/ssl/signer.pem                |  15 -
 .../tls/tls_check_dual_cert_selection.test.py      | 127 ++++++++
 13 files changed, 505 insertions(+), 220 deletions(-)

diff --git a/iocore/net/P_SSLCertLookup.h b/iocore/net/P_SSLCertLookup.h
index 1e89361..7ee0f2a 100644
--- a/iocore/net/P_SSLCertLookup.h
+++ b/iocore/net/P_SSLCertLookup.h
@@ -100,7 +100,7 @@ public:
   {
   }
   SSLCertContext(shared_SSL_CTX sc, shared_SSLMultiCertConfigParams u)
-    : ctx_mutex(), ctx(sc), opt(u->opt), userconfig(nullptr), keyblock(nullptr)
+    : ctx_mutex(), ctx(sc), opt(u->opt), userconfig(u), keyblock(nullptr)
   {
   }
   SSLCertContext(shared_SSL_CTX sc, shared_SSLMultiCertConfigParams u, shared_ssl_ticket_key_block kb)
diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h
index da85e1a..ac43cf2 100644
--- a/iocore/net/P_SSLUtils.h
+++ b/iocore/net/P_SSLUtils.h
@@ -34,6 +34,9 @@
 #include "records/I_RecCore.h"
 #include "P_SSLCertLookup.h"
 
+#include <set>
+#include <map>
+
 struct SSLConfigParams;
 class SSLNetVConnection;
 
@@ -54,28 +57,39 @@ ssl_curve_id SSLGetCurveNID(SSL *ssl);
 class SSLMultiCertConfigLoader
 {
 public:
+  struct CertLoadData {
+    std::vector<std::string> cert_names_list, key_list, ca_list, ocsp_list;
+  };
   SSLMultiCertConfigLoader(const SSLConfigParams *p) : _params(p) {}
   virtual ~SSLMultiCertConfigLoader(){};
 
   bool load(SSLCertLookup *lookup);
 
   virtual SSL_CTX *default_server_ssl_ctx();
-  virtual SSL_CTX *init_server_ssl_ctx(std::vector<X509 *> &certList, const SSLMultiCertConfigParams *sslMultCertSettings);
-
-  static bool load_certs(SSL_CTX *ctx, std::vector<X509 *> &certList, const SSLConfigParams *params,
-                         const SSLMultiCertConfigParams *ssl_multi_cert_params);
+  virtual SSL_CTX *init_server_ssl_ctx(CertLoadData const &data, const SSLMultiCertConfigParams *sslMultCertSettings,
+                                       std::set<std::string> &names);
+
+  static bool load_certs(SSL_CTX *ctx, CertLoadData const &data, const SSLConfigParams *params,
+                         const SSLMultiCertConfigParams *sslMultCertSettings);
+  bool load_certs_and_cross_reference_names(std::vector<X509 *> &cert_list, CertLoadData &data, const SSLConfigParams *params,
+                                            const SSLMultiCertConfigParams *sslMultCertSettings,
+                                            std::set<std::string> &common_names,
+                                            std::unordered_map<int, std::set<std::string>> &unique_names);
   static bool set_session_id_context(SSL_CTX *ctx, const SSLConfigParams *params,
                                      const SSLMultiCertConfigParams *sslMultCertSettings);
 
-  static bool index_certificate(SSLCertLookup *lookup, SSLCertContext const &cc, X509 *cert, const char *certname);
+  static bool index_certificate(SSLCertLookup *lookup, SSLCertContext const &cc, const char *sni_name);
   static int check_server_cert_now(X509 *cert, const char *certname);
   static void clear_pw_references(SSL_CTX *ssl_ctx);
 
 protected:
   const SSLConfigParams *_params;
 
+  bool _store_single_ssl_ctx(SSLCertLookup *lookup, shared_SSLMultiCertConfigParams sslMultCertSettings, shared_SSL_CTX ctx,
+                             std::set<std::string> &names);
+
 private:
-  virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams ssl_multi_cert_params);
+  virtual bool _store_ssl_ctx(SSLCertLookup *lookup, shared_SSLMultiCertConfigParams ssl_multi_cert_params);
   virtual void _set_handshake_callbacks(SSL_CTX *ctx);
 };
 
diff --git a/iocore/net/QUICMultiCertConfigLoader.cc b/iocore/net/QUICMultiCertConfigLoader.cc
index 2b1aecf..288c0f0 100644
--- a/iocore/net/QUICMultiCertConfigLoader.cc
+++ b/iocore/net/QUICMultiCertConfigLoader.cc
@@ -80,7 +80,8 @@ QUICMultiCertConfigLoader::default_server_ssl_ctx()
 }
 
 SSL_CTX *
-QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, const SSLMultiCertConfigParams *multi_cert_params)
+QUICMultiCertConfigLoader::init_server_ssl_ctx(SSLMultiCertConfigLoader::CertLoadData const &data,
+                                               const SSLMultiCertConfigParams *multi_cert_params, std::set<std::string> &names)
 {
   const SSLConfigParams *params = this->_params;
 
@@ -92,7 +93,7 @@ QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, c
     }
 
     if (multi_cert_params->cert) {
-      if (!SSLMultiCertConfigLoader::load_certs(ctx, cert_list, params, multi_cert_params)) {
+      if (!SSLMultiCertConfigLoader::load_certs(ctx, data, params, multi_cert_params)) {
         goto fail;
       }
     }
@@ -152,26 +153,6 @@ QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, c
 
   SSL_CTX_set_alpn_select_cb(ctx, QUICMultiCertConfigLoader::ssl_select_next_protocol, nullptr);
 
-#if TS_USE_TLS_OCSP
-  if (SSLConfigParams::ssl_ocsp_enabled) {
-    QUICConfDebug("SSL OCSP Stapling is enabled");
-    SSL_CTX_set_tlsext_status_cb(ctx, ssl_callback_ocsp_stapling);
-    const char *cert_name = multi_cert_params ? multi_cert_params->cert.get() : nullptr;
-
-    for (auto cert : cert_list) {
-      if (!ssl_stapling_init_cert(ctx, cert, cert_name, nullptr)) {
-        Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", cert_name);
-      }
-    }
-  } else {
-    QUICConfDebug("SSL OCSP Stapling is disabled");
-  }
-#else
-  if (SSLConfigParams::ssl_ocsp_enabled) {
-    Warning("failed to enable SSL OCSP Stapling; this version of OpenSSL does not support it");
-  }
-#endif /* TS_USE_TLS_OCSP */
-
   if (SSLConfigParams::init_ssl_ctx_cb) {
     SSLConfigParams::init_ssl_ctx_cb(ctx, true);
   }
@@ -180,85 +161,60 @@ QUICMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, c
 
 fail:
   SSLReleaseContext(ctx);
-  for (auto cert : cert_list) {
-    X509_free(cert);
-  }
-
   return nullptr;
 }
 
-SSL_CTX *
+bool
 QUICMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams multi_cert_params)
 {
+  bool retval = true;
   std::vector<X509 *> cert_list;
-  shared_SSL_CTX ctx(this->init_server_ssl_ctx(cert_list, multi_cert_params.get()), SSL_CTX_free);
-  shared_ssl_ticket_key_block keyblock = nullptr;
-  bool inserted                        = false;
-
-  if (!ctx || !multi_cert_params) {
-    lookup->is_valid = false;
-    return nullptr;
-  }
+  SSLMultiCertConfigLoader::CertLoadData data;
+  std::set<std::string> common_names;
+  std::unordered_map<int, std::set<std::string>> unique_names;
+  const SSLConfigParams *params = this->_params;
+  this->load_certs_and_cross_reference_names(cert_list, data, params, multi_cert_params.get(), common_names, unique_names);
 
-  const char *certname = multi_cert_params->cert.get();
-  for (auto cert : cert_list) {
-    if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert, certname)) {
+  for (size_t i = 0; i < cert_list.size(); i++) {
+    const char *current_cert_name = data.cert_names_list[i].c_str();
+    if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert_list[i], current_cert_name)) {
       /* At this point, we know cert is bad, and we've already printed a
          descriptive reason as to why cert is bad to the log file */
-      QUICConfDebug("Marking certificate as NOT VALID: %s", certname);
+      QUICConfDebug("Marking certificate as NOT VALID: %s", current_cert_name);
       lookup->is_valid = false;
     }
   }
 
-  // Index this certificate by the specified IP(v6) address. If the address is "*", make it the default context.
-  if (multi_cert_params->addr) {
-    if (strcmp(multi_cert_params->addr, "*") == 0) {
-      if (lookup->insert(multi_cert_params->addr, SSLCertContext(ctx, multi_cert_params, keyblock)) >= 0) {
-        inserted            = true;
-        lookup->ssl_default = ctx;
-        this->_set_handshake_callbacks(ctx.get());
-      }
-    } else {
-      IpEndpoint ep;
-
-      if (ats_ip_pton(multi_cert_params->addr, &ep) == 0) {
-        QUICConfDebug("mapping '%s' to certificate %s", (const char *)multi_cert_params->addr, (const char *)certname);
-        if (lookup->insert(ep, SSLCertContext(ctx, multi_cert_params, keyblock)) >= 0) {
-          inserted = true;
-        }
-      } else {
-        Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)multi_cert_params->addr);
-        lookup->is_valid = false;
-      }
-    }
-  }
+  shared_SSL_CTX ctx(this->init_server_ssl_ctx(data, multi_cert_params.get(), common_names), SSL_CTX_free);
 
-  // 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.
-  QUICConfDebug("importing SNI names from %s", (const char *)certname);
-  for (auto cert : cert_list) {
-    if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, multi_cert_params), cert, certname)) {
-      inserted = true;
-    }
-  }
+  shared_ssl_ticket_key_block keyblock = nullptr;
 
-  if (inserted) {
-    if (SSLConfigParams::init_ssl_ctx_cb) {
-      SSLConfigParams::init_ssl_ctx_cb(ctx.get(), true);
-    }
+  if (!ctx || !multi_cert_params || !this->_store_single_ssl_ctx(lookup, multi_cert_params, ctx, common_names)) {
+    lookup->is_valid = false;
+    retval           = false;
   }
 
-  if (!inserted) {
-    SSLReleaseContext(ctx.get());
-    ctx = nullptr;
+  for (auto iter = unique_names.begin(); retval && iter != unique_names.end(); ++iter) {
+    size_t i = iter->first;
+
+    SSLMultiCertConfigLoader::CertLoadData single_data;
+    single_data.cert_names_list.push_back(data.cert_names_list[i]);
+    single_data.key_list.push_back(i < data.key_list.size() ? data.key_list[i] : "");
+    single_data.ca_list.push_back(i < data.ca_list.size() ? data.ca_list[i] : "");
+    single_data.ocsp_list.push_back(i < data.ocsp_list.size() ? data.ocsp_list[i] : "");
+
+    shared_SSL_CTX unique_ctx(this->init_server_ssl_ctx(single_data, multi_cert_params.get(), iter->second), SSL_CTX_free);
+    if (!unique_ctx || !this->_store_single_ssl_ctx(lookup, multi_cert_params, unique_ctx, iter->second)) {
+      lookup->is_valid = false;
+      retval           = false;
+    }
   }
 
   for (auto &i : cert_list) {
     X509_free(i);
   }
 
-  return ctx.get();
+  return retval;
 }
 
 void
diff --git a/iocore/net/QUICMultiCertConfigLoader.h b/iocore/net/QUICMultiCertConfigLoader.h
index f29bda6..796c083 100644
--- a/iocore/net/QUICMultiCertConfigLoader.h
+++ b/iocore/net/QUICMultiCertConfigLoader.h
@@ -46,10 +46,12 @@ public:
   QUICMultiCertConfigLoader(const SSLConfigParams *p) : SSLMultiCertConfigLoader(p) {}
 
   virtual SSL_CTX *default_server_ssl_ctx() override;
-  virtual SSL_CTX *init_server_ssl_ctx(std::vector<X509 *> &cert_list, const SSLMultiCertConfigParams *multi_cert_params) override;
+  // override;
+  SSL_CTX *init_server_ssl_ctx(SSLMultiCertConfigLoader::CertLoadData const &data,
+                               const SSLMultiCertConfigParams *sslMultCertSettings, std::set<std::string> &names) override;
 
 private:
-  virtual SSL_CTX *_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams multi_cert_params) override;
+  bool _store_ssl_ctx(SSLCertLookup *lookup, shared_SSLMultiCertConfigParams ssl_multi_cert_params) override;
   virtual void _set_handshake_callbacks(SSL_CTX *ssl_ctx) override;
   static int ssl_select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,
                                       unsigned inlen, void *);
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index f9f8ec4..2c32232 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -60,6 +60,7 @@
 #include <openssl/pem.h>
 #include <openssl/rand.h>
 #include <openssl/x509.h>
+#include <openssl/x509v3.h>
 
 #if HAVE_OPENSSL_TS_H
 #include <openssl/ts.h>
@@ -1034,63 +1035,15 @@ asn1_strdup(ASN1_STRING *s)
    @static
 */
 bool
-SSLMultiCertConfigLoader::index_certificate(SSLCertLookup *lookup, SSLCertContext const &cc, X509 *cert, const char *certname)
+SSLMultiCertConfigLoader::index_certificate(SSLCertLookup *lookup, SSLCertContext const &cc, const char *sni_name)
 {
-  X509_NAME *subject = nullptr;
-  bool inserted      = false;
+  bool inserted = false;
 
-  if (nullptr == cert) {
-    Error("Failed to load certificate %s", certname);
-    lookup->is_valid = false;
-    return false;
-  }
-
-  // Insert a key for the subject CN.
-  subject = X509_get_subject_name(cert);
-  ats_scoped_str subj_name;
-  if (subject) {
-    int pos = -1;
-    for (;;) {
-      pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos);
-      if (pos == -1) {
-        break;
-      }
-
-      X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, pos);
-      ASN1_STRING *cn    = X509_NAME_ENTRY_get_data(e);
-      subj_name          = asn1_strdup(cn);
-
-      Debug("ssl", "mapping '%s' to certificate %s", (const char *)subj_name, certname);
-      if (lookup->insert(subj_name, cc) >= 0) {
-        inserted = true;
-      }
-    }
+  Debug("ssl", "mapping '%s'", sni_name);
+  if (lookup->insert(sni_name, cc) >= 0) {
+    inserted = true;
   }
 
-#if HAVE_OPENSSL_TS_H
-  // Traverse the subjectAltNames (if any) and insert additional keys for the SSL context.
-  GENERAL_NAMES *names = static_cast<GENERAL_NAMES *>(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
-  if (names) {
-    unsigned count = sk_GENERAL_NAME_num(names);
-    for (unsigned i = 0; i < count; ++i) {
-      GENERAL_NAME *name;
-
-      name = sk_GENERAL_NAME_value(names, i);
-      if (name->type == GEN_DNS) {
-        ats_scoped_str dns(asn1_strdup(name->d.dNSName));
-        // only try to insert if the alternate name is not the main name
-        if (subj_name == nullptr || strcmp(dns, subj_name) != 0) {
-          Debug("ssl", "mapping '%s' to certificates %s", (const char *)dns, certname);
-          if (lookup->insert(dns, cc) >= 0) {
-            inserted = true;
-          }
-        }
-      }
-    }
-
-    GENERAL_NAMES_free(names);
-  }
-#endif // HAVE_OPENSSL_TS_H
   return inserted;
 }
 
@@ -1193,7 +1146,8 @@ setClientCertLevel(SSL *ssl, uint8_t certLevel)
    This is public function because of used by SSLCreateServerContext.
  */
 SSL_CTX *
-SSLMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, const SSLMultiCertConfigParams *sslMultCertSettings)
+SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SSLMultiCertConfigParams *sslMultCertSettings,
+                                              std::set<std::string> &names)
 {
   const SSLConfigParams *params = this->_params;
 
@@ -1277,7 +1231,7 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, co
     }
 
     if (sslMultCertSettings->cert) {
-      if (!SSLMultiCertConfigLoader::load_certs(ctx, cert_list, params, sslMultCertSettings)) {
+      if (!SSLMultiCertConfigLoader::load_certs(ctx, data, params, sslMultCertSettings)) {
         goto fail;
       }
     }
@@ -1395,10 +1349,6 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, co
 fail:
   SSLMultiCertConfigLoader::clear_pw_references(ctx);
   SSLReleaseContext(ctx);
-  for (auto cert : cert_list) {
-    X509_free(cert);
-  }
-
   return nullptr;
 }
 
@@ -1407,29 +1357,34 @@ SSLCreateServerContext(const SSLConfigParams *params, const SSLMultiCertConfigPa
                        const char *key_path)
 {
   SSLMultiCertConfigLoader loader(params);
-  std::vector<X509 *> cert_list;
   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());
-
-  if (cert_path) {
+  std::vector<X509 *> cert_list;
+  std::set<std::string> common_names;
+  std::unordered_map<int, std::set<std::string>> unique_names;
+  SSLMultiCertConfigLoader::CertLoadData data;
+  if (loader.load_certs_and_cross_reference_names(cert_list, data, params, sslMultiCertSettings, common_names, unique_names)) {
+    ctx.reset(loader.init_server_ssl_ctx(data, sslMultiCertSettings, common_names));
+  }
+  for (auto &i : cert_list) {
+    X509_free(i);
+  }
+  if (ctx && 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') {
+      ctx = nullptr;
+    } else 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;
+    if (ctx) {
+      if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key_path, SSL_FILETYPE_PEM)) {
+        SSLError("SSLCreateServerContext(): failed to load server private key.");
+        ctx = nullptr;
+      } else if (!SSL_CTX_check_private_key(ctx.get())) {
+        SSLError("SSLCreateServerContext(): server private key does not match server certificate.");
+        ctx = nullptr;
+      }
     }
   }
-
   return ctx.release();
 }
 
@@ -1437,29 +1392,65 @@ SSLCreateServerContext(const SSLConfigParams *params, const SSLMultiCertConfigPa
    Insert SSLCertContext (SSL_CTX ans options) into SSLCertLookup with key.
    Do NOT call SSL_CTX_set_* functions from here. SSL_CTX should be set up by SSLMultiCertConfigLoader::init_server_ssl_ctx().
  */
-SSL_CTX *
+bool
 SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams sslMultCertSettings)
 {
+  bool retval = true;
   std::vector<X509 *> cert_list;
-  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;
-    return nullptr;
-  }
+  std::set<std::string> common_names;
+  std::unordered_map<int, std::set<std::string>> unique_names;
+  SSLMultiCertConfigLoader::CertLoadData data;
+  const SSLConfigParams *params = this->_params;
+  this->load_certs_and_cross_reference_names(cert_list, data, params, sslMultCertSettings.get(), common_names, unique_names);
 
-  const char *certname = sslMultCertSettings->cert.get();
+  int i = 0;
   for (auto cert : cert_list) {
-    if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert, certname)) {
+    const char *current_cert_name = data.cert_names_list[i].c_str();
+    if (0 > SSLMultiCertConfigLoader::check_server_cert_now(cert, current_cert_name)) {
       /* At this point, we know cert is bad, and we've already printed a
          descriptive reason as to why cert is bad to the log file */
-      Debug("ssl", "Marking certificate as NOT VALID: %s", certname);
+      Debug("ssl", "Marking certificate as NOT VALID: %s", current_cert_name);
+      lookup->is_valid = false;
+    }
+    i++;
+  }
+
+  shared_SSL_CTX ctx(this->init_server_ssl_ctx(data, sslMultCertSettings.get(), common_names), SSL_CTX_free);
+
+  if (!ctx || !sslMultCertSettings || !this->_store_single_ssl_ctx(lookup, sslMultCertSettings, ctx, common_names)) {
+    lookup->is_valid = false;
+    retval           = false;
+  }
+
+  for (auto iter = unique_names.begin(); retval && iter != unique_names.end(); ++iter) {
+    size_t i = iter->first;
+
+    SSLMultiCertConfigLoader::CertLoadData single_data;
+    single_data.cert_names_list.push_back(data.cert_names_list[i]);
+    single_data.key_list.push_back(i < data.key_list.size() ? data.key_list[i] : "");
+    single_data.ca_list.push_back(i < data.ca_list.size() ? data.ca_list[i] : "");
+    single_data.ocsp_list.push_back(i < data.ocsp_list.size() ? data.ocsp_list[i] : "");
+
+    shared_SSL_CTX unique_ctx(this->init_server_ssl_ctx(single_data, sslMultCertSettings.get(), iter->second), SSL_CTX_free);
+    if (!unique_ctx || !this->_store_single_ssl_ctx(lookup, sslMultCertSettings, unique_ctx, iter->second)) {
       lookup->is_valid = false;
+      retval           = false;
     }
   }
 
+  for (auto &i : cert_list) {
+    X509_free(i);
+  }
+
+  return retval;
+}
+
+bool
+SSLMultiCertConfigLoader::_store_single_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams sslMultCertSettings,
+                                                shared_SSL_CTX ctx, std::set<std::string> &names)
+{
+  bool inserted                        = false;
+  shared_ssl_ticket_key_block keyblock = nullptr;
   // Load the session ticket key if session tickets are not disabled
   if (sslMultCertSettings->session_ticket_enabled != 0) {
     keyblock = shared_ssl_ticket_key_block(ssl_context_enable_tickets(ctx.get(), nullptr), ticket_block_free);
@@ -1477,7 +1468,6 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL
       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, keyblock)) >= 0) {
           inserted = true;
         }
@@ -1491,9 +1481,8 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL
   // 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), cert, certname)) {
+  for (auto sni_name : names) {
+    if (SSLMultiCertConfigLoader::index_certificate(lookup, SSLCertContext(ctx, sslMultCertSettings), sni_name.c_str())) {
       inserted = true;
     }
   }
@@ -1508,10 +1497,6 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL
     ctx = nullptr;
   }
 
-  for (auto &i : cert_list) {
-    X509_free(i);
-  }
-
   return ctx.get();
 }
 
@@ -1658,7 +1643,7 @@ SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
   if (lookup->ssl_default == nullptr) {
     shared_SSLMultiCertConfigParams sslMultiCertSettings(new SSLMultiCertConfigParams);
     sslMultiCertSettings->addr = ats_strdup("*");
-    if (this->_store_ssl_ctx(lookup, sslMultiCertSettings) == nullptr) {
+    if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) {
       Error("failed set default context");
       return false;
     }
@@ -1919,20 +1904,27 @@ SSLConnect(SSL *ssl)
 }
 
 /**
-   Load certificates to SSL_CTX
-   @static
+ * Process the config to pull out the list of file names, and process the certs to get the list
+ * of subject and sni names.  Thanks to dual cert configurations, there may be mulitple files of each type.
+ * If some names are not in all the listed certs they are listed in the uniqe_names map, keyed by the index
+ * of the including certificate
  */
 bool
-SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_list, const SSLConfigParams *params,
-                                     const SSLMultiCertConfigParams *sslMultCertSettings)
+SSLMultiCertConfigLoader::load_certs_and_cross_reference_names(std::vector<X509 *> &cert_list,
+                                                               SSLMultiCertConfigLoader::CertLoadData &data,
+                                                               const SSLConfigParams *params,
+                                                               const SSLMultiCertConfigParams *sslMultCertSettings,
+                                                               std::set<std::string> &common_names,
+                                                               std::unordered_map<int, std::set<std::string>> &unique_names)
 {
-  SimpleTokenizer cert_tok((const char *)sslMultCertSettings->cert, SSL_CERT_SEPARATE_DELIM);
+  SimpleTokenizer cert_tok(sslMultCertSettings->cert ? (const char *)sslMultCertSettings->cert : "", SSL_CERT_SEPARATE_DELIM);
   SimpleTokenizer key_tok((sslMultCertSettings->key ? (const char *)sslMultCertSettings->key : ""), SSL_CERT_SEPARATE_DELIM);
 
   if (sslMultCertSettings->key && cert_tok.getNumTokensRemaining() != key_tok.getNumTokensRemaining()) {
     Error("the number of certificates in ssl_cert_name and ssl_key_name doesn't match");
     return false;
   }
+
   SimpleTokenizer ca_tok("", SSL_CERT_SEPARATE_DELIM);
   if (sslMultCertSettings->ca) {
     ca_tok.setString(sslMultCertSettings->ca);
@@ -1942,13 +1934,6 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_lis
     }
   }
 
-#if TS_USE_TLS_OCSP
-  if (SSLConfigParams::ssl_ocsp_enabled) {
-    Debug("ssl", "SSL OCSP Stapling is enabled");
-    SSL_CTX_set_tlsext_status_cb(ctx, ssl_callback_ocsp_stapling);
-  } else {
-    Debug("ssl", "SSL OCSP Stapling is disabled");
-  }
   SimpleTokenizer ocsp_tok("", SSL_CERT_SEPARATE_DELIM);
   if (sslMultCertSettings->ocsp_response) {
     ocsp_tok.setString(sslMultCertSettings->ocsp_response);
@@ -1957,14 +1942,141 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_lis
       return false;
     }
   }
+
+  for (const char *keyname = key_tok.getNext(); keyname; keyname = key_tok.getNext()) {
+    data.key_list.push_back(keyname);
+  }
+
+  for (const char *caname = ca_tok.getNext(); caname; caname = ca_tok.getNext()) {
+    data.ca_list.push_back(caname);
+  }
+
+  for (const char *ocspname = ocsp_tok.getNext(); ocspname; ocspname = ocsp_tok.getNext()) {
+    data.ocsp_list.push_back(ocspname);
+  }
+
+  bool first_pass = true;
+  int cert_index  = 0;
+  for (const char *certname = cert_tok.getNext(); certname; certname = cert_tok.getNext()) {
+    data.cert_names_list.push_back(certname);
+    std::string completeServerCertPath = Layout::relative_to(params->serverCertPathOnly, certname);
+    scoped_BIO bio(BIO_new_file(completeServerCertPath.c_str(), "r"));
+    X509 *cert = nullptr;
+    if (bio) {
+      cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
+    }
+    if (!bio || !cert) {
+      SSLError("failed to load certificate chain from %s", completeServerCertPath.c_str());
+      return false;
+    }
+
+    cert_list.push_back(cert);
+    if (SSLConfigParams::load_ssl_file_cb) {
+      SSLConfigParams::load_ssl_file_cb(completeServerCertPath.c_str());
+    }
+
+    std::set<std::string> name_set;
+    // Grub through the names in the certs
+    X509_NAME *subject = nullptr;
+
+    // Insert a key for the subject CN.
+    subject = X509_get_subject_name(cert);
+    ats_scoped_str subj_name;
+    if (subject) {
+      int pos = -1;
+      for (;;) {
+        pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos);
+        if (pos == -1) {
+          break;
+        }
+
+        X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, pos);
+        ASN1_STRING *cn    = X509_NAME_ENTRY_get_data(e);
+        subj_name          = asn1_strdup(cn);
+
+        Debug("ssl", "subj '%s' in certificate %s %p", (const char *)subj_name, certname, cert);
+        name_set.insert(subj_name.get());
+      }
+    }
+
+    // Traverse the subjectAltNames (if any) and insert additional keys for the SSL context.
+    GENERAL_NAMES *names = static_cast<GENERAL_NAMES *>(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+    if (names) {
+      unsigned count = sk_GENERAL_NAME_num(names);
+      for (unsigned i = 0; i < count; ++i) {
+        GENERAL_NAME *name;
+
+        name = sk_GENERAL_NAME_value(names, i);
+        if (name->type == GEN_DNS) {
+          ats_scoped_str dns(asn1_strdup(name->d.dNSName));
+          name_set.insert(dns.get());
+        }
+      }
+    }
+    if (first_pass) {
+      first_pass   = false;
+      common_names = name_set;
+    } else {
+      // Check that all elements in common_names are in name_set
+      for (auto name : common_names) {
+        auto iter = name_set.find(name);
+        if (iter == name_set.end()) {
+          // Common_name not in new set, move name to unique set
+          auto iter = unique_names.find(cert_index - 1);
+          if (iter == unique_names.end()) {
+            std::set<std::string> new_set;
+            new_set.insert(name);
+            unique_names.insert({cert_index - 1, new_set});
+          } else {
+            iter->second.insert(name);
+          }
+          auto erase_iter = common_names.find(name);
+          common_names.erase(erase_iter);
+        } else {
+          // New name already in common set, go ahead and remove it from further consideration
+          name_set.erase(iter);
+        }
+      }
+      // Anything still in name_set was not in common_names
+      for (auto name : name_set) {
+        auto iter = unique_names.find(cert_index);
+        if (iter == unique_names.end()) {
+          std::set<std::string> new_set;
+          new_set.insert(name);
+          unique_names.insert({cert_index, new_set});
+        } else {
+          iter->second.insert(name);
+        }
+      }
+    }
+    cert_index++;
+  }
+  return true;
+}
+
+/**
+   Load certificates to SSL_CTX
+   @static
+ */
+bool
+SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, SSLMultiCertConfigLoader::CertLoadData const &data,
+                                     const SSLConfigParams *params, const SSLMultiCertConfigParams *sslMultCertSettings)
+{
+#if TS_USE_TLS_OCSP
+  if (SSLConfigParams::ssl_ocsp_enabled) {
+    Debug("ssl", "SSL OCSP Stapling is enabled");
+    SSL_CTX_set_tlsext_status_cb(ctx, ssl_callback_ocsp_stapling);
+  } else {
+    Debug("ssl", "SSL OCSP Stapling is disabled");
+  }
 #else
   if (SSLConfigParams::ssl_ocsp_enabled) {
     Warning("failed to enable SSL OCSP Stapling; this version of OpenSSL does not support it");
   }
 #endif /* TS_USE_TLS_OCSP */
 
-  for (const char *certname = cert_tok.getNext(); certname; certname = cert_tok.getNext()) {
-    std::string completeServerCertPath = Layout::relative_to(params->serverCertPathOnly, certname);
+  for (size_t i = 0; i < data.cert_names_list.size(); i++) {
+    std::string completeServerCertPath = Layout::relative_to(params->serverCertPathOnly, data.cert_names_list[i]);
     scoped_BIO bio(BIO_new_file(completeServerCertPath.c_str(), "r"));
     X509 *cert = nullptr;
     if (bio) {
@@ -1983,12 +2095,11 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_lis
     // Load up any additional chain certificates
     SSL_CTX_add_extra_chain_cert_bio(ctx, bio);
 
-    const char *keyPath = key_tok.getNext();
+    const char *keyPath = data.key_list[i].c_str();
     if (!SSLPrivateKeyHandler(ctx, params, completeServerCertPath, keyPath)) {
       return false;
     }
 
-    cert_list.push_back(cert);
     if (SSLConfigParams::load_ssl_file_cb) {
       SSLConfigParams::load_ssl_file_cb(completeServerCertPath.c_str());
     }
@@ -2011,7 +2122,7 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_lis
 
     // Now, load any additional certificate chains specified in this entry.
     if (sslMultCertSettings->ca) {
-      const char *ca_name = ca_tok.getNext();
+      const char *ca_name = data.ca_list[i].c_str();
       if (ca_name != nullptr) {
         ats_scoped_str completeServerCertChainPath(Layout::relative_to(params->serverCertPathOnly, ca_name));
         if (!SSL_CTX_add_extra_chain_cert_file(ctx, completeServerCertChainPath)) {
@@ -2026,18 +2137,19 @@ SSLMultiCertConfigLoader::load_certs(SSL_CTX *ctx, std::vector<X509 *> &cert_lis
 #if TS_USE_TLS_OCSP
     if (SSLConfigParams::ssl_ocsp_enabled) {
       if (sslMultCertSettings->ocsp_response) {
-        const char *ocsp_response_name = ocsp_tok.getNext();
+        const char *ocsp_response_name = data.ocsp_list[i].c_str();
         ats_scoped_str completeOCSPResponsePath(Layout::relative_to(params->ssl_ocsp_response_path_only, ocsp_response_name));
-        if (!ssl_stapling_init_cert(ctx, cert, certname, (const char *)completeOCSPResponsePath)) {
-          Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", certname);
+        if (!ssl_stapling_init_cert(ctx, cert, data.cert_names_list[i].c_str(), (const char *)completeOCSPResponsePath)) {
+          Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", data.cert_names_list[i].c_str());
         }
       } else {
-        if (!ssl_stapling_init_cert(ctx, cert, certname, nullptr)) {
-          Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", certname);
+        if (!ssl_stapling_init_cert(ctx, cert, data.cert_names_list[i].c_str(), nullptr)) {
+          Warning("failed to configure SSL_CTX for OCSP Stapling info for certificate at %s", data.cert_names_list[i].c_str());
         }
       }
     }
 #endif /* TS_USE_TLS_OCSP */
+    X509_free(cert);
   }
 
   return true;
diff --git a/tests/gold_tests/tls/ssl/signed-foo-ec.key b/tests/gold_tests/tls/ssl/signed-foo-ec.key
new file mode 100644
index 0000000..cbf1418
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-foo-ec.key
@@ -0,0 +1,8 @@
+-----BEGIN EC PARAMETERS-----
+BggqhkjOPQMBBw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIG2eKyPB3C9Efe1s7MQm6wdbFpAsIbpovesRCIJnYMb4oAoGCCqGSM49
+AwEHoUQDQgAEpz5axf97f4X/kx6PN8nyjkx5ree8nPmRI/TB0rq3e0ldH32hCrVQ
+chq/mZzJCD3D/uuS27xJhl6t29JyABfAlw==
+-----END EC PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/signed-foo-ec.pem b/tests/gold_tests/tls/ssl/signed-foo-ec.pem
new file mode 100644
index 0000000..14289ad
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-foo-ec.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHjCCAYcCCQC81MtBCwmQvDANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAklMMRIwEAYDVQQHEwlDaGFtcGFpZ24xDjAMBgNVBAoTBVlh
+aG9vMQ0wCwYDVQQLEwRFZGdlMSgwJgYDVQQDEx9qdWljZXByb2R1Y2UuY29ycC5u
+ZTEueWFob28uY29tMSQwIgYJKoZIhvcNAQkBFhVwZXJzaWEuYXppekB5YWhvby5j
+b20wHhcNMjAwMjE3MjIzOTEyWhcNMzAwMjE0MjIzOTEyWjBQMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCSUwxEjAQBgNVBAcMCUNoYW1wYWlnbjEOMAwGA1UECgwFeWFo
+b28xEDAOBgNVBAMMB2Zvby5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASn
+PlrF/3t/hf+THo83yfKOTHmt57yc+ZEj9MHSurd7SV0ffaEKtVByGr+ZnMkIPcP+
+65LbvEmGXq3b0nIAF8CXMA0GCSqGSIb3DQEBCwUAA4GBAALTvnMS/uh2BmORNzh/
+MdxfJxmgJiAPGCclJGiAdAnRAUhR0i2XlSnkFiCzxbIc8rwv84beztmeRnnLUcJK
+Qc4eSdrsHyfH3g8eFmzNW0sVDaYOiXVRReif4wQzO0mf8a3m5tBWcwBt2VucO0bL
+Qh8dytlcF7egrVhXMVGHVwzk
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/ssl/signed-san-ec.key b/tests/gold_tests/tls/ssl/signed-san-ec.key
new file mode 100644
index 0000000..8dd236e
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-san-ec.key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIBh9PX40P1qCTpRxqbrxRyHsy1dAlEMZmUJpOZjgS2eXoAoGCCqGSM49
+AwEHoUQDQgAEBwvhzslwxPu7OdDlJFHpFoCTfa2zxqD7MI9AI5263g5Dov8Kcs1t
+Kr1GTz3sunVCEOjVv5ASNokITXFu7+Bvcw==
+-----END EC PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/signed-san-ec.pem b/tests/gold_tests/tls/ssl/signed-san-ec.pem
new file mode 100644
index 0000000..78cc9d4
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-san-ec.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICVTCCAb6gAwIBAgIJALzUy0ELCZDBMA0GCSqGSIb3DQEBCwUAMIGdMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCSUwxEjAQBgNVBAcTCUNoYW1wYWlnbjEOMAwGA1UE
+ChMFWWFob28xDTALBgNVBAsTBEVkZ2UxKDAmBgNVBAMTH2p1aWNlcHJvZHVjZS5j
+b3JwLm5lMS55YWhvby5jb20xJDAiBgkqhkiG9w0BCQEWFXBlcnNpYS5heml6QHlh
+aG9vLmNvbTAeFw0yMDAyMTgxNjM4MTZaFw0zMDAyMTUxNjM4MTZaMFkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJJTDESMBAGA1UEBwwJQ2hhbXBhaWduMRUwEwYDVQQK
+DAxWZXJpem9uTWVkaWExEjAQBgNVBAMMCWdyb3VwLmNvbTBZMBMGByqGSM49AgEG
+CCqGSM49AwEHA0IABAcL4c7JcMT7uznQ5SRR6RaAk32ts8ag+zCPQCOdut4OQ6L/
+CnLNbSq9Rk897Lp1QhDo1b+QEjaJCE1xbu/gb3OjJzAlMCMGA1UdEQQcMBqCB29u
+ZS5jb22CB3R3by5jb22CBmVjLmNvbTANBgkqhkiG9w0BAQsFAAOBgQCRgoh5YGCc
+V++/kil6USZaQ0TTNhAtCeao9p5WCN1NdHNtnulacu0cYPCI0cbpy2CVZC6JMrNE
+21SFssxKaeM1yoyIDEjIkr5IaCCOnr5XdOAO6/eISapkIPE/1GEbxyEgk1yqTJkr
+KB22uwprz1abKaQNBfTR2bV+57JlmnNuzg==
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/ssl/signed-san.key b/tests/gold_tests/tls/ssl/signed-san.key
new file mode 100644
index 0000000..aee6da6
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-san.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCtQayAL7EHSxEP
+uclHEa4mMh/pO5N8VULdVA6sHN9k5mUAEnL3p3nNsdVE9pLtHIxgcoXCcLAPG+eL
+bSAk+C/l+pubz8GAWHdOC/3xpdrzQjOiYzi2Megl6M+VpwaMOd0s14zdFZloS37y
+M7GnjLKIRO8dMz744SSAPZYIvnx1XAJ/zA9uLbYzvFWbwkuT8G2+Jo+IHl4FyMX2
+wTTXLeYf66RKUnTFcNG7ysNbUr7hDJR8E/lek00K3t8YGzYrqCtHIH3OLkpKNU3l
+HhWRQ0zrNRwfF+hWfYXq3SZ5+C1eo2RZe8SHPbNPbpJxGMMkxNRkCyWuMywoc2dA
+nm1HyI2nAgMBAAECggEAKkhOyvHYqEj/nvDeWEPOVnABLbBma/960/0Bn6tkMYGw
+wHXALQRoS3TM8Ymjjc5by+XnEu7haK6MsZAuOhd/yQaCF2J6fNIaO6fdj63EY32S
+kFzaqExBtY69qm4awPoWKi1oqUPuLm/OSVmoT5WctHjuShgJlD+N4uYkyXmDcjhd
+cED+Uy6LCK0TelhkN2J3TBMD1yI2bccORTVtihXWQhr7ROzXiyV5O143LFvSTNdr
+l8FlOQbJhIxThUmydecONi1PYAkP/ySYFqLjLfCmonoVJFdjdgJsiXBEtUJ/8CQY
++ar2vqRL4/4Y+9eerlZFy3e6lidofPu0YGTWdCTcUQKBgQDSvBW5+61DZ8p9f3UX
+04an8CyKgHDcH/lqKQMB8GvEhWCQG2nnrs6fQieiKj4P5bwuf01oNu4txxIg1GnS
+r5MTZfzCEe89X06vcwzcTOQsMfUlzx7qMpQaqzshwchFLpCD2pj2vGIlL+g6piN0
++X7RjRykBe0Nx5wYx3RYI78DCwKBgQDSeLuz2Q3n8Oqz+m9Zl+g1jMNgY861+zv5
+fZb43xXegef82RUata9ZGaRelODYdWmNiRGGAgfe1ddOW0xc3Ve1W9lonFLE84je
+Oc57S2q9LiVuKJbhDQ6br1eGLv8RywsUhNdWRBIH9Mn3+1nIXAtlHt7Jp3lDeVO/
+WJa3vvyBVQKBgFDTMsISdXHU7SUVLaPlzU+8HllAygijetXsxOqJe8v0HAUpfoUN
+1tHeXbUk3ojaZEKxMM83wkJsh9dvoObd0FswUrFcj5XKaDOCvPwBwcHxp0TJG+JX
+Y9aWtidMW7OtGGB6BxEbT8lTho54CkFjL/DPXpzKaRFP7d7TIRxtGWXhAoGANjTw
+KvbZNQaAfFAgw5NzM++IFlg+UfJd1Pj6nChgqokMpbuHSvTGL42CHvX7HuTGhbRq
+tffp7QNoS38KINTFFSmNyfqQ+ra6Znm+61RWLlknPMLpcRb6zzAOu7l46i1AMk2w
+ZEBt4Gy0Y9Dxo7/JE4cq3AbtHWqvHhYD41kmEW0CgYB2gL7IbeCeQG0wNOb0xHtu
+aXQzK5JUOut811QJLNPzxS+G70bbSRC9gkqFAizCSvCqzao7/3pw9unQzaR0h/3V
+OKEZF8angN9ORP5mOmlMQSvUtAZYfiuCMnZ4EeAhklA9hbAqGScevrdJUxnCN2c3
+DtR0mYMkmrdwISjW9aZnrg==
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/signed-san.pem b/tests/gold_tests/tls/ssl/signed-san.pem
new file mode 100644
index 0000000..15aeed3
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/signed-san.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIJALzUy0ELCZDAMA0GCSqGSIb3DQEBCwUAMIGdMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCSUwxEjAQBgNVBAcTCUNoYW1wYWlnbjEOMAwGA1UE
+ChMFWWFob28xDTALBgNVBAsTBEVkZ2UxKDAmBgNVBAMTH2p1aWNlcHJvZHVjZS5j
+b3JwLm5lMS55YWhvby5jb20xJDAiBgkqhkiG9w0BCQEWFXBlcnNpYS5heml6QHlh
+aG9vLmNvbTAeFw0yMDAyMTgxNjM2MTZaFw0zMDAyMTUxNjM2MTZaMFkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJJTDESMBAGA1UEBwwJQ2hhbXBhaWduMRUwEwYDVQQK
+DAxWZXJpem9uTWVkaWExEjAQBgNVBAMMCWdyb3VwLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAK1BrIAvsQdLEQ+5yUcRriYyH+k7k3xVQt1UDqwc
+32TmZQAScvenec2x1UT2ku0cjGByhcJwsA8b54ttICT4L+X6m5vPwYBYd04L/fGl
+2vNCM6JjOLYx6CXoz5WnBow53SzXjN0VmWhLfvIzsaeMsohE7x0zPvjhJIA9lgi+
+fHVcAn/MD24ttjO8VZvCS5Pwbb4mj4geXgXIxfbBNNct5h/rpEpSdMVw0bvKw1tS
+vuEMlHwT+V6TTQre3xgbNiuoK0cgfc4uSko1TeUeFZFDTOs1HB8X6FZ9herdJnn4
+LV6jZFl7xIc9s09uknEYwyTE1GQLJa4zLChzZ0CebUfIjacCAwEAAaMoMCYwJAYD
+VR0RBB0wG4IHb25lLmNvbYIHdHdvLmNvbYIHcnNhLmNvbTANBgkqhkiG9w0BAQsF
+AAOBgQA00nnSb9iqOa8EPJrkbEasuAqe5gw7ehDgaVHLxUrWeJUPwNJdnbYK4hLw
+qWeRKM6Qgxt8rjC/vqDjAxuNjHqFbdhL3supu2bHaBH5xFRqibY5rOY6AkL9SfMU
+r8Lj/NQvqtIzoFM81rhSTDRoHNazVv0TjbcZKTAT25ARX4HQIw==
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/ssl/signer.pem b/tests/gold_tests/tls/ssl/signer.pem
index 58b9b97..111cd07 100644
--- a/tests/gold_tests/tls/ssl/signer.pem
+++ b/tests/gold_tests/tls/ssl/signer.pem
@@ -1,18 +1,3 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXQIBAAKBgQDWMHOiUF+ORmZjAxI8MWE9dblb7gQSJ36WCXlPFiFx6ynF+S1E
-kXAYpIip5X0pzDUaIbLukxJUAAnOtMEO0PCgxJQUrEtRWh8wiJdbdQJF0Zs/9R+u
-SUgb61f+mdTQvhqefBGx+xrpfAcgtcWiZuSA9Q3fvpDj5WOWSPWXBUuxywIDAQAB
-AoGBAJPxRX2gjFAGWmQbU/YVmXfNH6navh8X/nx9sLeqrpE0AFeJI/ZPiqDKzMal
-B43eSfNxwVi+ZxN0L1ICUbL9KKZvHs/QBxWLA1fGVAXrz7sRplEVvakPpTfHoEnv
-sKaMWVKaK/S5WGbDhElb6zb/Lwo19DsIAPjGYqFvzFJBmobJAkEA9iSeTGkR9X26
-GywZoYrIMlRh34htOIRx1UUq88rFzdrCF21kQ4lhBIkX5OZMMy652i2gyak4OZTe
-YewIv8jw9QJBAN7EQNHG8jPwXfVp91/fqxVQEfumuP2i6uiWWYQgZCmla2+0xcLZ
-pMQ6sQEe10hhTrVnzHgAUVp50Ntn2jwBX78CQF09veGAI9d1Cxzj9cmmAvRd1r2Q
-tp8kPOLnUsALXib+6WtqewLCdcf8DtsdClyRJMIraq85tRzK8fryKNZNzkkCQEgA
-yS7FDj5JgCU15hZgFk1iPx3HCt44jZM2HaL+UUHAzRQjKxTLAl3G1rWVAWLMyQML
-lORoveLvotl4HOruSsMCQQCAx9dV9JUSFoyc1CWILp/FgUH/se4cjQCThGO0DoQQ
-vGTYmntY7j9WRJ9esQrjdD6Clw8zM/45GIBNwnXzqo7Z
------END RSA PRIVATE KEY-----
 -----BEGIN CERTIFICATE-----
 MIICszCCAhwCCQD4jSkztmlO1TANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC
 VVMxCzAJBgNVBAgTAklMMRIwEAYDVQQHEwlDaGFtcGFpZ24xDjAMBgNVBAoTBVlh
diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py
new file mode 100644
index 0000000..8c1a25d
--- /dev/null
+++ b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py
@@ -0,0 +1,127 @@
+'''
+'''
+#  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 ATS offering both RSA and EC certificates
+'''
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", select_ports=True, enable_tls=True)
+server = Test.MakeOriginServer("server", ssl=True)
+dns = Test.MakeDNServer("dns")
+
+request_header = {"headers": "GET / HTTP/1.1\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)
+
+# add ssl materials like key, certificates for the server
+ts.addSSLfile("ssl/signed-foo.pem")
+ts.addSSLfile("ssl/signed-foo.key")
+ts.addSSLfile("ssl/signed-foo-ec.pem")
+ts.addSSLfile("ssl/signed-foo-ec.key")
+ts.addSSLfile("ssl/signed-san.pem")
+ts.addSSLfile("ssl/signed-san.key")
+ts.addSSLfile("ssl/signed-san-ec.pem")
+ts.addSSLfile("ssl/signed-san-ec.key")
+ts.addSSLfile("ssl/signer.pem")
+ts.addSSLfile("ssl/signer.key")
+ts.addSSLfile("ssl/server.pem")
+ts.addSSLfile("ssl/server.key")
+
+ts.Disk.remap_config.AddLine(
+    'map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
+
+ts.Disk.ssl_multicert_config.AddLines([
+    'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem ssl_key_name=signed-foo-ec.key,signed-foo.key',
+    'ssl_cert_name=signed-san-ec.pem,signed-san.pem ssl_key_name=signed-san-ec.key,signed-san.key',
+    '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.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+    'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+    '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.url_remap.pristine_host_hdr': 1,
+    'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port),
+    'proxy.config.exec_thread.autoconfig.scale': 1.0,
+    'proxy.config.dns.resolv_conf': 'NULL',
+    'proxy.config.diags.debug.tags': 'ssl',
+    'proxy.config.diags.debug.enabled': 1
+})
+
+dns.addRecords(records={"foo.com.": ["127.0.0.1"]})
+dns.addRecords(records={"bar.com.": ["127.0.0.1"]})
+
+# Should receive a EC cert
+tr = Test.AddTestRun("Default for foo should return EC cert")
+tr.Setup.Copy("ssl/signer.pem")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername foo.com -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(dns)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port))
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: ECDSA", "Should select EC cert")
+
+# Should receive a RSA cert
+tr = Test.AddTestRun("Only offer RSA ciphers, should receive RSA cert")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername foo.com -sigalgs 'RSA-PSS+SHA256' -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: RSA-PSS", "Should select RSA cert")
+
+# Should receive a EC cert
+tr = Test.AddTestRun("Default for one.com should return EC cert")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername one.com -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: ECDSA", "Should select EC cert")
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("CN = group.com", "Should select a group SAN");
+
+# Should receive a RSA cert
+tr = Test.AddTestRun("Only offer RSA ciphers, should receive RSA cert")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername one.com -sigalgs 'RSA-PSS+SHA256' -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: RSA-PSS", "Should select RSA cert")
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("CN = group.com", "Should select a group SAN");
+
+# Should receive a RSA cert
+tr = Test.AddTestRun("rsa.com only in rsa cert")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername rsa.com -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: RSA-PSS", "Should select RSA cert")
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("CN = group.com", "Should select a group SAN");
+
+# Should receive a EC cert
+tr = Test.AddTestRun("ec.com only in ec cert")
+tr.Processes.Default.Command = "echo foo | openssl s_client -servername ec.com -connect 127.0.0.1:{0}".format(ts.Variables.ssl_port)
+tr.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("Peer signature type: ECDSA", "Should select EC cert")
+tr.Processes.Default.Streams.All += Testers.ContainsExpression("CN = group.com", "Should select a group SAN");
+