diff --git a/CHANGES b/CHANGES index 21cb847..df00a74 100644 --- a/CHANGES +++ b/CHANGES @@ -145,6 +145,12 @@ New features: Added option ip-transparent (socket option IP_TRANSPARENT) Thanks to Wang Shanker for sending a patch. + OPENSSL-CONNECT now automatically uses the SNI feature, option + openssl-no-sni turns it off. Option openssl-snihost overrides the value + of option openssl-commonname or the server name. + Tests: OPENSSL_SNI OPENSSL_NO_SNI + Thanks to Travis Burtrum for providing the initial patch + ####################### V 1.7.3.4: Corrections: diff --git a/config.h.in b/config.h.in index cf6115e..3cb88e0 100644 --- a/config.h.in +++ b/config.h.in @@ -508,6 +508,9 @@ /* Define if you have the OpenSSL SSL_CTX_clear_mode macro or function */ #undef HAVE_SSL_CTX_clear_mode +/* Define if you have the OpenSSL SSL_set_tlsext_host_name define/function */ +#undef HAVE_SSL_set_tlsext_host_name + /* Define if you have the flock function */ #undef HAVE_FLOCK diff --git a/configure.ac b/configure.ac index 38dc83b..cda8903 100644 --- a/configure.ac +++ b/configure.ac @@ -1484,6 +1484,7 @@ AC_CHECK_FUNC(DH_set0_pqg, AC_DEFINE(HAVE_DH_set0_pqg), AC_CHECK_LIB(crypt, DH_s AC_CHECK_FUNC(ASN1_STRING_get0_data, AC_DEFINE(HAVE_ASN1_STRING_get0_data), AC_CHECK_LIB(crypt, ASN1_STRING_get0_data, [LIBS=-lcrypt $LIBS])) AC_CHECK_FUNC(RAND_status, AC_DEFINE(HAVE_RAND_status)) AC_CHECK_FUNC(SSL_CTX_clear_mode, AC_DEFINE(HAVE_SSL_CTX_clear_mode)) +AC_CHECK_FUNC(SSL_set_tlsext_host_name, AC_DEFINE(HAVE_SSL_set_tlsext_host_name)) AC_MSG_CHECKING(for type EC_KEY) AC_CACHE_VAL(sc_cv_type_EC_TYPE, diff --git a/doc/socat.yo b/doc/socat.yo index e489dd5..589c0cc 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -2777,6 +2777,17 @@ label(OPTION_OPENSSL_COMMONNAME)dit(bf(tt(commonname=))) certificates commonname. This option has only meaning when option link(verify)(OPTION_OPENSSL_VERIFY) is not disabled and the chosen cipher provides a peer certificate. +label(OPTION_OPENSSL_NO_SNI)dit(bf(tt(no-sni=))) + Do not use the client side Server Name Indication (SNI) feature that selects + the desired server certificate.nl() + Note: SNI is automatically used since socat() version 1.7.4.0 and uses + link(commonname)(OPTION_OPENSSL_COMMONNAME) or the given host name. +label(OPTION_OPENSSL_SNIHOST)dit(bf(tt(snihost=))) + Set the client side Server Name Indication (SNI) host name different from + the addressed server name or common name. This might be useful when the + server certificate has multiple host names or wildcard names because the + SNI host name is passed in cleartext to the server and might be eavesdropped; + with this option a mock name of the desired certificate may be transferred. label(OPTION_OPENSSL_FIPS)dit(bf(tt(fips))) Enables FIPS mode if compiled in. For info about the FIPS encryption implementation standard see lurl(http://oss-institute.org/fips-faq.html). diff --git a/test.sh b/test.sh index ebc7b81..c4a2b73 100755 --- a/test.sh +++ b/test.sh @@ -15,6 +15,7 @@ val_t=0.1 NUMCOND=true #NUMCOND="test \$N -gt 70" VERBOSE= +FOREIGN= while [ "$1" ]; do case "X$1" in X-t?*) val_t="${1#-t}" ;; @@ -25,6 +26,7 @@ while [ "$1" ]; do X-N?*) NUMCOND="test \$N -gt ${1#-N}" ;; X-N) shift; NUMCOND="test \$N -ge $1" ;; X-C) rm -f testcert*.conf testcert.dh testcli*.* testsrv*.* ;; + X-foreign) FOREIGN=1 ;; # allow access to 3rd party Internet hosts *) break; esac shift @@ -14309,6 +14311,101 @@ esac N=$((N+1)) +# Test the OpenSSL SNI feature +NAME=OPENSSL_SNI +case "$TESTS" in +*%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%$NAME%*) +TEST="$NAME: Test the OpenSSL SNI feature" +# Connect to a server that is known to use SNI. Use an SNI name, not the +# certifications default name. When the TLS connection is established +# the test succeeded. +SNISERVER=badssl.com +if ! eval $NUMCOND; then :; +elif ! testaddrs openssl >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}OPENSSL not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! feat=$(testoptions openssl-snihost); then + $PRINTF "test $F_n $TEST... ${YELLOW}$feat not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [ -z "$FOREIGN" ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}use test.sh option -foreign${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts FILE:/dev/null OPENSSL-CONNECT:$SNISERVER:443" +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"${te}0" +rc0=$? +if [ $rc0 -eq 0 ]; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0" >&2 + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + +# Test the openssl-no-sni option +NAME=OPENSSL_NO_SNI +case "$TESTS" in +*%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%$NAME%*) +TEST="$NAME: Test the openssl-no-sni option" +# Connect to a server that is known to use SNI. Use an SNI name, not the +# certifications default name, and use option openssl-no-sni. +# When the TLS connection failed the test succeeded. +# Please note that this test is only relevant when test OPENSSL_SNI succeeded. +SNISERVER=badssl.com +if ! eval $NUMCOND; then :; +elif ! testaddrs openssl >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}OPENSSL not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! feat=$(testoptions openssl-no-sni); then + $PRINTF "test $F_n $TEST... ${YELLOW}$feat not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [ -z "$FOREIGN" ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}use test.sh option -foreign${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +CMD0="$TRACE $SOCAT $opts FILE:/dev/null OPENSSL-CONNECT:$SNISERVER:443,openssl-no-sni" +printf "test $F_n $TEST... " $N +$CMD0 >/dev/null 2>"${te}0" +rc0=$? +if [ $rc0 -ne 0 ]; then + $PRINTF "$OK\n" + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "$CMD0" >&2 + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + ################################################################################## #================================================================================= # here come tests that might affect your systems integrity. Put normal tests diff --git a/xio-openssl.c b/xio-openssl.c index 6cb926d..2be9ce1 100644 --- a/xio-openssl.c +++ b/xio-openssl.c @@ -131,6 +131,10 @@ const struct optdesc opt_openssl_compress = { "openssl-compress", "compress const struct optdesc opt_openssl_fips = { "openssl-fips", "fips", OPT_OPENSSL_FIPS, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC }; #endif const struct optdesc opt_openssl_commonname = { "openssl-commonname", "cn", OPT_OPENSSL_COMMONNAME, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC }; +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) +const struct optdesc opt_openssl_no_sni = { "openssl-no-sni", "nosni", OPT_OPENSSL_NO_SNI, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC }; +const struct optdesc opt_openssl_snihost = { "openssl-snihost", "snihost", OPT_OPENSSL_SNIHOST, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC }; +#endif /* If FIPS is compiled in, we need to track if the user asked for FIPS mode. @@ -220,6 +224,8 @@ static int bool opt_ver = true; /* verify peer certificate */ char *opt_cert = NULL; /* file name of client certificate */ const char *opt_commonname = NULL; /* for checking peer certificate */ + bool opt_no_sni; + const char *opt_snihost = NULL; /* for SNI host */ int result; if (!(xioflags & XIO_MAYCONVERT)) { @@ -249,11 +255,27 @@ static int retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert); retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname); +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + retropt_bool(opts, OPT_OPENSSL_NO_SNI, &opt_no_sni); + retropt_string(opts, OPT_OPENSSL_SNIHOST, (char **)&opt_snihost); +#endif if (opt_commonname == NULL) { - opt_commonname = hostname; + opt_commonname = strdup(hostname); + if (opt_commonname == NULL) { + Error1("strdup("F_Zu"): out of memory", strlen(hostname)+1); + } } +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + if (opt_snihost == NULL) { + opt_snihost = strdup(opt_commonname); + if (opt_snihost == NULL) { + Error1("strdup("F_Zu"): out of memory", strlen(opt_commonname)+1); + } + } +#endif + result = _xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert, &ctx, (bool *)&use_dtls); if (result != STAT_OK) return STAT_NORETRY; @@ -319,7 +341,8 @@ static int return result; } - result = _xioopen_openssl_connect(xfd, opt_ver, opt_commonname, ctx, level); + result = _xioopen_openssl_connect(xfd, opt_ver, opt_commonname, + opt_no_sni, opt_snihost, ctx, level); switch (result) { case STAT_OK: break; #if WITH_RETRY @@ -376,6 +399,9 @@ static int openssl_conn_loginfo(xfd->para.openssl.ssl); + free((void *)opt_commonname); + free((void *)opt_snihost); + /* fill in the fd structure */ return STAT_OK; } @@ -388,6 +414,8 @@ static int int _xioopen_openssl_connect(struct single *xfd, bool opt_ver, const char *opt_commonname, + bool no_sni, + const char *snihost, SSL_CTX *ctx, int level) { SSL *ssl; @@ -412,6 +440,17 @@ int _xioopen_openssl_connect(struct single *xfd, return result; } +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + if (!no_sni) { + if (!SSL_set_tlsext_host_name(ssl, snihost)) { + Error1("Failed to set SNI host \"%s\"", snihost); + sycSSL_free(xfd->para.openssl.ssl); + xfd->para.openssl.ssl = NULL; + return STAT_NORETRY; + } + } +#endif + result = xioSSL_connect(xfd, opt_commonname, opt_ver, level); if (result != STAT_OK) { sycSSL_free(xfd->para.openssl.ssl); diff --git a/xio-openssl.h b/xio-openssl.h index a6ca357..e9edcfc 100644 --- a/xio-openssl.h +++ b/xio-openssl.h @@ -34,6 +34,8 @@ extern const struct optdesc opt_openssl_compress; extern const struct optdesc opt_openssl_fips; #endif extern const struct optdesc opt_openssl_commonname; +extern const struct optdesc opt_openssl_no_sni; +extern const struct optdesc opt_openssl_snihost; extern int _xioopen_openssl_prepare(struct opt *opts, struct single *xfd, @@ -42,6 +44,7 @@ extern int extern int _xioopen_openssl_connect(struct single *xfd, bool opt_ver, const char *opt_commonname, + bool no_sni, const char *snihost, SSL_CTX *ctx, int level); extern int _xioopen_openssl_listen(struct single *xfd, bool opt_ver, diff --git a/xioopts.c b/xioopts.c index 9947001..1774e87 100644 --- a/xioopts.c +++ b/xioopts.c @@ -964,6 +964,9 @@ const struct optname optionnames[] = { IF_SOCKET ("no-check", &opt_so_no_check) #endif IF_TUN ("no-pi", &opt_iff_no_pi) +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + IF_OPENSSL("no-sni", &opt_openssl_no_sni) +#endif IF_TUN ("noarp", &opt_iff_noarp) #ifdef O_NOATIME IF_OPEN ("noatime", &opt_o_noatime) @@ -1000,6 +1003,9 @@ const struct optname optionnames[] = { #ifdef SO_NOREUSEADDR /* AIX 4.3.3 */ IF_SOCKET ("noreuseaddr", &opt_so_noreuseaddr) #endif /* SO_NOREUSEADDR */ +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + IF_OPENSSL("nosni", &opt_openssl_no_sni) +#endif IF_TUN ("notrailers", &opt_iff_notrailers) #ifdef O_NSHARE IF_OPEN ("nshare", &opt_o_nshare) @@ -1164,8 +1170,14 @@ const struct optname optionnames[] = { #endif #if HAVE_SSL_set_min_proto_version || defined(SSL_set_min_proto_version) IF_OPENSSL("openssl-min-proto-version", &opt_openssl_min_proto_version) +#endif +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + IF_OPENSSL("openssl-no-sni", &opt_openssl_no_sni) #endif IF_OPENSSL("openssl-pseudo", &opt_openssl_pseudo) +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + IF_OPENSSL("openssl-snihost", &opt_openssl_snihost) +#endif IF_OPENSSL("openssl-verify", &opt_openssl_verify) IF_TERMIOS("opost", &opt_opost) #if HAVE_TERMIOS_OSPEED @@ -1434,6 +1446,9 @@ const struct optname optionnames[] = { #ifdef SO_SNDLOWAT IF_SOCKET ("sndlowat", &opt_so_sndlowat) #endif +#if defined(HAVE_SSL_set_tlsext_host_name) || defined(SSL_set_tlsext_host_name) + IF_OPENSSL("snihost", &opt_openssl_snihost) +#endif #ifdef SO_ACCEPTCONN /* AIX433 */ IF_SOCKET ("so-acceptconn", &opt_so_acceptconn) #endif /* SO_ACCEPTCONN */ diff --git a/xioopts.h b/xioopts.h index 30e9fd2..a87b66d 100644 --- a/xioopts.h +++ b/xioopts.h @@ -490,7 +490,9 @@ enum e_optcode { OPT_OPENSSL_MAX_PROTO_VERSION, OPT_OPENSSL_METHOD, OPT_OPENSSL_MIN_PROTO_VERSION, + OPT_OPENSSL_NO_SNI, OPT_OPENSSL_PSEUDO, + OPT_OPENSSL_SNIHOST, OPT_OPENSSL_VERIFY, OPT_OPOST, /* termios.c_oflag */ OPT_OSPEED, /* termios.c_ospeed */