diff --git a/README.md b/README.md index 2748c24e..f83550f5 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,8 @@ Detail about the entire set of options can be found by invoking `hitch -h`: --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line before actual data (Default: off) + --sni-nomatch-abort Abort handshake when client submits an unrecognized SNI server name + (Default: off) -t --test Test configuration and exit -p --pidfile=FILE PID file diff --git a/src/configuration.c b/src/configuration.c index ea6bdc8d..cb44d0b0 100644 --- a/src/configuration.c +++ b/src/configuration.c @@ -58,6 +58,7 @@ #define CFG_RING_SLOTS "ring-slots" #define CFG_RING_DATA_LEN "ring-data-len" #define CFG_PIDFILE "pidfile" +#define CFG_SNI_NOMATCH_ABORT "sni-nomatch-abort" #ifdef USE_SHARED_CACHE #define CFG_SHARED_CACHE "shared-cache" @@ -140,6 +141,7 @@ hitch_config * config_new (void) { r->CIPHER_SUITE = NULL; r->ENGINE = NULL; r->BACKLOG = 100; + r->SNI_NOMATCH_ABORT = 0; VTAILQ_INIT(&r->LISTEN_ARGS); ALLOC_OBJ(fa, FRONT_ARG_MAGIC); @@ -793,6 +795,9 @@ void config_param_validate (char *k, char *v, hitch_config *cfg, char *file, int else if (strcmp(k, CFG_RING_DATA_LEN) == 0) { r = config_param_val_int_pos(v, &cfg->RING_DATA_LEN); } + else if (strcmp(k, CFG_SNI_NOMATCH_ABORT) == 0) { + r = config_param_val_bool(v, &cfg->SNI_NOMATCH_ABORT); + } else { fprintf( stderr, @@ -1018,6 +1023,10 @@ void config_print_usage_fd (char *prog, hitch_config *cfg, FILE *out) { fprintf(out, " --proxy-proxy Proxy HaProxy's PROXY (IPv4 or IPv6) protocol line\n" ); fprintf(out, " before actual data\n"); fprintf(out, " (Default: %s)\n", config_disp_bool(cfg->PROXY_PROXY_LINE)); + fprintf(out, " --sni-nomatch-abort Abort handshake when client " + "submits an unrecognized SNI server name\n" ); + fprintf(out, " (Default: %s)\n", + config_disp_bool(cfg->SNI_NOMATCH_ABORT)); fprintf(out, "\n"); fprintf(out, " -t --test Test configuration and exit\n"); fprintf(out, " -p --pidfile=FILE PID file\n"); @@ -1223,6 +1232,13 @@ void config_print_default (FILE *fd, hitch_config *cfg) { fprintf(fd, FMT_STR, CFG_PROXY_PROXY, config_disp_bool(cfg->PROXY_PROXY_LINE)); fprintf(fd, "\n"); + fprintf(fd, "# Abort handshake when the client submits an unrecognized SNI" + " server name.\n"); + fprintf(fd, "#\n"); + fprintf(fd, "# type: boolean\n"); + fprintf(fd, FMT_STR, CFG_SNI_NOMATCH_ABORT, config_disp_bool(cfg->SNI_NOMATCH_ABORT)); + fprintf(fd, "\n"); + fprintf(fd, "# EOF\n"); } #endif /* NO_CONFIG_FILE */ @@ -1272,7 +1288,7 @@ void config_parse_cli(int argc, char **argv, hitch_config *cfg) { { CFG_WRITE_PROXY, 0, &cfg->WRITE_PROXY_LINE, 1 }, { CFG_WRITE_PROXY_V2, 0, &cfg->WRITE_PROXY_LINE_V2, 1 }, { CFG_PROXY_PROXY, 0, &cfg->PROXY_PROXY_LINE, 1 }, - + { CFG_SNI_NOMATCH_ABORT, 0, &cfg->SNI_NOMATCH_ABORT, 1 }, { "test", 0, NULL, 't' }, { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'h' }, diff --git a/src/configuration.h b/src/configuration.h index 902bb97c..0399b741 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -90,6 +90,7 @@ struct __hitch_config { int RING_SLOTS; int RING_DATA_LEN; char *PIDFILE; + int SNI_NOMATCH_ABORT; }; typedef struct __hitch_config hitch_config; diff --git a/src/hitch.c b/src/hitch.c index f91dff40..84bcdc3f 100644 --- a/src/hitch.c +++ b/src/hitch.c @@ -739,23 +739,28 @@ sni_match(const struct ctx_list *cl, const char *srvname) * based on the SNI header */ int sni_switch_ctx(SSL *ssl, int *al, void *data) { - (void)data; - (void)al; - const char *servername; - const ctx_list *cl; - - servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (!servername) return SSL_TLSEXT_ERR_NOACK; - - VTAILQ_FOREACH(cl, &sni_ctxs, list) { - CHECK_OBJ_NOTNULL(cl, CTX_LIST_MAGIC); - if (sni_match(cl, servername)) { - SSL_set_SSL_CTX(ssl, cl->ctx); - return SSL_TLSEXT_ERR_NOACK; - } - } + (void)data; + (void)al; + const char *servername; + const ctx_list *cl; + + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) + return SSL_TLSEXT_ERR_NOACK; + + VTAILQ_FOREACH(cl, &sni_ctxs, list) { + CHECK_OBJ_NOTNULL(cl, CTX_LIST_MAGIC); + if (sni_match(cl, servername)) { + SSL_set_SSL_CTX(ssl, cl->ctx); + return SSL_TLSEXT_ERR_OK; + } + } - return SSL_TLSEXT_ERR_NOACK; + /* No matching certs */ + if (CONFIG->SNI_NOMATCH_ABORT) + return SSL_TLSEXT_ERR_ALERT_FATAL; + else + return SSL_TLSEXT_ERR_NOACK; } #endif /* OPENSSL_NO_TLSEXT */ diff --git a/tests/common.sh b/tests/common.sh index 2bd5c7de..e52fb740 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -27,6 +27,6 @@ die() { runcurl() { # Verify that we got a HTTP reply. - BUF=$(curl --silent --insecure https://$1:$2/ 2>&1) + BUF=$(curl $CURL_EXTRA --silent --insecure https://$1:$2/ 2>&1) test "$?" = "0" || die "Incorrect HTTP response code." } diff --git a/tests/test07-nomatch-abort b/tests/test07-nomatch-abort new file mode 100755 index 00000000..7c6b13c0 --- /dev/null +++ b/tests/test07-nomatch-abort @@ -0,0 +1,34 @@ +#/bin/bash +# +# Test --sni-nomatch-abort +# +. common.sh +set +o errexit + +#PORT2=$(($RANDOM + 1024)) + +$HITCH $HITCH_ARGS --backend=[hyse.org]:80 --frontend=[${LISTENADDR}]:$LISTENPORT \ + certs/site1.example.com certs/site2.example.com certs/default.example.com \ + --sni-nomatch-abort +test "$?" = "0" || die "Hitch did not start." + +# No SNI - should not be affected. +echo -e "\n" | openssl s_client -prexit -connect $LISTENADDR:$LISTENPORT 2>/dev/null > $DUMPFILE +test "$?" = "0" || die "s_client failed" +grep -q -c "subject=/CN=default.example.com" $DUMPFILE +test "$?" = "0" || die "s_client got wrong certificate on listen port #1" + +# SNI request w/ valid servername +echo -e "\n" | openssl s_client -servername site1.example.com -prexit -connect $LISTENADDR:$LISTENPORT 2>/dev/null > $DUMPFILE +test "$?" = "0" || die "s_client failed" +grep -q -c "subject=/CN=site1.example.com" $DUMPFILE +test "$?" = "0" || die "s_client got wrong certificate in listen port #2" + +# SNI w/ unknown servername +echo | openssl s_client -servername invalid.example.com -prexit -connect $LISTENADDR:$LISTENPORT > $DUMPFILE 2>&1 +test "$?" != "0" || die "s_client did NOT fail when it should have. " +grep -q -c "unrecognized name" $DUMPFILE +test "$?" = "0" || die "Expected 'unrecognized name' error." + +CURL_EXTRA="--resolve site1.example.com:$LISTENPORT:127.0.0.1" +runcurl site1.example.com $LISTENPORT