From 05afec429d2a27054e6965932fd592cc9b0b8da0 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Mon, 12 Jan 2015 23:11:26 +0100 Subject: [PATCH] OpenSSL peer certificate subject,issuer details are passed to env --- CHANGES | 8 +++++ sysutils.c | 36 +++++++++++++++++++ sysutils.h | 3 ++ test.sh | 99 +++++++++++++++++++++++++++++++++++++++++++++++++-- xio-openssl.c | 54 +++++++++++++++++++++++----- 5 files changed, 190 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 9ed68b3..8a47a57 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,14 @@ security: Turn off nested signal handler invocations Thanks to Peter Lobsinger for reporting and explaining this issue. +new features: + 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_* + corrections: Bind with ABSTRACT commands used non-abstract namespace (Linux). Test: ABSTRACT_BIND diff --git a/sysutils.c b/sysutils.c index 6ef2fc8..b3badcb 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 fda3175..2636bcb 100644 --- a/sysutils.h +++ b/sysutils.h @@ -92,6 +92,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 1fd75db..c089d94 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 @@ -2204,7 +2207,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 -out $name.crt -days 3653 >/dev/null 2>&1 + openssl req -new -config $TESTCERT_CONF -key $name.key -x509 -out $name.crt -days 3653 >/dev/null 2>&1 cat $name.key $name.crt >$name.pem } @@ -2214,7 +2217,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 -config testcert.conf -out $name.crt -days 3653 >/dev/null 2>&1 + openssl req -newkey dsa:$name-dsa.pem -keyout $name.key -nodes -x509 -config $TESTCERT_CONF -out $name.crt -days 3653 >/dev/null 2>&1 cat $name-dsa.pem $name-dh.pem $name.key $name.crt >$name.pem } @@ -11448,6 +11451,98 @@ 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 +# +test_proto="$(echo "$KEYW" |tr A-Z a-z)" +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" +# 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 +#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) + 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 [ "$(grep SOCAT_${SSLDIST}_${MODULE}_${FIELD} "${tf}" |sed -e 's/^[^=]*=//' |sed -e "s/[\"']//g")" = "$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 "$CMD0 &" + cat "${te}0" + echo "$CMD1" + cat "${te}1" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +fi +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 +" +set +xv + + ############################################################################### # 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 66d9c7d..8079d6e 100644 --- a/xio-openssl.c +++ b/xio-openssl.c @@ -602,7 +602,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: @@ -1042,22 +1042,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) { @@ -1192,6 +1229,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;