diff --git a/CHANGES b/CHANGES index 17d853a..1ba0f58 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,6 @@ security: Turn off nested signal handler invocations Thanks to Peter Lobsinger for reporting and explaining this issue. - corrections: LISTEN based addresses applied some address options, e.g. so-keepalive, to the listening file descriptor instead of the connected file @@ -282,6 +281,13 @@ new features: feature of newer OpenSSL versions. Thanks to Michael Hanselmann for providing this contribution (sponsored by Google Inc.) + OpenSSL addresses set couple of environment variables from values in + peer certificate, e.g.: + SOCAT_OPENSSL_X509_SUBJECT, SOCAT_OPENSSL_X509_ISSUER, + SOCAT_OPENSSL_X509_COMMONNAME, + SOCAT_OPENSSL_X509V3_SUBJECTALTNAME_DNS + Tests: ENV_OPENSSL_{CLIENT,SERVER}_X509_* + docu minor corrections in docu (thanks to Paggas) diff --git a/sysutils.c b/sysutils.c index 3d1545b..1188409 100644 --- a/sysutils.c +++ b/sysutils.c @@ -726,6 +726,42 @@ int xiosetenv2(const char *varname, const char *varname2, const char *value, # undef XIO_ENVNAMELEN } +int xiosetenv3(const char *varname, const char *varname2, const char *varname3, + 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); + strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); + l += 1; + strncat(envname+l, varname, XIO_ENVNAMELEN-l-1); + l += strlen(envname+l); + strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); + l += 1; + strncat(envname+l, varname2, XIO_ENVNAMELEN-l-1); + l += strlen(envname+l); + strncat(envname+l, "_", XIO_ENVNAMELEN-l-1); + l += 1; + 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; +# undef XIO_ENVNAMELEN +} + /* like xiosetenv(), but uses an unsigned long value */ int xiosetenvulong(const char *varname, unsigned long value, int overwrite) { diff --git a/sysutils.h b/sysutils.h index 2b80042..f0deeae 100644 --- a/sysutils.h +++ b/sysutils.h @@ -93,6 +93,9 @@ extern int xiosetenv(const char *varname, const char *value, int overwrite); extern int xiosetenv2(const char *varname, const char *varname2, const char *value, int overwrite); +extern int +xiosetenv3(const char *varname, const char *varname2, const char *varname3, + const char *value, int overwrite); 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 c37fce8..d2688c3 100755 --- a/test.sh +++ b/test.sh @@ -78,6 +78,9 @@ LOCALHOST6=[::1] PROTO=$((144+RANDOM/2048)) PORT=12002 SOURCEPORT=2002 +TESTCERT_CONF=testcert.conf +TESTCERT_SUBJECT="/C=XY" +TESTCERT_ISSUER="/C=XY" CAT=cat OD_C="od -c" # precision sleep; takes seconds with fractional part @@ -2226,7 +2229,7 @@ gentestcert () { local name="$1" if [ -s $name.key -a -s $name.crt -a -s $name.pem ]; then return; fi openssl genrsa $OPENSSL_RAND -out $name.key 768 >/dev/null 2>&1 - openssl req -new -config testcert.conf -key $name.key -x509 -days 3653 -out $name.crt >/dev/null 2>&1 + openssl req -new -config $TESTCERT_CONF -key $name.key -x509 -days 3653 -out $name.crt >/dev/null 2>&1 cat $name.key $name.crt >$name.pem } @@ -2237,7 +2240,7 @@ gentestdsacert () { if [ -s $name.key -a -s $name.crt -a -s $name.pem ]; then return; fi openssl dsaparam -out $name-dsa.pem 512 >/dev/null 2>&1 openssl dhparam -dsaparam -out $name-dh.pem 512 >/dev/null 2>&1 - openssl req -newkey dsa:$name-dsa.pem -keyout $name.key -nodes -x509 -days 3653 -config testcert.conf -out $name.crt >/dev/null 2>&1 + 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 } @@ -11833,6 +11836,100 @@ PORT=$((PORT+1)) N=$((N+1)) +# test: OPENSSL sets of environment variables with important values of peer certificate +while read ssldist MODE MODULE FIELD TESTADDRESS PEERADDRESS VALUE; do +if [ -z "$ssldist" ] || [[ "$ssldist" == \#* ]]; then continue; fi +# +SSLDIST=${ssldist^^*} +NAME="ENV_${SSLDIST}_${MODE}_${MODULE}_${FIELD}" +case "$TESTS" in +*%$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 +# describing the desired field. +if ! eval $NUMCOND; then :; +elif ! feat=$(testaddrs $FEAT); then + $PRINTF "test $F_n $TEST... ${YELLOW}$(echo "$feat" |tr a-z A-Z) not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +gentestcert testsrv +gentestcert testcli +test_proto=tcp4 +case "$MODE" in + SERVER) + CMD0="$SOCAT $opts -u $TESTADDRESS system:\"echo SOCAT_${SSLDIST}_${MODULE}_${FIELD}=\\\$SOCAT_${SSLDIST}_${MODULE}_${FIELD}; sleep 1\"" + CMD1="$SOCAT $opts -u /dev/null $PEERADDRESS" + printf "test $F_n $TEST... " $N + eval "$CMD0 2>\"${te}0\" >\"$tf\" &" + pid0=$! + wait${test_proto}port $PORT 1 + $CMD1 2>"${te}1" + rc1=$? + waitfile "$tf" 2 + kill $pid0 2>/dev/null; wait + ;; + CLIENT) + CMD0="$SOCAT $opts -u /dev/null $PEERADDRESS" + CMD1="$SOCAT $opts -u $TESTADDRESS system:\"echo SOCAT_${SSLDIST}_${MODULE}_${FIELD}=\\\$SOCAT_${SSLDIST}_${MODULE}_${FIELD}; sleep 1\"" + printf "test $F_n $TEST... " $N + $CMD0 2>"${te}0" & + pid0=$! + wait${test_proto}port $PORT 1 + eval "$CMD1 2>\"${te}1\" >\"$tf\"" + rc1=$? + waitfile "$tf" 2 + kill $pid0 2>/dev/null; wait + ;; +esac +if [ $rc1 != 0 ]; then + $PRINTF "$NO_RESULT (client failed):\n" + echo "$CMD0 &" + cat "${te}0" + echo "$CMD1" + cat "${te}1" + numCANT=$((numCANT+1)) +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 &" + cat "${te}0" + echo "$CMD1" + cat "${te}1" + fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED\n" + echo "expected \"$VALUE\", got \"$effval\"" >&2 + echo "$CMD0 &" + cat "${te}0" + echo "$CMD1" + cat "${te}1" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +fi # NUMCOND, feats + ;; +esac +N=$((N+1)) +# +done <<<" +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 +" + + ############################################################################### # tests: option umask with "passive" NAMED group addresses while read addr fileopt addropts proto diropt ADDR2; do diff --git a/xio-openssl.c b/xio-openssl.c index eb8049c..a24449c 100644 --- a/xio-openssl.c +++ b/xio-openssl.c @@ -734,7 +734,7 @@ int _xioopen_openssl_listen(struct single *xfd, ERR_lib_error_string(err), ERR_func_error_string(err), ERR_reason_error_string(err)); } - /* Msg1(level, "SSL_connect(): %s", ERR_error_string(e, buf));*/ + /* Msg1(level, "SSL_accept(): %s", ERR_error_string(e, buf));*/ } break; case SSL_ERROR_SSL: @@ -1173,22 +1173,59 @@ 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; + } + 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; +} + static int openssl_handle_peer_certificate(struct single *xfd, bool opt_ver, int level) { X509 *peer_cert; - char *str; - char buff[2048]; /* hold peer certificate */ + /*ASN1_TIME not_before, not_after;*/ int status; /* SSL_CTX_add_extra_chain_cert SSL_get_verify_result */ if ((peer_cert = SSL_get_peer_certificate(xfd->para.openssl.ssl)) != NULL) { - Debug("peer certificate:"); - if ((str = X509_NAME_oneline(X509_get_subject_name(peer_cert), buff, sizeof(buff))) != NULL) - Debug1("\tsubject: %s", str); /*free (str); SIGSEGV*/ - if ((str = X509_NAME_oneline(X509_get_issuer_name(peer_cert), buff, sizeof(buff))) != NULL) - Debug1("\tissuer: %s", str); /*free (str); SIGSEGV*/ + 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) { @@ -1346,6 +1383,7 @@ ssize_t xioread_openssl(struct single *pipe, void *buff, size_t bufsiz) { case SSL_ERROR_NONE: /* this is not an error, but I dare not continue for security reasons*/ Error("ok"); + break; case SSL_ERROR_ZERO_RETURN: Error("connection closed by peer"); break;