diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 5beef6fa..86eb22b8 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -48,7 +48,7 @@ struct LogFragment; namespace ssl { -struct CertLookupTree; +class CertLookupTree; } // namespace ssl diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index e6e1aeb8..834dd34d 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -51,6 +51,7 @@ #include "shrpx_downstream_connection_pool.h" #include "util.h" #include "ssl.h" +#include "template.h" using namespace nghttp2; @@ -133,8 +134,7 @@ int servername_callback(SSL *ssl, int *al, void *arg) { if (cert_tree) { const char *hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (hostname) { - auto ssl_ctx = - cert_lookup_tree_lookup(cert_tree, hostname, strlen(hostname)); + auto ssl_ctx = cert_tree->lookup(hostname, strlen(hostname)); if (ssl_ctx) { SSL_set_SSL_CTX(ssl, ssl_ctx); } @@ -720,44 +720,22 @@ int check_cert(SSL *ssl) { return 0; } -CertLookupTree *cert_lookup_tree_new() { - auto tree = new CertLookupTree(); - auto root = new CertNode(); - root->ssl_ctx = 0; - root->str = 0; - root->first = root->last = 0; - tree->root = root; - return tree; -} - -namespace { -void cert_node_del(CertNode *node) { - for (auto &a : node->next) { - cert_node_del(a); - } - delete node; -} -} // namespace - -void cert_lookup_tree_del(CertLookupTree *lt) { - cert_node_del(lt->root); - for (auto &s : lt->hosts) { - delete[] s; - } - delete lt; +CertLookupTree::CertLookupTree() { + root_.ssl_ctx = nullptr; + root_.str = nullptr; + root_.first = root_.last = 0; } namespace { // The |offset| is the index in the hostname we are examining. We are // going to scan from |offset| in backwards. -void cert_lookup_tree_add_cert(CertLookupTree *lt, CertNode *node, - SSL_CTX *ssl_ctx, char *hostname, size_t len, - int offset) { +void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname, + size_t len, int offset) { int i, next_len = node->next.size(); char c = hostname[offset]; CertNode *cn = nullptr; for (i = 0; i < next_len; ++i) { - cn = node->next[i]; + cn = node->next[i].get(); if (cn->str[cn->first] == c) { break; } @@ -769,85 +747,92 @@ void cert_lookup_tree_add_cert(CertLookupTree *lt, CertNode *node, // some restrictions for wildcard hostname. We just ignore // these rules here but do the proper check when we do the // match. - node->wildcard_certs.push_back(std::make_pair(hostname, ssl_ctx)); - } else { - int j; - auto new_node = new CertNode(); - new_node->str = hostname; - new_node->first = offset; - // If wildcard is found, set the region before it because we - // don't include it in [first, last). - for (j = offset; j >= 0 && hostname[j] != '*'; --j) - ; - new_node->last = j; - if (j == -1) { - new_node->ssl_ctx = ssl_ctx; - } else { - new_node->ssl_ctx = nullptr; - new_node->wildcard_certs.push_back(std::make_pair(hostname, ssl_ctx)); - } - node->next.push_back(new_node); + node->wildcard_certs.emplace_back(hostname, ssl_ctx); + return; } - } else { + int j; - for (i = cn->first, j = offset; - i > cn->last && j >= 0 && cn->str[i] == hostname[j]; --i, --j) + auto new_node = make_unique(); + new_node->str = hostname; + new_node->first = offset; + // If wildcard is found, set the region before it because we + // don't include it in [first, last). + for (j = offset; j >= 0 && hostname[j] != '*'; --j) ; - if (i == cn->last) { - if (j == -1) { - if (cn->ssl_ctx) { - // same hostname, we don't overwrite exiting ssl_ctx - } else { - cn->ssl_ctx = ssl_ctx; - } - } else { - // The existing hostname is a suffix of this hostname. - // Continue matching at potion j. - cert_lookup_tree_add_cert(lt, cn, ssl_ctx, hostname, len, j); - } + new_node->last = j; + if (j == -1) { + new_node->ssl_ctx = ssl_ctx; } else { - auto new_node = new CertNode(); - new_node->ssl_ctx = cn->ssl_ctx; - new_node->str = cn->str; - new_node->first = i; - new_node->last = cn->last; - new_node->wildcard_certs.swap(cn->wildcard_certs); - new_node->next.swap(cn->next); - - cn->next.push_back(new_node); - - cn->last = i; - if (j == -1) { - // This hostname is a suffix of the existing hostname. - cn->ssl_ctx = ssl_ctx; - } else { - // This hostname and existing one share suffix. - cn->ssl_ctx = nullptr; - cert_lookup_tree_add_cert(lt, cn, ssl_ctx, hostname, len, j); - } + new_node->ssl_ctx = nullptr; + new_node->wildcard_certs.emplace_back(hostname, ssl_ctx); } + node->next.push_back(std::move(new_node)); + return; } + + int j; + for (i = cn->first, j = offset; + i > cn->last && j >= 0 && cn->str[i] == hostname[j]; --i, --j) + ; + if (i == cn->last) { + if (j == -1) { + // If the same hostname already exists, we don't overwrite + // exiting ssl_ctx + if (!cn->ssl_ctx) { + cn->ssl_ctx = ssl_ctx; + } + return; + } + + // The existing hostname is a suffix of this hostname. Continue + // matching at potion j. + cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j); + return; + } + + { + auto new_node = make_unique(); + new_node->ssl_ctx = cn->ssl_ctx; + new_node->str = cn->str; + new_node->first = i; + new_node->last = cn->last; + new_node->wildcard_certs.swap(cn->wildcard_certs); + new_node->next.swap(cn->next); + + cn->next.push_back(std::move(new_node)); + } + + cn->last = i; + if (j == -1) { + // This hostname is a suffix of the existing hostname. + cn->ssl_ctx = ssl_ctx; + return; + } + + // This hostname and existing one share suffix. + cn->ssl_ctx = nullptr; + cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j); } } // namespace -void cert_lookup_tree_add_cert(CertLookupTree *lt, SSL_CTX *ssl_ctx, - const char *hostname, size_t len) { +void CertLookupTree::add_cert(SSL_CTX *ssl_ctx, const char *hostname, + size_t len) { if (len == 0) { return; } // Copy hostname including terminal NULL - char *host_copy = new char[len + 1]; + hosts_.push_back(make_unique(len + 1)); + const auto &host_copy = hosts_.back(); for (size_t i = 0; i < len; ++i) { host_copy[i] = util::lowcase(hostname[i]); } host_copy[len] = '\0'; - lt->hosts.push_back(host_copy); - cert_lookup_tree_add_cert(lt, lt->root, ssl_ctx, host_copy, len, len - 1); + cert_lookup_tree_add_cert(&root_, ssl_ctx, host_copy.get(), len, len - 1); } namespace { -SSL_CTX *cert_lookup_tree_lookup(CertLookupTree *lt, CertNode *node, - const char *hostname, size_t len, int offset) { +SSL_CTX *cert_lookup_tree_lookup(CertNode *node, const char *hostname, + size_t len, int offset) { int i, j; for (i = node->first, j = offset; i > node->last && j >= 0 && node->str[i] == util::lowcase(hostname[j]); @@ -860,30 +845,29 @@ SSL_CTX *cert_lookup_tree_lookup(CertLookupTree *lt, CertNode *node, if (node->ssl_ctx) { // exact match return node->ssl_ctx; - } else { - // Do not perform wildcard-match because '*' must match at least - // one character. - return nullptr; } + + // Do not perform wildcard-match because '*' must match at least + // one character. + return nullptr; } - for (auto &wildcert : node->wildcard_certs) { + for (const auto &wildcert : node->wildcard_certs) { if (tls_hostname_match(wildcert.first, hostname)) { return wildcert.second; } } - char c = util::lowcase(hostname[j]); - for (auto &next_node : node->next) { + auto c = util::lowcase(hostname[j]); + for (const auto &next_node : node->next) { if (next_node->str[next_node->first] == c) { - return cert_lookup_tree_lookup(lt, next_node, hostname, len, j); + return cert_lookup_tree_lookup(next_node.get(), hostname, len, j); } } return nullptr; } } // 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); +SSL_CTX *CertLookupTree::lookup(const char *hostname, size_t len) { + return cert_lookup_tree_lookup(&root_, hostname, len, len - 1); } int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, @@ -910,10 +894,9 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx, std::vector ip_addrs; get_altnames(cert, dns_names, ip_addrs, common_name); for (auto &dns_name : dns_names) { - cert_lookup_tree_add_cert(lt, ssl_ctx, dns_name.c_str(), dns_name.size()); + lt->add_cert(ssl_ctx, dns_name.c_str(), dns_name.size()); } - cert_lookup_tree_add_cert(lt, ssl_ctx, common_name.c_str(), - common_name.size()); + lt->add_cert(ssl_ctx, common_name.c_str(), common_name.size()); return 0; } @@ -956,7 +939,7 @@ SSL_CTX *setup_server_ssl_context() { return ssl_ctx; } - auto cert_tree = cert_lookup_tree_new(); + auto cert_tree = new CertLookupTree(); worker_config->cert_tree = cert_tree; diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index ab2a7936..68d3182e 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -85,40 +85,40 @@ void get_altnames(X509 *cert, std::vector &dns_names, // 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; // Next CertNode index of CertLookupTree::nodes - std::vector next; + std::vector> next; + // SSL_CTX for exact match + SSL_CTX *ssl_ctx; char *str; // [first, last) in the reverse direction in str, first >= // last. This indices only work for str member. int first, last; }; -struct CertLookupTree { - std::vector certs; - std::vector hosts; - CertNode *root; +class CertLookupTree { +public: + CertLookupTree(); + + // Adds |ssl_ctx| with hostname pattern |hostname| with length |len| + // to the lookup tree. The |hostname| must be NULL-terminated. + void add_cert(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 *lookup(const char *hostname, size_t len); + +private: + CertNode root_; + // Stores pointers to copied hostname when adding hostname and + // ssl_ctx pair. + std::vector> hosts_; }; -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. diff --git a/src/shrpx_ssl_test.cc b/src/shrpx_ssl_test.cc index 9443424f..13e06dc7 100644 --- a/src/shrpx_ssl_test.cc +++ b/src/shrpx_ssl_test.cc @@ -28,13 +28,14 @@ #include "shrpx_ssl.h" #include "util.h" +#include "template.h" using namespace nghttp2; namespace shrpx { void test_shrpx_ssl_create_lookup_tree(void) { - ssl::CertLookupTree *tree = ssl::cert_lookup_tree_new(); + auto tree = make_unique(); SSL_CTX *ctxs[] = { SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()), @@ -51,40 +52,34 @@ void test_shrpx_ssl_create_lookup_tree(void) { "oo.bar"}; int num = util::array_size(ctxs); for (int i = 0; i < num; ++i) { - ssl::cert_lookup_tree_add_cert(tree, ctxs[i], hostnames[i], - strlen(hostnames[i])); + tree->add_cert(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]))); + CU_ASSERT(ctxs[0] == tree->lookup(hostnames[0], strlen(hostnames[0]))); + CU_ASSERT(ctxs[1] == tree->lookup(hostnames[1], strlen(hostnames[1]))); const char h1[] = "2www.example.org"; - CU_ASSERT(ctxs[2] == ssl::cert_lookup_tree_lookup(tree, h1, strlen(h1))); + CU_ASSERT(ctxs[2] == tree->lookup(h1, strlen(h1))); const char h2[] = "www2.example.org"; - CU_ASSERT(0 == ssl::cert_lookup_tree_lookup(tree, h2, strlen(h2))); + CU_ASSERT(0 == tree->lookup(h2, strlen(h2))); const char h3[] = "x1.host.domain"; - CU_ASSERT(ctxs[3] == ssl::cert_lookup_tree_lookup(tree, h3, strlen(h3))); + CU_ASSERT(ctxs[3] == tree->lookup(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))); + CU_ASSERT(0 == tree->lookup(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)); - CU_ASSERT(ctxs[5] == ssl::cert_lookup_tree_lookup(tree, hostnames[5], - strlen(hostnames[5]))); - CU_ASSERT(ctxs[6] == ssl::cert_lookup_tree_lookup(tree, hostnames[6], - strlen(hostnames[6]))); + CU_ASSERT(ctxs[4] == tree->lookup(h5, strlen(h5))); + CU_ASSERT(0 == tree->lookup("", 0)); + CU_ASSERT(ctxs[5] == tree->lookup(hostnames[5], strlen(hostnames[5]))); + CU_ASSERT(ctxs[6] == tree->lookup(hostnames[6], strlen(hostnames[6]))); const char h6[] = "pdylay.sourceforge.net"; for (int i = 0; i < 7; ++i) { - CU_ASSERT(0 == ssl::cert_lookup_tree_lookup(tree, h6 + i, strlen(h6) - i)); + CU_ASSERT(0 == tree->lookup(h6 + i, strlen(h6) - i)); } const char h7[] = "x.foo.bar"; - CU_ASSERT(ctxs[8] == ssl::cert_lookup_tree_lookup(tree, h7, strlen(h7))); - CU_ASSERT(ctxs[9] == ssl::cert_lookup_tree_lookup(tree, hostnames[9], - strlen(hostnames[9]))); - ssl::cert_lookup_tree_del(tree); + CU_ASSERT(ctxs[8] == tree->lookup(h7, strlen(h7))); + CU_ASSERT(ctxs[9] == tree->lookup(hostnames[9], strlen(hostnames[9]))); + for (int i = 0; i < num; ++i) { SSL_CTX_free(ctxs[i]); } @@ -94,15 +89,15 @@ void test_shrpx_ssl_create_lookup_tree(void) { SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())}; const char *names[] = {"rab", "zab", "zzub", "ab"}; num = util::array_size(ctxs2); - tree = ssl::cert_lookup_tree_new(); + + tree = make_unique(); for (int i = 0; i < num; ++i) { - ssl::cert_lookup_tree_add_cert(tree, ctxs2[i], names[i], strlen(names[i])); + tree->add_cert(ctxs2[i], names[i], strlen(names[i])); } for (int i = 0; i < num; ++i) { - CU_ASSERT(ctxs2[i] == - ssl::cert_lookup_tree_lookup(tree, names[i], strlen(names[i]))); + CU_ASSERT(ctxs2[i] == tree->lookup(names[i], strlen(names[i]))); } - ssl::cert_lookup_tree_del(tree); + for (int i = 0; i < num; ++i) { SSL_CTX_free(ctxs2[i]); } @@ -110,15 +105,14 @@ void test_shrpx_ssl_create_lookup_tree(void) { 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(SSLv23_method()); + ssl::CertLookupTree tree; + auto ssl_ctx = SSL_CTX_new(SSLv23_method()); const char certfile[] = NGHTTP2_TESTS_DIR "/testdata/cacert.pem"; - rv = ssl::cert_lookup_tree_add_cert_from_file(tree, ssl_ctx, certfile); + 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); + CU_ASSERT(ssl_ctx == tree.lookup(localhost, sizeof(localhost) - 1)); + SSL_CTX_free(ssl_ctx); } diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 8339cde0..cb527eda 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -48,7 +48,7 @@ class Http2Session; class ConnectBlocker; namespace ssl { -struct CertLookupTree; +class CertLookupTree; } // namespace ssl struct WorkerStat { diff --git a/src/shrpx_worker_config.h b/src/shrpx_worker_config.h index 1dad4b1d..b281c2f8 100644 --- a/src/shrpx_worker_config.h +++ b/src/shrpx_worker_config.h @@ -32,7 +32,7 @@ namespace shrpx { namespace ssl { -struct CertLookupTree; +class CertLookupTree; } // namespace ssl struct TicketKeys;