Added a more extensive SSL sandbox.
After running `make examples`, if SSL is enabled, you
can quickly test HTTPS, with optional client-based
certificate authentication using the following process within
the build directory:
```
./examples/https/bin/generate.sh
-- Test without client auth
./examples/example_https \
-cert examples/https/server-crt.pem \
-key examples/https/server-key.pem
curl -vk https://localhost:4443/
-- Test WITH client auth
./examples/example_https \
-cert examples/https/server-crt.pem \
-key examples/https/server-key.pem \
-ca examples/https/ca-crt.pem \
-verify-peer \
-verify-depth 2 \
-enforce-peer-cert
curl -kv \
--key examples/https/client1-key.pem \
--cert examples/https/client1-crt.pem \
https://localhost:4443/
```
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_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_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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#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] <host>:<port>\n"
+ " -cert <file> : Server PEM-encoded X.509 Certificate file\n"
+ " -key <file> : Server PEM-encoded Private Key file\n"
+ " -ca <file> : File of PEM-encoded Server CA Certificates\n"
+ " -capath <path> : Directory of PEM-encoded CA Certificates for Client Auth\n"
+ " -ciphers <str> : Accepted SSL Ciphers\n"
+ " -verify-peer : Enable SSL client verification\n"
+ " -enforce-peer-cert : Reject clients without a cert\n"
+ " -verify-depth <n> : Maximum depth of CA Certificates in Client Certificate verification\n"
+ " -enable-protocol <p> : Enable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n"
+ " -disable-protocol <p> : Disable one of the following protocols: SSLv2, SSLv3, TLSv1, or ALL\n"
+ " -ctx-timeout <n> : 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;
+}