From b18af854af17919ff501415ab1832815e850935d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 Feb 2013 23:27:05 +0900 Subject: [PATCH] shrpx: Add --subcert option to add additional certificate/private key This option specifies additional certificate and private key file. Shrpx will choose certificates based on the hostname indicated by client using TLS SNI extension. This option can be used multiple times. --- src/.gitignore | 1 + src/Makefile.am | 20 ++- src/shrpx-unittest.cc | 84 +++++++++++++ src/shrpx.cc | 29 ++++- src/shrpx_config.cc | 30 +++++ src/shrpx_config.h | 13 +- src/shrpx_listen_handler.cc | 5 +- src/shrpx_listen_handler.h | 2 +- src/shrpx_ssl.cc | 238 ++++++++++++++++++++++++++++++++---- src/shrpx_ssl.h | 70 ++++++++++- src/shrpx_ssl_test.cc | 91 ++++++++++++++ src/shrpx_ssl_test.h | 35 ++++++ src/util.cc | 9 -- src/util.h | 9 ++ 14 files changed, 590 insertions(+), 46 deletions(-) create mode 100644 src/shrpx-unittest.cc create mode 100644 src/shrpx_ssl_test.cc create mode 100644 src/shrpx_ssl_test.h diff --git a/src/.gitignore b/src/.gitignore index 8786971c..5ce3f421 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,4 @@ spdycat spdyd shrpx +shrpx-unittest diff --git a/src/Makefile.am b/src/Makefile.am index 4459db5e..c5e0b731 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,6 +31,8 @@ AM_LDFLAGS = @OPENSSL_LIBS@ @XML_LIBS@ @LIBEVENT_OPENSSL_LIBS@ @SRC_LIBS@ \ LDADD = $(top_builddir)/lib/libspdylay.la bin_PROGRAMS = spdycat spdyd +check_PROGRAMS = +TESTS = if HAVE_LIBEVENT_OPENSSL bin_PROGRAMS += shrpx @@ -72,9 +74,8 @@ spdyd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ spdyd.cc if HAVE_LIBEVENT_OPENSSL -shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ +SHRPX_SRCS = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_config.cc shrpx_config.h \ - shrpx.cc shrpx.h \ shrpx_error.h \ shrpx_listen_handler.cc shrpx_listen_handler.h \ shrpx_client_handler.cc shrpx_client_handler.h \ @@ -95,6 +96,21 @@ shrpx_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} \ shrpx_worker.cc shrpx_worker.h \ shrpx_accesslog.cc shrpx_accesslog.h\ http-parser/http_parser.c http-parser/http_parser.h + +shrpx_SOURCES = ${SHRPX_SRCS} shrpx.cc shrpx.h + +if HAVE_CUNIT +check_PROGRAMS += shrpx-unittest +shrpx_unittest_SOURCES = shrpx-unittest.cc \ + shrpx_ssl_test.cc shrpx_ssl_test.h\ + ${SHRPX_SRCS} +shrpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\ + -DSPDYLAY_TESTS_DIR=\"$(top_srcdir)/tests\" +shrpx_unittest_LDFLAGS = -static @OPENSSL_LIBS@ @LIBEVENT_OPENSSL_LIBS@\ + @CUNIT_LIBS@ @TESTS_LIBS@ +TESTS += shrpx-unittest +endif # HAVE_CUNIT + endif # HAVE_LIBEVENT_OPENSSL endif # ENABLE_SRC diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc new file mode 100644 index 00000000..34c6bdf8 --- /dev/null +++ b/src/shrpx-unittest.cc @@ -0,0 +1,84 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +/* include test cases' include files here */ +#include "shrpx_ssl_test.h" + +static int init_suite1(void) +{ + return 0; +} + +static int clean_suite1(void) +{ + return 0; +} + + +int main(int argc, char* argv[]) +{ + CU_pSuite pSuite = NULL; + unsigned int num_tests_failed; + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_library_init(); + + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + /* add a suite to the registry */ + pSuite = CU_add_suite("shrpx_TestSuite", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + + /* add the tests to the suite */ + if(!CU_add_test(pSuite, "ssl_create_lookup_tree", + shrpx::test_shrpx_ssl_create_lookup_tree) || + !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", + shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file)) { + CU_cleanup_registry(); + return CU_get_error(); + } + + /* Run all tests using the CUnit Basic interface */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_tests_failed = CU_get_number_of_tests_failed(); + CU_cleanup_registry(); + if(CU_get_error() == CUE_SUCCESS) { + return num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return CU_get_error(); + } +} diff --git a/src/shrpx.cc b/src/shrpx.cc index d6ec7024..3cbb94ff 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -242,7 +242,10 @@ int event_loop() { event_base *evbase = event_base_new(); - ListenHandler *listener_handler = new ListenHandler(evbase); + SSL_CTX *ssl_ctx = get_config()->client_mode ? + ssl::create_ssl_client_context() : get_config()->default_ssl_ctx; + + ListenHandler *listener_handler = new ListenHandler(evbase, ssl_ctx); if(get_config()->daemon) { if(daemon(0, 0) == -1) { @@ -379,6 +382,7 @@ void fill_default_config() mod_config()->backend_ipv4 = false; mod_config()->backend_ipv6 = false; mod_config()->tty = isatty(fileno(stderr)); + mod_config()->cert_tree = 0; } } // namespace @@ -481,6 +485,12 @@ void print_help(std::ostream& out) << " server's private key. If none is given and\n" << " the private key is password protected it'll\n" << " be requested interactively.\n" + << " --subcert=:\n" + << " Specify additional certificate and private\n" + << " key file. Shrpx will choose certificates\n" + << " based on the hostname indicated by client\n" + << " using TLS SNI extension. This option can be\n" + << " used multiple times.\n" << "\n" << " SPDY:\n" << " -c, --spdy-max-concurrent-streams=\n" @@ -587,6 +597,7 @@ int main(int argc, char **argv) {"backend-ipv6", no_argument, &flag, 21 }, {"private-key-passwd-file", required_argument, &flag, 22}, {"no-via", no_argument, &flag, 23}, + {"subcert", required_argument, &flag, 24}, {0, 0, 0, 0 } }; int option_index = 0; @@ -733,6 +744,10 @@ int main(int argc, char **argv) // --no-via cmdcfgs.push_back(std::make_pair(SHRPX_OPT_NO_VIA, "yes")); break; + case 24: + // --subcert + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_SUBCERT, optarg)); + break; default: break; } @@ -742,6 +757,13 @@ int main(int argc, char **argv) } } + // Initialize OpenSSL before parsing options because we create + // SSL_CTX there. + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + SSL_library_init(); + ssl::setup_ssl_lock(); + if(conf_exists(get_config()->conf_path)) { if(load_config(get_config()->conf_path) == -1) { LOG(FATAL) << "Failed to load configuration from " @@ -826,11 +848,6 @@ int main(int argc, char **argv) act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, 0); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - SSL_library_init(); - ssl::setup_ssl_lock(); - event_loop(); ssl::teardown_ssl_lock(); diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 9eca8b8f..601ce2cf 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -37,6 +37,7 @@ #include #include "shrpx_log.h" +#include "shrpx_ssl.h" #include "util.h" using namespace spdylay; @@ -46,6 +47,7 @@ namespace shrpx { const char SHRPX_OPT_PRIVATE_KEY_FILE[] = "private-key-file"; const char SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE[] = "private-key-passwd-file"; const char SHRPX_OPT_CERTIFICATE_FILE[] = "certificate-file"; +const char SHRPX_OPT_SUBCERT[] = "subcert"; const char SHRPX_OPT_BACKEND[] = "backend"; const char SHRPX_OPT_FRONTEND[] = "frontend"; @@ -274,6 +276,21 @@ int parse_config(const char *opt, const char *optarg) set_config_str(&mod_config()->private_key_passwd, passwd.c_str()); } else if(util::strieq(opt, SHRPX_OPT_CERTIFICATE_FILE)) { set_config_str(&mod_config()->cert_file, optarg); + } else if(util::strieq(opt, SHRPX_OPT_SUBCERT)) { + // Private Key file and certificate file separated by ':'. + const char *sp = strchr(optarg, ':'); + if(sp) { + std::string keyfile(optarg, sp); + // TODO Do we need private key for subcert? + SSL_CTX *ssl_ctx = ssl::create_ssl_context(keyfile.c_str(), sp+1); + if(!get_config()->cert_tree) { + mod_config()->cert_tree = ssl::cert_lookup_tree_new(); + } + if(ssl::cert_lookup_tree_add_cert_from_file(get_config()->cert_tree, + ssl_ctx, sp+1) == -1) { + return -1; + } + } } else if(util::strieq(opt, SHRPX_OPT_SYSLOG)) { mod_config()->syslog = util::strieq(optarg, "yes"); } else if(util::strieq(opt, SHRPX_OPT_SYSLOG_FACILITY)) { @@ -303,6 +320,19 @@ int parse_config(const char *opt, const char *optarg) LOG(ERROR) << "Unknown option: " << opt; return -1; } + if(get_config()->cert_file && get_config()->private_key_file) { + mod_config()->default_ssl_ctx = + ssl::create_ssl_context(get_config()->private_key_file, + get_config()->cert_file); + if(get_config()->cert_tree) { + if(ssl::cert_lookup_tree_add_cert_from_file(get_config()->cert_tree, + get_config()->default_ssl_ctx, + get_config()->cert_file) + == -1) { + return -1; + } + } + } return 0; } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 08ffa21d..c05c556e 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -32,13 +32,22 @@ #include #include #include +#include + +#include namespace shrpx { +namespace ssl { + +struct CertLookupTree; + +} // namespace ssl + extern const char SHRPX_OPT_PRIVATE_KEY_FILE[]; extern const char SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE[]; extern const char SHRPX_OPT_CERTIFICATE_FILE[]; - +extern const char SHRPX_OPT_SUBCERT[]; extern const char SHRPX_OPT_BACKEND[]; extern const char SHRPX_OPT_FRONTEND[]; extern const char SHRPX_OPT_WORKERS[]; @@ -85,6 +94,8 @@ struct Config { char *private_key_file; char *private_key_passwd; char *cert_file; + SSL_CTX *default_ssl_ctx; + ssl::CertLookupTree *cert_tree; bool verify_client; const char *server_name; char *downstream_host; diff --git a/src/shrpx_listen_handler.cc b/src/shrpx_listen_handler.cc index 4a9cf4ba..d617a7af 100644 --- a/src/shrpx_listen_handler.cc +++ b/src/shrpx_listen_handler.cc @@ -40,10 +40,9 @@ namespace shrpx { -ListenHandler::ListenHandler(event_base *evbase) +ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx) : evbase_(evbase), - ssl_ctx_(get_config()->client_mode ? - ssl::create_ssl_client_context() : ssl::create_ssl_context()), + ssl_ctx_(ssl_ctx), worker_round_robin_cnt_(0), workers_(0), num_worker_(0), diff --git a/src/shrpx_listen_handler.h b/src/shrpx_listen_handler.h index 80409bc6..cbfc02df 100644 --- a/src/shrpx_listen_handler.h +++ b/src/shrpx_listen_handler.h @@ -46,7 +46,7 @@ class SpdySession; class ListenHandler { public: - ListenHandler(event_base *evbase); + ListenHandler(event_base *evbase, SSL_CTX *ssl_ctx); ~ListenHandler(); int accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen); void create_worker_thread(size_t num); diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index d6e17ae3..5929118c 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -106,7 +106,25 @@ int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) } } // namespace -SSL_CTX* create_ssl_context() +namespace { +int servername_callback(SSL *ssl, int *al, void *arg) +{ + if(get_config()->cert_tree) { + const char *hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if(hostname) { + SSL_CTX *ssl_ctx = cert_lookup_tree_lookup(get_config()->cert_tree, + hostname, strlen(hostname)); + if(ssl_ctx) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + } + } + } + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace + +SSL_CTX* create_ssl_context(const char *private_key_file, + const char *cert_file) { SSL_CTX *ssl_ctx; ssl_ctx = SSL_CTX_new(SSLv23_server_method()); @@ -137,15 +155,13 @@ SSL_CTX* create_ssl_context() SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)get_config()); } - if(SSL_CTX_use_PrivateKey_file(ssl_ctx, - get_config()->private_key_file, + if(SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, SSL_FILETYPE_PEM) != 1) { LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " << ERR_error_string(ERR_get_error(), NULL); DIE(); } - if(SSL_CTX_use_certificate_chain_file(ssl_ctx, - get_config()->cert_file) != 1) { + if(SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " << ERR_error_string(ERR_get_error(), NULL); DIE(); @@ -161,6 +177,8 @@ SSL_CTX* create_ssl_context() SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback); } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); + // We speak "http/1.1", "spdy/2" and "spdy/3". const char *protos[] = { "spdy/3", "spdy/2", "http/1.1" }; set_npn_prefs(proto_list, protos, 3); @@ -360,23 +378,11 @@ int verify_hostname(const char *hostname, } } // namespace -int check_cert(SSL *ssl) +void get_altnames(X509 *cert, + std::vector& dns_names, + std::vector& ip_addrs, + std::string& common_name) { - X509 *cert = SSL_get_peer_certificate(ssl); - if(!cert) { - LOG(ERROR) << "No certificate found"; - return -1; - } - util::auto_delete cert_deleter(cert, X509_free); - long verify_res = SSL_get_verify_result(ssl); - if(verify_res != X509_V_OK) { - LOG(ERROR) << "Certificate verification failed: " - << X509_verify_cert_error_string(verify_res); - return -1; - } - std::string common_name; - std::vector dns_names; - std::vector ip_addrs; GENERAL_NAMES* altnames; altnames = reinterpret_cast (X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0)); @@ -411,8 +417,8 @@ int check_cert(SSL *ssl) } X509_NAME *subjectname = X509_get_subject_name(cert); if(!subjectname) { - LOG(ERROR) << "Could not get X509 name object from the certificate."; - return -1; + LOG(WARNING) << "Could not get X509 name object from the certificate."; + return; } int lastpos = -1; while(1) { @@ -435,6 +441,26 @@ int check_cert(SSL *ssl) OPENSSL_free(out); break; } +} + +int check_cert(SSL *ssl) +{ + X509 *cert = SSL_get_peer_certificate(ssl); + if(!cert) { + LOG(ERROR) << "No certificate found"; + return -1; + } + util::auto_delete cert_deleter(cert, X509_free); + long verify_res = SSL_get_verify_result(ssl); + if(verify_res != X509_V_OK) { + LOG(ERROR) << "Certificate verification failed: " + << X509_verify_cert_error_string(verify_res); + return -1; + } + std::string common_name; + std::vector dns_names; + std::vector ip_addrs; + get_altnames(cert, dns_names, ip_addrs, common_name); if(verify_hostname(get_config()->downstream_host, &get_config()->downstream_addr, get_config()->downstream_addrlen, @@ -482,6 +508,172 @@ void teardown_ssl_lock() delete [] ssl_locks; } +CertLookupTree* cert_lookup_tree_new() +{ + CertLookupTree *tree = new CertLookupTree(); + CertNode *root = new CertNode(); + root->ssl_ctx = 0; + root->c = 0; + tree->root = root; + return tree; +} + +namespace { +void cert_node_del(CertNode *node) +{ + for(std::vector::iterator i = node->next.begin(), + eoi = node->next.end(); i != eoi; ++i) { + cert_node_del(*i); + } + for(std::vector >::iterator i = + node->wildcard_certs.begin(), eoi = node->wildcard_certs.end(); + i != eoi; ++i) { + delete [] (*i).first; + } + delete node; +} +} // namespace + +void cert_lookup_tree_del(CertLookupTree *lt) +{ + cert_node_del(lt->root); + delete lt; +} + +namespace { +// The |offset| is the index in the hostname we are examining. +void cert_lookup_tree_add_cert(CertLookupTree *lt, CertNode *node, + SSL_CTX *ssl_ctx, + const char *hostname, size_t len, int offset) +{ + if(offset == -1) { + if(!node->ssl_ctx) { + node->ssl_ctx = ssl_ctx; + } + return; + } + int i, next_len = node->next.size(); + char c = util::lowcase(hostname[offset]); + for(i = 0; i < next_len; ++i) { + if(node->next[i]->c == c) { + break; + } + } + if(i == next_len) { + CertNode *parent = node; + int j; + for(j = offset; j >= 0; --j) { + if(hostname[j] == '*') { + // We assume hostname as wildcard hostname when first '*' is + // encountered. Note that as per RFC 6125 (6.4.3), there are + // some restrictions for wildcard hostname. We just ignore + // these rules here but do the proper check when we do the + // match. + char *hostcopy = strdup(hostname); + for(int k = 0; hostcopy[k]; ++k) { + hostcopy[k] = util::lowcase(hostcopy[k]); + } + parent->wildcard_certs.push_back(std::make_pair(hostcopy, ssl_ctx)); + break; + } + CertNode *new_node = new CertNode(); + new_node->ssl_ctx = 0; + new_node->c = util::lowcase(hostname[j]); + parent->next.push_back(new_node); + parent = new_node; + } + if(j == -1) { + // non-wildcard hostname, exact match case. + parent->ssl_ctx = ssl_ctx; + } + } else { + cert_lookup_tree_add_cert(lt, node->next[i], ssl_ctx, + hostname, len, offset-1); + } +} +} // namespace + +void cert_lookup_tree_add_cert(CertLookupTree *lt, SSL_CTX *ssl_ctx, + const char *hostname, size_t len) +{ + if(len == 0) { + return; + } + cert_lookup_tree_add_cert(lt, lt->root, ssl_ctx, hostname, len, len-1); +} + +namespace { + +SSL_CTX* cert_lookup_tree_lookup(CertLookupTree *lt, CertNode *node, + const char *hostname, size_t len, int offset) +{ + if(offset == -1) { + if(node->ssl_ctx) { + return node->ssl_ctx; + } else { + // Do not perform wildcard-match because '*' must match at least + // one character. + return 0; + } + } + for(std::vector >::iterator i = + node->wildcard_certs.begin(), eoi = node->wildcard_certs.end(); + i != eoi; ++i) { + if(tls_hostname_match((*i).first, hostname)) { + return (*i).second; + } + } + char c = util::lowcase(hostname[offset]); + for(std::vector::iterator i = node->next.begin(), + eoi = node->next.end(); i != eoi; ++i) { + if((*i)->c == c) { + return cert_lookup_tree_lookup(lt, *i, hostname, len, offset-1); + } + } + return 0; +} +} // namespace + +SSL_CTX* cert_lookup_tree_lookup(CertLookupTree *lt, + const char *hostname, size_t len) +{ + return cert_lookup_tree_lookup(lt, lt->root, hostname, len, len-1); +} + + +int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, + const char *certfile) +{ + BIO *bio = BIO_new(BIO_s_file()); + if(!bio) { + LOG(ERROR) << "BIO_new failed"; + return -1; + } + util::auto_delete bio_deleter(bio, BIO_vfree); + if(!BIO_read_filename(bio, certfile)) { + LOG(ERROR) << "Could not read certificate file '" << certfile << "'"; + return -1; + } + X509 *cert = PEM_read_bio_X509(bio, 0, 0, 0); + if(!cert) { + LOG(ERROR) << "Could not read X509 structure from file '" + << certfile << "'"; + return -1; + } + util::auto_delete cert_deleter(cert, X509_free); + std::string common_name; + std::vector dns_names; + std::vector ip_addrs; + get_altnames(cert, dns_names, ip_addrs, common_name); + for(std::vector::iterator i = dns_names.begin(), + eoi = dns_names.end(); i != eoi; ++i) { + cert_lookup_tree_add_cert(lt, ssl_ctx, (*i).c_str(), (*i).size()); + } + cert_lookup_tree_add_cert(lt, ssl_ctx, common_name.c_str(), + common_name.size()); + return 0; +} + } // namespace ssl } // namespace shrpx diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index 9f5cf87d..0f3b60b5 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -27,6 +27,8 @@ #include "shrpx.h" +#include + #include #include @@ -38,7 +40,8 @@ class ClientHandler; namespace ssl { -SSL_CTX* create_ssl_context(); +SSL_CTX* create_ssl_context(const char *private_key_file, + const char *cert_file); SSL_CTX* create_ssl_client_context(); @@ -54,6 +57,71 @@ void setup_ssl_lock(); void teardown_ssl_lock(); +// Retrieves DNS and IP address in subjectAltNames and commonName from +// the |cert|. +void get_altnames(X509 *cert, + std::vector& dns_names, + std::vector& ip_addrs, + std::string& common_name); + +// CertLookupTree forms lookup tree to get SSL_CTX whose DNS or +// commonName matches hostname in query. The tree is trie data +// structure form from the tail of the hostname pattern. Each CertNode +// contains one ASCII character in the c member and the next member +// contains the following CertNode pointers ('following' means +// character before the current one). The CertNode where a hostname +// pattern ends contains its SSL_CTX pointer in the ssl_ctx member. +// For wildcard hostname pattern, we store the its pattern and SSL_CTX +// in CertNode one before first "*" found from the tail. +// +// When querying SSL_CTX with particular hostname, we match from its +// tail in our lookup tree. If the query goes to the first character +// of the hostname and current CertNode has non-NULL ssl_ctx member, +// then it is the exact match. The ssl_ctx member is returned. Along +// the way, if CertNode which contains non-empty wildcard_certs member +// is encountered, wildcard hostname matching is performed against +// them. If there is a match, its SSL_CTX is returned. If none +// matches, query is continued to the next character. + +struct CertNode { + // SSL_CTX for exact match + SSL_CTX *ssl_ctx; + // list of wildcard domain name and its SSL_CTX pair, the wildcard + // '*' appears in this position. + std::vector > wildcard_certs; + // ASCII byte in this position + char c; + // Next CertNode index of CertLookupTree::nodes + std::vector next; +}; + +struct CertLookupTree { + std::vector certs; + CertNode *root; +}; + +CertLookupTree* cert_lookup_tree_new(); +void cert_lookup_tree_del(CertLookupTree *lt); + +// Adds |ssl_ctx| with hostname pattern |hostname| with length |len| +// to the lookup tree |lt|. The |hostname| must be NULL-terminated. +void cert_lookup_tree_add_cert(CertLookupTree *lt, SSL_CTX *ssl_ctx, + const char *hostname, size_t len); + +// Looks up SSL_CTX using the given |hostname| with length |len|. If +// more than one SSL_CTX which matches the query, it is undefined +// which one is returned. The |hostname| must be NULL-terminated. If +// no matching SSL_CTX found, returns NULL. +SSL_CTX* cert_lookup_tree_lookup(CertLookupTree *lt, const char *hostname, + size_t len); + +// Adds |ssl_ctx| to lookup tree |lt| using hostnames read from +// |certfile|. The subjectAltNames and commonName are considered as +// eligible hostname. This function returns 0 if it succeeds, or -1. +// Even if no ssl_ctx is added to tree, this function returns 0. +int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, + const char *certfile); + } // namespace ssl } // namespace shrpx diff --git a/src/shrpx_ssl_test.cc b/src/shrpx_ssl_test.cc new file mode 100644 index 00000000..75eaaa7c --- /dev/null +++ b/src/shrpx_ssl_test.cc @@ -0,0 +1,91 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_ssl_test.h" + +#include + +#include "shrpx_ssl.h" + +namespace shrpx { + +void test_shrpx_ssl_create_lookup_tree(void) +{ + ssl::CertLookupTree* tree = ssl::cert_lookup_tree_new(); + SSL_CTX *ctxs[] = {SSL_CTX_new(TLSv1_method()), + SSL_CTX_new(TLSv1_method()), + SSL_CTX_new(TLSv1_method()), + SSL_CTX_new(TLSv1_method()), + SSL_CTX_new(TLSv1_method())}; + const char *hostnames[] = { "example.com", + "www.example.org", + "*www.example.org", + "x*.host.domain", + "*yy.host.domain"}; + int num = sizeof(ctxs)/sizeof(ctxs[0]); + for(int i = 0; i < num; ++i) { + ssl::cert_lookup_tree_add_cert(tree, ctxs[i], hostnames[i], + strlen(hostnames[i])); + } + + CU_ASSERT(ctxs[0] == ssl::cert_lookup_tree_lookup(tree, hostnames[0], + strlen(hostnames[0]))); + CU_ASSERT(ctxs[1] == ssl::cert_lookup_tree_lookup(tree, hostnames[1], + strlen(hostnames[1]))); + const char h1[] = "2www.example.org"; + CU_ASSERT(ctxs[2] == ssl::cert_lookup_tree_lookup(tree, h1, strlen(h1))); + const char h2[] = "www2.example.org"; + CU_ASSERT(0 == ssl::cert_lookup_tree_lookup(tree, h2, strlen(h2))); + const char h3[] = "x1.host.domain"; + CU_ASSERT(ctxs[3] == ssl::cert_lookup_tree_lookup(tree, h3, strlen(h3))); + // Does not match *yy.host.domain, because * must match at least 1 + // character. + const char h4[] = "yy.host.domain"; + CU_ASSERT(0 == ssl::cert_lookup_tree_lookup(tree, h4, strlen(h4))); + const char h5[] = "zyy.host.domain"; + CU_ASSERT(ctxs[4] == ssl::cert_lookup_tree_lookup(tree, h5, strlen(h5))); + CU_ASSERT(0 == ssl::cert_lookup_tree_lookup(tree, "", 0)); + + ssl::cert_lookup_tree_del(tree); + for(int i = 0; i < num; ++i) { + SSL_CTX_free(ctxs[i]); + } +} + +void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void) +{ + int rv; + ssl::CertLookupTree* tree = ssl::cert_lookup_tree_new(); + SSL_CTX *ssl_ctx = SSL_CTX_new(TLSv1_method()); + const char certfile[] = SPDYLAY_TESTS_DIR"/testdata/cacert.pem"; + rv = ssl::cert_lookup_tree_add_cert_from_file(tree, ssl_ctx, certfile); + CU_ASSERT(0 == rv); + const char localhost[] = "localhost"; + CU_ASSERT(ssl_ctx == ssl::cert_lookup_tree_lookup(tree, localhost, + sizeof(localhost)-1)); + ssl::cert_lookup_tree_del(tree); + SSL_CTX_free(ssl_ctx); +} + +} // namespace shrpx diff --git a/src/shrpx_ssl_test.h b/src/shrpx_ssl_test.h new file mode 100644 index 00000000..ff9967f8 --- /dev/null +++ b/src/shrpx_ssl_test.h @@ -0,0 +1,35 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHRPX_SSL_TEST_H +#define SHRPX_SSL_TEST_H + +namespace shrpx { + +void test_shrpx_ssl_create_lookup_tree(void); +void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void); + +} // namespace shrpx + +#endif /* SHRPX_SSL_TEST_H */ diff --git a/src/util.cc b/src/util.cc index f491db57..1a3235a2 100644 --- a/src/util.cc +++ b/src/util.cc @@ -182,15 +182,6 @@ char upcase(char c) } } -char lowcase(char c) -{ - if('A' <= c && c <= 'Z') { - return c-'A'+'a'; - } else { - return c; - } -} - } // namespace util } // namespace spdylay diff --git a/src/util.h b/src/util.h index 27bd1794..8f1d7682 100644 --- a/src/util.h +++ b/src/util.h @@ -298,6 +298,15 @@ char upcase(char c); char lowcase(char c); +inline char lowcase(char c) +{ + if('A' <= c && c <= 'Z') { + return c-'A'+'a'; + } else { + return c; + } +} + template std::string utos(T n) {