diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 76ad4d0..31c3b8b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(test_query EXCLUDE_FROM_ALL test_query.c) add_executable(test_perf EXCLUDE_FROM_ALL test_perf.c) add_executable(example_vhost EXCLUDE_FROM_ALL example_vhost.c) add_executable(example_pause EXCLUDE_FROM_ALL example_pause.c) +add_executable(example_https EXCLUDE_FROM_ALL https/example_https.c) if (NOT EVHTP_DISABLE_EVTHR) add_executable(test_proxy EXCLUDE_FROM_ALL test_proxy.c) @@ -23,7 +24,16 @@ target_link_libraries(test_query evhtp ${LIBEVHTP_EXTERNAL_LIBS} ${SYS_LIBS}) target_link_libraries(test_perf evhtp ${LIBEVHTP_EXTERNAL_LIBS} ${SYS_LIBS}) target_link_libraries(example_vhost evhtp ${LIBEVHTP_EXTERNAL_LIBS} ${SYS_LIBS}) target_link_libraries(example_pause evhtp ${LIBEVHTP_EXTERNAL_LIBS} ${SYS_LIBS}) +target_link_libraries(example_https evhtp ${LIBEVHTP_EXTERNAL_LIBS} ${SYS_LIBS}) -add_dependencies(examples example_pause example_vhost test_extensive test_basic test_vhost test_client test_query test_perf) +add_dependencies(examples example_https example_pause example_vhost test_extensive test_basic test_vhost test_client test_query test_perf) +file (COPY + https/etc/ca.cnf + https/etc/client1.cnf + https/etc/client2.cnf + https/etc/server.cnf + DESTINATION + https/etc/) +configure_file(https/bin/generate.sh.in https/bin/generate.sh @ONLY) diff --git a/examples/https/bin/generate.sh.in b/examples/https/bin/generate.sh.in new file mode 100755 index 0000000..6f78154 --- /dev/null +++ b/examples/https/bin/generate.sh.in @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +CONFIG_DIR="@PROJECT_BINARY_DIR@/examples/https" + +# Create new CA +openssl req -new -x509 -days 9999 \ + -config "$CONFIG_DIR/etc/ca.cnf" \ + -keyout "$CONFIG_DIR/ca-key.pem" \ + -out "$CONFIG_DIR/ca-crt.pem" + +# Generate private key for server +openssl genrsa -out "$CONFIG_DIR/server-key.pem" 4096 + +# Generate cert signing request +openssl req -new \ + -config "$CONFIG_DIR/etc/server.cnf" \ + -key "$CONFIG_DIR/server-key.pem" \ + -out "$CONFIG_DIR/server-csr.pem" + +# Sign the request +openssl x509 -req \ + -extfile "$CONFIG_DIR/etc/server.cnf" \ + -days 999 \ + -passin "pass:password" \ + -in "$CONFIG_DIR/server-csr.pem" \ + -CA "$CONFIG_DIR/ca-crt.pem" \ + -CAkey "$CONFIG_DIR/ca-key.pem" \ + -CAcreateserial \ + -out "$CONFIG_DIR/server-crt.pem" + +# Generate a few client certs +openssl genrsa -out "$CONFIG_DIR/client1-key.pem" 4096 +openssl genrsa -out "$CONFIG_DIR/client2-key.pem" 4096 + +# create two cert sign requests +openssl req -new -config "$CONFIG_DIR/etc/client1.cnf" -key $CONFIG_DIR/client1-key.pem -out $CONFIG_DIR/client1-csr.pem +openssl req -new -config $CONFIG_DIR/etc/client2.cnf -key $CONFIG_DIR/client2-key.pem -out $CONFIG_DIR/client2-csr.pem + +# sign the above client certs +openssl x509 -req \ + -extfile $CONFIG_DIR/etc/client1.cnf \ + -days 999 \ + -passin "pass:password" \ + -in $CONFIG_DIR/client1-csr.pem \ + -CA $CONFIG_DIR/ca-crt.pem \ + -CAkey $CONFIG_DIR/ca-key.pem \ + -CAcreateserial \ + -out $CONFIG_DIR/client1-crt.pem + +openssl x509 -req \ + -extfile $CONFIG_DIR/etc/client2.cnf \ + -days 999 \ + -passin "pass:password" \ + -in $CONFIG_DIR/client2-csr.pem \ + -CA $CONFIG_DIR/ca-crt.pem \ + -CAkey $CONFIG_DIR/ca-key.pem \ + -CAcreateserial \ + -out $CONFIG_DIR/client2-crt.pem diff --git a/examples/https/etc/ca.cnf b/examples/https/etc/ca.cnf new file mode 100644 index 0000000..53eab64 --- /dev/null +++ b/examples/https/etc/ca.cnf @@ -0,0 +1,31 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = ca-serial +crl = ca-crl.pem +database = ca-database.txt +name_opt = CA_default +cert_opt = CA_default +default_crl_days = 9999 +default_md = md5 + +[ req ] +default_bits = 4096 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +output_password = password + +[ req_distinguished_name ] +C = US +ST = MA +L = Boston +O = Critical Stack +OU = evhtp +CN = ca +emailAddress = nate@cl0d.com + +[ req_attributes ] +challengePassword = test diff --git a/examples/https/etc/client1.cnf b/examples/https/etc/client1.cnf new file mode 100644 index 0000000..6e881ff --- /dev/null +++ b/examples/https/etc/client1.cnf @@ -0,0 +1,26 @@ +[ req ] +default_bits = 4096 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = MA +L = Boston +O = Critical Stack +OU = evhtp +CN = client1 +emailAddress = nate@cl0d.com + +[ req_attributes ] +challengePassword = password + +[ v3_ca ] +authorityInfoAccess = @issuer_info + +[ issuer_info ] +OCSP;URI.0 = http://ocsp.example.com/ +caIssuers;URI.0 = http://example.com/ca.cert diff --git a/examples/https/etc/client2.cnf b/examples/https/etc/client2.cnf new file mode 100644 index 0000000..2cbf570 --- /dev/null +++ b/examples/https/etc/client2.cnf @@ -0,0 +1,26 @@ +[ req ] +default_bits = 4096 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = MA +L = Boston +O = Critical Stack +OU = evhtp +CN = client2 +emailAddress = nate@cl0d.com + +[ req_attributes ] +challengePassword = password + +[ v3_ca ] +authorityInfoAccess = @issuer_info + +[ issuer_info ] +OCSP;URI.0 = http://ocsp.example.com/ +caIssuers;URI.0 = http://example.com/ca.cert diff --git a/examples/https/etc/server.cnf b/examples/https/etc/server.cnf new file mode 100644 index 0000000..2c36ee4 --- /dev/null +++ b/examples/https/etc/server.cnf @@ -0,0 +1,26 @@ +[ req ] +default_bits = 4096 +days = 9999 +distinguished_name = req_distinguished_name +attributes = req_attributes +prompt = no +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = MA +L = Boston +O = Critical Stack +OU = evhtp +CN = localhost +emailAddress = nate@cl0d.com + +[ req_attributes ] +challengePassword = password + +[ v3_ca ] +authorityInfoAccess = @issuer_info + +[ issuer_info ] +OCSP;URI.0 = http://ocsp.example.com/ +caIssuers;URI.0 = http://example.com/ca.cert diff --git a/examples/https/example_https.c b/examples/https/example_https.c new file mode 100644 index 0000000..f39105b --- /dev/null +++ b/examples/https/example_https.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../log.h" +#include "internal.h" +#include "evhtp/evhtp.h" + +static void +http__callback_(evhtp_request_t * req, void * arg) { + return evhtp_send_reply(req, EVHTP_RES_OK); +} + +static int +ssl__x509_verify_(int ok, X509_STORE_CTX * store) { + char buf[256]; + X509 * err_cert; + int err; + int depth; + SSL * ssl; + evhtp_connection_t * connection; + evhtp_ssl_cfg_t * ssl_cfg; + + err_cert = X509_STORE_CTX_get_current_cert(store); + err = X509_STORE_CTX_get_error(store); + depth = X509_STORE_CTX_get_error_depth(store); + ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx()); + + X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); + + connection = SSL_get_app_data(ssl); + ssl_cfg = connection->htp->ssl_cfg; + + if (depth > ssl_cfg->verify_depth) { + ok = 0; + err = X509_V_ERR_CERT_CHAIN_TOO_LONG; + + X509_STORE_CTX_set_error(store, err); + } + + if (!ok) { + log_error("SSL: verify error:num=%d:%s:depth=%d:%s", err, + X509_verify_cert_error_string(err), depth, buf); + } + + return ok; +} + +enum { + OPTARG_CERT = 1000, + OPTARG_KEY, + OPTARG_CA, + OPTARG_CAPATH, + OPTARG_CIPHERS, + OPTARG_VERIFY_PEER, + OPTARG_ENFORCE_PEER_CERT, + OPTARG_VERIFY_DEPTH, + OPTARG_ENABLE_CACHE, + OPTARG_CACHE_TIMEOUT, + OPTARG_CACHE_SIZE, + OPTARG_CTX_TIMEOUT, + OPTARG_ENABLE_PROTOCOL, + OPTARG_DISABLE_PROTOCOL +}; + +static const char * help = + "Usage %s [opts] :\n" + " -cert : Server PEM-encoded X.509 Certificate file\n" + " -key : Server PEM-encoded Private Key file\n" + " -ca : File of PEM-encoded Server CA Certificates\n" + " -capath : Directory of PEM-encoded CA Certificates for Client Auth\n" + " -ciphers : Accepted SSL Ciphers\n" + " -verify-peer : Enable SSL client verification\n" + " -enforce-peer-cert : Reject clients without a cert\n" + " -verify-depth : Maximum depth of CA Certificates in Client Certificate verification\n" + " -enable-protocol

: Enable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n" + " -disable-protocol

: Disable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n" + " -ctx-timeout : SSL Session Timeout (SSL >= 1.0)\n"; + +evhtp_ssl_cfg_t * +parse__ssl_opts_(int argc, char ** argv) { + int opt = 0; + int long_index = 0; + int ssl_verify_mode = 0; + struct stat f_stat; + evhtp_ssl_cfg_t * ssl_config = calloc(1, sizeof(evhtp_ssl_cfg_t)); + + + ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + + static struct option long_options[] = { + { "cert", required_argument, 0, OPTARG_CERT }, + { "key", required_argument, 0, OPTARG_KEY }, + { "ca", required_argument, 0, OPTARG_CA }, + { "capath", required_argument, 0, OPTARG_CAPATH }, + { "ciphers", required_argument, 0, OPTARG_CIPHERS }, + { "verify-peer", no_argument, 0, OPTARG_VERIFY_PEER }, + { "enforce-peer-cert", no_argument, 0, OPTARG_ENFORCE_PEER_CERT }, + { "verify-depth", required_argument, 0, OPTARG_VERIFY_DEPTH }, + { "enable-cache", no_argument, 0, OPTARG_ENABLE_CACHE }, + { "cache-timeout", required_argument, 0, OPTARG_CACHE_TIMEOUT }, + { "cache-size", required_argument, 0, OPTARG_CACHE_SIZE }, + { "enable-protocol", required_argument, 0, OPTARG_ENABLE_PROTOCOL }, + { "disable-protocol", required_argument, 0, OPTARG_DISABLE_PROTOCOL }, + { "ctx-timeout", required_argument, 0, OPTARG_CTX_TIMEOUT }, + { "help", no_argument, 0, 'h' }, + { NULL, 0, 0, 0 } + }; + + while ((opt = getopt_long_only(argc, argv, "", long_options, &long_index)) != -1) { + switch (opt) { + case 'h': + printf(help, argv[0]); + exit(EXIT_FAILURE); + case OPTARG_CERT: + ssl_config->pemfile = strdup(optarg); + break; + case OPTARG_KEY: + ssl_config->privfile = strdup(optarg); + break; + case OPTARG_CA: + ssl_config->cafile = strdup(optarg); + break; + case OPTARG_CAPATH: + ssl_config->capath = strdup(optarg); + break; + case OPTARG_CIPHERS: + ssl_config->ciphers = strdup(optarg); + break; + case OPTARG_VERIFY_DEPTH: + ssl_config->verify_depth = atoi(optarg); + break; + case OPTARG_VERIFY_PEER: + ssl_verify_mode |= SSL_VERIFY_PEER; + break; + case OPTARG_ENFORCE_PEER_CERT: + ssl_verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + break; + case OPTARG_ENABLE_CACHE: + ssl_config->scache_type = evhtp_ssl_scache_type_internal; + break; + case OPTARG_CACHE_TIMEOUT: + ssl_config->scache_timeout = atoi(optarg); + break; + case OPTARG_CACHE_SIZE: + ssl_config->scache_size = atoi(optarg); + break; + case OPTARG_CTX_TIMEOUT: + ssl_config->ssl_ctx_timeout = atoi(optarg); + break; + case OPTARG_ENABLE_PROTOCOL: + if (!strcasecmp(optarg, "SSLv2")) { + ssl_config->ssl_opts &= ~SSL_OP_NO_SSLv2; + } else if (!strcasecmp(optarg, "SSLv3")) { + ssl_config->ssl_opts &= ~SSL_OP_NO_SSLv3; + } else if (!strcasecmp(optarg, "TLSv1")) { + ssl_config->ssl_opts &= ~SSL_OP_NO_TLSv1; + } else if (!strcasecmp(optarg, "ALL")) { + ssl_config->ssl_opts = 0; + } + + break; + case OPTARG_DISABLE_PROTOCOL: + if (!strcasecmp(optarg, "SSLv2")) { + ssl_config->ssl_opts |= SSL_OP_NO_SSLv2; + } else if (!strcasecmp(optarg, "SSLv3")) { + ssl_config->ssl_opts |= SSL_OP_NO_SSLv3; + } else if (!strcasecmp(optarg, "TLSv1")) { + ssl_config->ssl_opts |= SSL_OP_NO_TLSv1; + } else if (!strcasecmp(optarg, "ALL")) { + ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + } + break; + + default: + break; + } /* switch */ + } + + if (ssl_verify_mode != 0) { + ssl_config->verify_peer = ssl_verify_mode; + ssl_config->x509_verify_cb = ssl__x509_verify_; + } + + + if (ssl_config->pemfile) { + if (stat(ssl_config->pemfile, &f_stat) != 0) { + log_error("Cannot load SSL cert '%s' (%s)", ssl_config->pemfile, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + if (ssl_config->privfile) { + if (stat(ssl_config->privfile, &f_stat) != 0) { + log_error("Cannot load SSL key '%s' (%s)", ssl_config->privfile, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + if (ssl_config->cafile) { + if (stat(ssl_config->cafile, &f_stat) != 0) { + log_error("Cannot find SSL CA File '%s' (%s)", ssl_config->cafile, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + if (ssl_config->capath) { + if (stat(ssl_config->capath, &f_stat) != 0) { + log_error("Cannot find SSL CA PATH '%s' (%s)", ssl_config->capath, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + return ssl_config; +} /* parse__ssl_opts_ */ + +int +main(int argc, char ** argv) { + evhtp_t * htp; + struct event_base * evbase; + + evbase = event_base_new(); + evhtp_alloc_assert(evbase); + + htp = evhtp_new(evbase, NULL); + evhtp_alloc_assert(htp); + + evhtp_ssl_init(htp, parse__ssl_opts_(argc, argv)); + evhtp_set_gencb(htp, http__callback_, NULL); + + evhtp_bind_socket(htp, "127.0.0.1", 4443, 128); + { + struct sockaddr_in sin; + socklen_t len = sizeof(struct sockaddr); + uint16_t port; + + getsockname( + evconnlistener_get_fd(htp->server), + (struct sockaddr *)&sin, &len); + + port = ntohs(sin.sin_port); + + log_info("curl https://127.0.0.1:%d/", port); + } + + event_base_loop(evbase, 0); + + + return 0; +}