From 1a8ed3d70a4aa353f415442ec5732b25ea8ea338 Mon Sep 17 00:00:00 2001 From: Dirk Date: Sat, 23 Jan 2016 19:18:33 +0100 Subject: [PATCH] - JSON/CSV from #268, labeled it experimental - fixes partly #31 - several fixes to it (backticks, single sq brackets, renaming funcs, removed lf in JSON, ...) --- testssl.sh | 1283 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 935 insertions(+), 348 deletions(-) diff --git a/testssl.sh b/testssl.sh index 96ec054..50b8dc6 100755 --- a/testssl.sh +++ b/testssl.sh @@ -3,7 +3,7 @@ # vim:ts=5:sw=5:expandtab # we have a spaces softtab, that ensures readability with other editors too -[ -z "$BASH_VERSINFO" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit 245 +[ -z "$BASH_VERSINFO" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit 245 [ $(kill -l | grep -c SIG) -eq 0 ] && printf "\n\033[1;35m Please make sure you're calling me without leading \"sh\"! Bye...\033[m\n\n" >&2 && exit 245 # testssl.sh is a program for spotting weak SSL encryption, ciphers, version and some @@ -20,33 +20,33 @@ # license permitted. # If you enclose this script or parts of it in your software, it has to # be accompanied by the same license (see link) and the place where to get -# the recent version of this program. Do not violate the license andif +# the recent version of this program. Do not violate the license andif # you do not agree to all of these terms, do not use it in the first place. # # OpenSSL which is being used and maybe distributed via one of this projects' # web site is subject to their licensing: https://www.openssl.org/source/license.txt -# +# # The client simulation data comes from SSLlabs and is licensed to the 'Qualys SSL Labs # Terms of Use' (v2.2), see https://www.ssllabs.com/downloads/Qualys_SSL_Labs_Terms_of_Use.pdf, # stating a CC BY 3.0 US license: https://creativecommons.org/licenses/by/3.0/us/ # -# Please note: USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS". +# Please note: USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS". # -# USE IT AT your OWN RISK! +# USE IT AT your OWN RISK! # Seriously! The threat is you run this code on your computer and input could be / # is being supplied via untrusted sources. -# HISTORY: +# HISTORY: # Back in 2006 it all started with a few openssl commands... -# That's because openssl is a such a good swiss army knife (see e.g. -# wiki.openssl.org/index.php/Command_Line_Utilities) that it was difficult to resist -# wrapping some shell commands around it, which I used for my pen tests. This is how +# That's because openssl is a such a good swiss army knife (see e.g. +# wiki.openssl.org/index.php/Command_Line_Utilities) that it was difficult to resist +# wrapping some shell commands around it, which I used for my pen tests. This is how # everything started. # Now it has grown up, it has bash socket support for some features which basically replacing # more and more functions of OpenSSL and will serve as some kind of library in the future. # The socket checks in bash may sound cool and unique -- they are -- but probably you # can achieve e.g. the same result with my favorite interactive shell: zsh (zmodload zsh/net/socket -# -- checkout zsh/net/tcp) too! +# -- checkout zsh/net/tcp) too! # /bin/bash though is way more often used within Linux and it's perfect # for cross platform support, see MacOS X and also under Windows the MSYS2 extension or Cygwin. # Cross-platform is one of the three main goals of this script. Second: Ease of installation. @@ -60,8 +60,8 @@ # internet. And those examples above 4) are 3rd parties. If these restrictions are all fine # with you and you need a management compatible rating -- go ahead and use those. -# But also if your fine with those restrictions: testssl.sh is meant as a tool in your hand -# and it's way more flexible. +# But also if your fine with those restrictions: testssl.sh is meant as a tool in your hand +# and it's way more flexible. # # Oh, and did I mention testssl.sh is open source? @@ -70,7 +70,7 @@ # are disabled as they'll impact security otherwise. For security # testing though we need all broken features. testssl.sh will # over time replace those checks with bash sockets -- however it's -# still recommended to use the supplied binaries or cook your own, see +# still recommended to use the supplied binaries or cook your own, see # https://github.com/drwetter/testssl.sh/blob/master/bin/Readme.md . # Don't worry if feature X is not available you'll get a warning about # this missing feature! The idea is if this script can't tell something @@ -108,7 +108,7 @@ if git log &>/dev/null; then else readonly REL_DATE=$(tail -5 "$0" | awk '/dirkw Exp/ { print $5 }') fi -readonly SYSTEM=$(uname -s) +readonly SYSTEM=$(uname -s) date --help >/dev/null 2>&1 && \ readonly HAS_GNUDATE=true || \ readonly HAS_GNUDATE=false @@ -135,17 +135,19 @@ declare -x OPENSSL COLOR=${COLOR:-2} # 2: Full color, 1: b/w+positioning, 0: no ESC at all COLORBLIND=${COLORBLIND:-false} # if true, swap blue and green in the output SHOW_EACH_C=${SHOW_EACH_C:-0} # where individual ciphers are tested show just the positively ones tested #FIXME: upside down value -SNEAKY=${SNEAKY:-false} # is the referer and useragent we leave behind just usual? +SNEAKY=${SNEAKY:-false} # is the referer and useragent we leave behind just usual? QUIET=${QUIET:-false} # don't output the banner. By doing this yiu acknowledge usage term appearing in the banner 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. +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 - # 3: slight hexdumps + other info, + # 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. LOGFILE=${LOGFILE:-""} # logfile if used +JSONFILE="testssl.json" # jsonfile if used +CSVFILE="testssl.csv" # csvfile if used HAS_IPv6=${HAS_IPv6:-false} # if you have OPENSSL with IPv6 support AND IPv6 networking set it to yes and testssl.sh works! # tuning vars, can not be set by a cmd line switch @@ -172,7 +174,7 @@ IKNOW_FNAME=false # further global vars just declared here readonly NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1" -# alpn_protos needs to be space-separated, not comma-seperated +# alpn_protos needs to be space-separated, not comma-seperated readonly ALPN_PROTOs="h2 h2-17 h2-16 h2-15 h2-14 spdy/3.1 http/1.1" TEMPDIR="" TMPFILE="" @@ -228,6 +230,7 @@ GET_REQ11="" HEAD_REQ10="" readonly UA_STD="TLS tester from $SWURL" readonly UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0" +FIRST_FINDING=true # Is this the first finding we are outputting to file? # Devel stuff, see -q below TLS_LOW_BYTE="" @@ -297,7 +300,7 @@ readonly SSLv2_CLIENT_HELLO=" ###### output functions ###### -# a little bit of sanitzing with bash internal search&replace -- otherwise printf will hiccup at '%' and '--' does the rest. +# a little bit of sanitzing with bash internal search&replace -- otherwise printf will hiccup at '%' and '--' does the rest. out() { /usr/bin/printf -- "${1//%/%%}"; } outln() { out "$1\n"; } #TODO: Still no shell injection safe but if just run it from the cmd line: that's fine @@ -333,7 +336,7 @@ pr_litegreenln() { pr_litegreen "$1"; outln; } pr_green() { [[ "$COLOR" -eq 2 ]] && ( "$COLORBLIND" && out "\033[1;34m$1" || out "\033[1;32m$1" ) || out "$1"; pr_off; } # This is the best pr_greenln() { pr_green "$1"; outln; } -pr_yellow() { [[ "$COLOR" -eq 2 ]] && out "\033[1;33m$1" || out "$1"; pr_off; } # academic or minor problem +pr_yellow() { [[ "$COLOR" -eq 2 ]] && out "\033[1;33m$1" || out "$1"; pr_off; } # academic or minor problem pr_yellowln() { pr_yellow "$1"; outln; } pr_brown() { [[ "$COLOR" -eq 2 ]] && out "\033[0;33m$1" || out "$1"; pr_off; } # it is not a bad problem but you shouldn't do this pr_brownln() { pr_brown "$1"; outln; } @@ -343,7 +346,7 @@ pr_brownln() { pr_brown "$1"; outln; } pr_off() { [[ "$COLOR" -ne 0 ]] && out "\033[m\c"; } pr_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[1m$1" || out "$1"; pr_off; } pr_boldln() { pr_bold "$1" ; outln; } -pr_italic() { [[ "$COLOR" -ne 0 ]] && out "\033[3m$1" || out "$1"; pr_off; } +pr_italic() { [[ "$COLOR" -ne 0 ]] && out "\033[3m$1" || out "$1"; pr_off; } pr_underline() { [[ "$COLOR" -ne 0 ]] && out "\033[4m$1" || out "$1"; pr_off; } pr_reverse() { [[ "$COLOR" -ne 0 ]] && out "\033[7m$1" || out "$1"; pr_off; } pr_reverse_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[7m\033[1m$1" || out "$1"; pr_off; } @@ -420,11 +423,43 @@ set_color_functions() { fi } +strip_quote() { + #FIXME this doesn't work + echo $1|sed "s/\"/\\'/g" # Fix syntax highlighting +} + +fileout_header() { + "$do_json" && printf "[\n" > "$JSONFILE" + "$do_csv" && echo "\"id\",\"ip\",\"port\",\"severity\",\"finding\"" > "$CSVFILE" +} + +fileout_footer() { + "$do_json" && printf "]\n" >> "$JSONFILE" +} + +fileout() { # ID, IP, PORT, SEVERITY, FINDING + if "$do_json"; then + "$FIRST_FINDING" || echo "," >> $JSONFILE + echo -e " + { + 'id' : '$1', + 'ip' : '$2', + 'port' : '$3', + 'severity' : '$4', + 'finding' : '$5' + }" >> $JSONFILE + fi + # does the following do a) any sanititation? + #if "$do_csv"; then + # echo -e "\"$(strip_quote "$1")\",\"$(strip_quote "$2")\",\"$(strip_quote "$3")\",\"$(strip_quote "$4")\",\"$(strip_quote "$5")\"" >>$CSVFILE + #fi + "$FIRST_FINDING" && FIRST_FINDING=false +} ###### helper function definitions ###### debugme() { - [[ $DEBUG -ge 2 ]] && "$@" + [[ "$DEBUG" -ge 2 ]] && "$@" } hex2dec() { @@ -501,7 +536,7 @@ is_ipv6addr() { #check on chars allowed: [[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \ return 1 - return 0 + return 0 } @@ -579,21 +614,26 @@ runs_HTTP() { case $SERVICE in HTTP) out " $SERVICE" + fileout "service" "$NODEIP" "$PORT" "INFO" "Service detected: $SERVICE" ret=0 ;; IMAP|POP|SMTP|NNTP) out " $SERVICE, thus skipping HTTP specific checks" + fileout "service" "$NODEIP" "$PORT" "INFO" "Service detected: $SERVICE, thus skipping HTTP specific checks" ret=0 ;; *) if $CLIENT_AUTH; then out "certificate based authentication => skipping all HTTP checks" echo "certificate based authentication => skipping all HTTP checks" >$TMPFILE + fileout "client_auth" "$NODEIP" "$PORT" "WARN" "certificate based authentication => skipping all HTTP checks" else out " Couldn't determine what's running on port $PORT" if $ASSUMING_HTTP; then SERVICE=HTTP out " -- ASSUMING_HTTP set though" + fileout "service" "$NODEIP" "$PORT" "WARN" "Couldn't determine service, --ASSUMING_HTTP set" ret=0 else out ", assuming no HTTP service => skipping all HTTP checks" + fileout "service" "$NODEIP" "$PORT" "WARN" "Couldn't determine service, skipping all HTTP checks" ret=1 fi fi @@ -622,7 +662,7 @@ run_http_header() { if [[ $? -eq 0 ]]; then # we do the get command again as it terminated within $HEADER_MAXSLEEP. Thus it didn't hang, we do it # again in the foreground ito get an ccurate header time! - printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI >$HEADERFILE 2>$ERRFILE + printf "$GET_REQ11" | $OPENSSL s_client $OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI >$HEADERFILE 2>$ERRFILE NOW_TIME=$(date "+%s") HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 } /^Date:/ { print $2 }' $HEADERFILE) HAD_SLEPT=0 @@ -656,24 +696,55 @@ run_http_header() { pr_bold " HTTP Status Code " [[ -z "$status_code" ]] && pr_litemagentaln "No status code" && return 3 - out " $status_code$msg_thereafter" + out " $status_code$msg_thereafter" case $status_code in - 301|302|307|308) + 301|302|307|308) redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n') - out ", redirecting to \"$redirect"\" - if [[ $redirect == "http://"* ]]; then + out ", redirecting to \"$redirect\"" + if [[ $redirect != "https://"* ]]; then pr_litered " -- Redirect to insecure URL (NOT ok)" + fileout "status_code" "$NODEIP" "$PORT" "NOT OK" \, "Redirect to insecure URL (NOT ok). Url: \"$redirect\"" fi + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter, redirecting to \"$redirect\"" ;; - 200) ;; - 206) out " -- WTF?" ;; - 400) pr_litemagenta " (Hint: better try another URL)" ;; - 401) grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)" + 200) + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + ;; + 206) + out " -- WTF?" + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter -- WTF?" + ;; + 400) + pr_litemagenta " (Hint: better try another URL)" + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: better try another URL)" + ;; + 401) + grep -aq "^WWW-Authenticate" $HEADERFILE && out " "; strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)" + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter $(grep -a "^WWW-Authenticate" $HEADERFILE)" + ;; + 403) + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + ;; + 404) + out " (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")" + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")" + ;; + 405) + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter" + ;; + *) + pr_litemagenta ". Oh, didn't expect a $status_code$msg_thereafter" + fileout "status_code" "$NODEIP" "$PORT" "info" \ + "Testing HTTP header response @ \"$URL_PATH\", $status_code$msg_thereafter. Oh, didn't expect a $status_code$msg_thereafter" ;; - 403) ;; - 404) out " (Hint: supply a path which doesn't give a \"$status_code$msg_thereafter\")" ;; - 405) ;; - *) pr_litemagenta ". Oh, didn't expect a $status_code$msg_thereafter";; esac outln @@ -689,7 +760,8 @@ detect_ipv4() { local result local first=true local spaces=" " - + local count + if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 fi @@ -697,7 +769,8 @@ detect_ipv4() { # remove pagespeed header as it is mistakenly identified as ipv4 address https://github.com/drwetter/testssl.sh/issues/158 # also facebook has a CSP rule for 127.0.0.1 if egrep -vi "pagespeed|page-speed|Content-Security-Policy" $HEADERFILE | grep -iqE "$ipv4address"; then - pr_bold " IPv4 address in header " + pr_bold " IPv4 address in header " + count=0 while read line; do result="$(grep -E "$ipv4address" <<< "$line")" result=$(strip_lf "$result") @@ -709,11 +782,13 @@ detect_ipv4() { first=false fi pr_litered "$result" - outln "\n$spaces$your_ip_msg" + outln "spaces$your_ip_msg" + fileout "ip_in_header_$count" "$NODEIP" "$PORT" "NOT OK" "IPv4 address in header $result $your_ip_msg" fi + count=$count+1 done < $HEADERFILE fi -} +} run_http_date() { @@ -738,8 +813,10 @@ run_http_date() { # process was killed, so we need to add an error: [[ $HAD_SLEPT -ne 0 ]] && difftime="$difftime (± 1.5)" out "$difftime sec from localtime"; + fileout "http_clock_skew" "$NODEIP" "$PORT" "INFO" "HTTP clock skew $difftime sec from localtime" else out "Got no HTTP time, maybe try different URL?"; + fileout "http_clock_skew" "$NODEIP" "$PORT" "INFO" "HTTP clock skew not measured. Got no HTTP time, maybe try different URL?" fi debugme out ", epoch: $HTTP_TIME" fi @@ -750,13 +827,20 @@ run_http_date() { includeSubDomains() { if grep -aiqw includeSubDomains "$1"; then pr_litegreen ", includeSubDomains" + return 1 else pr_litecyan ", just this domain" + return 0 fi } preload() { - grep -aiqw preload "$1" && pr_litegreen ", preload" + if grep -aiqw preload "$1"; then + pr_litegreen ", preload" + return 1 + else + return 0 + fi } @@ -777,16 +861,27 @@ run_hsts() { hsts_age_days=$(( hsts_age_sec / 86400)) if [[ $hsts_age_days -gt $HSTS_MIN ]]; then pr_litegreen "$hsts_age_days days" ; out "=$hsts_age_sec s" + fileout "hsts_time" "$NODEIP" "$PORT" "OK" "HSTS timeout $hsts_age_days days (=$hsts_age_sec seconds) > $HSTS_MIN days" else out "$hsts_age_sec s = " pr_brown "$hsts_age_days days, <$HSTS_MIN days is too short" + fileout "hsts_time" "$NODEIP" "$PORT" "NOT OK" "HSTS timeout too short. $hsts_age_days days (=$hsts_age_sec seconds) < $HSTS_MIN days" + fi + if includeSubDomains "$TMPFILE"; then + fileout "hsts_subdomains" "$NODEIP" "$PORT" "OK" "HSTS includes subdomains" + else + fileout "hsts_subdomains" "$NODEIP" "$PORT" "WARN" "HSTS only for this domain, consider to include subdomains as well" + fi + if preload "$TMPFILE"; then + fileout "hsts_preload" "$NODEIP" "$PORT" "OK" "HSTS domain is marked for preloading" + else + fileout "hsts_preload" "$NODEIP" "$PORT" "INFO" "HSTS domain is NOT marked for preloading" fi - includeSubDomains "$TMPFILE" - preload "$TMPFILE" #FIXME: To be checked against e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc # and https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json else out "--" + fileout "hsts" "$NODEIP" "$PORT" "NOT OK" "No support for HTTP Strict Transport Security" fi outln @@ -803,6 +898,8 @@ run_hpkp() { local spaces=" " local key_found=false local i + local hpkp_headers + local first_hpkp_header if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 @@ -814,13 +911,17 @@ run_hpkp() { if egrep -aciw '^Public-Key-Pins|Public-Key-Pins-Report-Only' $HEADERFILE | egrep -waq "1" ; then : else - pr_brown "two HPKP headers: " + hpkp_headers="" + pr_brown "multiple HPKP headers: " for i in $(newline_to_spaces "$(egrep -ai '^Public-Key-Pins' $HEADERFILE | awk -F':' '/Public-Key-Pins/ { print $1 }')"); do pr_italic $i + hpkp_headers="$hpkp_headers$i " out " " done - out "\n$spaces using first " - pr_italic "$(awk -F':' '/Public-Key-Pins/ { print $1 }' $HEADERFILE | head -1), " + out "spaces using first " + first_hpkp_header=$(awk -F':' '/Public-Key-Pins/ { print $1 }' $HEADERFILE | head -1) + pr_italic "$first_hpkp_header, " + fileout "hpkp_multiple" "$NODEIP" "$PORT" "WARN" "Multiple HPKP headershpkp_headers\nUsing first header: $first_hpkp_header" fi # remove leading Public-Key-Pins*, any colons, double quotes and trailing spaces and taking the first -- whatever that is @@ -834,8 +935,10 @@ run_hpkp() { out "# of keys: " if [[ $hpkp_nr_keys -eq 1 ]]; then pr_litered "1 (NOT ok), " + fileout "hpkp_keys" "$NODEIP" "$PORT" "NOT OK" "Only one key pinned in HPKP header, this means the site may become unavaiable if the key is revoked" else out "$hpkp_nr_keys, " + fileout "hpkp_keys" "$NODEIP" "$PORT" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked" fi # print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk @@ -843,13 +946,23 @@ run_hpkp() { hpkp_age_days=$((hpkp_age_sec / 86400)) if [[ $hpkp_age_days -ge $HPKP_MIN ]]; then pr_litegreen "$hpkp_age_days days" ; out "=$hpkp_age_sec s" + fileout "hpkp_age" "$NODEIP" "$PORT" "OK" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec)" else out "$hpkp_age_sec s = " pr_brown "$hpkp_age_days days (<$HPKP_MIN days is not good enough)" + fileout "hpkp_age" "$NODEIP" "$PORT" "NOT OK" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN days is not good enough." fi - includeSubDomains "$TMPFILE" - preload "$TMPFILE" + if includeSubDomains "$TMPFILE"; then + fileout "hpkp_subdomains" "$NODEIP" "$PORT" "INFO" "HPKP header is valid for subdomains as well" + else + fileout "hpkp_subdomains" "$NODEIP" "$PORT" "INFO" "HPKP header is valid for this domain only" + fi + if preload "$TMPFILE"; then + fileout "hpkp_preload" "$NODEIP" "$PORT" "INFO" "HPKP header is marked for browser preloading" + else + fileout "hpkp_preload" "$NODEIP" "$PORT" "INFO" "HPKP header is NOT marked for browser preloading" + fi [[ -s "$HOSTCERT" ]] || get_host_cert # get the key fingerprints @@ -857,19 +970,22 @@ run_hpkp() { $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)" while read hpkp_key; do if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then - out "\n$spaces matching host key: " + out "spaces matching host key: " pr_litegreen "$hpkp_key" + fileout "hpkp_keymatch" "$NODEIP" "$PORT" "OK" "Key matches a key pinned in the HPKP header" key_found=true fi debugme out "\n $hpkp_key | $hpkp_key_hostcert" done < <(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') if ! $key_found ; then - out "\n$spaces" + out "spaces" pr_litered " No matching key for pins found " out "(CAs pinned? -- not yet checked)" + fileout "hpkp_keymatch" "$NODEIP" "$PORT" "WARN" "The TLS key does not match any key pinned in the HPKP header. If you pinned a CA key you can ignore this" fi else out "--" + fileout "hpkp" "$NODEIP" "$PORT" "WARN" "No support for HTTP Public Key Pinning" fi outln @@ -919,18 +1035,23 @@ run_server_banner() { grep -ai '^Server' $HEADERFILE >$TMPFILE if [[ $? -eq 0 ]]; then serverbanner=$(sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE) - if [[ x"$serverbanner" == "x\n" ]] || [[ x"$serverbanner" == "x\n\r" ]] || [[ x"$serverbanner" == "x" ]]; then + if [[ x"$serverbanner" == "x\n" ]] || [[ x"$serverbanner" == "x\n\r" ]] || [[ -z "$serverbanner" ]]; then outln "banner exists but empty string" + fileout "serverbanner" "$NODEIP" "$PORT" "INFO" "Server banner exists but empty string" else emphasize_stuff_in_headers "$serverbanner" - [[ "$serverbanner" = *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]] && \ + fileout "serverbanner" "$NODEIP" "$PORT" "INFO" "Server banner identified: $serverbanner" + if [[ "$serverbanner" = *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]]; then pr_litemagentaln " It's recommended to run another test w/ OpenSSL 1.01 !" # see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 + fileout "IIS6_openssl_mismatch" "$NODEIP" "$PORT" "WARN" "It is recommended to rerun this test w/ OpenSSL 1.01\nSee https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892" + fi fi # mozilla.github.io/server-side-tls/ssl-config-generator/ # https://support.microsoft.com/en-us/kb/245030 else outln "(no \"Server\" line in header, interesting!)" + fileout "serverbanner" "$NODEIP" "$PORT" "WARN" "No Server banner in header, interesting!" fi tmpfile_handle $FUNCNAME.txt @@ -941,15 +1062,17 @@ run_rp_banner() { local line local first=true local spaces=" " + local rp_banners="" if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 fi pr_bold " Reverse Proxy banner " - egrep -ai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded' $HEADERFILE >$TMPFILE + egrep -ai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded' $HEADERFILE >$TMPFILE if [[ $? -ne 0 ]]; then outln "--" - else + fileout "rp_header" "$NODEIP" "$PORT" "INFO" "No reverse proxy banner found" + else while read line; do line=$(strip_lf "$line") if ! $first; then @@ -958,7 +1081,9 @@ run_rp_banner() { first=false fi emphasize_stuff_in_headers "$line" + rp_banners="$rp_bannersline" done < $TMPFILE + fileout "rp_header" "$NODEIP" "$PORT" "INFO" "Reverse proxy banner(s) found: $rp_banners" fi outln @@ -971,6 +1096,7 @@ run_application_banner() { local line local first=true local spaces=" " + local app_banners="" if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 @@ -979,6 +1105,7 @@ run_application_banner() { egrep -ai '^X-Powered-By|^X-AspNet-Version|^X-Version|^Liferay-Portal|^X-OWA-Version' $HEADERFILE >$TMPFILE if [[ $? -ne 0 ]]; then outln "--" + fileout "app_banner" "$NODEIP" "$PORT" "INFO" "No Application Banners found" else cat $TMPFILE | while read line; do line=$(strip_lf "$line") @@ -988,7 +1115,9 @@ run_application_banner() { first=false fi emphasize_stuff_in_headers "$line" + app_banners="$app_bannersline" done + fileout "app_banner" "$NODEIP" "$PORT" "WARN" "Application Banners found: $app_banners" fi tmpfile_handle $FUNCNAME.txt return 0 @@ -997,7 +1126,7 @@ run_application_banner() { run_cookie_flags() { # ARG1: Path, ARG2: path local -i nr_cookies local nr_httponly nr_secure - local negative_word + local negative_word if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 @@ -1005,8 +1134,9 @@ run_cookie_flags() { # ARG1: Path, ARG2: path pr_bold " Cookie(s) " grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE if [[ $? -eq 0 ]]; then - nr_cookies=$(wc -l < $TMPFILE | sed 's/ //g') + nr_cookies=$(count_lines "$TMPFILE") out "$nr_cookies issued: " + fileout "cookie_count" "$NODEIP" "$PORT" "INFO" "$nr_cookies cookie(s) issued at \"$1\"" if [[ $nr_cookies -gt 1 ]]; then negative_word="NONE" else @@ -1018,15 +1148,26 @@ run_cookie_flags() { # ARG1: Path, ARG2: path [123456789]) pr_litegreen "$nr_secure/$nr_cookies";; esac out " secure, " + if [[ $nr_cookies == $nr_secure ]]; then + fileout "cookie_secure" "$NODEIP" "$PORT" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as secure" + else + fileout "cookie_secure" "$NODEIP" "$PORT" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as secure" + fi nr_httponly=$(grep -cai httponly $TMPFILE) case $nr_httponly in 0) pr_brown "$negative_word" ;; [123456789]) pr_litegreen "$nr_httponly/$nr_cookies";; esac out " HttpOnly" + if [[ $nr_cookies == $nr_httponly ]]; then + fileout "cookie_httponly" "$NODEIP" "$PORT" "OK" "All $nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly" + else + fileout "cookie_httponly" "$NODEIP" "$PORT" "WARN" "$nr_secure/$nr_cookies cookie(s) issued at \"$1\" marked as HttpOnly" + fi else out "(none issued at \"$1\")" - fi + fileout "cookie_count" "$NODEIP" "$PORT" "INFO" "No cookies issued at \"$1\"" + fi outln tmpfile_handle $FUNCNAME.txt @@ -1047,10 +1188,11 @@ run_more_flags() { fi pr_bold " Security headers " # convert spaces to | (for egrep) - egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g') + egrep_pattern=$(echo "$good_flags2test $other_flags2test"| sed -e 's/ /|\^/g' -e 's/^/\^/g') egrep -ai "$egrep_pattern" $HEADERFILE >$TMPFILE if [[ $? -ne 0 ]]; then outln "--" + fileout "sec_headers" "$NODEIP" "$PORT" "WARN" "No security (or other interesting) headers detected" ret=1 else ret=0 @@ -1059,7 +1201,7 @@ run_more_flags() { result_str=$(grep -wi "^$f2t" $TMPFILE | grep -vi "$f2t"-) result_str=$(strip_lf "$result_str") [[ -z "$result_str" ]] && continue - if ! $first; then + if ! "$first"; then out "$spaces" # output leading spaces if the first header else first=false @@ -1069,7 +1211,7 @@ run_more_flags() { #pr_litegreen "$(sed 's/:.*$/:/' <<< "$result_str")" # print value in plain text: outln "${result_str#*:}" - + fileout "${result_str%%:*}" "$NODEIP" "$PORT" "OK" "${result_str%%:*}: ${result_str#*:}" done # now the same with other flags for f2t in $other_flags2test; do @@ -1084,6 +1226,7 @@ run_more_flags() { pr_litecyan "${result_str%%:*}:" # print value in plain text: outln "${result_str#*:}" + fileout "${result_str%%:*}" "$NODEIP" "$PORT" "WARN" "${result_str%%:*}: ${result_str#*:}" done fi #TODO: I am not testing for the correctness or anything stupid yet, e.g. "X-Frame-Options: allowall" @@ -1131,7 +1274,7 @@ prettyprint_local() { if [[ -z "$1" ]]; then $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE | while read hexcode dash ciph sslvers kx auth enc mac export ; do # -V doesn't work with openssl < 1.0 normalize_ciphercode $hexcode - neat_list $HEXC $ciph $kx $enc + neat_list "$HEXC" "$ciph" "$kx" "$enc" outln done else @@ -1141,8 +1284,8 @@ prettyprint_local() { normalize_ciphercode $hexcode # for numbers we don't do word matching: [[ $arg =~ $re ]] && \ - neat_list $HEXC $ciph $kx $enc | grep -ai "$arg" || \ - neat_list $HEXC $ciph $kx $enc | grep -wai "$arg" + neat_list "$HEXC" "$ciph" "$kx" "$enc" | grep -ai "$arg" || \ + neat_list "$HEXC" "$ciph" "$kx" "$enc" | grep -wai "$arg" done done fi @@ -1182,21 +1325,41 @@ std_cipherlists() { debugme cat $ERRFILE case $3 in 0) # ok to offer - [[ $sclient_success -eq 0 ]] && \ - pr_greenln "offered (OK)" || \ - pr_brownln "not offered (NOT ok)" ;; + if [[ $sclient_success -eq 0 ]]; then + pr_greenln "offered (OK)" + fileout "std_$4" "$NODEIP" "$PORT" "OK" "$2 offered (OK)" + else + pr_brownln "not offered (NOT ok)" + fileout "std_$4" "$NODEIP" "$PORT" "NOT OK" "$2 not offered (NOT OK)" + fi + ;; 1) # the ugly ones - [[ $sclient_success -eq 0 ]] && \ - pr_redln "offered (NOT ok)" || \ - pr_greenln "not offered (OK)" ;; + if [[ $sclient_success -eq 0 ]]; then + pr_redln "offered (NOT ok)" + fileout "std_$4" "$NODEIP" "$PORT" "NOT OK" "$2 offered (NOT OK) - ugly" + else + pr_greenln "not offered (OK)" + fileout "std_$4" "$NODEIP" "$PORT" "OK" "$2 not offered (OK)" + fi + ;; 2) # bad but not worst - [[ $sclient_success -eq 0 ]] && \ - pr_literedln "offered (NOT ok)" || \ - pr_litegreenln "not offered (OK)" ;; - 3) # not totally bad - [[ $sclient_success -eq 0 ]] && \ - pr_brownln "offered (NOT ok)" || \ - outln "not offered (OK)" ;; + if [[ $sclient_success -eq 0 ]]; then + pr_literedln "offered (NOT ok)" + fileout "std_$4" "$NODEIP" "$PORT" "NOT OK" "$2 offered (NOT OK) - bad" + else + pr_litegreenln "not offered (OK)" + fileout "std_$4" "$NODEIP" "$PORT" "OK" "$2 not offered (OK)" + fi + ;; + 3) # not totally bad + if [[ $sclient_success -eq 0 ]]; then + pr_brownln "offered (NOT ok)" + fileout "std_$4" "$NODEIP" "$PORT" "NOT OK" "$2 offered (NOT OK) - not too bad" + else + outln "not offered (OK)" + fileout "std_$4" "$NODEIP" "$PORT" "OK" "$2 not offered (OK)" + fi + ;; *) # we shouldn't reach this pr_litemagenta "? (please report this)" ;; esac @@ -1204,6 +1367,7 @@ std_cipherlists() { else singlespaces=$(echo "$2" | sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g') local_problem "No $singlespaces configured in $OPENSSL" + fileout "std_$4" "$NODEIP" "$PORT" "WARN" "Cipher $2 ($1) not supported by local OpenSSL ($OPENSSL)" fi # we need 1xlf in those cases: debugme echo @@ -1301,9 +1465,13 @@ test_just_one(){ local re='^[0-9A-Fa-f]+$' pr_headline " Testing single cipher with " - [[ $1 =~ $re ]] && \ - pr_headline "matching number pattern \"$1\" " || \ + if [[ $1 =~ $re ]]; then + pr_headline "matching number pattern \"$1\" " + tjolines="$tjolines matching number pattern \"$1\"\n\n" + else pr_headline "word pattern "\"$1\"" (ignore case) " + tjolines="$tjolines word pattern \"$1\" (ignore case)\n\n" + fi outln ! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits)" outln @@ -1316,7 +1484,7 @@ test_just_one(){ normalize_ciphercode $hexcode # is argument a number? if [[ $arg =~ $re ]]; then - neat_list $HEXC $ciph $kx $enc | grep -qai "$arg" + neat_list $HEXC $ciph $kx $enc | grep -qai "$arg" else neat_list $HEXC $ciph $kx $enc | grep -qwai "$arg" fi @@ -1335,12 +1503,15 @@ test_just_one(){ neat_list $HEXC $ciph "$kx" $enc if [[ $sclient_success -eq 0 ]]; then pr_cyan " available" + fileout "cipher_$HEXC" "$NODEIP" "$PORT" "INFO" "$(neat_header)(neat_list $HEXC $ciph "$kx" $enc) available" else out " not a/v" + fileout "cipher_$HEXC" "$NODEIP" "$PORT" "INFO" "$(neat_header)(neat_list $HEXC $ciph "$kx" $enc) not a/v" fi outln fi done + exit done outln @@ -1356,35 +1527,39 @@ run_allciphers(){ local -i sclient_success=0 local hexcode n ciph sslvers kx auth enc mac export local dhlen + local available nr_ciphers=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)") outln pr_headlineln " Testing all $nr_ciphers locally available ciphers against the server, ordered by encryption strength " - ! $HAS_DH_BITS && pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits)" + "$HAS_DH_BITS" || pr_litemagentaln " (Your $OPENSSL cannot show DH/ECDH bits)" outln neat_header $OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>>$ERRFILE | while read hexcode n ciph sslvers kx auth enc mac export; do # FIXME: e.g. OpenSSL < 1.0 doesn't understand "-V" --> we can't do anything about it! $OPENSSL s_client -cipher $ciph $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $ERRFILE | while read hexcode n ciph sslvers kx auth enc mac export; do # -V doesn't work with openssl < 1.0 $OPENSSL s_client -cipher $ciph $proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE + $OPENSSL s_client -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE debugme echo "$OPENSSL s_client -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE + $OPENSSL s_client $tls -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE debugme echo "$OPENSSL s_client $tls -cipher ${ciphers[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE $ERRFILE >$TMPFILE if ! sclient_connect_successful $? $TMPFILE; then pr_litemagenta "no matching cipher in this list found (pls report this): " outln "$list_fwd . " has_cipher_order=false ret=6 + fileout "order_bug" "$NODEIP" "$PORT" "WARN" "Could not determine server cipher order, no matching cipher in this list found (pls report this): $list_fwd" fi fi @@ -2101,9 +2363,11 @@ run_server_preference() { if [[ "$cipher1" != "$cipher2" ]]; then pr_litered "nope (NOT ok)" remark4default_cipher=" (limited sense as client will pick)" + fileout "order" "$NODEIP" "$PORT" "NOT OK" "Server does NOT set a cipher order (NOT ok)" else pr_green "yes (OK)" remark4default_cipher="" + fileout "order" "$NODEIP" "$PORT" "OK" "Server sets a cipher order (ok)" fi [[ $DEBUG -ge 2 ]] && out " $cipher1 | $cipher2" outln @@ -2117,26 +2381,78 @@ run_server_preference() { fi default_proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') case "$default_proto" in - *TLSv1.2) pr_greenln $default_proto ;; - *TLSv1.1) pr_litegreenln $default_proto ;; - *TLSv1) outln $default_proto ;; - *SSLv2) pr_redln $default_proto ;; - *SSLv3) pr_redln $default_proto ;; - "") pr_litemagenta "default proto empty"; [[ $OSSL_VER == 1.0.2* ]] && outln " (Hint: if IIS6 give OpenSSL 1.01 a try)" ;; - *) pr_litemagenta "FIXME line $LINENO: $default_proto" ;; + *TLSv1.2) + pr_greenln $default_proto + fileout "order_proto" "$NODEIP" "$PORT" "OK" "Default protocol TLS1.2 (OK)" + ;; + *TLSv1.1) + pr_litegreenln $default_proto + fileout "order_proto" "$NODEIP" "$PORT" "OK" "Default protocol TLS1.1 (OK)" + ;; + *TLSv1) + outln $default_proto + fileout "order_proto" "$NODEIP" "$PORT" "INFO" "Default protocol TLS1.0" + ;; + *SSLv2) + pr_redln $default_proto + fileout "order_proto" "$NODEIP" "$PORT" "NOT OK" "Default protocol SSLv2" + ;; + *SSLv3) + pr_redln $default_proto + fileout "order_proto" "$NODEIP" "$PORT" "NOT OK" "Default protocol SSLv3" + ;; + "") + pr_litemagenta "default proto empty" + if [[ $OSSL_VER == 1.0.2* ]]; then + outln " (Hint: if IIS6 give OpenSSL 1.01 a try)" + fileout "order_proto" "$NODEIP" "$PORT" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.01 a try)" + else + fileout "order_proto" "$NODEIP" "$PORT" "WARN" "Default protocol empty" + fi + ;; + *) + pr_litemagenta "FIXME line $LINENO: $default_proto" + fileout "order_proto" "$NODEIP" "$PORT" "WARN" "FIXME line $LINENO: $default_proto" + ;; esac pr_bold " Negotiated cipher " default_cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') case "$default_cipher" in - *NULL*|*EXP*) pr_red "$default_cipher" ;; - *RC4*) pr_litered "$default_cipher" ;; - *CBC*) pr_brown "$default_cipher" ;; # FIXME BEAST: We miss some CBC ciphers here, need to work w/ a list - *GCM*) pr_green "$default_cipher" ;; # best ones - *CHACHA20*) pr_green "$default_cipher" ;; # best ones - ECDHE*AES*) pr_yellow "$default_cipher" ;; # it's CBC. --> lucky13 - "") pr_litemagenta "default cipher empty" ; [[ $OSSL_VER == 1.0.2* ]] && out " (Hint: if IIS6 give OpenSSL 1.01 a try)" ;; - *) out "$default_cipher" ;; + *NULL*|*EXP*) + pr_red "$default_cipher" + + fileout "order_cipher" "$NODEIP" "$PORT" "NOT OK" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) $remark4default_cipher" + ;; + *RC4*) + pr_litered "$default_cipher" + fileout "order_cipher" "$NODEIP" "$PORT" "NOT OK" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) remark4default_cipher" + ;; + *CBC*) + pr_brown "$default_cipher" + fileout "order_cipher" "$NODEIP" "$PORT" "NOT OK" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (NOT ok) $remark4default_cipher" + ;; # FIXME BEAST: We miss some CBC ciphers here, need to work w/ a list + *GCM*|*CHACHA20*) + pr_green "$default_cipher" + fileout "order_cipher" "$NODEIP" "$PORT" "OK" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (OK) $remark4default_cipher" + ;; # best ones + ECDHE*AES*) + pr_yellow "$default_cipher" + fileout "order_cipher" "$NODEIP" "$PORT" "WARN" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") (cbc) $remark4default_cipher" + ;; # it's CBC. --> lucky13 + "") + pr_litemagenta "default cipher empty" ; + if [[ $OSSL_VER == 1.0.2* ]]; then + out " (Hint: if IIS6 give OpenSSL 1.01 a try)" + fileout "order_cipher" "$NODEIP" "$PORT" "WARN" "Default cipher empty (Hint: if IIS6 give OpenSSL 1.01 a try) $remark4default_cipher" + else + fileout "order_cipher" "$NODEIP" "$PORT" "WARN" "Default cipher empty $remark4default_cipher" + fi + ;; + *) + out "$default_cipher" + fileout "order_cipher" "$NODEIP" "$PORT" "INFO" "Default cipher: $default_cipher$(read_dhbits_from_file "$TMPFILE") $remark4default_cipher" + ;; esac read_dhbits_from_file "$TMPFILE" outln "$remark4default_cipher" @@ -2145,8 +2461,8 @@ run_server_preference() { pr_bold " Negotiated cipher per proto"; outln " $remark4default_cipher" i=1 for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do - #locally_supported -"$p" " " || continue - locally_supported -"$p" || continue + #locally_supported -"$p" " " || continue + locally_supported -"$p" || continue $OPENSSL s_client $STARTTLS -"$p" $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$ERRFILE >$TMPFILE if sclient_connect_successful $? $TMPFILE; then proto[i]=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') @@ -2191,6 +2507,7 @@ run_server_preference() { fi fi fi + fileout "order_${proto[i]}_cipher" "$NODEIP" "$PORT" "INFO" "Default cipher on ${proto[i]}: ${cipher[i]}remark4default_cipher" done fi fi @@ -2207,11 +2524,12 @@ run_server_preference() { cipher_pref_check() { local p proto protos - local tested_cipher cipher + local tested_cipher cipher order pr_bold " Cipher order" for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do + order="" $OPENSSL s_client $STARTTLS -"$p" $BUGS -connect $NODEIP:$PORT $PROXY $SNI $ERRFILE >$TMPFILE if sclient_connect_successful $? $TMPFILE; then tested_cipher="" @@ -2221,14 +2539,17 @@ cipher_pref_check() { outln printf " %-10s %s " "$proto:" "$cipher" tested_cipher="-"$cipher + order="$cipher" while true; do $OPENSSL s_client $STARTTLS -"$p" $BUGS -cipher "ALL:$tested_cipher" -connect $NODEIP:$PORT $PROXY $SNI >$ERRFILE >$TMPFILE sclient_connect_successful $? $TMPFILE || break cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') out "$cipher " + order+=" $cipher" tested_cipher="$tested_cipher:-$cipher" done fi + [[ -z "$order" ]] || fileout "order_$p" "$NODEIP" "$PORT" "INFO" "Default cipher order for protocol $p: $order" done outln @@ -2237,18 +2558,22 @@ cipher_pref_check() { else protos=$($OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg \"\" >$ERRFILE | grep -a "^Protocols " | sed -e 's/^Protocols.*server: //' -e 's/,//g') for p in $protos; do + order="" $OPENSSL s_client -host $NODE -port $PORT $BUGS -nextprotoneg "$p" $PROXY >$ERRFILE >$TMPFILE cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') 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 sclient_connect_successful $? $TMPFILE || break cipher=$(grep -aw "Cipher" $TMPFILE | egrep -avw "New|is" | sed -e 's/^.*Cipher.*://' -e 's/ //g') out "$cipher " tested_cipher="$tested_cipher:-$cipher" + order+=" $cipher" done - outln + outln + [[ -z $order ]] || fileout "order_spdy_$p" "$NODEIP" "$PORT" "INFO" "Default cipher order for SPDY protocol $p:order" done fi @@ -2280,7 +2605,7 @@ get_all_certs() { pr_litemagenta "problem getting all certs" savedir=$(pwd); cd $TEMPDIR # http://backreference.org/2010/05/09/ocsp-verification-with-openssl/ - awk -v n=-1 '/-----BEGIN CERTIFICATE-----/{ inc=1; n++ } + awk -v n=-1 '/-----BEGIN CERTIFICATE-----/{ inc=1; n++ } inc { print > ("level" n ".crt") } /---END CERTIFICATE-----/{ inc=0 }' $TEMPDIR/allcerts.txt nrsaved=$(count_words "$(echo level?.crt 2>/dev/null)") @@ -2311,24 +2636,28 @@ verify_retcode_helper() { determine_trust() { local i=1 local bundle_fname - local -a certificate_file verify_retcode trust + local -a certificate_file verify_retcode trust local ok_was="" local notok_was="" local code local ca_bundles="$INSTALL_DIR/etc/*.pem" local spaces=" " + local addtl_warning if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.0" ]]; then pr_litemagentaln "Your $OPENSSL is too new, needed is version 1.0.2" + fileout "trust" "$NODEIP" "$PORT" "WARN" "Your $OPENSSL is too new, need version 1.0.2 to determine trust" return 7 elif [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR != "1.0.2" ]]; then pr_litemagentaln "Your $OPENSSL is too old, needed is version >=1.0.2" + addtl_warning="Your $OPENSSL is too old, need version 1.0.2 to determine trust. Results may be unreliable." + fileout "trust_warn" "$NODEIP" "$PORT" "WARN" "$addtl_warning" fi debugme outln for bundle_fname in $ca_bundles; do certificate_file[i]=$(basename $bundle_fname | sed 's/\.pem//') if [[ ! -r $bundle_fname ]]; then - pr_litemagentaln "\"$bundle_fname\" cannot be found / not readable" + pr_litemagentaln "\"$bundle_fname\" cannot be found / not readable" return 7 fi debugme printf -- " %-12s" "${certificate_file[i]}" @@ -2348,18 +2677,20 @@ determine_trust() { debugme out " " # all stores ok if ${trust[1]} && ${trust[2]} && ${trust[3]} && ${trust[4]}; then - pr_litegreen "Ok " + pr_litegreen "Ok " + fileout "trust" "$NODEIP" "$PORT" "OK" "All certificate trust checks passed.addtl_warning" # at least one failed else - pr_red "NOT ok" + pr_red "NOT ok" # all failed (we assume with the same issue) if ! ${trust[1]} && ! ${trust[2]} && ! ${trust[3]} && ! ${trust[4]}; then verify_retcode_helper "${verify_retcode[2]}" + fileout "trust" "$NODEIP" "$PORT" "NOT OK" "All certificate trust checks failed: verify_retcode_helper "${verify_retcode[2]}".addtl_warning" else # is one ok and the others not? if ${trust[1]} || ${trust[2]} || ${trust[3]} || ${trust[4]}; then pr_red ":" - out "\n$spaces" + out "spaces" pr_red "FAILED:" for i in 1 2 3 4; do if ${trust[i]}; then @@ -2369,16 +2700,18 @@ determine_trust() { #notok_was="${certificate_file[i]} $notok_was" pr_litered " ${certificate_file[i]} " verify_retcode_helper "${verify_retcode[i]}" - fi + notok_was="${certificate_file[i]} - verify_retcode_helper "${verify_retcode[i]}"notok_was" + fi done #pr_litered "$notok_was " #outln "$code" outln #lf + green ones - [[ $DEBUG -eq 0 ]] && out "$spaces" + [[ "$DEBUG" -eq 0 ]] && out "$spaces" pr_litegreen "OK: $ok_was" fi - fi + fileout "trust" "$NODEIP" "$PORT" "NOT OK" "Some certificate trust checks failed OK : $ok_was NOT ok:notok_was $addtl_warning" + fi fi outln return 0 @@ -2401,14 +2734,17 @@ tls_time() { if [[ "${#difftime}" -gt 5 ]]; then # openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint pr_bold " TLS timestamp" ; outln " random values, no fingerprinting possible " + fileout "tls_time" "$NODEIP" "$PORT" "INFO" "Your TLS time seems to be filled with random values to prevent fingerprinting" else [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" pr_bold " TLS clock skew" ; outln " $difftime sec from localtime"; + fileout "tls_time" "$NODEIP" "$PORT" "INFO" "Your TLS time is skewed from your localtime by $difftime seconds" fi debugme out "$TLS_TIME" outln else pr_bold " TLS timestamp" ; out " "; pr_litemagentaln "SSLv3 through TLS 1.2 didn't return a timestamp" + fileout "tls_time" "$NODEIP" "$PORT" "INFO" "No TLS timestamp returned by SSLv3 through TLSv1.2" fi return 0 } @@ -2418,7 +2754,7 @@ tls_time() { sclient_connect_successful() { [[ $1 -eq 0 ]] && return 0 [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]] && return 0 - # second check saved like + # second check saved like # fgrep 'Cipher is (NONE)' "$2" &> /dev/null && return 1 # what's left now is: master key empty and Session-ID not empty ==> probably client based auth with x509 certificate return 1 @@ -2463,9 +2799,13 @@ run_server_defaults() { local proto local sessticket_str lifetime unit keysize sig_algo key_algo local expire days2expire secs2warn ocsp_uri crl startdate enddate issuer_C issuer_O issuer sans san cn cn_nosni + local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_fingerprint_serial local policy_oid local spaces=" " local wildcard=false + local cnfinding + local cnok="OK" + local expfinding expok="OK" outln pr_headlineln " Testing server defaults (Server Hello) " @@ -2477,9 +2817,11 @@ run_server_defaults() { #extensions=$(grep -aw "^TLS server extension" $TMPFILE | sed -e 's/^TLS server extension \"//' -e 's/\".*$/,/g') if [[ -z "$TLS_EXTENSIONS" ]]; then outln "(none)" + fileout "tls_extensions" "$NODEIP" "$PORT" "INFO" "TLS server extensions (std): (none)" else - #echo $extensions | sed 's/ /,/g' + #echo $extensions | sed 's/ /,/g' outln "$TLS_EXTENSIONS" + fileout "tls_extensions" "$NODEIP" "$PORT" "INFO" "TLS server extensions (std): $TLS_EXTENSIONS" fi cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE @@ -2489,18 +2831,22 @@ run_server_defaults() { sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime) if [[ -z "$sessticket_str" ]]; then outln "(none)" + fileout "session_ticket" "$NODEIP" "$PORT" "INFO" "TLS session tickes RFC 5077 not supported" else lifetime=$(echo $sessticket_str | grep -a lifetime | sed 's/[A-Za-z:() ]//g') unit=$(echo $sessticket_str | grep -a lifetime | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g') out "$lifetime $unit " pr_yellowln "(PFS requires session ticket keys to be rotated <= daily)" + fileout "session_ticket" "$NODEIP" "$PORT" "INFO" "TLS session tickes RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)" fi - + pr_bold " SSL Session ID support " if $NO_SSL_SESSIONID; then outln "no" + fileout "session_id" "$NODEIP" "$PORT" "INFO" "SSL session ID support: no" else outln "yes" + fileout "session_id" "$NODEIP" "$PORT" "INFO" "SSL session ID support: yes" fi pr_bold " Server key size " @@ -2510,51 +2856,89 @@ run_server_defaults() { if [[ -z "$keysize" ]]; then outln "(couldn't determine)" + fileout "key_size" "$NODEIP" "$PORT" "WARN" "Server keys size cannot be determined" else if [[ "$keysize" -le 768 ]]; then if [[ $sig_algo =~ ecdsa ]] || [[ $key_algo =~ ecPublicKey ]]; then pr_litegreen "EC $keysize" + fileout "key_size" "$NODEIP" "$PORT" "OK" "Server keys $keysize bits EC (OK)" else pr_red "$keysize" + fileout "key_size" "$NODEIP" "$PORT" "NOT OK" "Server keys $keysize bits (NOT ok)" fi elif [[ "$keysize" -le 1024 ]]; then pr_brown "$keysize" + fileout "key_size" "$NODEIP" "$PORT" "NOT OK" "Server keys $keysize bits (NOT ok)" elif [[ "$keysize" -le 2048 ]]; then out "$keysize" + fileout "key_size" "$NODEIP" "$PORT" "INFO" "Server keys $keysize bits" elif [[ "$keysize" -le 4096 ]]; then pr_litegreen "$keysize" + fileout "key_size" "$NODEIP" "$PORT" "OK" "Server keys $keysize bits (OK)" else out "weird keysize: $keysize" + fileout "key_size" "$NODEIP" "$PORT" "WARN" "Server keys $keysize bits (Odd)" fi fi outln " bit" pr_bold " Signature Algorithm " case $sig_algo in - sha1WithRSAEncryption) pr_brownln "SHA1 with RSA" ;; - sha256WithRSAEncryption) pr_litegreenln "SHA256 with RSA" ;; - sha384WithRSAEncryption) pr_litegreenln "SHA384 with RSA" ;; - sha512WithRSAEncryption) pr_litegreenln "SHA512 with RSA" ;; - ecdsa-with-SHA256) pr_litegreenln "ECDSA with SHA256" ;; - md5*) pr_redln "MD5" ;; - *) outln "$sig_algo" ;; + sha1WithRSAEncryption) + pr_brownln "SHA1 with RSA" + fileout "algorithm" "$NODEIP" "$PORT" "WARN" "Signature Algorithm: SHA1 with RSA (warning)" + ;; + sha256WithRSAEncryption) + pr_litegreenln "SHA256 with RSA" + fileout "algorithm" "$NODEIP" "$PORT" "OK" "Signature Algorithm: SHA256 with RSA (OK)" + ;; + sha384WithRSAEncryption) + pr_litegreenln "SHA384 with RSA" + fileout "algorithm" "$NODEIP" "$PORT" "OK" "Signature Algorithm: SHA384 with RSA (OK)" + ;; + sha512WithRSAEncryption) + pr_litegreenln "SHA512 with RSA" + fileout "algorithm" "$NODEIP" "$PORT" "OK" "Signature Algorithm: SHA512 with RSA (OK)" + ;; + ecdsa-with-SHA256) + pr_litegreenln "ECDSA with SHA256" + fileout "algorithm" "$NODEIP" "$PORT" "OK" "Signature Algorithm: ECDSA with SHA256 (OK)" + ;; + md5*) + pr_redln "MD5" + fileout "algorithm" "$NODEIP" "$PORT" "NOT OK" "Signature Algorithm: MD5 (NOT ok)" + ;; + *) + outln "$sig_algo" + fileout "algorithm" "$NODEIP" "$PORT" "INFO" "Signature Algorithm: $sign_algo" + ;; esac # old, but interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html pr_bold " Fingerprint / Serial " - outln "$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' ) / $($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')" - outln "$spaces$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )" + cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g')" + cert_fingerprint_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')" + cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )" + outln "$cert_fingerprint_sha1 / $cert_fingerprint_serial" + outln "$spaces$cert_fingerprint_sha2" + fileout "fingerprint" "$NODEIP" "$PORT" "INFO" "Fingerprints / Serial: $cert_fingerprint_sha1 / $cert_fingerprint_serial, $cert_fingerprint_sha2" pr_bold " Common Name (CN) " + cnfinding="Common Name (CN) : " if $OPENSSL x509 -in $HOSTCERT -noout -subject 2>>$ERRFILE | grep -wq CN; then cn=$($OPENSSL x509 -in $HOSTCERT -noout -subject 2>>$ERRFILE | sed 's/subject= //' | sed -e 's/^.*CN=//' -e 's/\/emailAdd.*//') pr_dquoted "$cn" + cnfinding="$cn" if echo -n "$cn" | grep -q '^*.' ; then out " (wildcard certificate" + cnfinding+="(wildcard certificate " if [[ "$cn" == "*.$(echo -n "$cn" | sed 's/^\*.//')" ]]; then out " match)" + cnfinding+=" match)" wildcard=true else + cnfinding+=" NO match)" + cnok="INFO" : #FIXME: we need to test also the SANs as they can contain a wild card (google.de .e.g) ==> 2.7dev fi @@ -2562,6 +2946,8 @@ run_server_defaults() { else cn="(no CN field in subject)" out "$cn" + cnfinding="$cn" + cnok="INFO" fi $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO 2>>$ERRFILE $HOSTCERT.nosni @@ -2579,38 +2965,50 @@ run_server_defaults() { if [[ $NODE == "$cn_nosni" ]]; then if [[ $SERVICE == "HTTP" ]] || $CLIENT_AUTH; then outln " (works w/o SNI)" + cnfinding+=" (works w/o SNI)" else outln " (matches certificate directly)" + cnfinding+=" (matches certificate directly)" # for services != HTTP it depends on the protocol, server and client but it is not named "SNI" fi else if [[ $SERVICE != "HTTP" ]]; then outln + cnfinding+="\n" #pr_brownln " (non-SNI clients don't match CN but for non-HTTP services it might be ok)" #FIXME: this is irritating and needs to be redone. Then also the wildcard match needs to be tested against "$cn_nosni" elif [[ -z "$cn_nosni" ]]; then out " (request w/o SNI didn't succeed"; - [[ $sig_algo =~ ecdsa ]] && out ", usual for EC certificates" + cnfinding+=" (request w/o SNI didn't succeed" + if [[ $sig_algo =~ ecdsa ]]; then + out ", usual for EC certificates" + cnfinding+=", usual for EC certificates" + fi outln ")" + cnfinding+=")" elif [[ "$cn_nosni" == "*no CN field*" ]]; then outln ", (request w/o SNI: $cn_nosni)" + cnfinding+=", (request w/o SNI: $cn_nosni)" else out " (CN in response to request w/o SNI: "; pr_dquoted "$cn_nosni"; outln ")" + cnfinding+=" (CN in response to request w/o SNI: \"$cn_nosni\")" fi fi + fileout "cn" "$NODEIP" "$PORT" "$cnok" "$cnfinding" sans=$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A3 "Subject Alternative Name" | grep "DNS:" | \ sed -e 's/DNS://g' -e 's/ //g' -e 's/,/ /g' -e 's/othername://g') # ^^^ CACert - pr_bold " subjectAltName (SAN) " if [[ -n "$sans" ]]; then for san in $sans; do pr_dquoted "$san" out " " done + fileout "san" "$NODEIP" "$PORT" "INFO" "subjectAltName (SAN) : $sans" else out "-- " + fileout "san" "$NODEIP" "$PORT" "INFO" "subjectAltName (SAN) : --" fi outln pr_bold " Issuer " @@ -2623,13 +3021,17 @@ run_server_defaults() { fi if [[ "$issuer_O" == "issuer=" ]] || [[ "$issuer_O" == "issuer= " ]] || [[ "$issuer" == "$CN" ]]; then pr_redln "selfsigned (NOT ok)" + fileout "issuer" "$NODEIP" "$PORT" "NOT OK" "Issuer: selfsigned (NOT ok)" else pr_dquoted "$issuer" out " (" pr_dquoted "$issuer_O" if [[ -n "$issuer_C" ]]; then - out " from " + out " from " pr_dquoted "$issuer_C" + fileout "issuer" "$NODEIP" "$PORT" "INFO" "Issuer: \"$issuer\" ( \"$issuer_O\" from \"$issuer_C\")" + else + fileout "issuer" "$NODEIP" "$PORT" "INFO" "Issuer: \"$issuer\" ( \"$issuer_O\" )" fi outln ")" fi @@ -2644,14 +3046,16 @@ run_server_defaults() { [[ 2.16.578.1.26.1.3.3 == "$policy_oid" ]] || \ [[ 1.3.6.1.4.1.17326.10.14.2.1.2 == "$policy_oid" ]] || \ [[ 1.3.6.1.4.1.17326.10.8.12.1.2 == "$policy_oid" ]] || \ - [[ 1.3.6.1.4.1.13177.10.1.3.10 == "$policy_oid" ]] ; then + [[ 1.3.6.1.4.1.13177.10.1.3.10 == "$policy_oid" ]] ; then out "yes " + fileout "ev" "$NODEIP" "$PORT" "OK" "Extended Validation (EV) (experimental) : yes" else out "no " + fileout "ev" "$NODEIP" "$PORT" "INFO" "Extended Validation (EV) (experimental) : no" fi debugme echo "($(newline_to_spaces "$policy_oid"))" outln -#TODO: use browser OIDs: +#TODO: use browser OIDs: # https://mxr.mozilla.org/mozilla-central/source/security/certverifier/ExtendedValidation.cpp # http://src.chromium.org/chrome/trunk/src/net/cert/ev_root_ca_metadata.cc # https://certs.opera.com/03/ev-oids.xml @@ -2667,11 +3071,13 @@ run_server_defaults() { startdate=$(LC_ALL=C date -j -f "%b %d %T %Y %Z" "$($OPENSSL x509 -in $HOSTCERT -noout -startdate 2>>$ERRFILE | cut -d= -f 2)" +"%F %H:%M") LC_ALL=C days2expire=$(( $(date -j -f "%F %H:%M %z" "$enddate" "+%s") - $(date "+%s") )) # in seconds fi - days2expire=$((days2expire / 3600 / 24 )) + days2expire=$((days2expire / 3600 / 24 )) expire=$($OPENSSL x509 -in $HOSTCERT -checkend 0 2>>$ERRFILE) if ! echo $expire | grep -qw not; then pr_red "expired!" + expfinding="expired!" + expok="NOT OK" else secs2warn=$((24 * 60 * 60 * DAYS2WARN2)) # low threshold first expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) @@ -2680,59 +3086,81 @@ run_server_defaults() { expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) if echo "$expire" | grep -qw not; then pr_litegreen "$days2expire >= $DAYS2WARN1 days" + expfinding+="$days2expire >= $DAYS2WARN1 days" else - pr_brown "expires < $DAYS2WARN1 days ($days2expire)" + pr_brown "expires < $DAYS2WARN1 days ($days2expire)" + expfinding+="expires < $DAYS2WARN1 days ($days2expire)" + expok="WARN" fi else pr_litered "expires < $DAYS2WARN2 days ($days2expire) !" + expfinding+="expires < $DAYS2WARN2 days ($days2expire) !" + expok="NOT OK" fi fi outln " ($startdate --> $enddate)" + fileout "expiration" "$NODEIP" "$PORT" "$expok" "Certificate Expiration : $expfinding ($startdate --> $enddate)" pr_bold " # of certificates provided"; outln " $(get_all_certs)" + fileout "certcount" "$NODEIP" "$PORT" "INFO" "# of certificates provided : $(get_all_certs)" + pr_bold " Chain of trust"; out " (experim.) " - determine_trust + determine_trust #Also handles fileout pr_bold " Certificate Revocation List " crl="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://')" if [[ -z "$crl" ]]; then pr_literedln "--" + fileout "crl" "$NODEIP" "$PORT" "NOT OK" "No CRL provided (NOT ok)" elif grep -q http <<< "$crl"; then if [[ $(count_lines "$crl") -eq 1 ]]; then outln "$crl" + fileout "crl" "$NODEIP" "$PORT" "INFO" "Certificate Revocation List : $crl" else # more than one CRL - out_row_aligned "$crl" "$spaces" + out_row_aligned "$crl" "$spaces" + fileout "crl" "$NODEIP" "$PORT" "INFO" "Certificate Revocation List : $crl" fi else - pr_litemagentaln "no parsable output \"$url\", pls report" + pr_litemagentaln "no parsable output \"$crl\", pls report" + fileout "crl" "$NODEIP" "$PORT" "WARN" "Certificate Revocation List : no parsable output \"$crl\", pls report" fi pr_bold " OCSP URI " ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE) - [[ x"$ocsp_uri" == "x" ]] && pr_literedln "--" || echo "$ocsp_uri" + if [[ -z "$ocsp_uri" ]]; then + pr_literedln "--" + fileout "ocsp_uri" "$NODEIP" "$PORT" "NOT OK" "OCSP URI : -- (NOT ok)" + else + outln "$ocsp_uri" + fileout "ocsp_uri" "$NODEIP" "$PORT" "INFO" "OCSP URI : $ocsp_uri" + fi pr_bold " OCSP stapling " if grep -a "OCSP response" $TMPFILE | grep -q "no response sent" ; then out " not offered" + fileout "ocsp_stapling" "$NODEIP" "$PORT" "INFO" "OCSP stapling : not offered" else if grep -a "OCSP Response Status" $TMPFILE | grep -q successful; then pr_litegreen " offered" + fileout "ocsp_stapling" "$NODEIP" "$PORT" "OK" "OCSP stapling : offered" else if $GOST_STATUS_PROBLEM; then out " (GOST servers make problems here, sorry)" + fileout "ocsp_stapling" "$NODEIP" "$PORT" "OK" "OCSP stapling : (GOST servers make problems here, sorry)" ret=0 else outln " not sure what's going on here, debug:" grep -aA 20 "OCSP response" $TMPFILE + fileout "ocsp_stapling" "$NODEIP" "$PORT" "OK" "OCSP stapling : not sure what's going on here, debug: grep -aA 20 "OCSP response" $TMPFILE" ret=2 fi fi fi outln - # if we call tls_time before tmpfile_handle it throws an error because the function tls_sockets removed $TMPFILE + # if we call tls_time before tmpfile_handle it throws an error because the function tls_sockets removed $TMPFILE # already -- and that was a different one -- means that would get overwritten anyway tmpfile_handle $FUNCNAME.txt @@ -2755,6 +3183,7 @@ run_pfs() { #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:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA" local -i nr_supported_ciphers=0 + local pfs_ciphers outln pr_headlineln " Testing (perfect) forward secrecy, (P)FS -- omitting 3DES, RC4 and Null Encryption here " @@ -2764,6 +3193,7 @@ run_pfs() { if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_PFS" ]]; then outln local_problem "You only have $nr_supported_ciphers PFS ciphers on the client side " + fileout "pfs" "$NODEIP" "$PORT" "WARN" "(Perfect) Forward Secrecy tests: Skipped. You only have $nr_supported_ciphers PFS ciphers on the client site. ($CLIENT_MIN_PFS are required)" return 1 fi @@ -2773,9 +3203,12 @@ run_pfs() { outln if [[ $sclient_success -ne 0 ]] || [[ $(grep -ac "BEGIN CERTIFICATE" $TMPFILE) -eq 0 ]]; then pr_brownln "Not OK: No ciphers supporting Forward Secrecy offered" + fileout "pfs" "$NODEIP" "$PORT" "NOT OK" "(Perfect) Forward Secrecy : Not OK: No ciphers supporting Forward Secrecy offered" else pfs_offered=0 + pfs_ciphers="" pr_litegreen " PFS is offered (OK)" + fileout "pfs" "$NODEIP" "$PORT" "OK" "(Perfect) Forward Secrecy : PFS is offered (OK)" if $WIDE; then outln ", ciphers follow (client/browser support is here specially important) \n" neat_header @@ -2808,6 +3241,7 @@ run_pfs() { outln else out "$pfs_cipher " + pfs_ciphers+="$pfs_cipher " fi done < <($OPENSSL ciphers -V "$pfs_cipher_list" 2>$ERRFILE) # -V doesn't work with openssl < 1.0 # ^^^^^ posix redirect as shopt will either segfault or doesn't work with old bash versions @@ -2815,7 +3249,10 @@ run_pfs() { $WIDE || outln if [[ "$pfs_offered" -eq 1 ]]; then - pr_brown "no PFS ciphers found" + pr_brown "no PFS ciphers found" + fileout "pfs_ciphers" "$NODEIP" "$PORT" "NOT OK" "(Perfect) Forward Secrecy Ciphers: no PFS ciphers found (NOT ok)" + else + fileout "pfs_ciphers" "$NODEIP" "$PORT" "INFO" "(Perfect) Forward Secrecy Ciphers: $pfs_ciphers" fi fi outln @@ -2836,16 +3273,19 @@ run_pfs() { spdy_pre(){ if [[ -n "$STARTTLS" ]]; then [[ -n "$1" ]] && out "$1" - out "(SPDY is a HTTP protocol and thus not tested here)" + out "(SPDY is an HTTP protocol and thus not tested here)" + fileout "spdy_npn" "$NODEIP" "$PORT" "INFO" "SPDY/NPN : (SPY is an HTTP protocol and thus not tested here)" return 1 fi if [[ -n "$PROXY" ]]; then [[ -n "$1" ]] && pr_litemagenta " $1 " pr_litemagenta "not tested as proxies do not support proxying it" + fileout "spdy_npn" "$NODEIP" "$PORT" "INFO" "SPDY/NPN : not tested as proxies do not support proxying it" return 1 fi if ! $HAS_SPDY; then local_problem "$OPENSSL doesn't support SPDY/NPN"; + fileout "spdy_npn" "$NODEIP" "$PORT" "WARN" "SPDY/NPN : not tested $OPENSSL doesn't support SPDY/NPN" return 7 fi return 0 @@ -2855,15 +3295,18 @@ http2_pre(){ if [[ -n "$STARTTLS" ]]; then [[ -n "$1" ]] && out "$1" outln "(HTTP/2 is a HTTP protocol and thus not tested here)" + fileout "https_alpn" "$NODEIP" "$PORT" "INFO" "HTTP2/ALPN : HTTP/2 is and HTTP protocol and thus not tested" return 1 fi if [[ -n "$PROXY" ]]; then [[ -n "$1" ]] && pr_litemagenta " $1 " pr_litemagenta "not tested as proxies do not support proxying it" + fileout "https_alpn" "$NODEIP" "$PORT" "INFO" "HTTP2/ALPN : HTTP/2 was not tested as proxies do not support proxying it" return 1 fi if ! $HAS_ALPN; then local_problem "$OPENSSL doesn't support HTTP2/ALPN"; + fileout "https_alpn" "$NODEIP" "$PORT" "WARN" "HTTP2/ALPN : HTTP/2 was not tested as $OPENSSL does not support it" return 7 fi return 0 @@ -2882,15 +3325,18 @@ run_spdy() { tmpstr=$(grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //') if [[ -z "$tmpstr" ]] || [[ "$tmpstr" == " " ]]; then outln "not offered" + fileout "spdy_npn" "$NODEIP" "$PORT" "INFO" "SPDY/NPN : not offered" ret=1 else # now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded if echo $tmpstr | egrep -aq "h2|spdy|http" ; then - out "$tmpstr" + out "$tmpstr" outln " (advertised)" + fileout "spdy_npn" "$NODEIP" "$PORT" "INFO" "SPDY/NPN : $tmpstr (advertised)" ret=0 else pr_litemagentaln "please check manually, server response was ambigious ..." + fileout "spdy_npn" "$NODEIP" "$PORT" "INFO" "SPDY/NPN : please check manually, server response was ambigious ..." ret=10 fi fi @@ -2904,8 +3350,9 @@ run_spdy() { run_http2() { local tmpstr - local -i ret=0 + local -i ret=0 local had_alpn_proto=false + local alpn_finding="" pr_bold " HTTP2/ALPN " if ! http2_pre ; then @@ -2921,17 +3368,21 @@ run_http2() { if [[ "$tmpstr" == *"$proto" ]]; then if ! $had_alpn_proto; then out "$proto" + alpn_finding+="$proto" had_alpn_proto=true else out ", $proto" + alpn_finding+=", $proto" fi fi done if $had_alpn_proto; then outln " (offered)" + fileout "https_alpn" "$NODEIP" "$PORT" "INFO" "HTTP2/ALPN : offered\nProtocols: $alpn_finding" ret=0 else outln "not offered" + fileout "https_alpn" "$NODEIP" "$PORT" "INFO" "HTTP2/ALPN : not offered" ret=1 fi tmpfile_handle $FUNCNAME.txt @@ -2944,7 +3395,7 @@ starttls_line() { debugme echo -e "\n=== sending \"$1\" ..." echo -e "$1" >&5 - # we don't know how much to read and it's blocking! So we just put a cat into the + # we don't know how much to read and it's blocking! So we just put a cat into the # background and read until $STARTTLS_SLEEP and: cross our fingers cat <&5 >$TMPFILE & wait_kill $! $STARTTLS_SLEEP @@ -2954,7 +3405,7 @@ starttls_line() { if egrep -q "$2" $TMPFILE; then debugme echo "---> reply matched \"$2\"" else - # slow down for exim and friends who need a proper handshake:, see + # slow down for exim and friends who need a proper handshake:, see # https://github.com/drwetter/testssl.sh/issues/218 FAST_STARTTLS=false debugme echo -e "\n=== sending with automated FAST_STARTTLS=false \"$1\" ..." @@ -3040,7 +3491,7 @@ fd_socket() { ;; smtp|smtps) # SMTP, see https://tools.ietf.org/html/rfc4217 $FAST_STARTTLS || starttls_just_read - $FAST_STARTTLS || starttls_line "EHLO testssl.sh" "220|250" && starttls_just_send "EHLO testssl.sh" + $FAST_STARTTLS || starttls_line "EHLO testssl.sh" "220|250" && starttls_just_send "EHLO testssl.sh" starttls_line "STARTTLS" "220" ;; pop3|pop3s) # POP, see https://tools.ietf.org/html/rfc2595 @@ -3068,9 +3519,9 @@ fd_socket() { [[ -z $XMPP_HOST ]] && XMPP_HOST="$NODE" jabber=$(cat < - @@ -3208,7 +3659,7 @@ parse_tls_serverhello() { [[ "$DEBUG" -eq 5 ]] && echo $tls_hello_ascii # one line without any blanks if [[ -z "$tls_hello_ascii" ]]; then - debugme echo "server hello empty, TCP connection closed" + debugme echo "server hello empty, TCP connection closed" return 1 # no server hello received fi @@ -3226,14 +3677,14 @@ parse_tls_serverhello() { if [[ $tls_content_type == "15" ]]; then # TLS ALERT tls_err_level=${tls_hello_ascii:10:2} # 1: warning, 2: fatal - tls_err_descr=${tls_hello_ascii:12:2} # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable, + tls_err_descr=${tls_hello_ascii:12:2} # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable, # 113/0x71: bad_certificate_status_response, #114/0x72: bad_certificate_hash_value if [[ $DEBUG -ge 2 ]]; then echo "tls_protocol (reclyr): 0x$tls_protocol" echo "tls_content_type: 0x$tls_content_type" echo "tls_len_all: $tls_len_all" echo "tls_err_descr: 0x${tls_err_descr} / = $(hex2dec ${tls_err_descr})" - echo "tls_err_level: ${tls_err_level} (warning:1, fatal:2)" + echo "tls_err_level: ${tls_err_level} (warning:1, fatal:2)" fi # now, here comes a strange thing... -- on the first glance # IF an apache 2.2/2.4 server e.g. has a default servername configured but we send SNI @@ -3298,22 +3749,32 @@ sslv2_sockets() { pr_litemagenta "strange v2 reply " outln " (rerun with DEBUG >=2)" [[ $DEBUG -ge 3 ]] && hexdump -C "$SOCK_REPLY_FILE" | head -1 - ret=7 ;; + ret=7 + fileout "sslv2" "$NODEIP" "$PORT" "WARN" "SSLv2: received a strange SSLv2 replay (rerun with DEBUG>=2)" + ;; 1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP! pr_greenln "not offered (OK)" - ret=0 ;; + ret=0 + fileout "sslv2" "$NODEIP" "$PORT" "OK" "SSLv2 not offered (OK)" + ;; 0) # reset pr_greenln "not offered (OK)" - ret=0 ;; + ret=0 + fileout "sslv2" "$NODEIP" "$PORT" "OK" "SSLv2 not offered (OK)" + ;; 3) # everything else lines=$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null | wc -l | sed 's/ //g') [[ "$DEBUG" -ge 2 ]] && out " ($lines lines) " if [[ "$lines" -gt 1 ]]; then ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) if [[ 0 -eq "$ciphers_detected" ]]; then - pr_litered "supported but couldn't detect a cipher"; outln " (may need further attention)" + pr_litered "supported but couldn't detect a cipher"; + outln " (may need further attention)" + fileout "sslv2" "$NODEIP" "$PORT" "NOT OK" "SSLv2 offered (NOT ok), but could not detect a cipher (may need further attention)" else - pr_red "offered (NOT ok)"; outln " -- $ciphers_detected ciphers" + pr_red "offered (NOT ok)"; + outln " -- $ciphers_detected ciphers" + fileout "sslv2" "$NODEIP" "$PORT" "NOT OK" "SSLv2 offered (NOT ok).\nDetected ciphers: $ciphers_detected" fi ret=1 fi ;; @@ -3340,7 +3801,7 @@ socksend_tls_clienthello() { local len_sni_listlen len_sni_ext len_extension_hex local cipher_suites len_ciph_suites len_ciph_suites_word local len_client_hello_word len_all_word - + #len_servername=$(echo ${#NODE}) len_servername=${#NODE} hexdump_format_str="$len_servername/1 \"%02x,\"" @@ -3430,10 +3891,10 @@ socksend_tls_clienthello() { extension_signature_algorithms=" 00, 0d, # Type: signature_algorithms , see RFC 5246 00, 20, # len - 00,1e, 06,01, 06,02, 06,03, 05,01, 05,02, 05,03, + 00,1e, 06,01, 06,02, 06,03, 05,01, 05,02, 05,03, 04,01, 04,02, 04,03, 03,01, 03,02, 03,03, 02,01, 02,02, 02,03" -# Extension: Haertbeat 00 0f +# Extension: Haertbeat 00 0f # len 00 01 # peer allowed to send requests 01 @@ -3461,7 +3922,7 @@ socksend_tls_clienthello() { return 0 } -# arg1: TLS version low byte +# arg1: TLS version low byte # (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) tls_sockets() { local -i ret=0 @@ -3533,20 +3994,21 @@ tls_sockets() { # mainly adapted from https://gist.github.com/takeshixx/10107280 run_heartbleed(){ - [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_headlineln " Testing for heartbleed vulnerability " && outln + [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for heartbleed vulnerability " && outln pr_bold " Heartbleed\c"; out " (CVE-2014-0160) " [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions if ! grep -q heartbeat <<< "$TLS_EXTENSIONS"; then pr_green "not vulnerable (OK)" outln " (no heartbeat extension)" + fileout "heartbleed" "$NODEIP" "$PORT" "OK" "Heartbleed (CVE-2014-0160): not vulnerable (OK) (no heartbeat extension)" return 0 fi # determine TLS versions offered <-- needs to come from another place $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -tlsextdebug >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE RST outln @@ -3752,9 +4224,19 @@ run_ccs_injection(){ if [[ "$byte6" == "0a" ]] || [[ "$lines" -gt 1 ]]; then pr_green "not vulnerable (OK)" + if [[ $retval -eq 3 ]]; then + fileout "ccs" "$NODEIP" "$PORT" "OK" "CCS (CVE-2014-0224): not vulnerable (OK) (timed out)" + else + fileout "ccs" "$NODEIP" "$PORT" "OK" "CCS (CVE-2014-0224): not vulnerable (OK)" + fi ret=0 else pr_red "VULNERABLE (NOT ok)" + if [[ $retval -eq 3 ]]; then + fileout "ccs" "$NODEIP" "$PORT" "NOT OK" "CCS (CVE-2014-0224): VULNERABLE (NOT ok) (timed out)" + else + fileout "ccs" "$NODEIP" "$PORT" "NOT OK" "CCS (CVE-2014-0224): VULNERABLE (NOT ok)" + fi ret=1 fi [[ $retval -eq 3 ]] && out " (timed out)" @@ -3775,7 +4257,7 @@ run_renego() { local insecure_renogo_str="Secure Renegotiation IS NOT" local sec_renego sec_client_renego - [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_headlineln " Testing for Renegotiation vulnerabilities " && outln + [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Renegotiation vulnerabilities " && outln pr_bold " Secure Renegotiation "; out "(CVE-2009-3555) " # and RFC5746, OSVDB 59968-59974 # community.qualys.com/blogs/securitylabs/2009/11/05/ssl-and-tls-authentication-gap-vulnerability-discovered @@ -3785,12 +4267,22 @@ run_renego() { sec_renego=$? # 0= Secure Renegotiation IS NOT supported #FIXME: didn't occur to me yet but why not also to check on "Secure Renegotiation IS supported" case $sec_renego in - 0) pr_redln "VULNERABLE (NOT ok)" ;; - 1) pr_greenln "not vulnerable (OK)" ;; - *) pr_litemagentaln "FIXME (bug): $sec_renego" ;; + 0) + pr_redln "VULNERABLE (NOT ok)" + fileout "secure_renego" "$NODEIP" "$PORT" "NOT OK" "Secure Renegotiation (CVE-2009-3555) : VULNERABLE (NOT ok)" + ;; + 1) + pr_greenln "not vulnerable (OK)" + fileout "secure_renego" "$NODEIP" "$PORT" "OK" "Secure Renegotiation (CVE-2009-3555) : not vulnerable (OK)" + ;; + *) + pr_litemagentaln "FIXME (bug): $sec_renego" + fileout "secure_renego" "$NODEIP" "$PORT" "WARN" "Secure Renegotiation (CVE-2009-3555) : FIXME (bug) $sec_renego" + ;; esac else - pr_litemagentaln "handshake didn't succeed" + pr_litemagentaln "handshake didn't succeed" + fileout "secure_renego" "$NODEIP" "$PORT" "WARN" "Secure Renegotiation (CVE-2009-3555) : handshake didn't succeed" fi pr_bold " Secure Client-Initiated Renegotiation " # RFC 5746 @@ -3799,33 +4291,52 @@ run_renego() { case "$OSSL_VER" in 0.9.8*) # we need this for Mac OSX unfortunately case "$OSSL_VER_APPENDIX" in - [a-l]) local_problem "$OPENSSL cannot test this secure renegotiation vulnerability" - return 3 ;; - [m-z]) ;; # all ok - esac ;; - 1.0.1*|1.0.2*) legacycmd="-legacy_renegotiation" ;; - 0.9.9*|1.0*) ;; # all ok + [a-l]) + local_problem "$OPENSSL cannot test this secure renegotiation vulnerability" + fileout "sec_client_renego" "$NODEIP" "$PORT" "WARN" "Secure Client-Initiated Renegotiation : $OPENSSL cannot test this secure renegotiation vulnerability" + return 3 + ;; + [m-z]) + ;; # all ok + esac + ;; + 1.0.1*|1.0.2*) + legacycmd="-legacy_renegotiation" + ;; + 0.9.9*|1.0*) + ;; # all ok esac - if $CLIENT_AUTH; then + if "$CLIENT_AUTH"; then pr_litemagentaln "client authentication prevents this from being tested" + fileout "sec_client_renego" "$NODEIP" "$PORT" "WARN" "Secure Client-Initiated Renegotiation : client authentication prevents this from being tested" sec_client_renego=1 else # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background # msg enables us to look deeper into it while debugging - echo R | $OPENSSL s_client $OPTIMAL_PROTO $BUGS $legacycmd $STARTTLS -msg -connect $NODEIP:$PORT $SNI $PROXY >$TMPFILE 2>>$ERRFILE & + echo R | $OPENSSL s_client $OPTIMAL_PROTO $BUGS $legacycmd $STARTTLS -msg -connect $NODEIP:$PORT $SNI $PROXY >$TMPFILE 2>>$ERRFILE & wait_kill $! $HEADER_MAXSLEEP if [[ $? -eq 3 ]]; then pr_litegreen "likely not vulnerable (OK)"; outln " (timed out)" # it hung + fileout "sec_client_renego" "$NODEIP" "$PORT" "OK" "Secure Client-Initiated Renegotiation : likely not vulnerable (OK) (timed out)" sec_client_renego=1 else # second try in the foreground as we are sure now it won't hang echo R | $OPENSSL s_client $legacycmd $STARTTLS $BUGS -msg -connect $NODEIP:$PORT $SNI $PROXY >$TMPFILE 2>>$ERRFILE sec_client_renego=$? # 0=client is renegotiating & doesn't return an error --> vuln! - case $sec_client_renego in - 0) pr_litered "VULNERABLE (NOT ok)"; outln ", DoS threat" ;; - 1) pr_litegreenln "not vulnerable (OK)" ;; - *) "FIXME (bug): $sec_client_renego" ;; + case "$sec_client_renego" in + 0) + pr_litered "VULNERABLE (NOT ok)"; outln ", DoS threat" + fileout "sec_client_renego" "$NODEIP" "$PORT" "NOT OK" "Secure Client-Initiated Renegotiation : VULNERABLE (NOT ok), DoS threat" + ;; + 1) + pr_litegreenln "not vulnerable (OK)" + fileout "sec_client_renego" "$NODEIP" "$PORT" "OK" "Secure Client-Initiated Renegotiation : not vulnerable (OK)" + ;; + *) + pr_literedln "FIXME (bug): $sec_client_renego" + fileout "sec_client_renego" "$NODEIP" "$PORT" "WARN" "Secure Client-Initiated Renegotiation : FIXME (bug) $sec_client_renego - Please report" + ;; esac fi fi @@ -3846,29 +4357,35 @@ run_crime() { # means anyway "game over", w/wo CRIME # www.h-online.com/security/news/item/Vulnerability-in-SSL-encryption-is-barely-exploitable-1708604.html - [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_headlineln " Testing for CRIME vulnerability " && outln + [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for CRIME vulnerability " && outln pr_bold " CRIME, TLS " ; out "(CVE-2012-4929) " # first we need to test whether OpenSSL binary has zlib support $OPENSSL zlib -e -a -in /dev/stdin &>/dev/stdout $TMPFILE if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_litegreen "not vulnerable (OK)" - if [[ $SERVICE != "HTTP" ]] && ! $CLIENT_AUTH; then + if [[ $SERVICE != "HTTP" ]] && ! $CLIENT_AUTH; then out " (not using HTTP anyway)" + fileout "crime" "$NODEIP" "$PORT" "OK" "CRIME, TLS (CVE-2012-4929) : Not vulnerable (OK) (not using HTTP anyway)" + else + fileout "crime" "$NODEIP" "$PORT" "OK" "CRIME, TLS (CVE-2012-4929) : Not vulnerable (OK)" fi ret=0 else if [[ $SERVICE == "HTTP" ]]; then pr_litered "VULNERABLE (NOT ok)" + fileout "crime" "$NODEIP" "$PORT" "NOT OK" "CRIME, TLS (CVE-2012-4929) : VULNERABLE (NOT ok)" else pr_brown "VULNERABLE (NOT ok), but not using HTTP: probably no exploit known" + fileout "crime" "$NODEIP" "$PORT" "NOT OK" "CRIME, TLS (CVE-2012-4929) : VULNERABLE (NOT ok), but not using HTTP: probably no exploit known" fi ret=1 fi @@ -3924,6 +4441,7 @@ run_breach() { local url local spaces=" " local disclaimer="" + local when_makesense="Can be ignored for static pages or if no secrets in the page" [[ $SERVICE != "HTTP" ]] && return 7 @@ -3948,18 +4466,25 @@ run_breach() { debugme grep '^Content-Encoding' $TMPFILE if [[ ! -s $TMPFILE ]]; then pr_litemagenta "failed (HTTP header request stalled" - [[ $was_killed -ne 0 ]] && pr_litemagenta " and was terminated" + if [[ $was_killed -ne 0 ]]; then + pr_litemagenta " and was terminated" + fileout "breach" "$NODEIP" "$PORT" "WARN" "BREACH (CVE-2013-3587) : Test failed (HTTP request stalled and was terminated)" + else + fileout "breach" "$NODEIP" "$PORT" "WARN" "BREACH (CVE-2013-3587) : Test failed (HTTP request stalled)" + fi pr_litemagenta ") " ret=3 elif [[ -z $result ]]; then pr_green "no HTTP compression (OK) " outln "$disclaimer" + fileout "breach" "$NODEIP" "$PORT" "OK" "BREACH (CVE-2013-3587) : no HTTP compression (OK) $disclaimer" ret=0 else pr_litered "potentially NOT ok, uses $result HTTP compression." outln "$disclaimer" - outln "$spaces Can be ignored for static pages or if no secrets in the page" - ret=1 + outln "$spaces$when_makesense" + fileout "breach" "$NODEIP" "$PORT" "NOT OK" "BREACH (CVE-2013-3587) : potentially VULNERABLE, uses $result HTTP compression. $disclaimer ($when_makesense)" + ret=1 fi # Any URL can be vulnerable. I am testing now only the given URL! @@ -3974,7 +4499,7 @@ run_ssl_poodle() { local cbc_ciphers="SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:IDEA-CBC-SHA:IDEA-CBC-MD5:RC2-CBC-MD5:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP1024-RC2-CBC-MD5:DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5" local cbc_ciphers_krb="KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5" - [ $VULN_COUNT -le $VULN_THRESHLD ] && outln && pr_headlineln " Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption) " && outln + [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption) " && outln pr_bold " POODLE, SSL"; out " (CVE-2014-3566) " #nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $cbc_ciphers:cbc_ciphers_krb)) cbc_ciphers=$($OPENSSL ciphers -v 'ALL:eNULL' 2>$ERRFILE | awk '/CBC/ { print $1 }' | tr '\n' ':') @@ -3983,11 +4508,13 @@ run_ssl_poodle() { $OPENSSL s_client -ssl3 $STARTTLS $BUGS -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE $ERRFILE @@ -4187,10 +4730,11 @@ run_beast(){ for proto in ssl3 tls1; do $OPENSSL s_client -"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>>$ERRFILE $TMPFILE 2>>$ERRFILE 1.0 pr_yellow "VULNERABLE" @@ -4266,14 +4812,16 @@ run_beast(){ pr_yellow "VULNERABLE" outln " -- but also supports higher protocols (possible mitigation):$higher_proto_supported" fi + fileout "beast" "$NODEIP" "$PORT" "NOT OK" "BEAST (CVE-2011-3389) : VULNERABLE -- but also supports higher protocols (possible mitigation):$higher_proto_supported" else - if $WIDE; then + if "$WIDE"; then outln else out "${spaces}" fi pr_brown "VULNERABLE (NOT ok)" outln " -- and no higher protocols as mitigation supported" + fileout "beast" "$NODEIP" "$PORT" "NOT OK" "BEAST (CVE-2011-3389) : VULNERABLE -- and no higher protocols as mitigation supported" fi fi $first && pr_litegreenln "no CBC ciphers found for any protocol (OK)" @@ -4286,6 +4834,7 @@ run_lucky13() { #FIXME: to do . CVE-2013-0169 # in a nutshell: don't offer CBC suites (again). MAC as a fix for padding oracles is not enough. Best: TLS v1.2+ AES GCM echo "FIXME" + fileout "lucky13" "$NODEIP" "$PORT" "WARN" "LUCKY13 (CVE-2013-0169) : No tested. Not implemented. #FIXME" return -1 } @@ -4298,6 +4847,7 @@ run_rc4() { local -i sclient_success local hexcode dash rc4_cipher sslvers kx auth enc mac export local rc4_ciphers_list="ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:DHE-DSS-RC4-SHA:AECDH-RC4-SHA:ADH-RC4-MD5:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:RC4-MD5:RSA-PSK-RC4-SHA:PSK-RC4-SHA:KRB5-RC4-SHA:KRB5-RC4-MD5:RC4-64-MD5:EXP1024-DHE-DSS-RC4-SHA:EXP1024-RC4-SHA:EXP-ADH-RC4-MD5:EXP-RC4-MD5:EXP-RC4-MD5:EXP-KRB5-RC4-SHA:EXP-KRB5-RC4-MD5" + local rc4_detected="" if [[ $VULN_COUNT -le $VULN_THRESHLD ]] || $WIDE; then outln @@ -4309,9 +4859,11 @@ run_rc4() { $OPENSSL s_client -cipher $rc4_ciphers_list $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE sclient_connect_successful $? $TMPFILE @@ -4319,7 +4871,7 @@ run_rc4() { if [[ $sclient_success -ne 0 ]] && [[ "$SHOW_EACH_C" -eq 0 ]]; then continue # no successful connect AND not verbose displaying each cipher fi - if $WIDE; then + if "$WIDE"; then normalize_ciphercode $hexcode neat_list $HEXC $rc4_cipher $kx $enc if [[ "$SHOW_EACH_C" -ne 0 ]]; then @@ -4336,10 +4888,13 @@ run_rc4() { else pr_litered "$rc4_cipher " fi + rc4_detected+="$c4_cipher " done < <($OPENSSL ciphers -V $rc4_ciphers_list:@STRENGTH) outln + fileout "rc4" "$NODEIP" "$PORT" "NOT OK" "RC4 (CVE-2013-2566, CVE-2015-2808) : VULNERABLE (NOT ok) Detected ciphers: $rc4_detected" else pr_litegreenln "no RC4 ciphers detected (OK)" + fileout "rc4" "$NODEIP" "$PORT" "OK" "RC4 (CVE-2013-2566, CVE-2015-2808) : not vulnerable (OK)" rc4_offered=0 fi outln @@ -4367,6 +4922,7 @@ run_tls_truncation() { old_fart() { outln "Get precompiled bins or compile https://github.com/PeterMosmans/openssl ." + fileout "old_fart" "$NODEIP" "$PORT" "WARN" "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed.\nGet precompiled bins or compile https://github.com/PeterMosmans/openssl ." fatal "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed." -2 } @@ -4376,11 +4932,11 @@ get_install_dir() { #INSTALL_DIR=$(cd "$(dirname "$0")" && pwd)/$(basename "$0") INSTALL_DIR=$(dirname ${BASH_SOURCE[0]}) - [[ -r "$RUN_DIR/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/etc/mapping-rfc.txt" + [[ -r "$RUN_DIR/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/etc/mapping-rfc.txt" [[ -r "$INSTALL_DIR/etc/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/etc/mapping-rfc.txt" if [[ ! -r "$MAPPING_FILE_RFC" ]]; then # those will disapper: - [[ -r "$RUN_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/mapping-rfc.txt" + [[ -r "$RUN_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$RUN_DIR/mapping-rfc.txt" [[ -r "$INSTALL_DIR/mapping-rfc.txt" ]] && MAPPING_FILE_RFC="$INSTALL_DIR/mapping-rfc.txt" fi @@ -4430,7 +4986,7 @@ test_openssl_suffix() { fi return 1 } - + find_openssl_binary() { # 0. check environment variable whether it's executable @@ -4564,7 +5120,7 @@ $PROG_NAME URI ("$PROG_NAME URI" does everything except -E) -t, --starttls does a default run against a STARTTLS enabled --xmpphost for STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed --mx tests MX records from high to low priority (STARTTLS, port 25) - --ip a) tests the supplied v4 or v6 address instead of resolving host(s) in URI + --ip a) tests the supplied v4 or v6 address instead of resolving host(s) in URI b) arg "one" means: just test the first DNS returns (useful for multiple IPs) --file mass testing option: Reads command lines from , one line per instance. Comments via # allowed, EOF signals end of . Implicitly turns on "--warnings batch" @@ -4583,18 +5139,27 @@ tuning options (can also be preset via environment variables): --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 + +output options: + --warnings "batch" doesn't wait for keypress, "off" or "false" skips connection warning --quiet don't output the banner. By doing this you acknowledge usage terms normally appearing in the banner - --log, --logging logs stdout to in current working directory - --logfile logs stdout to if file is a dir or to specified file --wide wide output for tests like RC4, BEAST. PFS also with hexcode, kx, strength, RFC name --show-each for wide outputs: display all ciphers tested -- not only succeeded ones --mapping don't display the RFC Cipher Suite Name - --warnings "batch" doesn't wait for keypress, "off" or "false" skips connection warning --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 +file output options: + --log, --logging logs stdout to in current working directory + --logfile logs stdout to if file is a dir or to specified file + --json additional output of findings to a JSON file (defaults to testssl.json unless set) (experimental) + --jsonfile additional output to JSON and output JSON to the specified file (experimental) + --csv additional output of findings to a CSV file (defaults to testssl.csv unless set) (experimental) + --csvfile set output to CSV and output CSV to the specified file (experimental) + All options requiring a value can also be called with '=' e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl . + is always the last parameter. Need HTML output? Just pipe through "aha" (ANSI HTML Adapter: github.com/theZiz/aha) like @@ -4605,12 +5170,11 @@ EOF exit $1 } - maketempf() { TEMPDIR=$(mktemp -d /tmp/ssltester.XXXXXX) || exit -6 TMPFILE=$TEMPDIR/tempfile.txt || exit -6 if [[ "$DEBUG" -eq 0 ]]; then - ERRFILE="/dev/null" + ERRFILE="/dev/null" else ERRFILE=$TEMPDIR/errorfile.txt || exit -6 fi @@ -4708,8 +5272,8 @@ mybanner() { $PROG_NAME $VERSION from $SWURL ($idtag) - This program is free software. Distribution and - modification under GPLv2 permitted. + This program is free software. Distribution and + modification under GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! Please file bugs @ https://testssl.sh/bugs/ @@ -4726,9 +5290,9 @@ EOF cwd=$(/bin/pwd) || \ cwd=$RUN_DIR if [[ "$openssl_location" =~ $(/bin/pwd)/bin ]]; then - OPENSSL_LOCATION="\$PWD/bin/$(basename "$openssl_location")" + OPENSSL_LOCATION="\$PWD/bin/$(basename "$openssl_location")" elif [[ "$openssl_location" =~ $cwd ]] && [[ "$cwd" != '.' ]]; then - OPENSSL_LOCATION="${openssl_location%%$cwd}" + OPENSSL_LOCATION="${openssl_location%%$cwd}" else OPENSSL_LOCATION="$openssl_location" fi @@ -4739,13 +5303,14 @@ EOF cleanup () { if [[ "$DEBUG" -ge 1 ]]; then - outln + outln pr_underline "DEBUG (level $DEBUG): see files in $TEMPDIR" outln else [[ -d "$TEMPDIR" ]] && rm -rf "$TEMPDIR"; fi outln + fileout_footer } fatal() { @@ -4841,7 +5406,7 @@ parse_hn_port() { SNI="-servername $NODE" # now do logging if instructed - if $do_logging; then + if "$do_logging"; then if [[ -z "$LOGFILE" ]]; then LOGFILE=$NODE-$(date +"%Y%m%d-%H%M".log) elif [[ -d "$LOGFILE" ]]; then @@ -4900,7 +5465,7 @@ filter_ip4_address() { get_local_aaaa() { local ip6="" local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts" - + # for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution ip6=$(grep -wh "$NODE" $etchosts 2>/dev/null | grep ':' | grep -v '^#' | egrep "[[:space:]]$NODE" | awk '{ print $1 }') if is_ipv6addr "$ip6"; then @@ -4913,7 +5478,7 @@ get_local_aaaa() { get_local_a() { local ip4="" local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts" - + # for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution ip4=$(grep -wh "$1" $etchosts 2>/dev/null | egrep -v ':|^#' | egrep "[[:space:]]$1" | awk '{ print $1 }') if is_ipv4addr "$ip4"; then @@ -4996,7 +5561,7 @@ get_aaaa_record() { } -# now get all IP addresses +# now get all IP addresses determine_ip_addresses() { local ip4="" local ip6="" @@ -5012,7 +5577,7 @@ determine_ip_addresses() { else LOCAL_A=true # we have the ip4 from local host entry and need to signal this to testssl fi - # same now for ipv6 + # same now for ipv6 ip6=$(get_local_aaaa $NODE) if [[ -z $ip6 ]]; then check_resolver_bins @@ -5120,10 +5685,10 @@ sclient_auth() { [[ $1 -eq 0 ]] && return 0 # no client auth (CLIENT_AUTH=false is preset globally) if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then # connect succeeded if grep -q '^<<< .*CertificateRequest' "$2"; then # CertificateRequest message in -msg - CLIENT_AUTH=true + CLIENT_AUTH=true return 0 fi - if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session + if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then # probably no SSL session if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then # do another sanity check to be sure CLIENT_AUTH=false NO_SSL_SESSIONID=true # NO_SSL_SESSIONI is preset globally to false for all other cases @@ -5136,7 +5701,7 @@ sclient_auth() { } -# this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances +# this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances # (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail! # Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap # The first try in the loop is empty as we prefer not to specify always a protocol if it works w/o. @@ -5193,7 +5758,7 @@ determine_optimal_proto() { # arg1: ftp smtp, pop3, imap, xmpp, telnet, ldap (maybe with trailing s) determine_service() { - local ua + local ua local protocol if ! fd_socket; then # check if we can connect to $NODEIP:$PORT @@ -5205,8 +5770,8 @@ determine_service() { datebanner " Start" outln - if [[ -z "$1" ]]; then - # no STARTTLS. + if [[ -z "$1" ]]; then + # no STARTTLS. determine_optimal_proto "$1" $SNEAKY && \ ua="$UA_SNEAKY" || \ @@ -5216,7 +5781,7 @@ determine_service() { GET_REQ10="GET $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n" HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n" runs_HTTP $OPTIMAL_PROTO - else + else # STARTTLS protocol=${1%s} # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc case "$protocol" in @@ -5304,7 +5869,7 @@ draw_line() { mx_all_ips() { local mxs mx - local mxport + local mxport local -i ret=0 STARTTLS_PROTOCOL="smtp" @@ -5319,7 +5884,7 @@ mx_all_ips() { for mx in $mxs; do draw_line "-" $((TERM_DWITH * 2 / 3)) outln - parse_hn_port "$mx:$mxport" + parse_hn_port "$mx:$mxport" determine_ip_addresses || continue if [[ $(count_words "$(echo -n "$IPADDRs")") -gt 1 ]]; then # we have more than one ipv4 address to check pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs" @@ -5348,7 +5913,7 @@ run_mass_testing_parallel() { if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then fatal "Can't read file \"$FNAME\"" "-1" fi - pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln + pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln outln "(output is in ....\n)" while read cmdline; do cmdline=$(filter_input "$cmdline") @@ -5356,7 +5921,7 @@ run_mass_testing_parallel() { [[ "$cmdline" == "EOF" ]] && break echo "$0 -q $cmdline" draw_line "=" $((TERM_DWITH / 2)); outln; - determine_logfile + determine_logfile $0 -q $cmdline >$LOGFILE & sleep $PARALLEL_SLEEP done < "$FNAME" @@ -5384,7 +5949,7 @@ run_mass_testing() { -# This initializes boolean global do_* variables. They keep track of what to do +# This initializes boolean global do_* variables. They keep track of what to do # -- as the name insinuates initialize_globals() { do_allciphers=false @@ -5401,6 +5966,8 @@ initialize_globals() { do_mx_all_ips=false do_mass_testing=false do_logging=false + do_json=false + do_csv=false do_pfs=false do_protocols=false do_rc4=false @@ -5475,7 +6042,7 @@ debug_globals() { # arg1+2 are just the options parse_opt_equal_sign() { if [[ "$1" == *=* ]]; then - echo "$1" | awk -F'=' '{ print $2 }' + echo "$1" | awk -F'=' '{ print $2 }' return 1 # = means we don't need to shift args! else echo $2 @@ -5491,7 +6058,7 @@ parse_cmd_line() { while [[ $# -gt 0 ]]; do case $1 in -h|--help) - help 0 + help 0 ;; -b|--banner|-v|--version) find_openssl_binary @@ -5505,11 +6072,11 @@ parse_cmd_line() { ;; --mx465) # doesn't work with major ISPs do_mx_all_ips=true - PORT=465 + PORT=465 ;; --mx587) # doesn't work with major ISPs do_mx_all_ips=true - PORT=587 + PORT=587 ;; --ip|--ip=*) CMDLINE_IP=$(parse_opt_equal_sign "$1" "$2") @@ -5521,7 +6088,7 @@ parse_cmd_line() { mybanner openssl_age prettyprint_local $(parse_opt_equal_sign "$1" "$2") - exit $? + exit $? ;; -x|-x=*|--single[-_]cipher|--single[-_]cipher=*) do_test_just_one=true @@ -5588,15 +6155,15 @@ parse_cmd_line() { do_logjam=true do_beast=true do_rc4=true - VULN_COUNT=10 + VULN_COUNT=10 ;; -B|--heartbleed) do_heartbleed=true - let "VULN_COUNT++" + let "VULN_COUNT++" ;; -I|--ccs|--ccs[-_]injection) do_ccs_injection=true - let "VULN_COUNT++" + let "VULN_COUNT++" ;; -R|--renegotiation) do_renego=true @@ -5644,16 +6211,16 @@ parse_cmd_line() { # DEBUG=3 ./testssl.sh --devel 01 yandex.ru --> TLS 1.0 # DEBUG=3 ./testssl.sh --devel 00 # DEBUG=3 ./testssl.sh --devel 22 - TLS_LOW_BYTE="$2"; + TLS_LOW_BYTE="$2"; if [[ $# -eq 4 ]]; then # protocol AND ciphers specified HEX_CIPHER="$3" shift fi shift do_tls_sockets=true - outln "\nTLS_LOW_BYTE/HEX_CIPHER: ${TLS_LOW_BYTE}/${HEX_CIPHER}" + outln "\nTLS_LOW_BYTE/HEX_CIPHER: ${TLS_LOW_BYTE}/${HEX_CIPHER}" ;; - --wide) + --wide) WIDE=true ;; --assuming[_-]http|--assume[-_]http) @@ -5674,27 +6241,27 @@ parse_cmd_line() { do_mass_testing=true ;; --warnings|--warnings=*) - WARNINGS=$(parse_opt_equal_sign "$1" "$2") + WARNINGS=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift case "$WARNINGS" in batch|off|false) ;; - *) pr_magentaln "\nwarnings can be either \"batch\", \"off\" or \"false\"" + *) pr_magentaln "\nwarnings can be either \"batch\", \"off\" or \"false\"" help 1 esac ;; --show[-_]each) SHOW_EACH_C=1 #FIXME: sense is vice versa - ;; + ;; --bugs) BUGS="-bugs" - ;; + ;; --debug|--debug=*) DEBUG=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift case $DEBUG in [0-6]) ;; *) pr_magentaln "\nunrecognized debug value \"$1\", must be between 0..6" 1>&2 - help 1 + help 1 esac ;; --color|--color=*) @@ -5711,14 +6278,32 @@ parse_cmd_line() { COLORBLIND=true ;; --log|--logging) - do_logging=true + do_logging=true ;; # DEFINITION of LOGFILE if no arg specified via ENV or automagically in parse_hn_ports() # following does the same but we can specify a log location additionally --logfile=*) LOGFILE=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift - do_logging=true - ;; + do_logging=true + ;; + --json) + do_json=true + ;; # DEFINITION of JSONFILE is not arg specified via ENV or automagically in parse_hn_ports() + # following does the same but we can specify a log location additionally + --jsonfile=*) + JSONFILE=$(parse_opt_equal_sign "$1" "$2") + [[ $? -eq 0 ]] && shift + do_json=true + ;; + --csv) + do_csv=true + ;; # DEFINITION of CSVFILE is not arg specified via ENV or automagically in parse_hn_ports() + # following does the same but we can specify a log location additionally + --csvfile=*) + CSVFILE=$(parse_opt_equal_sign "$1" "$2") + [[ $? -eq 0 ]] && shift + do_csv=true + ;; --openssl|--openssl=*) OPENSSL=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift @@ -5747,12 +6332,12 @@ parse_cmd_line() { SSL_NATIVE=true ;; (--) shift - break + break ;; - (-*) pr_magentaln "\n$0: unrecognized option \"$1\"" 1>&2; - help 1 + (-*) pr_magentaln "0: unrecognized option \"$1\"" 1>&2; + help 1 ;; - (*) break + (*) break ;; esac shift @@ -5793,6 +6378,7 @@ reset_hostdepended_vars() { OPTIMAL_PROTO="" } + lets_roll() { local ret @@ -5853,7 +6439,7 @@ lets_roll() { $do_client_simulation && { run_client_simulation; ret=$(($? + ret)); } outln - datebanner " Done" + datebanner " Done" return $ret } @@ -5872,8 +6458,9 @@ maketempf mybanner check_proxy openssl_age +fileout_header -# TODO: it's ugly to have those two vars here --> main() +# TODO: it is ugly to have those two vars here --> main() ret=0 ip="" @@ -5885,14 +6472,14 @@ if $do_mx_all_ips; then [[ $? -eq 1 ]] && set_scanning_defaults mx_all_ips "${URI}" $PORT ret=$? -else +else parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now if ! determine_ip_addresses && [[ -z "$CMDLINE_IP" ]]; then fatal "No IP address could be determined" fi - if [[ -n "$CMDLINE_IP" ]]; then + if [[ -n "$CMDLINE_IP" ]]; then [[ "$CMDLINE_IP" == "one" ]] && \ - CMDLINE_IP=$(echo -n "$IPADDRs" | awk '{ print $1 }') + CMDLINE_IP=$(echo -n "$IPADDRs" | awk '{ print $1 }') NODEIP="$CMDLINE_IP" # specific ip address for NODE was supplied lets_roll "${STARTTLS_PROTOCOL}" ret=$? @@ -5920,4 +6507,4 @@ fi exit $? -# $Id: testssl.sh,v 1.438 2016/01/15 16:30:46 dirkw Exp $ +# $Id: testssl.sh,v 1.443 2016/01/23 18:18:32 dirkw Exp $