From cd164e7b586539ec1d93f15b27c96b7966332ab0 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Thu, 2 Apr 2015 15:45:04 +0200 Subject: [PATCH] Check OpenSSL peers commonName+subjectAltName; new option openssl-commonname --- CHANGES | 11 ++ doc/socat.yo | 36 ++++- socat.c | 2 +- sysutils.c | 86 +++++------ sysutils.h | 6 +- test.sh | 126 ++++++++++++---- testcert.conf | 6 +- utils.c | 2 +- xio-openssl.c | 386 +++++++++++++++++++++++++++++++++++++++----------- xio-openssl.h | 5 +- xio-socket.c | 12 +- xioopts.c | 3 + xioopts.h | 3 +- 13 files changed, 514 insertions(+), 170 deletions(-) diff --git a/CHANGES b/CHANGES index 1ba0f58..0e990b6 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,17 @@ security: Use sigaction() instead of signal() for better control Turn off nested signal handler invocations Thanks to Peter Lobsinger for reporting and explaining this issue. + + Red Hat issue 1019975: add TLS host name checks + OpenSSL client checks if the server certificates names in + extensions/subjectAltName/DNS or in subject/commonName match the name + used to connect or the value of the openssl-commonname option. + Test: OPENSSL_CN_CLIENT_SECURITY + + OpenSSL server checks if the client certificates names in + extensions/subjectAltNames/DNS or subject/commonName match the value of + the openssl-commonname option when it is used. + Test: OPENSSL_CN_SERVER_SECURITY corrections: LISTEN based addresses applied some address options, e.g. so-keepalive, diff --git a/doc/socat.yo b/doc/socat.yo index e18c3ba..3006289 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -542,14 +542,23 @@ label(ADDRESS_OPENSSL_CONNECT)dit(bf(tt(OPENSSL::))) [link(IP address)(TYPE_IP_ADDRESS)] using TCP/IP version 4 or 6 depending on address specification, name resolution, or option link(pf)(OPTION_PROTOCOL_FAMILY).nl() - NOTE: The server certificate is only checked for validity against - link(cafile)(OPTION_OPENSSL_CAFILE) or link(capath)(OPTION_OPENSSL_CAPATH), - but not for match with the server's name or its IP address!nl() + NOTE: Up to version 1.7.2.4 and 2.0.0-b7 + the server certificate was only checked for validity against the system + certificate store or link(cafile)(OPTION_OPENSSL_CAFILE) or + link(capath)(OPTION_OPENSSL_CAPATH), + but not for match with the server's name or its IP address. + Since version 1.7.3.0 and 2.0.0-b8 socat checks the peer certificate for match with the + parameter or the value of the + link(openssl-commonname)(OPTION_OPENSSL_COMMONNAME) option. + Socat tries to match it against the certificates subject commonName, + and the certifications extension subjectAltName DNS names. Wildcards in the + certificate are supported.nl() Option groups: link(FD)(GROUP_FD),link(SOCKET)(GROUP_SOCKET),link(IP4)(GROUP_IP4),link(IP6)(GROUP_IP6),link(TCP)(GROUP_TCP),link(OPENSSL)(GROUP_OPENSSL),link(RETRY)(GROUP_RETRY) nl() Useful options: link(cipher)(OPTION_OPENSSL_CIPHERLIST), link(method)(OPTION_OPENSSL_METHOD), link(verify)(OPTION_OPENSSL_VERIFY), + link(commonname)(OPTION_OPENSSL_COMMONNAME) link(cafile)(OPTION_OPENSSL_CAFILE), link(capath)(OPTION_OPENSSL_CAPATH), link(certificate)(OPTION_OPENSSL_CERTIFICATE), @@ -577,6 +586,7 @@ label(ADDRESS_OPENSSL_LISTEN)dit(bf(tt(OPENSSL-LISTEN:))) link(cipher)(OPTION_OPENSSL_CIPHERLIST), link(method)(OPTION_OPENSSL_METHOD), link(verify)(OPTION_OPENSSL_VERIFY), + link(commonname)(OPTION_OPENSSL_COMMONNAME) link(cafile)(OPTION_OPENSSL_CAFILE), link(capath)(OPTION_OPENSSL_CAPATH), link(certificate)(OPTION_OPENSSL_CERTIFICATE), @@ -2736,7 +2746,7 @@ label(OPTION_OPENSSL_PSEUDO)dit(bf(tt(pseudo))) providing pseudo entropy. This is archieved by taking the current time in microseconds for feeding the libc pseudo random number generator with an initial value. openssl is then feeded with output from random\() calls.nl() - NOTE:This mechanism is not sufficient for generation of secure keys! + NOTE: This mechanism is not sufficient for generation of secure keys! 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). @@ -2750,6 +2760,14 @@ label(OPTION_OPENSSL_COMPRESS)dit(bf(tt(compress))) compression-related settings. NOTE: Requires OpenSSL 0.9.8 or higher and disabling compression with OpenSSL 0.9.8 affects all new connections in the process. +label(OPTION_OPENSSL_COMMONNAME)dit(bf(tt(commonname=))) + Specify the commonname that the peer certificate must match. With + link(OPENSSL-CONNECT)(ADDRESS_OPENSSL_CONNECT) address this overrides the + given hostname or IP target address; with + link(OPENSSL-LISTEN)(ADDRESS_OPENSSL_LISTEN) this turns on check of peer + certificates commonname. This option has only meaning when option + link(verify)(OPTION_OPENSSL_VERIFY) is not disabled and the choosen cipher + provides a peer certificate. enddit() startdit()enddit()nl() @@ -3534,6 +3552,16 @@ dit(bf(SOCAT_IPV6_TCLASS) (output)) With all IPv6 based RECVFROM addresses where address option link(ipv6-recvtclass)(OPTION_IPV6_RECVTCLASS) is applied, socat() sets this variable to the transfer class of the received packet. +dit(bf(SOCAT_OPENSSL_X509_ISSUER) (output)) Issuer field from peer certificate + +dit(bf(SOCAT_OPENSSL_X509_SUBJECT (output))) Subject field from peer certificate + +dit(bf(SOCAT_OPENSSL_X509_COMMONNAME) (output)) commonName entries from peer certificates subject. Multiple values are separated by " // ". + +dit(bf(SOCAT_OPENSSL_X509_*) (output)) all other entries from peer certificates subject + +dit(bf(SOCAT_OPENSSL_X509V3_DNS) (output)) DNS entries from peer certificates extensions - subjectAltName field. Multiple values are separated by " // ". + dit(bf(HOSTNAME) (input)) Is used to determine the hostname for logging (see link(-lh)(option_lh)). diff --git a/socat.c b/socat.c index 6564d77..abe2c79 100644 --- a/socat.c +++ b/socat.c @@ -275,7 +275,7 @@ int main(int argc, const char *argv[]) { Info(copyright_ssleay); #endif Debug2("socat version %s on %s", socatversion, timestamp); - xiosetenv("VERSION", socatversion, 1); /* SOCAT_VERSION */ + xiosetenv("VERSION", socatversion, 1, NULL); /* SOCAT_VERSION */ uname(&ubuf); /* ! here we circumvent internal tracing (Uname) */ Debug4("running on %s version %s, release %s, machine %s\n", ubuf.sysname, ubuf.version, ubuf.release, ubuf.machine); diff --git a/sysutils.c b/sysutils.c index 1188409..8cfd639 100644 --- a/sysutils.c +++ b/sysutils.c @@ -666,24 +666,19 @@ int ifindex(const char *ifname, unsigned int *ifindex, int anysock) { #endif /* WITH_IP4 || WITH_IP6 || WITH_INTERFACE */ -/* constructs an environment variable whose name is built from socats uppercase - program name, and underscore and varname; if a variable of this name already - exists a non zero value of overwrite lets the old value be overwritten. - returns 0 on success or <0 if an error occurred. */ -int xiosetenv(const char *varname, const char *value, int overwrite) { -# define XIO_ENVNAMELEN 256 - const char *progname; - char envname[XIO_ENVNAMELEN]; - size_t i, l; - - progname = diag_get_string('p'); - envname[0] = '\0'; strncat(envname, progname, XIO_ENVNAMELEN-1); - l = strlen(progname); - for (i = 0; i < l; ++i) envname[i] = toupper(envname[i]); - strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); - l += 1; - strncat(envname+l, varname, XIO_ENVNAMELEN-l-1); - if (Setenv(envname, value, overwrite) < 0) { +int _xiosetenv(const char *envname, const char *value, int overwrite, const char *sep) { + char *oldval; + char *newval; + if (overwrite >= 2 && (oldval = getenv(envname)) != NULL) { + size_t newlen = strlen(oldval)+strlen(sep)+strlen(value)+1; + if ((newval = Malloc(newlen+1)) == NULL) { + return -1; + } + snprintf(newval, newlen+1, "%s%s%s", oldval, sep, value); + } else { + newval = (char *)value; + } + if (Setenv(envname, newval, overwrite) < 0) { Warn3("setenv(\"%s\", \"%s\", 1): %s", envname, value, strerror(errno)); #if HAVE_UNSETENV @@ -692,11 +687,34 @@ int xiosetenv(const char *varname, const char *value, int overwrite) { return -1; } return 0; +} + +/* constructs an environment variable whose name is built from socats uppercase + program name, and underscore and varname; + if the variable of this name already exists arg overwrite determines: + 0: keep old value + 1: overwrite with new value + 2: append to old value, separated by *sep + returns 0 on success or <0 if an error occurred. */ +int xiosetenv(const char *varname, const char *value, int overwrite, const char *sep) { +# define XIO_ENVNAMELEN 256 + const char *progname; + char envname[XIO_ENVNAMELEN]; + size_t i, l; + + progname = diag_get_string('p'); + envname[0] = '\0'; strncat(envname, progname, XIO_ENVNAMELEN-1); + l = strlen(envname); + for (i = 0; i < l; ++i) envname[i] = toupper(envname[i]); + strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); + l += 1; + strncat(envname+l, varname, XIO_ENVNAMELEN-l-1); + return _xiosetenv(envname, value, overwrite, sep); # undef XIO_ENVNAMELEN } int xiosetenv2(const char *varname, const char *varname2, const char *value, - int overwrite) { + int overwrite, const char *sep) { # define XIO_ENVNAMELEN 256 const char *progname; char envname[XIO_ENVNAMELEN]; @@ -708,27 +726,19 @@ int xiosetenv2(const char *varname, const char *varname2, const char *value, strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); l += 1; strncat(envname+l, varname, XIO_ENVNAMELEN-l-1); - l += strlen(varname+l); + l += strlen(envname+l); strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); l += 1; strncat(envname+l, varname2, XIO_ENVNAMELEN-l-1); - l += strlen(varname+l); + l += strlen(envname+l); for (i = 0; i < l; ++i) envname[i] = toupper(envname[i]); - if (Setenv(envname, value, overwrite) < 0) { - Warn3("setenv(\"%s\", \"%s\", 1): %s", - envname, value, strerror(errno)); -#if HAVE_UNSETENV - Unsetenv(envname); /* dont want to have a wrong value */ -#endif - return -1; - } - return 0; + return _xiosetenv(envname, value, overwrite, sep); # undef XIO_ENVNAMELEN } int xiosetenv3(const char *varname, const char *varname2, const char *varname3, const char *value, - int overwrite) { + int overwrite, const char *sep) { # define XIO_ENVNAMELEN 256 const char *progname; char envname[XIO_ENVNAMELEN]; @@ -750,15 +760,7 @@ int xiosetenv3(const char *varname, const char *varname2, const char *varname3, strncat(envname+l, varname3, XIO_ENVNAMELEN-l-1); l += strlen(envname+l); for (i = 0; i < l; ++i) envname[i] = toupper(envname[i]); - if (Setenv(envname, value, overwrite) < 0) { - Warn3("setenv(\"%s\", \"%s\", 1): %s", - envname, value, strerror(errno)); -#if HAVE_UNSETENV - Unsetenv(envname); /* dont want to have a wrong value */ -#endif - return -1; - } - return 0; + return _xiosetenv(envname, value, overwrite, sep); # undef XIO_ENVNAMELEN } @@ -769,7 +771,7 @@ int xiosetenvulong(const char *varname, unsigned long value, int overwrite) { char envbuff[XIO_LONGLEN]; snprintf(envbuff, XIO_LONGLEN, "%lu", value); - return xiosetenv(varname, envbuff, overwrite); + return xiosetenv(varname, envbuff, overwrite, NULL); # undef XIO_LONGLEN } @@ -779,6 +781,6 @@ int xiosetenvushort(const char *varname, unsigned short value, int overwrite) { char envbuff[XIO_SHORTLEN]; snprintf(envbuff, XIO_SHORTLEN, "%hu", value); - return xiosetenv(varname, envbuff, overwrite); + return xiosetenv(varname, envbuff, overwrite, NULL); # undef XIO_SHORTLEN } diff --git a/sysutils.h b/sysutils.h index f0deeae..bd59757 100644 --- a/sysutils.h +++ b/sysutils.h @@ -89,13 +89,13 @@ extern int parseport(const char *portname, int proto); extern int ifindexbyname(const char *ifname, int anysock); extern int ifindex(const char *ifname, unsigned int *ifindex, int anysock); -extern int xiosetenv(const char *varname, const char *value, int overwrite); +extern int xiosetenv(const char *varname, const char *value, int overwrite, const char *sep); extern int xiosetenv2(const char *varname, const char *varname2, const char *value, - int overwrite); + int overwrite, const char *sep); extern int xiosetenv3(const char *varname, const char *varname2, const char *varname3, - const char *value, int overwrite); + const char *value, int overwrite, const char *sep); extern int xiosetenvulong(const char *varname, unsigned long value, int overwrite); extern int xiosetenvushort(const char *varname, unsigned short value, diff --git a/test.sh b/test.sh index d2688c3..e0a9a7e 100755 --- a/test.sh +++ b/test.sh @@ -70,8 +70,8 @@ else fi MCINTERFACE=lo # !!! Linux only #LOCALHOST=192.168.58.1 -#LOCALHOST=localhost -LOCALHOST=127.0.0.1 +LOCALHOST=localhost +#LOCALHOST=127.0.0.1 LOCALHOST6=[::1] #PROTO=$(awk '{print($2);}' /etc/protocols |sort -n |tail -n 1) #PROTO=$(($PROTO+1)) @@ -79,10 +79,24 @@ PROTO=$((144+RANDOM/2048)) PORT=12002 SOURCEPORT=2002 TESTCERT_CONF=testcert.conf -TESTCERT_SUBJECT="/C=XY" -TESTCERT_ISSUER="/C=XY" +TESTCERT6_CONF=testcert6.conf +# keep these values consistent with testcert.conf +TESTCERT_COMMONNAME="$LOCALHOST" +TESTCERT_COUNTRYNAME="$(grep ^countryName= testcert.conf)"; TESTCERT_COUNTRYNAME="${TESTCERT_COUNTRYNAME##*=}" +TESTCERT_LOCALITYNAME="$(grep ^L= testcert.conf)"; TESTCERT_LOCALITYNAME="${TESTCERT_LOCALITYNAME##*=}" +TESTCERT_ORGANIZATIONALUNITNAME="$(grep ^OU= testcert.conf)"; TESTCERT_ORGANIZATIONALUNITNAME="${TESTCERT_ORGANIZATIONALUNITNAME##*=}" +TESTCERT_ORGANIZATIONNAME="$(grep ^O= testcert.conf)"; TESTCERT_ORGANIZATIONNAME="${TESTCERT_ORGANIZATIONNAME##*=}" +TESTCERT_SUBJECT="C = XY, CN = localhost, O = dest-unreach, OU = socat, L = Lunar Base" +TESTCERT_ISSUER="C = XY, CN = localhost, O = dest-unreach, OU = socat, L = Lunar Base" CAT=cat OD_C="od -c" + +# clean up from previous runs +rm -f testcli.{crt,key,pem} +rm -f testsrv.{crt,key,pem} +rm -f testcli6.{crt,key,pem} +rm -f testsrv6.{crt,key,pem} + # precision sleep; takes seconds with fractional part psleep () { local T="$1" @@ -2243,6 +2257,17 @@ gentestdsacert () { openssl req -newkey dsa:$name-dsa.pem -keyout $name.key -nodes -x509 -days 3653 -config $TESTCERT_CONF -out $name.crt >/dev/null 2>&1 cat $name-dsa.pem $name-dh.pem $name.key $name.crt >$name.pem } + +gentestcert6 () { + local name="$1" + if [ -s $name.key -a -s $name.crt -a -s $name.pem ]; then return; fi + cat $TESTCERT_CONF | + { echo "# automatically generated by $0"; cat; } | + sed 's/\(commonName\s*=\s*\).*/\1[::1]/' >$TESTCERT6_CONF + openssl genrsa $OPENSSL_RAND -out $name.key 768 >/dev/null 2>&1 + openssl req -new -config $TESTCERT6_CONF -key $name.key -x509 -out $name.crt -days 3653 >/dev/null 2>&1 + cat $name.key $name.crt >$name.pem +} NAME=UNISTDIO @@ -4137,7 +4162,7 @@ TESTKEYW=${TESTADDR%%:*} # does our address implementation support halfclose? NAME=${NAMEKEYW}_HALFCLOSE case "$TESTS" in -*%$N%*|*%functions%*|*%socket%*|*%halfclose%*|*%$NAME%*) +*%$N%*|*%functions%*|*%$FEAT%*|*%socket%*|*%halfclose%*|*%$NAME%*) TEST="$NAME: $TESTKEYW half close" # have a "peer" socat "peer" that executes "$OD_C" and see if EOF on the # connecting socat brings the result of od @@ -5166,7 +5191,7 @@ N=$((N+1)) #! NAME=OUTBOUNDIN case "$TESTS" in -*%$N%*|*%functions%*|*%chain%*|*%proxy%*|*%$NAME%*) +*%$N%*|*%functions%|*%chain%**|*%openssl%*|*%proxy%*|*%$NAME%*) TEST="$NAME: gender changer via SSL through HTTP proxy, oneshot" if ! eval $NUMCOND; then :; elif ! feat=$(testaddrs openssl proxy); then @@ -5254,7 +5279,7 @@ PORT=$((RANDOM+16184)) #! NAME=INTRANETRIPPER case "$TESTS" in -*%$N%*|*%functions%*|*%chain%*|*%proxy%*|*%$NAME%*) +*%$N%*|*%functions%*|*%chain%*|*%openssl%*|*%proxy%*|*%$NAME%*) TEST="$NAME: gender changer via SSL through HTTP proxy, daemons" if ! eval $NUMCOND; then :; elif ! feat=$(testaddrs openssl proxy); then @@ -5387,7 +5412,6 @@ testserversec () { local stat result $PRINTF "test $F_n %s... " $N "$title" -#set -vx # first: without security # start server $TRACE $SOCAT $opts "$arg1,$secopt0" echo 2>"${te}1" & @@ -5404,7 +5428,7 @@ testserversec () { (echo "$da"; sleep $T) |$TRACE $SOCAT $opts - "$arg2" >"$tf" 2>"${te}2" stat="$?" kill $spid 2>/dev/null - #killall $TRACE $SOCAT 2>/dev/null + #killall $SOCAT 2>/dev/null if [ "$stat" != 0 ]; then $PRINTF "$NO_RESULT (ph.1 function fails): $TRACE $SOCAT:\n" echo "$TRACE $SOCAT $opts \"$arg1,$secopt0\" echo &" @@ -5814,7 +5838,7 @@ elif ! testaddrs openssl >/dev/null; then numCANT=$((numCANT+1)) else gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "range=$SECONDADDR/32" "ssl:127.0.0.1:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "range=$SECONDADDR/32" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5830,8 +5854,8 @@ elif ! testaddrs openssl >/dev/null; then numCANT=$((numCANT+1)) else gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "sp=$PORT" "ssl:127.0.0.1:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 -fi ;; # NUMCOND, feats +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "sp=$PORT" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 + fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) N=$((N+1)) @@ -5846,7 +5870,7 @@ elif ! testaddrs openssl >/dev/null; then numCANT=$((numCANT+1)) else gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "lowport" "ssl:127.0.0.1:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "lowport" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5866,7 +5890,7 @@ ha="$td/hosts.allow" hd="$td/hosts.deny" $ECHO "socat: $SECONDADDR" >"$ha" $ECHO "ALL: ALL" >"$hd" -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "tcpwrap-etc=$td" "ssl:127.0.0.1:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "tcpwrap-etc=$td" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5883,7 +5907,7 @@ elif ! testaddrs openssl >/dev/null; then else gentestcert testsrv gentestcert testcli -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify,cert=testsrv.crt,key=testsrv.key" "cafile=testcli.crt" "cafile=testsrv.crt" "ssl:127.0.0.1:$PORT,cafile=testsrv.crt,cert=testcli.pem,$SOCAT_EGD" 4 tcp $PORT -1 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip4,reuseaddr,fork,retry=1,$SOCAT_EGD,verify,cert=testsrv.crt,key=testsrv.key" "cafile=testcli.crt" "cafile=testsrv.crt" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,cert=testcli.pem,$SOCAT_EGD" 4 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5919,8 +5943,8 @@ elif ! feat=$(testaddrs tcp ip6) || ! runsip6 >/dev/null; then $PRINTF "test $F_n $TEST... ${YELLOW}TCP6 not available${NORMAL}\n" $N numCANT=$((numCANT+1)) else -gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "range=[::2/128]" "ssl:[::1]:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 6 tcp $PORT -1 +gentestcert6 testsrv6 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv6.crt,key=testsrv6.key" "" "range=[::2/128]" "SSL:[::1]:$PORT,cafile=testsrv6.crt,$SOCAT_EGD" 6 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5938,8 +5962,8 @@ elif ! feat=$(testaddrs tcp ip6) || ! runsip6 >/dev/null; then $PRINTF "test $F_n $TEST... ${YELLOW}TCP6 not available${NORMAL}\n" $N numCANT=$((numCANT+1)) else -gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "sp=$PORT" "ssl:[::1]:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 6 tcp $PORT -1 +gentestcert6 testsrv6 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv6.crt,key=testsrv6.key" "" "sp=$PORT" "SSL:[::1]:$PORT,cafile=testsrv6.crt,$SOCAT_EGD" 6 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5957,8 +5981,8 @@ elif ! feat=$(testaddrs tcp ip6) || ! runsip6 >/dev/null; then $PRINTF "test $F_n $TEST... ${YELLOW}TCP6 not available${NORMAL}\n" $N numCANT=$((numCANT+1)) else -gentestcert testsrv -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "lowport" "ssl:[::1]:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 6 tcp $PORT -1 +gentestcert6 testsrv6 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv6.crt,key=testsrv6.key" "" "lowport" "SSL:[::1]:$PORT,cafile=testsrv6.crt,$SOCAT_EGD" 6 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5973,17 +5997,65 @@ elif ! feat=$(testaddrs ip6 tcp libwrap openssl) || ! runsip6 >/dev/null; then $PRINTF "test $F_n $TEST... ${YELLOW}$feat not available${NORMAL}\n" $N numCANT=$((numCANT+1)) else -gentestcert testsrv +gentestcert6 testsrv6 ha="$td/hosts.allow" hd="$td/hosts.deny" $ECHO "socat: [::2]" >"$ha" $ECHO "ALL: ALL" >"$hd" -testserversec "$N" "$TEST" "$opts -s" "ssl-l:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv.crt,key=testsrv.key" "" "tcpwrap-etc=$td" "ssl:[::1]:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 6 tcp $PORT -1 +testserversec "$N" "$TEST" "$opts -s" "SSL-L:$PORT,pf=ip6,reuseaddr,fork,retry=1,$SOCAT_EGD,verify=0,cert=testsrv6.crt,key=testsrv6.key" "" "tcpwrap-etc=$td" "SSL:[::1]:$PORT,cafile=testsrv6.crt,$SOCAT_EGD" 6 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) N=$((N+1)) + +# test security with the openssl-commonname option on client side +NAME=OPENSSL_CN_CLIENT_SECURITY +case "$TESTS" in +*%$N%*|*%functions%*|*%security%*|*%openssl%*|*%tcp%*|*%tcp4%*|*%ip4%*|*%$NAME%*) +TEST="$NAME: security of client openssl-commonname option" +# connect using non matching server name/address with commonname +# options, this should succeed. Then without this option, should fail +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)) +elif ! testaddrs listen tcp ip4 >/dev/null || ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}TCP/IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) +else +gentestcert testsrv +gentestcert testcli +testserversec "$N" "$TEST" "$opts" "SSL:127.0.0.1:$PORT,fork,retry=2,verify,cafile=testsrv.crt" "commonname=$LOCALHOST" "" "SSL-L:$PORT,pf=ip4,reuseaddr,cert=testsrv.crt,key=testsrv.key,verify=0" 4 tcp "" 0 +fi ;; # testaddrs, NUMCOND +esac +PORT=$((PORT+1)) +N=$((N+1)) + +# test security with the openssl-commonname option on server side +NAME=OPENSSL_CN_SERVER_SECURITY +case "$TESTS" in +*%$N%*|*%functions%*|*%security%*|*%openssl%*|*%tcp%*|*%tcp4%*|*%ip4%*|*%$NAME%*) +TEST="$NAME: security of server openssl-commonname option" +# connect using with client certificate to server, this should succeed. +# Then use the server with a non matching openssl-commonname option, +# this must fail +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)) +elif ! testaddrs listen tcp ip4 >/dev/null || ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}TCP/IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) +else +gentestcert testsrv +gentestcert testcli +testserversec "$N" "$TEST" "$opts" "SSL-L:$PORT,pf=ip4,reuseaddr,cert=testsrv.crt,key=testsrv.key,cafile=testcli.crt" "" "commonname=onlyyou" "SSL:$LOCALHOST:$PORT,verify=0,cafile=testsrv.crt,cert=testcli.crt,key=testcli.key" 4 tcp "" 0 +fi ;; # testaddrs, NUMCOND +esac +PORT=$((PORT+1)) +N=$((N+1)) + NAME=OPENSSL_FIPS_SECURITY case "$TESTS" in @@ -9123,7 +9195,7 @@ NAME=OPENSSLREAD # keeps there and is not transferred by socat until the socket indicates more # data or EOF. case "$TESTS" in -*%$N%*|*%functions%*|*%$NAME%*) +*%$N%*|*%functions%*|*%openssl%*|*%$NAME%*) TEST="$NAME: socat handles data buffered by openssl" #idea: have a socat process (server) that gets an SSL block that is larger than # socat transfer block size; keep the socket connection open and kill the @@ -10015,10 +10087,10 @@ N=$((N+1)) set +xv # done <<<" -TCP4 TCP $LOCALHOST $SECONDADDR $PORT $((PORT+1)) +TCP4 TCP 127.0.0.1 $SECONDADDR $PORT $((PORT+1)) TCP6 IP6 [0000:0000:0000:0000:0000:0000:0000:0001] [0000:0000:0000:0000:0000:0000:0000:0001] $((PORT+2)) $((PORT+3)) UDP6 IP6 [0000:0000:0000:0000:0000:0000:0000:0001] [0000:0000:0000:0000:0000:0000:0000:0001] $((PORT+6)) $((PORT+7)) -SCTP4 SCTP $LOCALHOST $SECONDADDR $((PORT+8)) $((PORT+9)) +SCTP4 SCTP 127.0.0.1 $SECONDADDR $((PORT+8)) $((PORT+9)) SCTP6 SCTP [0000:0000:0000:0000:0000:0000:0000:0001] [0000:0000:0000:0000:0000:0000:0000:0001] $((PORT+10)) $((PORT+11)) UNIX UNIX $td/test\$N.server $td/test\$N.client , , " @@ -11737,7 +11809,7 @@ N=$((N+1)) # Linux) with "Invalid argument". NAME=OPENSSL_CONNECT_BIND case "$TESTS" in -*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%ssl%*|*%$NAME%*) +*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%ssl%*|*%openssl%*|*%$NAME%*) TEST="$NAME: test OPENSSL-CONNECT with bind option" # have a simple SSL server that just echoes data. # connect with socat using OPENSSL-CONNECT with bind, send data and check if the diff --git a/testcert.conf b/testcert.conf index 64b06cd..44dbd5a 100644 --- a/testcert.conf +++ b/testcert.conf @@ -5,5 +5,9 @@ default_bits = 768 distinguished_name=Test [ Test ] -countryName = XY +countryName=XY +commonName=localhost +O=dest-unreach +OU=socat +L=Lunar Base diff --git a/utils.c b/utils.c index be4ea28..a00343e 100644 --- a/utils.c +++ b/utils.c @@ -67,7 +67,7 @@ const struct wordent *keyw(const struct wordent *keywds, const char *name, unsig return NULL; } -/* Linux: setenv(), AIX: putenv() */ +/* Linux: setenv(), AIX (4.3?): putenv() */ #if !HAVE_SETENV int setenv(const char *name, const char *value, int overwrite) { int result; diff --git a/xio-openssl.c b/xio-openssl.c index a24449c..6367645 100644 --- a/xio-openssl.c +++ b/xio-openssl.c @@ -6,6 +6,9 @@ #include "xiosysincludes.h" #if WITH_OPENSSL /* make this address configure dependend */ +#include +#include + #include "xioopen.h" #include "xio-fd.h" @@ -45,11 +48,14 @@ static int xioopen_openssl_listen(int argc, const char *argv[], struct opt *opts int xioflags, xiofile_t *fd, unsigned groups, int dummy1, int dummy2, int dummy3); static int openssl_SSL_ERROR_SSL(int level, const char *funcname); -static int openssl_handle_peer_certificate(struct single *xfd, bool opt_ver, +static int openssl_handle_peer_certificate(struct single *xfd, + const char *peername, + bool opt_ver, int level); static int xioSSL_set_fd(struct single *xfd, int level); -static int xioSSL_connect(struct single *xfd, bool opt_ver, int level); - +static int xioSSL_connect(struct single *xfd, const char *opt_commonname, bool opt_ver, int level); +static int openssl_delete_cert_info(void); + /* description record for inter-address ssl connect with 0 parameters */ static const struct xioaddr_inter_desc xiointer_openssl_connect0 = { @@ -183,6 +189,7 @@ const struct optdesc opt_openssl_compress = { "openssl-compress", "compress #if WITH_FIPS 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 FIPS is compiled in, we need to track if the user asked for FIPS mode. @@ -263,6 +270,7 @@ static int /*0 SSL_CTX* ctx;*/ 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 */ int result; if (!(xioflags & XIO_MAYCONVERT)) { @@ -278,6 +286,12 @@ static int if (argc == 3) { hostname = argv[1]; portname = argv[2]; + if (hostname[0] == '\0') { + /* we catch this explicitely because empty commonname (peername) disables + commonName check of peer certificate */ + Error1("%s: empty host name", argv[0]); + return STAT_NORETRY; + } /* a "terminal" form where we build a tcp connection to given host and port */ @@ -288,6 +302,11 @@ static int retropt_bool(opts, OPT_FORK, &dofork); retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert); + retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname); + + if (opt_commonname == NULL) { + opt_commonname = hostname; + } result = _xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert, @@ -314,6 +333,7 @@ static int retropt_bool(opts, OPT_FORK, &dofork); retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert); + retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname); result = _xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert, @@ -378,7 +398,7 @@ static int } result = - _xioopen_openssl_connect(xfd, opt_ver, xfd->para.openssl.ctx, level); + _xioopen_openssl_connect(xfd, opt_ver, opt_commonname, xfd->para.openssl.ctx, level); switch (result) { case STAT_OK: break; #if WITH_RETRY @@ -449,6 +469,7 @@ static int SSL connection from an FD and a CTX. */ int _xioopen_openssl_connect(struct single *xfd, bool opt_ver, + const char *opt_commonname, SSL_CTX *ctx, int level) { SSL *ssl; @@ -473,14 +494,15 @@ static int return result; } - result = xioSSL_connect(xfd, opt_ver, level); + result = xioSSL_connect(xfd, opt_commonname, opt_ver, level); if (result != STAT_OK) { sycSSL_free(xfd->para.openssl.ssl); xfd->para.openssl.ssl = NULL; return result; } - result = openssl_handle_peer_certificate(xfd, opt_ver, level); + result = openssl_handle_peer_certificate(xfd, opt_commonname, + opt_ver, level); if (result != STAT_OK) { sycSSL_free(xfd->para.openssl.ssl); xfd->para.openssl.ssl = NULL; @@ -522,6 +544,7 @@ static int /*0 SSL_CTX* ctx;*/ bool opt_ver = true; /* verify peer certificate - changed with 1.6.0 */ char *opt_cert = NULL; /* file name of server certificate */ + const char *opt_commonname = NULL; /* for checking peer certificate */ int result; if (!(xioflags & XIO_MAYCONVERT)) { @@ -549,6 +572,8 @@ static int Warn("no certificate given; consider option \"cert\""); } + retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname); + result = _xioopen_openssl_prepare(opts, xfd, true, &opt_ver, opt_cert, &xfd->para.openssl.ctx); @@ -576,6 +601,8 @@ static int Warn("no certificate given; consider option \"cert\""); } + retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname); + applyopts(-1, opts, PH_EARLY); result = @@ -634,7 +661,7 @@ static int xfd->wfd = xfd->rfd; } result = - _xioopen_openssl_listen(xfd, opt_ver, xfd->para.openssl.ctx, level); + _xioopen_openssl_listen(xfd, opt_ver, opt_commonname, xfd->para.openssl.ctx, level); switch (result) { case STAT_OK: break; #if WITH_RETRY @@ -668,6 +695,7 @@ static int int _xioopen_openssl_listen(struct single *xfd, bool opt_ver, + const char *opt_commonname, SSL_CTX *ctx, int level) { char error_string[120]; @@ -748,7 +776,7 @@ int _xioopen_openssl_listen(struct single *xfd, return STAT_RETRYLATER; } - if (openssl_handle_peer_certificate(xfd, opt_ver, E_ERROR/*!*/) < 0) { + if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, E_ERROR/*!*/) < 0) { return STAT_NORETRY; } @@ -852,7 +880,6 @@ int #if OPENSSL_VERSION_NUMBER >= 0x00908000L retropt_string(opts, OPT_OPENSSL_COMPRESS, &opt_compress); #endif - #if WITH_FIPS if (opt_fips) { if (!sycFIPS_mode_set(1)) { @@ -865,6 +892,8 @@ int } #endif + openssl_delete_cert_info(); + OpenSSL_add_all_algorithms(); OpenSSL_add_all_ciphers(); OpenSSL_add_all_digests(); @@ -1104,19 +1133,21 @@ int static int openssl_SSL_ERROR_SSL(int level, const char *funcname) { unsigned long e; char buf[120]; /* this value demanded by "man ERR_error_string" */ + int stat = STAT_OK; - e = ERR_get_error(); - Debug1("ERR_get_error(): %lx", e); - if (e == ((ERR_LIB_RAND<<24)| - (RAND_F_SSLEAY_RAND_BYTES<<12)| - (RAND_R_PRNG_NOT_SEEDED)) /*0x24064064*/) { - Error("too few entropy; use options \"egd\" or \"pseudo\""); - return STAT_NORETRY; - } else { - Msg2(level, "%s(): %s", funcname, ERR_error_string(e, buf)); - return level==E_ERROR ? STAT_NORETRY : STAT_RETRYLATER; + while (e = ERR_get_error()) { + Debug1("ERR_get_error(): %lx", e); + if (e == ((ERR_LIB_RAND<<24)| + (RAND_F_SSLEAY_RAND_BYTES<<12)| + (RAND_R_PRNG_NOT_SEEDED)) /*0x24064064*/) { + Error("too few entropy; use options \"egd\" or \"pseudo\""); + stat = STAT_NORETRY; + } else { + Msg2(level, "%s(): %s", funcname, ERR_error_string(e, buf)); + stat = level==E_ERROR ? STAT_NORETRY : STAT_RETRYLATER; + } } - return STAT_OK; + return stat; } static const char *openssl_verify_messages[] = { @@ -1173,87 +1204,171 @@ static const char *openssl_verify_messages[] = { /* 50 */ "application verification failure", } ; -static int openssl_extract_cert_info(const char *field, X509_NAME *name) { - int n, i; - { - BIO *bio = BIO_new(BIO_s_mem()); - char *buf = NULL, *str; - size_t len; - X509_NAME_print_ex(bio, name, 0, XN_FLAG_ONELINE&~ASN1_STRFLGS_ESC_MSB); /* rc not documented */ - len = BIO_get_mem_data (bio, &buf); - if ((str = Malloc(len+1)) == NULL) { - BIO_free(bio); - return -1; + +/* delete all environment variables whose name begins with SOCAT_OPENSSL_ + resp. _OPENSSL_ */ +static int openssl_delete_cert_info(void) { +# define XIO_ENVNAMELEN 256 + const char *progname; + char envprefix[XIO_ENVNAMELEN]; + char envname[XIO_ENVNAMELEN]; + size_t i, l; + const char **entry; + + progname = diag_get_string('p'); + envprefix[0] = '\0'; strncat(envprefix, progname, XIO_ENVNAMELEN-1); + l = strlen(envprefix); + for (i = 0; i < l; ++i) envprefix[i] = toupper(envprefix[i]); + strncat(envprefix+l, "_OPENSSL_", XIO_ENVNAMELEN-l-1); + + entry = (const char **)environ; + while (*entry != NULL) { + if (!strncmp(*entry, envprefix, strlen(envprefix))) { + const char *eq = strchr(*entry, '='); + if (eq == NULL) eq = *entry + strlen(*entry); + envname[0] = '\0'; strncat(envname, *entry, eq-*entry); + Unsetenv(envname); + } else { + ++entry; } - str[len] = '\0'; - Info2("SSL peer cert %s: \"%s\"", field, buf); - xiosetenv2("OPENSSL_X509", field, buf, 1); - free(str); - BIO_free(bio); } + return 0; +} + +/* compares the peername used/provided by the client to cn as extracted from + the peer certificate. + supports wildcard cn like *.domain which matches domain and + host.domain + returns true on match */ +static bool openssl_check_name(const char *cn, const char *peername) { + const char *dotp; + if (peername == NULL) { + Info1("commonName \"%s\": no peername", cn); + return false; + } else if (peername[0] == '\0') { + Info1("commonName \"%s\": matched by empty peername", cn); + return true; + } + if (! (cn[0] == '*' && cn[1] == '.')) { + /* normal server name - this is simple */ + Debug1("commonName \"%s\" has no wildcard", cn); + if (strcmp(cn, peername) == 0) { + Debug2("commonName \"%s\" matches peername \"%s\"", cn, peername); + return true; + } else { + Info2("commonName \"%s\" does not match peername \"%s\"", cn, peername); + return false; + } + } + /* wildcard cert */ + Debug1("commonName \"%s\" is a wildcard name", cn); + /* case: just the base domain */ + if (strcmp(cn+2, peername) == 0) { + Debug2("wildcard commonName \"%s\" matches base domain \"%s\"", cn, peername); + return true; + } + /* case: subdomain; only one level! */ + dotp = strchr(peername, '.'); + if (dotp == NULL) { + Info2("peername \"%s\" is not a subdomain, thus is not matched by wildcard commonName \"%s\"", + peername, cn); + return false; + } + if (strcmp(cn+1, dotp) != 0) { + Info2("commonName \"%s\" does not match subdomain peername \"%s\"", cn, peername); + return false; + } + Debug2("commonName \"%s\" matches subdomain peername \"%s\"", cn, peername); + return true; +} + +/* retrieves the commonName field and compares it to the peername + returns true on match, false otherwise */ +static bool openssl_check_peername(X509_NAME *name, const char *peername) { + int ind = -1; + X509_NAME_ENTRY *entry; + ASN1_STRING *data; + unsigned char *text; + ind = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if (ind < 0) { + Info("no COMMONNAME field in peer certificate"); + return false; + } + entry = X509_NAME_get_entry(name, ind); + data = X509_NAME_ENTRY_get_data(entry); + text = ASN1_STRING_data(data); + return openssl_check_name((const char *)text, peername); +} + +/* retrieves certificate provided by peer, sets env vars containing +/* retrieves certificate provided by peer, sets env vars containing + certificates field values, and checks peername if provided by + calling function */ +/* parts of this code were copied from Gene Spaffords C/C++ Secure Programming at Etutorials.org: + http://etutorials.org/Programming/secure+programming/Chapter+10.+Public+Key+Infrastructure/10.8+Adding+Hostname+Checking+to+Certificate+Verification/ + The code examples in this tutorial do not seem to have explicit license restrictions. +*/ + +/* read in the "name" information (from field "issuer" or "subject") and + create environment variable with complete info, eg: + SOCAT_OPENSSL_X509_SUBJECT */ +static int openssl_setenv_cert_name(const char *field, X509_NAME *name) { + BIO *bio = BIO_new(BIO_s_mem()); + char *buf = NULL, *str; + size_t len; + X509_NAME_print_ex(bio, name, 0, XN_FLAG_ONELINE&~ASN1_STRFLGS_ESC_MSB); /* rc not documented */ + len = BIO_get_mem_data (bio, &buf); + if ((str = Malloc(len+1)) == NULL) { + BIO_free(bio); + return -1; + } + str[len] = '\0'; + Info2("SSL peer cert %s: \"%s\"", field, buf); + xiosetenv2("OPENSSL_X509", field, buf, 1, NULL); + free(str); + BIO_free(bio); + return 0; +} + +/* read in the "name" information (from field "issuer" or "subject") and + create environment variables with the fields, eg: + SOCAT_OPENSSL_X509_COMMONNAME +*/ +static int openssl_setenv_cert_fields(const char *field, X509_NAME *name) { + int n, i; n = X509_NAME_entry_count(name); + /* extract fields of cert name */ for (i = 0; i < n; ++i) { X509_NAME_ENTRY *entry; - char *text; - ASN1_STRING *data; ASN1_OBJECT *obj; + ASN1_STRING *data; + unsigned char *text; int nid; entry = X509_NAME_get_entry(name, i); - data = X509_NAME_ENTRY_get_data(entry); obj = X509_NAME_ENTRY_get_object(entry); + data = X509_NAME_ENTRY_get_data(entry); nid = OBJ_obj2nid(obj); - text = (char *)ASN1_STRING_data(data); - Debug3("SSL peer cert %s entry: %s=\"%s\"", field, OBJ_nid2ln(nid), text); - xiosetenv3("OPENSSL_X509", field, OBJ_nid2ln(nid), text, 0); + text = ASN1_STRING_data(data); + Debug3("SSL peer cert %s entry: %s=\"%s\"", (field[0]?field:"subject"), OBJ_nid2ln(nid), text); + if (field != NULL && field[0] != '\0') { + xiosetenv3("OPENSSL_X509", field, OBJ_nid2ln(nid), (const char *)text, 2, " // "); + } else { + xiosetenv2("OPENSSL_X509", OBJ_nid2ln(nid), (const char *)text, 2, " // "); + } } return 0; } static int openssl_handle_peer_certificate(struct single *xfd, + const char *peername, bool opt_ver, int level) { X509 *peer_cert; + X509_NAME *subjectname, *issuername; /*ASN1_TIME not_before, not_after;*/ + int extcount, i, ok = 0; int status; - /* SSL_CTX_add_extra_chain_cert - SSL_get_verify_result - */ - if ((peer_cert = SSL_get_peer_certificate(xfd->para.openssl.ssl)) != NULL) { - X509_NAME *name; - if ((name = X509_get_subject_name(peer_cert)) != NULL) - openssl_extract_cert_info("subject", name); - if ((name = X509_get_issuer_name(peer_cert)) != NULL) - openssl_extract_cert_info("issuer", name); - /* I'd like to provide dates too; see - http://markmail.org/message/yi4vspp7aeu3xwtu#query:+page:1+mid:jhnl4wklif3pgzqf+state:results */ - } - - if (peer_cert) { - if (opt_ver) { - long verify_result; - if ((verify_result = sycSSL_get_verify_result(xfd->para.openssl.ssl)) == X509_V_OK) { - Info("accepted peer certificate"); - status = STAT_OK; - } else { - const char *message = NULL; - if (verify_result >= 0 && - (size_t)verify_result < - sizeof(openssl_verify_messages)/sizeof(char*)) - { - message = openssl_verify_messages[verify_result]; - } - if (message) { - Msg1(level, "%s", message); - } else { - Msg1(level, "rejected peer certificate with error %ld", verify_result); - } - status = STAT_RETRYLATER; - } - } else { - Notice("no check of certificate"); - status = STAT_OK; - } - } else { + if ((peer_cert = SSL_get_peer_certificate(xfd->para.openssl.ssl)) == NULL) { if (opt_ver) { Msg(level, "no peer certificate"); status = STAT_RETRYLATER; @@ -1261,8 +1376,112 @@ static int openssl_handle_peer_certificate(struct single *xfd, Notice("no peer certificate and no check"); status = STAT_OK; } + return status; } + /* verify peer certificate (trust, signature, validity dates) */ + if (opt_ver) { + long verify_result; + if ((verify_result = sycSSL_get_verify_result(xfd->para.openssl.ssl)) != X509_V_OK) { + const char *message = NULL; + if (verify_result >= 0 && + (size_t)verify_result < + sizeof(openssl_verify_messages)/sizeof(char*)) { + message = openssl_verify_messages[verify_result]; + } + if (message) { + Msg1(level, "%s", message); + } else { + Msg1(level, "rejected peer certificate with error %ld", verify_result); + } + status = STAT_RETRYLATER; + X509_free(peer_cert); + return STAT_RETRYLATER; + } + Info("peer certificate is trusted"); + } + + /* set env vars from cert's subject and issuer values */ + if ((subjectname = X509_get_subject_name(peer_cert)) != NULL) { + openssl_setenv_cert_name("subject", subjectname); + openssl_setenv_cert_fields("", subjectname); + /*! I'd like to provide dates too; see + http://markmail.org/message/yi4vspp7aeu3xwtu#query:+page:1+mid:jhnl4wklif3pgzqf+state:results */ + } + if ((issuername = X509_get_issuer_name(peer_cert)) != NULL) { + openssl_setenv_cert_name("issuer", issuername); + } + + /* check peername against cert's subjectAltName DNS entries */ + /* this code is based on example from Gerhard Gappmeier in + http://openssl.6102.n7.nabble.com/How-to-extract-subjectAltName-td17236.html + */ + if ((extcount = X509_get_ext_count(peer_cert)) > 0) { + for (i = 0; !ok && i < extcount; ++i) { + const char *extstr; + X509_EXTENSION *ext; + const X509V3_EXT_METHOD *meth; + ext = X509_get_ext(peer_cert, i); + extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext))); + if (!strcasecmp(extstr, "subjectAltName")) { + void *names; + if (!(meth = X509V3_EXT_get(ext))) break; + names = X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL); + if (names) { + int numalts; + int i; + + /* get amount of alternatives, RFC2459 claims there MUST be at least one, but we don't depend on it... */ + numalts = sk_GENERAL_NAME_num ( names ); + /* loop through all alternatives */ + for ( i=0; ( itype ) { + + case GEN_DNS: + ASN1_STRING_to_UTF8(&pBuffer, pName->d.ia5); + xiosetenv("OPENSSL_X509V3_SUBJECTALTNAME_DNS", (char *)pBuffer, 2, " // "); + if (peername != NULL && + openssl_check_name((char *)pBuffer, /*const char*/peername)) { + ok = 1; + } + OPENSSL_free(pBuffer); + break; + + default: continue; + } + } + } + } + } + } + + if (!opt_ver) { + Notice("option openssl-verify disabled, no check of certificate"); + X509_free(peer_cert); + return STAT_OK; + } + if (peername == NULL || peername[0] == '\0') { + Notice("trusting certificate, no check of commonName"); + X509_free(peer_cert); + return STAT_OK; + } + if (ok) { + Notice("trusting certificate, commonName matches"); + X509_free(peer_cert); + return STAT_OK; + } + + /* here: all envs set; opt_ver, cert verified, no subjAltName match -> check subject CN */ + if (!openssl_check_peername(/*X509_NAME*/subjectname, /*const char*/peername)) { + Error("certificate is valid but its commonName does not match hostname"); + status = STAT_NORETRY; + } else { + Notice("trusting certificate, commonName matches"); + status = STAT_OK; + } X509_free(peer_cert); return status; } @@ -1310,7 +1529,8 @@ static int xioSSL_set_fd(struct single *xfd, int level) { in case of an error condition, this function check forever and retry options and ev. sleeps an interval. It returns NORETRY when the caller should not retry for any reason. */ -static int xioSSL_connect(struct single *xfd, bool opt_ver, int level) { +static int xioSSL_connect(struct single *xfd, const char *opt_commonname, + bool opt_ver, int level) { char error_string[120]; int errint, status, ret; unsigned long err; @@ -1355,7 +1575,7 @@ static int xioSSL_connect(struct single *xfd, bool opt_ver, int level) { break; case SSL_ERROR_SSL: status = openssl_SSL_ERROR_SSL(level, "SSL_connect"); - if (openssl_handle_peer_certificate(xfd, opt_ver, level/*!*/) < 0) { + if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, level/*!*/) < 0) { return STAT_RETRYLATER; } break; diff --git a/xio-openssl.h b/xio-openssl.h index 94ba972..62586fc 100644 --- a/xio-openssl.h +++ b/xio-openssl.h @@ -1,5 +1,5 @@ /* source: xio-openssl.h */ -/* Copyright Gerhard Rieger 2002-2012 */ +/* Copyright Gerhard Rieger */ /* Published under the GNU General Public License V.2, see file COPYING */ #ifndef __xio_openssl_included @@ -29,6 +29,7 @@ extern const struct optdesc opt_openssl_compress; #if WITH_FIPS extern const struct optdesc opt_openssl_fips; #endif +extern const struct optdesc opt_openssl_commonname; extern int _xioopen_openssl_prepare(struct opt *opts, struct single *xfd, @@ -36,9 +37,11 @@ extern int SSL_CTX **ctx); extern int _xioopen_openssl_connect(struct single *xfd, bool opt_ver, + const char *opt_commonname, SSL_CTX *ctx, int level); extern int _xioopen_openssl_listen(struct single *xfd, bool opt_ver, + const char *opt_commonname, SSL_CTX *ctx, int level); extern int xioclose_openssl(xiofile_t *xfd); extern int xioshutdown_openssl(xiofile_t *xfd, int how); diff --git a/xio-socket.c b/xio-socket.c index 695cb0e..6497aff 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -1741,11 +1741,11 @@ int xiodopacketinfo(struct msghdr *msgh, bool withlog, bool withenv) { } if (withenv) { if (*envp) { - xiosetenv(envp, valp, 1); + xiosetenv(envp, valp, 1, NULL); } else if (!strcasecmp(typp+strlen(typp)-strlen(namp), namp)) { - xiosetenv(typp, valp, 1); + xiosetenv(typp, valp, 1, NULL); } else { - xiosetenv2(typp, namp, valp, 1); + xiosetenv2(typp, namp, valp, 1, NULL); } } if (++i == num) break; @@ -2132,7 +2132,7 @@ int xiosetsockaddrenv(const char *lr, xiosetsockaddrenv_unix(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->un, salen, proto); - xiosetenv(namebuff, valuebuff, 1); + xiosetenv(namebuff, valuebuff, 1, NULL); break; #endif /* WITH_UNIX */ #if WITH_IP4 @@ -2142,7 +2142,7 @@ int xiosetsockaddrenv(const char *lr, xiosetsockaddrenv_ip4(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->ip4, proto); - xiosetenv(namebuff, valuebuff, 1); + xiosetenv(namebuff, valuebuff, 1, NULL); namebuff[strlen(lr)] = '\0'; ++idx; } while (result > 0); break; @@ -2155,7 +2155,7 @@ int xiosetsockaddrenv(const char *lr, xiosetsockaddrenv_ip6(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr), valuebuff, XIOSOCKADDRENVLEN, &sau->ip6, proto); - xiosetenv(namebuff, valuebuff, 1); + xiosetenv(namebuff, valuebuff, 1, NULL); namebuff[strlen(lr)] = '\0'; ++idx; } while (result > 0); break; diff --git a/xioopts.c b/xioopts.c index cbf0cce..0df9977 100644 --- a/xioopts.c +++ b/xioopts.c @@ -296,6 +296,8 @@ const struct optname optionnames[] = { IF_TERMIOS("clocal", &opt_clocal) IF_ANY ("cloexec", &opt_cloexec) IF_ANY ("close", &opt_end_close) + IF_OPENSSL("cn", &opt_openssl_commonname) + IF_OPENSSL("commonname", &opt_openssl_commonname) IF_EXEC ("commtype", &opt_commtype) #if WITH_EXT2 && defined(EXT2_COMPR_FL) IF_ANY ("compr", &opt_ext2_compr) @@ -1058,6 +1060,7 @@ const struct optname optionnames[] = { IF_OPENSSL("openssl-capath", &opt_openssl_capath) IF_OPENSSL("openssl-certificate", &opt_openssl_certificate) IF_OPENSSL("openssl-cipherlist", &opt_openssl_cipherlist) + IF_OPENSSL("openssl-commonname", &opt_openssl_commonname) #if OPENSSL_VERSION_NUMBER >= 0x00908000L IF_OPENSSL("openssl-compress", &opt_openssl_compress) #endif diff --git a/xioopts.h b/xioopts.h index d60b27a..1101de3 100644 --- a/xioopts.h +++ b/xioopts.h @@ -1,5 +1,5 @@ /* source: xioopts.h */ -/* Copyright Gerhard Rieger 2001-2012 */ +/* Copyright Gerhard Rieger */ /* Published under the GNU General Public License V.2, see file COPYING */ #ifndef __xioopts_h_included @@ -477,6 +477,7 @@ enum e_optcode { OPT_OPENSSL_CAPATH, OPT_OPENSSL_CERTIFICATE, OPT_OPENSSL_CIPHERLIST, + OPT_OPENSSL_COMMONNAME, #if OPENSSL_VERSION_NUMBER >= 0x00908000L OPT_OPENSSL_COMPRESS, #endif