Added SNI support to OPENSSL-CONNECT, with options no-sni, snihost

This commit is contained in:
Gerhard Rieger 2020-12-31 14:30:04 +01:00
parent d109e3131b
commit aa2b9c00b2
9 changed files with 179 additions and 2 deletions

View file

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

View file

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

View file

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

View file

@ -2777,6 +2777,17 @@ label(OPTION_OPENSSL_COMMONNAME)dit(bf(tt(commonname=<string>)))
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=<bool>)))
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=<string>)))
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).

97
test.sh
View file

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

View file

@ -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,10 +255,26 @@ 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);
@ -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);

View file

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

View file

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

View file

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