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) {