diff --git a/Readme.md b/Readme.md index 3b16045..ed1eee6 100644 --- a/Readme.md +++ b/Readme.md @@ -11,7 +11,7 @@ cryptographic flaws. #### Key features * Clear output: you can tell easily whether anything is good or bad -* Ease of installation: It works for Linux, Darwin, FreeBSD and +* Ease of installation: It works for Linux, Darwin, FreeBSD, NetBSD and MSYS2/Cygwin out of the box: no need to install or configure something, no gems, CPAN, pip or the like. * Flexibility: You can test any SSL/TLS enabled and STARTTLS service, not @@ -64,7 +64,7 @@ Done so far: * Check for multiple server certificates * Browser cipher simulation * Assistance for color-blind users -* Even more compatibility improvements for FreeBSD, RH-ish, F5 and Cisco systems +* Even more compatibility improvements for FreeBSD, NetBSD, Gentoo, RH-ish, F5 and Cisco systems * Considerable speed improvements for each cipher runs (-e/-E) * More robust socket interface * OpenSSL 1.1.0 compliant diff --git a/testssl.sh b/testssl.sh index 75ca0c5..65632c4 100755 --- a/testssl.sh +++ b/testssl.sh @@ -83,7 +83,7 @@ readonly PS4='${LINENO}> ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' # make sure that temporary files are cleaned up after use in ANY case trap "cleanup" QUIT EXIT -readonly VERSION="2.7dev" +readonly VERSION="2.8rc1" readonly SWCONTACT="dirk aet testssl dot sh" egrep -q "dev|rc" <<< "$VERSION" && \ SWURL="https://testssl.sh/dev/" || @@ -132,8 +132,7 @@ fi TERM_CURRPOS=0 # custom line wrapping needs alter the current horizontal cursor pos # following variables make use of $ENV, e.g. OPENSSL= ./testssl.sh -# 0 means (normally) true here. Some of the variables are also accessible with a command line switch -# most of them can be set also by a cmd line switch +# 0 means (normally) true here. Some of the variables are also accessible with a command line switch, see --help declare -x OPENSSL COLOR=${COLOR:-2} # 2: Full color, 1: b/w+positioning, 0: no ESC at all @@ -145,11 +144,13 @@ QUIET=${QUIET:-false} # don't output the banner. By doing this SSL_NATIVE=${SSL_NATIVE:-false} # we do per default bash sockets where possible "true": switch back to "openssl native" ASSUMING_HTTP=${ASSUMING_HTTP:-false} # in seldom cases (WAF, old servers, grumpy SSL) service detection fails. "True" enforces HTTP checks BUGS=${BUGS:-""} # -bugs option from openssl, needed for some BIG IP F5 -DEBUG=${DEBUG:-0} # 1.: the temp files won't be erased. - # 2: list more what's going on (formerly: eq VERBOSE=1, VERBERR=true), lists some errors of connections +DEBUG=${DEBUG:-0} # 1: normal putput the files in /tmp/ are kept for further debugging purposes + # 2: list more what's going on , also lists some errors of connections # 3: slight hexdumps + other info, - # 4: display bytes sent via sockets, 5: display bytes received via sockets, 6: whole 9 yards -WIDE=${WIDE:-false} # whether to display for some options the cipher or the table with hexcode/KX,Enc,strength etc. + # 4: display bytes sent via sockets + # 5: display bytes received via sockets + # 6: whole 9 yards +WIDE=${WIDE:-false} # whether to display for some options just ciphers or a table w hexcode/KX,Enc,strength etc. LOGFILE=${LOGFILE:-""} # logfile if used JSONFILE=${JSONFILE:-""} # jsonfile if used CSVFILE=${CSVFILE:-""} # csvfile if used @@ -170,10 +171,12 @@ USLEEP_SND=${USLEEP_SND:-0.1} # sleep time for general socket send USLEEP_REC=${USLEEP_REC:-0.2} # sleep time for general socket receive HSTS_MIN=${HSTS_MIN:-179} # >179 days is ok for HSTS HPKP_MIN=${HPKP_MIN:-30} # >=30 days should be ok for HPKP_MIN, practical hints? -readonly CLIENT_MIN_PFS=5 # number of ciphers needed to run a test for PFS DAYS2WARN1=${DAYS2WARN1:-60} # days to warn before cert expires, threshold 1 DAYS2WARN2=${DAYS2WARN2:-30} # days to warn before cert expires, threshold 2 VULN_THRESHLD=${VULN_THRESHLD:-1} # if vulnerabilities to check >$VULN_THRESHLD we DON'T show a separate header line in the output each vuln. check +readonly CLIENT_MIN_PFS=5 # number of ciphers needed to run a test for PFS + # generated from 'kEECDH:kEDH:!aNULL:!eNULL:!DES:!3DES:!RC4' with openssl 1.0.2i and openssl 1.1.0 +readonly ROBUST_PFS_CIPHERS="DHE-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA:DHE-DSS-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA:DHE-DSS-SEED-SHA:DHE-RSA-AES128-CCM8:DHE-RSA-AES128-CCM:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-CCM8:DHE-RSA-AES256-CCM:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-RSA-CHACHA20-POLY1305-OLD:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-SEED-SHA:ECDHE-ECDSA-AES128-CCM8:ECDHE-ECDSA-AES128-CCM:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305-OLD:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-RSA-CHACHA20-POLY1305-OLD:ECDHE-RSA-CHACHA20-POLY1305" HAD_SLEPT=0 CAPATH="${CAPATH:-/etc/ssl/certs/}" # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d) @@ -1533,9 +1536,13 @@ neat_list(){ kx="${3//Kx=/}" enc="${4//Enc=/}" - strength=$(sed -e 's/.*(//' -e 's/)//' <<< "$enc") # strength = encryption bits - strength="${strength//ChaCha20-Poly1305/ly1305}" - enc=$(sed -e 's/(.*)//g' -e 's/ChaCha20-Poly1305/ChaCha20-Po/g' <<< "$enc") # workaround for empty bits ChaCha20-Poly1305 + strength="${enc//\)/}" # retrieve (). first remove traling ")" + strength="${strength#*\(}" # exfiltrate (VAL + enc="${enc%%\(*}" + + enc="${enc//POLY1305/}" # remove POLY1305 + enc="${enc//\//}" # remove "/" + echo "$export" | grep -iq export && strength="$strength,exp" #printf -- "%q" "$kx" | xxd | head -1 @@ -4552,7 +4559,7 @@ run_server_preference() { [[ -n "$PROXY" ]] && arg=" SPDY/NPN is" [[ -n "$STARTTLS" ]] && arg=" " if spdy_pre " $arg" ; then # is NPN/SPDY supported and is this no STARTTLS? / no PROXY - $OPENSSL s_client -connect $NODEIP:$PORT $BUGS -nextprotoneg "$NPN_PROTOs" >$ERRFILE >$TMPFILE + $OPENSSL s_client -connect $NODEIP:$PORT $BUGS -nextprotoneg "$NPN_PROTOs" $SNI >$ERRFILE >$TMPFILE if sclient_connect_successful $? $TMPFILE; then proto[i]=$(grep -aw "Next protocol" $TMPFILE | sed -e 's/^Next protocol://' -e 's/(.)//' -e 's/ //g') if [[ -z "${proto[i]}" ]]; then @@ -4727,16 +4734,16 @@ cipher_pref_check() { if ! spdy_pre " SPDY/NPN: "; then # is NPN/SPDY supported and is this no STARTTLS? outln else - npn_protos=$($OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg \"\" >$ERRFILE | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g') + npn_protos=$($OPENSSL s_client $BUGS -nextprotoneg \"\" -connect $NODEIP:$PORT $SNI >$ERRFILE | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g') for p in $npn_protos; do order="" - $OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg "$p" $PROXY >$ERRFILE >$TMPFILE + $OPENSSL s_client $BUGS -nextprotoneg "$p" -connect $NODEIP:$PORT $SNI >$ERRFILE >$TMPFILE cipher=$(awk '/Cipher.*:/ { print $3 }' $TMPFILE) printf " %-10s %s " "$p:" "$cipher" tested_cipher="-"$cipher order="$cipher" while true; do - $OPENSSL s_client -cipher "ALL:$tested_cipher" -host $NODE -port $PORT $BUGS -nextprotoneg "$p" $PROXY >$ERRFILE >$TMPFILE + $OPENSSL s_client -cipher "ALL:$tested_cipher" $BUGS -nextprotoneg "$p" -connect $NODEIP:$PORT $SNI >$ERRFILE >$TMPFILE sclient_connect_successful $? $TMPFILE || break cipher=$(awk '/Cipher.*:/ { print $3 }' $TMPFILE) out "$cipher " @@ -4934,6 +4941,7 @@ sclient_connect_successful() { } # arg1 is "-cipher " or empty +# arg2 is a list of protocols to try (tls1_2, tls1_1, tls1, ssl3) or empty (if all should be tried) determine_tls_extensions() { local proto local success @@ -4943,9 +4951,15 @@ determine_tls_extensions() { "$HAS_ALPN" && alpn="h2-14,h2-15,h2" + if [[ -n "$2" ]]; then + protocols_to_try="$2" + else + protocols_to_try="tls1_2 tls1_1 tls1 ssl3" + fi + # throwing 1st every cipher/protocol at the server to know what works success=7 - for proto in tls1_2 tls1_1 tls1 ssl3; do + for proto in $protocols_to_try; do # alpn: echo | openssl s_client -connect google.com:443 -tlsextdebug -alpn h2-14 -servername google.com <-- suport needs to be checked b4 -- see also: ssl/t1_trce.c $OPENSSL s_client $STARTTLS $BUGS $1 -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug -nextprotoneg $alpn -status $ERRFILE >$TMPFILE sclient_connect_successful $? $TMPFILE && success=0 && break @@ -5013,6 +5027,43 @@ get_cn_from_cert() { return $? } +# Return 0 if the server name provided in arg1 matches the CN or SAN in arg2, otherwise return 1. +compare_server_name_to_cert() +{ + local servername=$1 + local cert=$2 + local cn dns_sans ip_sans san basename + + cn="$(get_cn_from_cert $cert)" + if [[ -n "$cn" ]]; then + [[ "$cn" == "$servername" ]] && return 0 + # If the CN contains a wildcard name, then do a wildcard match + if echo -n "$cn" | grep -q '^*.'; then + basename="$(echo -n "$cn" | sed 's/^\*.//')" + [[ "$cn" == "*.$basename" ]] && [[ "$servername" == *".$basename" ]] && return 0 + fi + fi + + # Check whether any of the DNS names in the certificate match the servername + dns_sans=$($OPENSSL x509 -in $cert -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ + tr ',' '\n' | grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g') + for san in $dns_sans; do + [[ "$san" == "$servername" ]] && return 0 + # If $san is a wildcard name, then do a wildcard match + if echo -n "$san" | grep -q '^*.'; then + basename="$(echo -n "$san" | sed 's/^\*.//')" + [[ "$san" == "*.$basename" ]] && [[ "$servername" == *".$basename" ]] && return 0 + fi + done + + # Check whether any of the IP addresses in the certificate match the serername + ip_sans=$($OPENSSL x509 -in $cert -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ + tr ',' '\n' | grep "IP Address:" | sed -e 's/IP Address://g' -e 's/ //g') + for san in $ip_sans; do + [[ "$san" == "$servername" ]] && return 0 + done + return 1 +} certificate_info() { local proto @@ -5256,7 +5307,7 @@ certificate_info() { fi else cn="no CN field in subject" - pr_warning "($cn)" + out "($cn)" cnfinding="$cn" cnok="INFO" fi @@ -5306,10 +5357,10 @@ certificate_info() { fi fileout "${json_prefix}cn" "$cnok" "$cnfinding" - sans=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A3 "Subject Alternative Name" | \ - egrep "DNS:|IP Address:|email:|URI:|DirName:|Registered ID:" | \ + sans=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ + egrep "DNS:|IP Address:|email:|URI:|DirName:|Registered ID:" | tr ',' '\n' | \ sed -e 's/ *DNS://g' -e 's/ *IP Address://g' -e 's/ *email://g' -e 's/ *URI://g' -e 's/ *DirName://g' \ - -e 's/ *Registered ID://g' -e 's/,/\n/g' \ + -e 's/ *Registered ID://g' \ -e 's/ *othername://g' -e 's/ *X400Name://g' -e 's/ *EdiPartyName://g') # ^^^ CACert out "$indent"; pr_bold " subjectAltName (SAN) " @@ -5481,7 +5532,7 @@ certificate_info() { run_server_defaults() { - local ciph match_found newhostcert + local ciph match_found newhostcert sni local sessticket_str="" local lifetime unit local line @@ -5489,8 +5540,9 @@ run_server_defaults() { local all_tls_extensions="" local -i certs_found=0 local -a previous_hostcert previous_intermediates keysize cipher ocsp_response ocsp_response_status - local -a ciphers_to_test - + local -a ciphers_to_test success + local cn_nosni cn_sni sans_nosni sans_sni san + # Try each public key type once: # ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys # ciphers_to_test[2]: cipher suites using certificates with RSA key encipherment public keys @@ -5515,11 +5567,29 @@ run_server_defaults() { ciphers_to_test[5]="aECDH" ciphers_to_test[6]="aECDSA" ciphers_to_test[7]="aGOST" - - for n in 1 2 3 4 5 6 7 ; do + + for (( n=1; n <= 14 ; n++ )); do + # Some servers use a different certificate if the ClientHello + # specifies TLSv1.1 and doesn't include a server name extension. + # So, for each public key type for which a certificate was found, + # try again, but only with TLSv1.1 and without SNI. + if [[ $n -ge 8 ]]; then + ciphers_to_test[n]="" + [[ ${success[n-7]} -eq 0 ]] && ciphers_to_test[n]="${ciphers_to_test[n-7]}" + fi + if [[ -n "${ciphers_to_test[n]}" ]] && [[ $(count_ciphers $($OPENSSL ciphers "${ciphers_to_test[n]}" 2>>$ERRFILE)) -ge 1 ]]; then - determine_tls_extensions "-cipher ${ciphers_to_test[n]}" - if [[ $? -eq 0 ]]; then + if [[ $n -ge 8 ]]; then + sni="$SNI" + SNI="" + determine_tls_extensions "-cipher ${ciphers_to_test[n]}" "tls1_1" + success[n]=$? + SNI="$sni" + else + determine_tls_extensions "-cipher ${ciphers_to_test[n]}" + success[n]=$? + fi + if [[ ${success[n]} -eq 0 ]]; then # check to see if any new TLS extensions were returned and add any new ones to all_tls_extensions while read -d "\"" -r line; do if [[ $line != "" ]] && ! grep -q "$line" <<< "$all_tls_extensions"; then @@ -5544,7 +5614,45 @@ run_server_defaults() { fi i=$((i + 1)) done - if ! $match_found ; then + if ! "$match_found" && [[ $n -ge 8 ]] && [[ $certs_found -ne 0 ]]; then + # A new certificate was found using TLSv1.1 without SNI. + # Check to see if the new certificate should be displayed. + # It should be displayed if it is either a match for the + # $NODE being tested or if it has the same subject + # (CN and SAN) as other certificates for this host. + compare_server_name_to_cert "$NODE" "$HOSTCERT" + success[n]=$? + + if [[ ${success[n]} -ne 0 ]]; then + cn_nosni="$(get_cn_from_cert $HOSTCERT)" + sans_nosni=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | grep "DNS:" | \ + sed -e 's/DNS://g' -e 's/ //g' -e 's/,/ /g' -e 's/othername://g') + + echo "${previous_hostcert[1]}" > $HOSTCERT + cn_sni="$(get_cn_from_cert $HOSTCERT)" + + # FIXME: Not sure what the matching rule should be. At + # the moment, the no SNI certificate is considered a + # match if the CNs are the same and the SANs (if + # present) contain at least one DNS name in common. + if [[ "$cn_nosni" == "$cn_sni" ]]; then + sans_sni=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | grep "DNS:" | \ + sed -e 's/DNS://g' -e 's/ //g' -e 's/,/ /g' -e 's/othername://g') + if [[ "$sans_nosni" == "$sans_sni" ]]; then + success[n]=0 + else + for san in $sans_nosni; do + [[ " $sans_sni " =~ " $san " ]] && success[n]=0 && break + done + fi + fi + fi + # If the certificate found for TLSv1.1 w/o SNI appears to + # be for a different host, then set match_found to true so + # that the new certificate will not be included in the output. + [[ ${success[n]} -ne 0 ]] && match_found=true + fi + if ! "$match_found"; then certs_found=$(($certs_found + 1)) cipher[certs_found]=${ciphers_to_test[n]} keysize[certs_found]=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //') @@ -5614,20 +5722,20 @@ run_server_defaults() { done } -# http://www.heise.de/security/artikel/Forward-Secrecy-testen-und-einrichten-1932806.html run_pfs() { local -i sclient_success - local pfs_offered=false + local pfs_offered=false ecdhe_offered=false local tmpfile local dhlen - local hexcode dash pfs_cipher sslvers kx auth enc mac - # https://community.qualys.com/blogs/securitylabs/2013/08/05/configuring-apache-nginx-and-openssl-for-forward-secrecy -- but with RC4: - #local pfs_ciphers='EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH RC4 !RC4-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS:@STRENGTH' - #w/o RC4: - #local pfs_ciphers='EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EDH+aRSA EECDH !RC4-SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS:@STRENGTH' - local pfs_cipher_list="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-CAMELLIA128-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA" - local -i nr_supported_ciphers=0 - local pfs_ciphers + local hexcode dash pfs_cipher sslvers kx auth enc mac curve + local pfs_cipher_list="$ROBUST_PFS_CIPHERS" + local ecdhe_cipher_list="" + local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") + local -a curves_ossl_output=("K-163" "sect163r1" "B-163" "sect193r1" "sect193r2" "K-233" "B-233" "sect239k1" "K-283" "B-283" "K-409" "B-409" "K-571" "B-571" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "P-192" "secp224k1" "P-224" "secp256k1" "P-256" "P-384" "P-521" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448") + local -a supported_curves=() + local -i nr_supported_ciphers=0 nr_curves=0 i j low high + local pfs_ciphers curves_offered curves_to_test temp + local curve_found curve_used outln pr_headlineln " Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption as well as 3DES and RC4 here " @@ -5645,11 +5753,11 @@ run_pfs() { return 1 fi - $OPENSSL s_client -cipher 'ECDH:DH' $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE $tmpfile $ERRFILE | awk '/CBC/ { print $1 }' | tr '\n' ':') + cbc_ciphers=$(actually_supported_ciphers $cbc_ciphers) debugme echo $cbc_ciphers $OPENSSL s_client -ssl3 $STARTTLS $BUGS -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE /dev/null >/dev/null if [[ $? -ne 0 ]] || [[ ! -x "$OPENSSL" ]]; then - fatal "\ncannot exec or find any openssl binary" -1 + fatal "\ncannot exec or find any openssl binary" -5 fi # http://www.openssl.org/news/openssl-notes.html @@ -7854,7 +8017,7 @@ find_openssl_binary() { return 0 } -openssl_age() { +check4openssl_oldfarts() { case "$OSSL_VER" in 0.9.7*|0.9.6*|0.9.5*) # 0.9.5a was latest in 0.9.5 an released 2000/4/1, that'll NOT suffice for this test @@ -7879,6 +8042,20 @@ openssl_age() { } +# FreeBSD needs to have /dev/fd mounted. This is a friendly hint, see #258 +check_bsd_mount() { + if [[ "$(uname)" == FreeBSD ]]; then + if ! mount | grep -q "^devfs"; then + outln "you seem to run $PROG_NAME= in a jail. Hopefully you're did \"mount -t fdescfs fdesc /dev/fd\"" + elif mount | grep '/dev/fd' | grep -q fdescfs; then + : + else + fatal "You need to mount fdescfs on FreeBSD: \"mount -t fdescfs fdesc /dev/fd\"" -3 + fi + fi +} + + help() { cat << EOF @@ -7940,7 +8117,7 @@ tuning options (can also be preset via environment variables): --bugs enables the "-bugs" option of s_client, needed e.g. for some buggy F5s --assuming-http if protocol check fails it assumes HTTP protocol and enforces HTTP checks --ssl-native fallback to checks with OpenSSL where sockets are normally used - --openssl use this openssl binary (default: look in \$PATH, \$RUN_DIR of $PROG_NAME + --openssl use this openssl binary (default: look in \$PATH, \$RUN_DIR of $PROG_NAME) --proxy : connect via the specified HTTP proxy -6 use also IPv6. Works only with supporting OpenSSL version and IPv6 connectivity --sneaky leave less traces in target logs: user agent, referer @@ -7955,7 +8132,7 @@ output options (can also be preset via environment variables): --mapping don't display the RFC Cipher Suite Name --color <0|1|2> 0: no escape or other codes, 1: b/w escape codes, 2: color (default) --colorblind swap green and blue in the output - --debug <0-6> 1: screen output normal but debug output in temp files. 2-6: see line ~120 + --debug <0-6> 1: screen output normal but keeps debug output in /tmp/. 2-6: see "grep -A 5 '^DEBUG=' testssl.sh" file output options (can also be preset via environment variables): --log, --logging logs stdout to in current working directory @@ -8123,6 +8300,13 @@ cleanup () { fatal() { pr_magentaln "Fatal error: $1" >&2 exit $2 + # 1: cmd line error + # 2: secondary/other cmd line error + # -1: other user error + # -2: network problem + # -3: s.th. fatal is not supported in the client + # -4: s.th. is not supported yet + # -5: openssl problem } @@ -8171,9 +8355,9 @@ EOF ignore_no_or_lame() { local a - [[ "$WARNINGS" == "off" ]] && return 0 - [[ "$WARNINGS" == "false" ]] && return 0 - [[ "$WARNINGS" == "batch" ]] && return 1 + [[ "$WARNINGS" == off ]] && return 0 + [[ "$WARNINGS" == false ]] && return 0 + [[ "$WARNINGS" == batch ]] && return 1 pr_magenta "$1 " read a case $a in @@ -8242,7 +8426,9 @@ prepare_logging() { fi >$LOGFILE outln "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE} - outln "## ($VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE, at $HNAME:$OPENSSL_LOCATION)\n" >>${LOGFILE} + outln "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE} + outln "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE} + outln "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE} exec > >(tee -a ${LOGFILE}) # not decided yet. Maybe good to have a separate file or none at all #exec 2> >(tee -a ${LOGFILE} >&2) @@ -8346,7 +8532,7 @@ get_a_record() { elif which dig &>/dev/null; then ip4=$(filter_ip4_address $(dig @224.0.0.251 -p 5353 +short -t a +notcp "$1" 2>/dev/null | sed '/^;;/d')) else - fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable." + fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable." -3 fi fi if [[ -z "$ip4" ]]; then @@ -8383,7 +8569,7 @@ get_aaaa_record() { elif which dig &>/dev/null; then ip6=$(filter_ip6_address $(dig @ff02::fb -p 5353 -t aaaa +short +notcp "$NODE")) else - fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable." + fatal "Local hostname given but no 'avahi-resolve' or 'dig' avaliable." -3 fi elif which host &> /dev/null ; then ip6=$(filter_ip6_address $(host -t aaaa "$NODE" | grep -v alias | grep -v "no AAAA record" | sed 's/^.*address //')) @@ -8497,11 +8683,11 @@ get_mx_record() { check_proxy() { if [[ -n "$PROXY" ]]; then if ! $OPENSSL s_client -help 2>&1 | grep -qw proxy; then - fatal "Your $OPENSSL is too old to support the \"--proxy\" option" -1 + fatal "Your $OPENSSL is too old to support the \"--proxy\" option" -5 fi PROXYNODE=${PROXY%:*} PROXYPORT=${PROXY#*:} - is_number "$PROXYPORT" || fatal "Proxy port cannot be determined from \"$PROXY\"" "-3" + is_number "$PROXYPORT" || fatal "Proxy port cannot be determined from \"$PROXY\"" "2" #if is_ipv4addr "$PROXYNODE" || is_ipv6addr "$PROXYNODE" ; then # IPv6 via openssl -proxy: that doesn't work. Sockets does @@ -8511,7 +8697,7 @@ check_proxy() { else check_resolver_bins PROXYIP=$(get_a_record $PROXYNODE 2>/dev/null | grep -v alias | sed 's/^.*address //') - [[ -z "$PROXYIP" ]] && fatal "Proxy IP cannot be determined from \"$PROXYNODE\"" "-3" + [[ -z "$PROXYIP" ]] && fatal "Proxy IP cannot be determined from \"$PROXYNODE\"" "2" fi PROXY="-proxy $PROXYIP:$PROXYPORT" fi @@ -8628,12 +8814,12 @@ determine_service() { ftp|smtp|pop3|imap|xmpp|telnet|ldap) STARTTLS="-starttls $protocol" SNI="" - if [[ $protocol == "xmpp" ]]; then + if [[ "$protocol" == xmpp ]]; then # for XMPP, openssl has a problem using -connect $NODEIP:$PORT. thus we use -connect $NODE:$PORT instead! NODEIP="$NODE" if [[ -n "$XMPP_HOST" ]]; then if ! $OPENSSL s_client --help 2>&1 | grep -q xmpphost; then - fatal "Your $OPENSSL does not support the \"-xmpphost\" option" -3 + fatal "Your $OPENSSL does not support the \"-xmpphost\" option" -5 fi STARTTLS="$STARTTLS -xmpphost $XMPP_HOST" # it's a hack -- instead of changing calls all over the place # see http://xmpp.org/rfcs/rfc3920.html @@ -8652,7 +8838,7 @@ determine_service() { outln ;; *) outln - fatal "momentarily only ftp, smtp, pop3, imap, xmpp, telnet and ldap allowed" -1 + fatal "momentarily only ftp, smtp, pop3, imap, xmpp, telnet and ldap allowed" -4 ;; esac fi @@ -8756,7 +8942,7 @@ run_mass_testing_parallel() { local global_cmdline=${CMDLINE%%--file*} if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then - fatal "Can't read file \"$FNAME\"" "-1" + fatal "Can't read file \"$FNAME\"" "2" fi pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln outln "(output is in ....\n)" @@ -8782,7 +8968,7 @@ run_mass_testing() { local global_cmdline=${CMDLINE%%--file*} if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then - fatal "Can't read file \"$FNAME\"" "-1" + fatal "Can't read file \"$FNAME\"" "2" fi pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n" @@ -9257,7 +9443,7 @@ reset_hostdepended_vars() { lets_roll() { local ret - [[ -z "$NODEIP" ]] && fatal "$NODE doesn't resolve to an IP address" -1 + [[ -z "$NODEIP" ]] && fatal "$NODE doesn't resolve to an IP address" 2 nodeip_to_proper_ip6 reset_hostdepended_vars determine_rdns @@ -9333,7 +9519,8 @@ find_openssl_binary maketempf mybanner check_proxy -openssl_age +check4openssl_oldfarts +check_bsd_mount # TODO: it is ugly to have those two vars here --> main() ret=0 @@ -9359,7 +9546,7 @@ else parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now prepare_logging if ! determine_ip_addresses && [[ -z "$CMDLINE_IP" ]]; then - fatal "No IP address could be determined" + fatal "No IP address could be determined" 2 fi if [[ -n "$CMDLINE_IP" ]]; then [[ "$CMDLINE_IP" == "one" ]] && \ @@ -9391,4 +9578,4 @@ fi exit $? -# $Id: testssl.sh,v 1.511 2016/07/01 16:26:03 dirkw Exp $ +# $Id: testssl.sh,v 1.526 2016/07/16 18:48:55 dirkw Exp $ diff --git a/utils/curves.bash b/utils/curves.bash new file mode 100755 index 0000000..4168c02 --- /dev/null +++ b/utils/curves.bash @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# PoC for checking the ellipticale curves negotiated +# x448 and x25519 are missing, others are not supported +# License see testssl.sh + +readonly RUN_DIR=$(dirname "$0") + +test_openssl_suffix() { + local naming_ext="$(uname).$(uname -m)" + local uname_arch="$(uname -m)" + local myarch_suffix="" + + [[ $uname_arch =~ 64 ]] && myarch_suffix=64 || myarch_suffix=32 + if [[ -f "$1/openssl" ]] && [[ -x "$1/openssl" ]]; then + OPENSSL="$1/openssl" + return 0 + elif [[ -f "$1/openssl.$naming_ext" ]] && [[ -x "$1/openssl.$naming_ext" ]]; then + OPENSSL="$1/openssl.$naming_ext" + return 0 + fi + return 1 +} + + +find_openssl_binary() { + # 0. check environment variable whether it's executable + if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then + pr_warningln "\ncannot find specified (\$OPENSSL=$OPENSSL) binary." + outln " Looking some place else ..." + elif [[ -x "$OPENSSL" ]]; then + : # 1. all ok supplied $OPENSSL was found and has excutable bit set -- testrun comes below + elif test_openssl_suffix $RUN_DIR; then + : # 2. otherwise try openssl in path of testssl.sh + elif test_openssl_suffix ../$RUN_DIR; then + : # 2. otherwise try openssl in path of testssl.sh + elif test_openssl_suffix ../$RUN_DIR/bin; then + : # 3. otherwise here, this is supposed to be the standard --platform independed path in the future!!! + elif test_openssl_suffix "$(dirname "$(which openssl)")"; then + : # 5. we tried hard and failed, so now we use the system binaries + fi + + # no ERRFILE initialized yet, thus we use /dev/null for stderr directly + $OPENSSL version -a 2>/dev/null >/dev/null + if [[ $? -ne 0 ]] || [[ ! -x "$OPENSSL" ]]; then + echo "\ncannot exec or find any openssl binary" + exit 1 + fi + echo + echo "using $OPENSSL" + echo +} + + +VERBOSE=false +if [[ $1 == "-v" ]]; then + VERBOSE=true + shift +fi + +HN="$1" +[ -z "$HN" ] && HN=testssl.sh +find_openssl_binary + +ERRFILE=$(mktemp /tmp/curve_tester.R.XXXXXX) || exit -6 +TMPFILE=$(mktemp /tmp/curve_tester.T.XXXXXX) || exit -6 + + +for curve in $($OPENSSL ecparam -list_curves | awk -F':' '/:/ { print $1 }'); do + #if bin/openssl.Linux.x86_64 s_client -curves $curve -connect $HN:443 -servername $HN /dev/null | grep -q "BEGIN CERTIFICATE" ; then + # echo 'YES' + #else + # echo '--' + #fi + $OPENSSL s_client -cipher ECDH -curves $curve -connect $HN:443 -servername $HN $ERRFILE | grep "Server Temp Key:" >$TMPFILE + if [[ $? -eq 0 ]]; then + printf "$curve: " + cat $TMPFILE | sed 's/^.*Server Temp Key: //' + else + if grep -q 'Error with' $ERRFILE; then + if "$VERBOSE"; then + echo "$curve: no client support" + fi + else + echo "$curve: --" + fi + fi +done + +rm -f $ERRFILE $TMPFILE + +# vim:ts=5:sw=5:expandtab +# $Id: curves.bash,v 1.3 2016/07/09 12:22:13 dirkw Exp $ +