diff --git a/CHANGES b/CHANGES index 8a47a57..0a47d74 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,17 @@ security: 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 + new features: OpenSSL addresses set couple of environment variables from values in peer certificate, e.g.: @@ -1033,7 +1044,7 @@ further corrections: ftp.sh script supports proxy address man page no longer installed with execute permissions (thanks to Peter - Bray) + Bray) fixed a malloc call bug that could cause SIGSEGV or false "out of memory" errors on EXEC and SYSTEM, depending on program name length and diff --git a/doc/socat.yo b/doc/socat.yo index f9a94f5..312f0de 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -492,14 +492,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 + 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 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), @@ -528,6 +537,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), @@ -2675,6 +2685,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. 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). @@ -3440,6 +3458,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 e4e73f1..e802b8a 100644 --- a/socat.c +++ b/socat.c @@ -274,7 +274,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 b3badcb..969d02c 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,35 @@ int xiosetenv(const char *varname, const char *value, int overwrite) { return -1; } return 0; -# undef XIO_ENVNAMELEN +} + +/* 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 +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, 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 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]; @@ -714,21 +733,13 @@ int xiosetenv2(const char *varname, const char *varname2, const char *value, strncat(envname+l, varname2, 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 } 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 +761,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 +772,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 +782,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 2636bcb..906aa59 100644 --- a/sysutils.h +++ b/sysutils.h @@ -88,13 +88,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 c089d94..35af6b5 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" @@ -1578,7 +1592,6 @@ testecho () { listFAIL="$listFAIL $N" fi fi # NUMCOND -#set +vx } # test if call to od and throughput of data works - with graceful shutdown and @@ -2221,6 +2234,17 @@ gentestdsacert () { 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 case "$TESTS " in @@ -4018,7 +4042,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 @@ -5043,7 +5067,7 @@ N=$((N+1)) #! NAME=OUTBOUNDIN case "$TESTS" in -*%$N%*|*%functions%*|*%proxy%*|*%$NAME%*) +*%$N%*|*%functions%*|*%openssl%*|*%proxy%*|*%$NAME%*) TEST="$NAME: gender changer via SSL through HTTP proxy, oneshot" if ! eval $NUMCOND; then :; elif ! feat=$(testaddrs openssl proxy); then @@ -5129,7 +5153,7 @@ PORT=$((RANDOM+16184)) #! NAME=INTRANETRIPPER case "$TESTS" in -*%$N%*|*%functions%*|*%proxy%*|*%$NAME%*) +*%$N%*|*%functions%*|*%openssl%*|*%proxy%*|*%$NAME%*) TEST="$NAME: gender changer via SSL through HTTP proxy, daemons" if ! eval $NUMCOND; then :; elif ! feat=$(testaddrs openssl proxy); then @@ -5257,7 +5281,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" & @@ -5327,8 +5350,7 @@ testserversec () { da="test$N.2 $(date) $RANDOM" (echo "$da"; sleep $T) |$TRACE $SOCAT $opts - "$arg2" >"$tf" 2>"${te}4" stat=$? - kill $spid - #kill $spid 2>/dev/null + kill $spid 2>/dev/null #set +vx #killall $TRACE $SOCAT 2>/dev/null if [ "$stat" != 0 ]; then @@ -5683,7 +5705,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)) @@ -5699,7 +5721,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" "" "sp=$PORT" "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" "" "sp=$PORT" "SSL:$LOCALHOST:$PORT,cafile=testsrv.crt,$SOCAT_EGD" 4 tcp $PORT -1 fi ;; # NUMCOND, feats esac PORT=$((PORT+1)) @@ -5715,7 +5737,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)) @@ -5735,7 +5757,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)) @@ -5752,7 +5774,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)) @@ -5788,8 +5810,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)) @@ -5807,8 +5829,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)) @@ -5826,8 +5848,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)) @@ -5842,18 +5864,66 @@ 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 *%$N%*|*%functions%*|*%security%*|*%openssl%*|*%fips%*|*%tcp%*|*%tcp4%*|*%ip4%*|*%$NAME%*) @@ -6138,7 +6208,7 @@ N=$((N+1)) NAME=OPENSSLLISTENDSA case "$TESTS" in -*%$N%*|*%functions%*|*%$NAME%*) +*%$N%*|*%functions%*|*%openssl%*|*%$NAME%*) TEST="$NAME: openssl listen with DSA certificate" if ! eval $NUMCOND; then :; elif ! testaddrs openssl >/dev/null; then @@ -6438,7 +6508,6 @@ SCTP4 $LOCALHOST PORT SCTP6 $LOCALHOST6 PORT UNIX FILE , " -#set +vx NAME=UNIXTOSTREAM @@ -8965,7 +9034,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 @@ -9862,10 +9931,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 , , " @@ -11352,7 +11421,7 @@ N=$((N+1)) # Linux) with "Invalid argument". NAME=OPENSSL_CONNECT_BIND case "$TESTS" in -*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%ssl%*|*%$NAME%*) +*%$N%*|*%functions%*|*%openssl%*|*%bugs%*|*%socket%*|*%ssl%*|*%$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 @@ -11451,17 +11520,15 @@ PORT=$((PORT+1)) N=$((N+1)) -#set -xv # test: OPENSSL sets of environment variables with important values of peer certificate -unset SOCAT_OPENSSL_X509_SUBJECT SOCAT_OPENSSL_X509_ISSUER -while read SSLDIST MODE MODULE FIELD VALUE TESTADDRESS PEERADDRESS; do -if [ -z "$SSLDIST" ] || [[ "$SSLDIST" == \#* ]]; then continue; fi +while read ssldist MODE MODULE FIELD TESTADDRESS PEERADDRESS VALUE; do +if [ -z "$ssldist" ] || [[ "$ssldist" == \#* ]]; then continue; fi # -test_proto="$(echo "$KEYW" |tr A-Z a-z)" +SSLDIST=${ssldist^^*} NAME="ENV_${SSLDIST}_${MODE}_${MODULE}_${FIELD}" case "$TESTS" in -*%functions%*|*%ip4%*|*%ipapp%*|*%tcp%*|*%$SSLDIST%*|*%envvar%*|*%$NAME%*) -TEST="$NAME: $SSLDIST generates environment variable SOCAT_${SSLDIST}_${MODULE}_${FIELD} with correct value" +*%$N%*|*%functions%*|*%ip4%*|*%ipapp%*|*%tcp%*|*%$ssldist%*|*%envvar%*|*%$NAME%*) +TEST="$NAME: $SSLDIST sets env SOCAT_${SSLDIST}_${MODULE}_${FIELD}" # have a server accepting a connection and invoking some shell code. The shell # code extracts and prints the SOCAT related environment vars. # outside code then checks if the environment contains the variables correctly @@ -11475,7 +11542,6 @@ tf="$td/test$N.stdout" te="$td/test$N.stderr" gentestcert testsrv gentestcert testcli -#CMD0="$SOCAT $opts -u ${SSLDIST}-LISTEN:$PORT,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 system:\"echo SOCAT_${SSLDIST}_${MODULE}_${FIELD}=\\\$SOCAT_${SSLDIST}_${MODULE}_${FIELD}; sleep 1\"" test_proto=tcp4 case "$MODE" in SERVER) @@ -11510,7 +11576,8 @@ if [ $rc1 != 0 ]; then echo "$CMD1" cat "${te}1" numCANT=$((numCANT+1)) -elif [ "$(grep SOCAT_${SSLDIST}_${MODULE}_${FIELD} "${tf}" |sed -e 's/^[^=]*=//' |sed -e "s/[\"']//g")" = "$VALUE" ]; then +elif effval="$(grep SOCAT_${SSLDIST}_${MODULE}_${FIELD} "${tf}" |sed -e 's/^[^=]*=//' |sed -e "s/[\"']//g")"; + [ "$effval" = "$VALUE" ]; then $PRINTF "$OK\n" if [ "$debug" ]; then echo "$CMD0 &" @@ -11521,6 +11588,7 @@ elif [ "$(grep SOCAT_${SSLDIST}_${MODULE}_${FIELD} "${tf}" |sed -e 's/^[^=]*=//' numOK=$((numOK+1)) else $PRINTF "$FAILED\n" + echo "expected \"$VALUE\", got \"$effval\"" >&2 echo "$CMD0 &" cat "${te}0" echo "$CMD1" @@ -11532,15 +11600,18 @@ fi # NUMCOND, feats ;; esac N=$((N+1)) -#set +xv # done <<<" -OPENSSL SERVER X509 SUBJECT $TESTCERT_SUBJECT OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 -OPENSSL SERVER X509 ISSUER $TESTCERT_ISSUER OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 -OPENSSL CLIENT X509 SUBJECT $TESTCERT_SUBJECT OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 -OPENSSL CLIENT X509 ISSUER $TESTCERT_ISSUER OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 +openssl SERVER X509 ISSUER OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_ISSUER +openssl SERVER X509 SUBJECT OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_SUBJECT +openssl SERVER X509 COMMONNAME OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_COMMONNAME +openssl SERVER X509 COUNTRYNAME OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_COUNTRYNAME +openssl SERVER X509 LOCALITYNAME OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_LOCALITYNAME +openssl SERVER X509 ORGANIZATIONALUNITNAME OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_ORGANIZATIONALUNITNAME +openssl SERVER X509 ORGANIZATIONNAME OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 $TESTCERT_ORGANIZATIONNAME +openssl CLIENT X509 SUBJECT OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 $TESTCERT_SUBJECT +openssl CLIENT X509 ISSUER OPENSSL-CONNECT:$LOCALHOST:$PORT,cert=testcli.pem,cafile=testsrv.crt,verify=1 OPENSSL-LISTEN:$PORT,so-reuseaddr,bind=$LOCALHOST,cert=testsrv.pem,cafile=testcli.crt,verify=1 $TESTCERT_ISSUER " -set +xv ############################################################################### 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 9807915..6e3805c 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 8079d6e..61d586c 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,10 +48,13 @@ 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 ssl connect */ @@ -110,6 +116,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. @@ -189,6 +196,7 @@ static int 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)) { @@ -203,6 +211,12 @@ static int } 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; + } xfd->howtoend = END_SHUTDOWN; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; @@ -211,6 +225,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, &ctx); @@ -270,7 +289,7 @@ static int return result; } - result = _xioopen_openssl_connect(xfd, opt_ver, ctx, level); + result = _xioopen_openssl_connect(xfd, opt_ver, opt_commonname, ctx, level); switch (result) { case STAT_OK: break; #if WITH_RETRY @@ -338,6 +357,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; @@ -362,14 +382,15 @@ int _xioopen_openssl_connect(struct single *xfd, 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; @@ -411,6 +432,7 @@ static int 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)) { @@ -443,6 +465,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 = @@ -501,7 +525,7 @@ static int return result; } - result = _xioopen_openssl_listen(xfd, opt_ver, ctx, level); + result = _xioopen_openssl_listen(xfd, opt_ver, opt_commonname, ctx, level); switch (result) { case STAT_OK: break; #if WITH_RETRY @@ -535,6 +559,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]; @@ -616,7 +641,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; } @@ -721,7 +746,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)) { @@ -734,6 +758,8 @@ int } #endif + openssl_delete_cert_info(); + OpenSSL_add_all_algorithms(); OpenSSL_add_all_ciphers(); OpenSSL_add_all_digests(); @@ -973,19 +999,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[] = { @@ -1042,87 +1070,169 @@ 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); - } - n = X509_NAME_entry_count(name); - for (i = 0; i < n; ++i) { - X509_NAME_ENTRY *entry; - char *text; - ASN1_STRING *data; - ASN1_OBJECT *obj; - int nid; - entry = X509_NAME_get_entry(name, i); - data = X509_NAME_ENTRY_get_data(entry); - obj = X509_NAME_ENTRY_get_object(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); } return 0; } +/* 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; + ASN1_OBJECT *obj; + ASN1_STRING *data; + unsigned char *text; + int nid; + entry = X509_NAME_get_entry(name, i); + obj = X509_NAME_ENTRY_get_object(entry); + data = X509_NAME_ENTRY_get_data(entry); + nid = OBJ_obj2nid(obj); + 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; +} + +/* 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 + 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. +*/ 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; @@ -1130,8 +1240,113 @@ 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; } @@ -1156,7 +1371,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; @@ -1201,7 +1417,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 d7be69d..9cad8f4 100644 --- a/xio-openssl.h +++ b/xio-openssl.h @@ -1,5 +1,5 @@ /* source: xio-openssl.h */ -/* Copyright Gerhard Rieger 2002-2010 */ +/* 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 0578e8e..e75bfc1 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -1661,11 +1661,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; @@ -2053,7 +2053,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 @@ -2063,7 +2063,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; @@ -2076,7 +2076,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 1cbf508..13118e7 100644 --- a/xioopts.c +++ b/xioopts.c @@ -290,6 +290,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 WITH_EXT2 && defined(EXT2_COMPR_FL) IF_ANY ("compr", &opt_ext2_compr) #endif @@ -1094,6 +1096,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 f6dd5b6..f4853cd 100644 --- a/xioopts.h +++ b/xioopts.h @@ -1,5 +1,5 @@ /* source: xioopts.h */ -/* Copyright Gerhard Rieger 2001-2009 */ +/* Copyright Gerhard Rieger */ /* Published under the GNU General Public License V.2, see file COPYING */ #ifndef __xioopts_h_included @@ -473,6 +473,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