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.
This commit is contained in:
Tatsuhiro Tsujikawa 2013-02-06 23:27:05 +09:00
parent 7b3d24bcc5
commit b18af854af
14 changed files with 590 additions and 46 deletions

1
src/.gitignore vendored
View File

@ -1,3 +1,4 @@
spdycat
spdyd
shrpx
shrpx-unittest

View File

@ -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

84
src/shrpx-unittest.cc Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <CUnit/Basic.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
/* 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();
}
}

View File

@ -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=<KEYPATH>:<CERTPATH>\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=<NUM>\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();

View File

@ -37,6 +37,7 @@
#include <fstream>
#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;
}

View File

@ -32,13 +32,22 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <vector>
#include <openssl/ssl.h>
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;

View File

@ -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),

View File

@ -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);

View File

@ -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<std::string>& dns_names,
std::vector<std::string>& 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<X509*> 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<std::string> dns_names;
std::vector<std::string> ip_addrs;
GENERAL_NAMES* altnames;
altnames = reinterpret_cast<GENERAL_NAMES*>
(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<X509*> 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<std::string> dns_names;
std::vector<std::string> 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<CertNode*>::iterator i = node->next.begin(),
eoi = node->next.end(); i != eoi; ++i) {
cert_node_del(*i);
}
for(std::vector<std::pair<char*, SSL_CTX*> >::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<std::pair<char*, SSL_CTX*> >::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<CertNode*>::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*> 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<X509*> cert_deleter(cert, X509_free);
std::string common_name;
std::vector<std::string> dns_names;
std::vector<std::string> ip_addrs;
get_altnames(cert, dns_names, ip_addrs, common_name);
for(std::vector<std::string>::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

View File

@ -27,6 +27,8 @@
#include "shrpx.h"
#include <vector>
#include <openssl/ssl.h>
#include <openssl/err.h>
@ -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<std::string>& dns_names,
std::vector<std::string>& 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<std::pair<char*, SSL_CTX*> > wildcard_certs;
// ASCII byte in this position
char c;
// Next CertNode index of CertLookupTree::nodes
std::vector<CertNode*> next;
};
struct CertLookupTree {
std::vector<SSL_CTX*> 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

91
src/shrpx_ssl_test.cc Normal file
View File

@ -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 <CUnit/CUnit.h>
#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

35
src/shrpx_ssl_test.h Normal file
View File

@ -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 */

View File

@ -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

View File

@ -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<typename T>
std::string utos(T n)
{