TS-462: Add ServerNameIndication support When we load a SSL certificate, parse out the subject CN and any subjectAltNames. If the OpenSSL version supports it, use the SNI extension to match the requested server name and swizzle the SSL_CTX appropriately.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/c5487c85 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/c5487c85 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/c5487c85 Branch: refs/heads/master Commit: c5487c852a02c00067b6064ec08663f696f1cc62 Parents: cad0e9b Author: James Peach <[email protected]> Authored: Mon Mar 5 22:19:29 2012 -0800 Committer: James Peach <[email protected]> Committed: Tue Mar 13 20:33:19 2012 -0700 ---------------------------------------------------------------------- iocore/net/SSLCertLookup.cc | 133 +++++++++++++++++++++++++++++++++- iocore/net/SSLNetProcessor.cc | 8 +- iocore/net/SSLNetVConnection.cc | 37 ++++++++++ 3 files changed, 170 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c5487c85/iocore/net/SSLCertLookup.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc index 0fbd0ef..42f6681 100644 --- a/iocore/net/SSLCertLookup.cc +++ b/iocore/net/SSLCertLookup.cc @@ -1,6 +1,6 @@ /** @file - A brief file description + SSL Context management @section license License @@ -25,6 +25,13 @@ #include "P_SSLCertLookup.h" #include "P_UnixNet.h" +#include "I_Layout.h" + +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/x509.h> +#include <openssl/asn1.h> +#include <openssl/ts.h> #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) // openssl returns a const SSL_METHOD typedef const SSL_METHOD * ink_ssl_method_t; @@ -34,6 +41,9 @@ typedef SSL_METHOD * ink_ssl_method_t; SSLCertLookup sslCertLookup; +static void +insert_ssl_certificate(InkHashTable *, SSL_CTX *, const char *); + #define SSL_IP_TAG "dest_ip" #define SSL_CERT_TAG "ssl_cert_name" #define SSL_PRIVATE_KEY_TAG "ssl_key_name" @@ -228,9 +238,21 @@ SSLCertLookup::addInfoToHash( // if (serverPrivateKey == NULL) // serverPrivateKey = cert; - ssl_NetProcessor.initSSLServerCTX(ctx, param, cert, caCert, serverPrivateKey, false); - ink_hash_table_insert(SSLCertLookupHashTable, strAddr, (void *) ctx); - return (true); + if (ssl_NetProcessor.initSSLServerCTX(ctx, this->param, cert, caCert, serverPrivateKey, false) == 0) { + char * certpath = Layout::relative_to(this->param->getServerCertPathOnly(), cert); + ink_hash_table_insert(SSLCertLookupHashTable, strAddr, (void *) ctx); + + // 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. + insert_ssl_certificate(SSLCertLookupHashTable, ctx, certpath); + + ats_free(certpath); + return (true); + } + + SSL_CTX_free(ctx); + return (false); } SSL_CTX * @@ -247,5 +269,108 @@ SSLCertLookup::findInfoInHash(char *strAddr) const SSLCertLookup::~SSLCertLookup() { + // XXX This is completely broken. You can't use ats_free to free + // a SSL_CTX *, you have to use SSL_CTX_free(). It doesn't matter + // right now because sslCertLookup is a singleton and never destroyed. ink_hash_table_destroy_and_xfree_values(SSLCertLookupHashTable); } + +struct ats_x509_certificate +{ + explicit ats_x509_certificate(X509 * x) : x509(x) {} + ~ats_x509_certificate() { if (x509) X509_free(x509); } + + X509 * x509; + +private: + ats_x509_certificate(const ats_x509_certificate&); + ats_x509_certificate& operator=(const ats_x509_certificate&); +}; + +struct ats_file_bio +{ + ats_file_bio(const char * path, const char * mode) + : bio(BIO_new_file(path, mode)) { + } + + ~ats_file_bio() { + (void)BIO_set_close(bio, BIO_CLOSE); + BIO_free(bio); + } + + operator bool() const { + return bio != NULL; + } + + BIO * bio; + +private: + ats_file_bio(const ats_file_bio&); + ats_file_bio& operator=(const ats_file_bio&); +}; + +static char * +asn1_strdup(ASN1_STRING * s) +{ + // Make sure we have an 8-bit encoding. + ink_assert(ASN1_STRING_type(s) == V_ASN1_IA5STRING || + ASN1_STRING_type(s) == V_ASN1_UTF8STRING || + ASN1_STRING_type(s) == V_ASN1_PRINTABLESTRING); + + return ats_strndup((const char *)ASN1_STRING_data(s), ASN1_STRING_length(s)); +} + +// Given a certificate and it's corresponding SSL_CTX context, insert hash +// table aliases for all of the subject and subjectAltNames. Note that we don't +// deal with wildcards (yet). +static void +insert_ssl_certificate(InkHashTable * htable, SSL_CTX * ctx, const char * certfile) +{ + GENERAL_NAMES * names = NULL; + X509_NAME * subject = NULL; + + ats_file_bio bio(certfile, "r"); + ats_x509_certificate certificate(PEM_read_bio_X509_AUX(bio.bio, NULL, NULL, NULL)); + + // Insert a key for the subject CN. + subject = X509_get_subject_name(certificate.x509); + 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); + char * name = asn1_strdup(cn); + + Debug("ssl", "mapping '%s' to certificate %s", name, certfile); + ink_hash_table_insert(htable, name, (void *)ctx); + ats_free(name); + } + } + + // Traverse the subjectAltNames (if any) and insert additional keys for the SSL context. + names = (GENERAL_NAMES *)X509_get_ext_d2i(certificate.x509, NID_subject_alt_name, NULL, NULL); + if (names) { + unsigned count = sk_GENERAL_NAME_num(names); + for (unsigned i = 0; i < count; ++i) { + GENERAL_NAME * name; + char * dns; + + name = sk_GENERAL_NAME_value(names, i); + switch (name->type) { + case GEN_DNS: + dns = asn1_strdup(name->d.dNSName); + Debug("ssl", "mapping '%s' to certificate %s", dns, certfile); + ink_hash_table_insert(htable, dns, (void *)ctx); + ats_free(dns); + break; + } + } + + GENERAL_NAMES_free(names); + } +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c5487c85/iocore/net/SSLNetProcessor.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLNetProcessor.cc b/iocore/net/SSLNetProcessor.cc index 89c31b0..e219480 100644 --- a/iocore/net/SSLNetProcessor.cc +++ b/iocore/net/SSLNetProcessor.cc @@ -348,12 +348,12 @@ SSLNetProcessor::initSSLServerCTX(SSL_CTX * lCtx, const SslConfigParams * param, } } - if (param->clientCertLevel == 2) + if (param->clientCertLevel == 2) { server_verify_client = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE; - else if (param->clientCertLevel == 1) + } else if (param->clientCertLevel == 1) { server_verify_client = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; - else // disable client cert support - { + } else { + // disable client cert support server_verify_client = SSL_VERIFY_NONE; Error("Illegal Client Certification Level in records.config\n"); } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/c5487c85/iocore/net/SSLNetVConnection.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index e619344..727e170 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -24,6 +24,10 @@ #include "P_Net.h" #include "P_SSLNextProtocolSet.h" +#if HAVE_OPENSSL_TLS1_H +#include <openssl/tls1.h> +#endif + #define SSL_READ_ERROR_NONE 0 #define SSL_READ_ERROR 1 #define SSL_READ_READY 2 @@ -42,6 +46,33 @@ ClassAllocator<SSLNetVConnection> sslNetVCAllocator("sslNetVCAllocator"); // Private // +#if TS_USE_TLS_SNI + +static int +ssl_servername_callback(SSL * ssl, int * ad, void * arg) +{ + SSL_CTX * ctx; + SSLCertLookup * lookup = (SSLCertLookup *) arg; + const char * servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + Debug("ssl", "ssl=%p ad=%d lookup=%p server=%s", ssl, *ad, lookup, servername); + + ctx = lookup->findInfoInHash((char *)servername); + if (ctx == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + + Debug("ssl", "found SSL context %p for requested name '%s'", ctx, servername); + SSL_set_SSL_CTX(ssl, ctx); + + // We need to return one of the SSL_TLSEXT_ERR constants. If we return an + // error, we can fill in *ad with an alert code to propgate to the + // client, see SSL_AD_*. + return SSL_TLSEXT_ERR_OK; +} + +#endif /* TS_USE_TLS_SNI */ + static SSL * make_ssl_connection(SSL_CTX * ctx, SSLNetVConnection * netvc) { @@ -458,6 +489,12 @@ SSLNetVConnection::sslStartHandShake(int event, int &err) if (ctx == NULL) { ctx = ssl_NetProcessor.ctx; } + +#if TS_USE_TLS_SNI + Debug("ssl", "setting SNI callbacks"); + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_callback); + SSL_CTX_set_tlsext_servername_arg(ctx, &sslCertLookup); +#endif } ssl = make_ssl_connection(ctx, this);
