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");
+