diff --git a/testssl.sh b/testssl.sh index abb8156..cff44de 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20,11 +20,11 @@ # 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 and if # 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 +# OpenSSL, which is being used and maybe distributed via one of this projects' +# web sites, 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, @@ -42,7 +42,7 @@ # 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 +# Now it has grown up, it has bash socket support for some features, which is 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 @@ -204,7 +204,6 @@ HAS_SSL2=false HAS_SSL3=false HAS_ALPN=false HAS_SPDY=false -HAS_SSL2=false ADD_RFC_STR="rfc" # display RFC ciphernames PORT=443 # unless otherwise auto-determined, see below NODE="" @@ -225,7 +224,7 @@ URI="" CERT_FINGERPRINT_SHA2="" SHOW_CENSYS_LINK=${SHOW_CENSYS_LINK:-true} STARTTLS_PROTOCOL="" -OPTIMAL_PROTO="" # we need this for IIS6 (sigh) and OpenSSL 1.02, otherwise some handshakes +OPTIMAL_PROTO="" # we need this for IIS6 (sigh) and OpenSSL 1.0.2, otherwise some handshakes # will fail, see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 STARTTLS_OPTIMAL_PROTO="" # same for STARTTLS, see https://github.com/drwetter/testssl.sh/issues/188 TLS_TIME="" @@ -489,11 +488,6 @@ hex2dec() { echo $((16#$1)) } -dec2hex() { - #/usr/bin/printf -- "%x" "$1" - echo $((0x$1)) -} - # trim spaces for BSD and old sed count_lines() { wc -l <<<"$1" | sed 's/ //g' @@ -966,7 +960,7 @@ run_hpkp() { out "# of keys: " if [[ $hpkp_nr_keys -eq 1 ]]; then pr_svrty_high "1 (NOT ok), " - fileout "hpkp_keys" "NOT ok" "Only one key pinned in HPKP header, this means the site may become unavaiable if the key is revoked" + fileout "hpkp_keys" "NOT ok" "Only one key pinned in HPKP header, this means the site may become unavailable if the key is revoked" else out "$hpkp_nr_keys, " fileout "hpkp_keys" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked" @@ -1077,9 +1071,9 @@ run_server_banner() { emphasize_stuff_in_headers "$serverbanner" fileout "serverbanner" "INFO" "Server banner identified: $serverbanner" if [[ "$serverbanner" = *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]]; then - pr_warningln " It's recommended to run another test w/ OpenSSL 1.01 !" + pr_warningln " It's recommended to run another test w/ OpenSSL 1.0.1 !" # see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892 - fileout "IIS6_openssl_mismatch" "WARN" "It is recommended to rerun this test w/ OpenSSL 1.01\nSee https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892" + fileout "IIS6_openssl_mismatch" "WARN" "It is recommended to rerun this test w/ OpenSSL 1.0.1. See https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892" fi fi # mozilla.github.io/server-side-tls/ssl-config-generator/ @@ -1393,8 +1387,8 @@ std_cipherlists() { ;; 3) # not totally bad if [[ $sclient_success -eq 0 ]]; then - pr_svrty_mediumln "offered (NOT ok)" - fileout "std_$4" "NOT ok" "$2 offered (NOT ok) - not too bad" + pr_svrty_mediumln "offered" + fileout "std_$4" "NOT ok" "$2 offered - not too bad" else outln "not offered (OK)" fileout "std_$4" "OK" "$2 not offered (OK)" @@ -1492,7 +1486,7 @@ neat_list(){ #printf -- "%q" "$kx" | xxd | head -1 # length correction for color escape codes (printf counts the escape color codes!!) - if printf -- "%q" "$kx" | egrep -aq '.;3.m|E\[1m' ; then # here's a color code which screws up the formatting with prinf below + if printf -- "%q" "$kx" | egrep -aq '.;3.m|E\[1m' ; then # here's a color code which screws up the formatting with printf below while [[ ${#kx} -lt 20 ]]; do kx="$kx " done @@ -1567,7 +1561,7 @@ test_just_one(){ } -# test for all ciphers locally configured (w/o distinguishing whether they are good or bad +# test for all ciphers locally configured (w/o distinguishing whether they are good or bad) run_allciphers() { local tmpfile local -i nr_ciphers=0 @@ -2063,7 +2057,7 @@ run_client_simulation() { ciphers+=("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DH-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DH-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DH-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5") tlsvers+=("-tls1_2 -tls1_1 -tls1") sni+=("$SNI") - #warning+=("Tests are based on OpenSSL 1.01, therefore ciphers 0xe and 0xb are missing") + #warning+=("Tests are based on OpenSSL 1.0.1, therefore ciphers 0xe and 0xb are missing") warning+=("") names+=("Safari 5.1.9/ OSX 10.6.8 ") @@ -2157,7 +2151,7 @@ run_client_simulation() { #FIXME: awk proto=$(grep -aw "Protocol" $TMPFILE | sed -e 's/^.*Protocol.*://' -e 's/ //g') if [[ "$proto" == TLSv1.2 ]]; then - # OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0 Need to figure out which one it is... + # OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0. Need to figure out which one it is... for tls in ${tlsvers[i]}; do $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]} >$ERRFILE >$TMPFILE @@ -2721,11 +2715,11 @@ cipher_pref_check() { for p in ssl2 ssl3 tls1 tls1_1 tls1_2; do order="" if [[ $p == ssl2 ]] && ! "$HAS_SSL2"; then - out "\n SSLv2: "; local_problem "/usr/bin/openssl doesn't support \"s_client -ssl2\""; + out "\n SSLv2: "; local_problem "$OPENSSL doesn't support \"s_client -ssl2\""; continue fi if [[ $p == ssl3 ]] && ! "$HAS_SSL3"; then - out "\n SSLv3: "; local_problem "/usr/bin/openssl doesn't support \"s_client -ssl3\""; + out "\n SSLv3: "; local_problem "$OPENSSL doesn't support \"s_client -ssl3\""; continue fi $OPENSSL s_client $STARTTLS -"$p" $BUGS -connect $NODEIP:$PORT $PROXY $SNI $ERRFILE >$TMPFILE @@ -2924,7 +2918,7 @@ tls_time() { local now difftime local spaces=" " - tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0 (mostfrequently used protocol) + tls_sockets "01" "$TLS_CIPHER" # try first TLS 1.0 (most frequently used protocol) [[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER" # TLS 1.2 [[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER" # TLS 1.1 [[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER" # SSL 3 @@ -3049,7 +3043,7 @@ certificate_info() { local cert_keysize=$4 local ocsp_response=$5 local ocsp_response_status=$6 - local cert_sig_algo cert_key_algo + local cert_sig_algo cert_sig_hash_algo cert_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 @@ -3082,6 +3076,10 @@ certificate_info() { pr_svrty_mediumln "SHA1 with RSA" fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: SHA1 with RSA (warning)" ;; + sha224WithRSAEncryption) + outln "SHA224 with RSA" + fileout "${json_prefix}algorithm" "INFO" "Signature Algorithm: SHA224 with RSA" + ;; sha256WithRSAEncryption) pr_done_goodln "SHA256 with RSA" fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: SHA256 with RSA (OK)" @@ -3094,10 +3092,75 @@ certificate_info() { pr_done_goodln "SHA512 with RSA" fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: SHA512 with RSA (OK)" ;; + ecdsa-with-SHA1) + pr_svrty_mediumln "ECDSA with SHA1" + fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: ECDSA with SHA1 (warning)" + ;; + ecdsa-with-SHA224) + outln "ECDSA with SHA224" + fileout "${json_prefix}algorithm" "INFO" "Signature Algorithm: ECDSA with SHA224" + ;; ecdsa-with-SHA256) pr_done_goodln "ECDSA with SHA256" fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: ECDSA with SHA256 (OK)" ;; + ecdsa-with-SHA384) + pr_done_goodln "ECDSA with SHA384" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: ECDSA with SHA384 (OK)" + ;; + ecdsa-with-SHA512) + pr_done_goodln "ECDSA with SHA512" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: ECDSA with SHA512 (OK)" + ;; + dsaWithSHA1) + pr_svrty_mediumln "DSA with SHA1" + fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: DSA with SHA1 (warning)" + ;; + dsa_with_SHA224) + outln "DSA with SHA224" + fileout "${json_prefix}algorithm" "INFO" "Signature Algorithm: DSA with SHA224" + ;; + dsa_with_SHA256) + pr_done_goodln "DSA with SHA256" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: DSA with SHA256 (OK)" + ;; + rsassaPss) + cert_sig_hash_algo="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A 1 "Signature Algorithm" | head -2 | tail -1 | sed 's/^.*Hash Algorithm: //')" + case $cert_sig_hash_algo in + sha1) + pr_svrty_mediumln "RSASSA-PSS with SHA1" + fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: RSASSA-PSS with SHA1 (warning)" + ;; + sha224) + outln "RSASSA-PSS with SHA224" + fileout "${json_prefix}algorithm" "INFO" "Signature Algorithm: RSASSA-PSS with SHA224" + ;; + sha256) + pr_done_goodln "RSASSA-PSS with SHA256" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: RSASSA-PSS with SHA256 (OK)" + ;; + sha384) + pr_done_goodln "RSASSA-PSS with SHA384" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: RSASSA-PSS with SHA384 (OK)" + ;; + sha512) + pr_done_goodln "RSASSA-PSS with SHA512" + fileout "${json_prefix}algorithm" "OK" "Signature Algorithm: RSASSA-PSS with SHA512 (OK)" + ;; + *) + out "RSASSA-PSS with $cert_sig_hash_algo" + pr_warningln " (Unknown hash algorithm)" + fileout "${json_prefix}algorithm" "WARN" "Signature Algorithm: RSASSA-PSS with $cert_sig_hash_algo" + esac + ;; + md2*) + pr_svrty_criticalln "MD2" + fileout "${json_prefix}algorithm" "NOT ok" "Signature Algorithm: MD2 (NOT ok)" + ;; + md4*) + pr_svrty_criticalln "MD4" + fileout "${json_prefix}algorithm" "NOT ok" "Signature Algorithm: MD4 (NOT ok)" + ;; md5*) pr_svrty_criticalln "MD5" fileout "${json_prefix}algorithm" "NOT ok" "Signature Algorithm: MD5 (NOT ok)" @@ -3120,7 +3183,7 @@ certificate_info() { # http://infoscience.epfl.ch/record/164526/files/NPDF-22.pdf # see http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf # Table 2 @ chapter 5.6.1 (~ p64) - if [[ $cert_sig_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then + if [[ $cert_key_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then if [[ "$cert_keysize" -le 110 ]]; then # a guess pr_svrty_critical "$cert_keysize" fileout "${json_prefix}key_size" "NOT ok" "Server keys $cert_keysize EC bits (NOT ok)" @@ -3140,8 +3203,8 @@ certificate_info() { out "keysize: $cert_keysize (not expected, FIXME)" fileout "${json_prefix}key_size" "WARN" "Server keys $cert_keysize bits (not expected)" fi - outln " bit" - elif [[ $cert_sig_algo = *RSA* ]]; then + outln " bits" + elif [[ $cert_key_algo = *RSA* ]] || [[ $cert_key_algo = *rsa* ]] || [[ $cert_key_algo = *dsa* ]]; then if [[ "$cert_keysize" -le 512 ]]; then pr_svrty_critical "$cert_keysize" outln " bits" @@ -3296,7 +3359,8 @@ certificate_info() { # http://events.ccc.de/congress/2010/Fahrplan/attachments/1777_is-the-SSLiverse-a-safe-place.pdf, see page 40pp out "$indent"; pr_bold " EV cert"; out " (experimental) " - policy_oid=$($OPENSSL x509 -in $HOSTCERT -text 2>>$ERRFILE | awk '/ .Policy: / { print $2 }') + # only the first one, seldom we have two + policy_oid=$($OPENSSL x509 -in $HOSTCERT -text 2>>$ERRFILE | awk '/ .Policy: / { print $2 }' | awk 'NR < 2') if echo "$issuer" | egrep -q 'Extended Validation|Extended Validated|EV SSL|EV CA' || \ [[ 2.16.840.1.114028.10.1.2 == "$policy_oid" ]] || \ [[ 2.16.840.1.114412.1.3.0.2 == "$policy_oid" ]] || \ @@ -3410,7 +3474,7 @@ certificate_info() { ret=0 else out "(response status unknown)" - fileout "${json_prefix}ocsp_stapling" "OK" "OCSP stapling : not sure what's going on here, debug: grep -aA 20 "OCSP response" <<<"$ocsp_response"" + fileout "${json_prefix}ocsp_stapling" "OK" "OCSP stapling : not sure what's going on here, debug: $ocsp_response" debugme grep -a -A20 -B2 "OCSP response" <<<"$ocsp_response" ret=2 fi @@ -3576,7 +3640,7 @@ run_pfs() { local pfs_ciphers outln - pr_headlineln " Testing (perfect) forward secrecy, (P)FS -- omitting 3DES, RC4 and Null Encryption here " + pr_headlineln " Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption as well as 3DES and RC4 here " if ! "$HAS_DH_BITS" && "$WIDE"; then pr_warningln " (Your $OPENSSL cannot show DH/ECDH bits)" fi @@ -3640,7 +3704,7 @@ run_pfs() { out "$pfs_cipher " fi fi - pfs_ciphers+="$pfs_cipher " + [[ $sclient_success -eq 0 ]] && pfs_ciphers+="$pfs_cipher " debugme rm $tmpfile done < <($OPENSSL ciphers -V "$pfs_cipher_list" 2>$ERRFILE) # -V doesn't work with openssl < 1.0 debugme echo $pfs_offered @@ -3734,8 +3798,8 @@ run_spdy() { fileout "spdy_npn" "INFO" "SPDY/NPN : $tmpstr (advertised)" ret=0 else - pr_cyanln "please check manually, server response was ambigious ..." - fileout "spdy_npn" "INFO" "SPDY/NPN : please check manually, server response was ambigious ..." + pr_cyanln "please check manually, server response was ambiguous ..." + fileout "spdy_npn" "INFO" "SPDY/NPN : please check manually, server response was ambiguous ..." ret=10 fi fi @@ -4036,92 +4100,268 @@ parse_sslv2_serverhello() { # arg1: name of file with socket reply parse_tls_serverhello() { local tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$1") - local tls_content_type tls_protocol tls_len_all -#TODO: all vars here + local tls_handshake_ascii="" tls_alert_ascii="" + local -i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len msg_len + local tls_serverhello_ascii="" + local -i tls_serverhello_ascii_len=0 + local tls_alert_descrip tls_sid_len_hex + local -i tls_sid_len offset + local tls_msg_type tls_content_type tls_protocol tls_protocol2 tls_hello_time + local tls_err_level tls_err_descr tls_cipher_suite tls_compression_method + local -i i TLS_TIME="" DETECTED_TLS_VERSION="" - # server hello, handshake details see http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record + # $tls_hello_ascii may contain trailing whitespace. Remove it: + tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" + [[ "$DEBUG" -eq 5 ]] && echo $tls_hello_ascii # one line without any blanks + + # Client messages, including handshake messages, are carried by the record layer. + # First, extract the handshake and alert messages. + # see http://en.wikipedia.org/wiki/Transport_Layer_Security-SSL#TLS_record # byte 0: content type: 0x14=CCS, 0x15=TLS alert x16=Handshake, 0x17 Aplication, 0x18=HB # byte 1+2: TLS version word, major is 03, minor 00=SSL3, 01=TLS1 02=TLS1.1 03=TLS 1.2 - # byte 3+4: length all - # byte 5: handshake type (2=hello) TLS alert: level (2=fatal), descr (0x28=handshake failure) - # byte 6+7+8: length server hello - # byte 9+10: 03, TLS version word see byte 1+2 - # byte 11-14: TLS timestamp for OpenSSL <1.01f - # byte 15-42: random, 28 bytes - # byte 43: session id length - # byte 44+45+sid-len: cipher suite! - # byte 46+sid-len: compression method: 00: none, 01: deflate - # byte 47+48+sid-len: extension length - - [[ "$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" - return 1 # no server hello received + # byte 3+4: fragment length + # bytes 5...: message fragment + tls_hello_ascii_len=${#tls_hello_ascii} + if [[ $DEBUG -ge 2 ]] && [[ $tls_hello_ascii_len -gt 0 ]]; then + echo "TLS message fragments:" fi - - # now scrape two bytes out of the reply per byte - tls_content_type="${tls_hello_ascii:0:2}" # normally this is x16 (Handshake) here - tls_protocol="${tls_hello_ascii:2:4}" - DETECTED_TLS_VERSION=$tls_protocol - - tls_len_all=${tls_hello_ascii:6:4} - - sid_len_offset=86 - tls_hello="${tls_hello_ascii:10:2}" # normally this is x02 - tls_protocol2="${tls_hello_ascii:18:4}" - tls_hello_time="${tls_hello_ascii:22:8}" - - 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, - # 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)" + for (( i=0; i - # we get a TLS ALERT saying "unrecognized_name" (0x70) and a warning (0x1), see RFC https://tools.ietf.org/html/rfc6066#page-17 - # note that RFC recommended to fail instead: https://tools.ietf.org/html/rfc6066#section-3 - # we need to handle this properly -- otherwise we always return that the protocol or cipher is not available! - if [[ "$tls_err_descr" == 70 ]] && [[ "${tls_err_level}" == "01" ]]; then - sid_len_offset=100 # we are 2x7 bytes off (formerly: 86 instead of 100) - tls_hello="${tls_hello_ascii:24:2}" # here, too (normally this is (02) - tls_protocol2="${tls_hello_ascii:32:4}" # here, too - tls_hello_time="${tls_hello_ascii:36:8}" # and here, too - else + tls_content_type="${tls_hello_ascii:i:2}" + i=$i+2 + tls_protocol="${tls_hello_ascii:i:4}" + i=$i+4 + msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}") + i=$i+4 + + if [[ $DEBUG -ge 2 ]]; then + echo " tls_protocol (reclyr): 0x$tls_protocol" + out " tls_content_type: 0x$tls_content_type" + case $tls_content_type in + 15) outln " (alert)" ;; + 16) outln " (handshake)" ;; + *) outln ;; + esac + echo " msg_len: $((msg_len/2))" + outln + fi + if [[ $tls_content_type != "15" ]] && [[ $tls_content_type != "16" ]]; then + debugme pr_warningln "Content type other than alert or handshake detected." + return 1 + elif [[ "${tls_protocol:0:2}" != "03" ]]; then + debugme pr_warningln "Protocol record_version.major is not 03." return 1 fi + DETECTED_TLS_VERSION=$tls_protocol + + if [[ $msg_len -gt $tls_hello_ascii_len-$i ]]; then + # This could just be a result of the server's response being + # split across two or more packets. Just grab the part that + # is available. + msg_len=$tls_hello_ascii_len-$i + fi + + if [[ $tls_content_type == "16" ]]; then + tls_handshake_ascii="$tls_handshake_ascii${tls_hello_ascii:i:msg_len}" + elif [[ $tls_content_type == "15" ]]; then # TLS ALERT + tls_alert_ascii="$tls_alert_ascii${tls_hello_ascii:i:msg_len}" + fi + done + + # Now check the alert messages. + tls_alert_ascii_len=${#tls_alert_ascii} + if [[ $tls_alert_ascii_len -gt 0 ]]; then + debugme echo "TLS alert messages:" + for (( i=0; i+3 < tls_alert_ascii_len; i=i+4 )); do + tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal + j=$i+2 + tls_err_descr=${tls_alert_ascii:j:2} # 112/0x70: Unrecognized name, 111/0x6F: certificate_unobtainable, + # 113/0x71: bad_certificate_status_response, #114/0x72: bad_certificate_hash_value + + debugme out " tls_err_descr: 0x${tls_err_descr} / = $(hex2dec ${tls_err_descr})" + case $tls_err_descr in + 00) tls_alert_descrip="close notify" ;; + 0A) tls_alert_descrip="unexpected message" ;; + 14) tls_alert_descrip="bad record mac" ;; + 15) tls_alert_descrip="decryption failed" ;; + 16) tls_alert_descrip="record overflow" ;; + 1E) tls_alert_descrip="decompression failure" ;; + 28) tls_alert_descrip="handshake failure" ;; + 29) tls_alert_descrip="no certificate RESERVED" ;; + 2A) tls_alert_descrip="bad certificate" ;; + 2B) tls_alert_descrip="unsupported certificate" ;; + 2C) tls_alert_descrip="certificate revoked" ;; + 2D) tls_alert_descrip="certificate expired" ;; + 2E) tls_alert_descrip="certificate unknown" ;; + 2F) tls_alert_descrip="illegal parameter" ;; + 30) tls_alert_descrip="unknown ca" ;; + 31) tls_alert_descrip="access denied" ;; + 32) tls_alert_descrip="decode error" ;; + 33) tls_alert_descrip="decrypt error" ;; + 3C) tls_alert_descrip="export restriction RESERVED" ;; + 46) tls_alert_descrip="protocol version" ;; + 47) tls_alert_descrip="insufficient security" ;; + 50) tls_alert_descrip="internal error" ;; + 56) tls_alert_descrip="inappropriate fallback" ;; + 5A) tls_alert_descrip="user canceled" ;; + 64) tls_alert_descrip="no renegotiation" ;; + 6E) tls_alert_descrip="unsupported extension" ;; + 6F) tls_alert_descrip="certificate unobtainable" ;; + 70) tls_alert_descrip="unrecognized name" ;; + 71) tls_alert_descrip="bad certificate status response" ;; + 72) tls_alert_descrip="bad certificate hash value" ;; + 73) tls_alert_descrip="unknown psk identity" ;; + 78) tls_alert_descrip="no application protocol" ;; + *) tls_alert_descrip="$(hex2dec "$tls_err_descr")";; + esac + + if [[ $DEBUG -ge 2 ]]; then + outln " ($tls_alert_descrip)" + out " tls_err_level: ${tls_err_level}" + case $tls_err_level in + 01) outln " (warning)" ;; + 02) outln " (fatal)" ;; + *) outln ;; + esac + outln + fi + if [[ "$tls_err_level" != "01" ]] && [[ "$tls_err_level" != "02" ]]; then + debugme pr_warningln "Unexpected AlertLevel (0x$tls_err_level)." + return 1 + elif [[ "$tls_err_level" == "02" ]]; then + # Fatal alert + return 1 + fi + done fi + # Now extract just the server hello handshake message. + tls_handshake_ascii_len=${#tls_handshake_ascii} + if [[ $DEBUG -ge 2 ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then + echo "TLS handshake messages:" + fi + for (( i=0; i=2)" [[ $DEBUG -ge 3 ]] && hexdump -C "$SOCK_REPLY_FILE" | head -1 ret=7 - fileout "sslv2" "WARN" "SSLv2: received a strange SSLv2 replay (rerun with DEBUG>=2)" + fileout "sslv2" "WARN" "SSLv2: received a strange SSLv2 reply (rerun with DEBUG>=2)" ;; 1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP! pr_done_bestln "not offered (OK)" @@ -5115,7 +5355,7 @@ run_drown() { outln " (rerun with DEBUG >=2)" [[ $DEBUG -ge 3 ]] && hexdump -C "$SOCK_REPLY_FILE" | head -1 ret=7 - fileout "DROWN" "MINOR_ERROR" "SSLv2: received a strange SSLv2 replay (rerun with DEBUG>=2)" + fileout "DROWN" "MINOR_ERROR" "SSLv2: received a strange SSLv2 reply (rerun with DEBUG>=2)" ;; 3) # vulnerable lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>/dev/null)") @@ -5371,9 +5611,9 @@ run_rc4() { fi outln else - pr_svrty_high "$rc4_cipher " + [[ $sclient_success -eq 0 ]] && pr_svrty_high "$rc4_cipher " fi - rc4_detected+="$rc4_cipher " + [[ $sclient_success -eq 0 ]] && rc4_detected+="$rc4_cipher " done < <($OPENSSL ciphers -V $rc4_ciphers_list:@STRENGTH) outln "$WIDE" && pr_svrty_high "VULNERABLE (NOT ok)" @@ -5408,11 +5648,11 @@ run_tls_truncation() { old_fart() { outln "Get precompiled bins or compile https://github.com/PeterMosmans/openssl ." - fileout "old_fart" "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 ." + fileout "old_fart" "WARN" "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed. Get 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 } -# try very hard to determine th install path to get ahold of the mapping file +# try very hard to determine the install path to get ahold of the mapping file # it provides "keycode/ RFC style name", see RFCs, cipher(1), www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm get_install_dir() { #INSTALL_DIR=$(cd "$(dirname "$0")" && pwd)/$(basename "$0") @@ -5609,7 +5849,8 @@ special invocations: partly mandatory parameters: URI host|host:port|URL|URL:port (port 443 is assumed unless otherwise specified) pattern an ignore case word pattern of cipher hexcode or any other string in the name, kx or bits - protocol is one of ftp,smtp,pop3,imap,xmpp,telnet,ldap (for the latter two you need e.g. the supplied openssl) + protocol is one of the STARTTLS protocols ftp,smtp,pop3,imap,xmpp,telnet,ldap + (for the latter two you need e.g. the supplied openssl) tuning options (can also be preset via environment variables): --bugs enables the "-bugs" option of s_client, needed e.g. for some buggy F5s @@ -5867,6 +6108,11 @@ parse_hn_port() { # strip trailing urlpath NODE=$(echo "$NODE" | sed -e 's/\/.*$//') + # if there's a trailing ':' probably a starttls/application protocol was specified + if grep -q ':$' <<< $NODE ; then + fatal "\"$1\" is not a valid URI" 1 + fi + # was the address supplied like [AA:BB:CC::]:port ? if echo "$NODE" | grep -q ']' ; then tmp_port=$(printf "$NODE" | sed 's/\[.*\]//' | sed 's/://') @@ -6190,7 +6436,7 @@ sclient_auth() { 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 + NO_SSL_SESSIONID=true # NO_SSL_SESSIONID is preset globally to false for all other cases return 0 fi fi @@ -6408,6 +6654,7 @@ mx_all_ips() { run_mass_testing_parallel() { local cmdline="" + local global_cmdline=${CMDLINE%%--file*} if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then fatal "Can't read file \"$FNAME\"" "-1" @@ -6418,7 +6665,7 @@ run_mass_testing_parallel() { cmdline=$(filter_input "$cmdline") [[ -z "$cmdline" ]] && continue [[ "$cmdline" == "EOF" ]] && break - cmdline="$0 --warnings=batch -q $cmdline" + cmdline="$0 $global_cmdline --warnings=batch -q $cmdline" draw_line "=" $((TERM_DWITH / 2)); outln; determine_logfile outln "$cmdline" @@ -6431,16 +6678,18 @@ run_mass_testing_parallel() { run_mass_testing() { local cmdline="" + local global_cmdline=${CMDLINE%%--file*} if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then fatal "Can't read file \"$FNAME\"" "-1" fi + pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n" while read cmdline; do cmdline=$(filter_input "$cmdline") [[ -z "$cmdline" ]] && continue [[ "$cmdline" == "EOF" ]] && break - cmdline="$0 --warnings=batch -q $cmdline" + cmdline="$0 $global_cmdline --warnings=batch -q $cmdline" draw_line "=" $((TERM_DWITH / 2)); outln; outln "$cmdline" $cmdline @@ -6794,7 +7043,7 @@ parse_cmd_line() { do_logging=true ;; # DEFINITION of LOGFILE if no arg specified: automagically in parse_hn_port() # following does the same but we can specify a log location additionally - --logfile=*) + --logfile|--logfile=*) LOGFILE=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift do_logging=true @@ -6803,7 +7052,7 @@ parse_cmd_line() { do_json=true ;; # DEFINITION of JSONFILE is not arg specified: automagically in parse_hn_port() # following does the same but we can specify a log location additionally - --jsonfile=*) + --jsonfile|--jsonfile=*) JSONFILE=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift do_json=true @@ -6812,7 +7061,7 @@ parse_cmd_line() { do_csv=true ;; # DEFINITION of CSVFILE is not arg specified: automagically in parse_hn_port() # following does the same but we can specify a log location additionally - --csvfile=*) + --csvfile|--csvfile=*) CSVFILE=$(parse_opt_equal_sign "$1" "$2") [[ $? -eq 0 ]] && shift do_csv=true @@ -7028,4 +7277,4 @@ fi exit $? -# $Id: testssl.sh,v 1.478 2016/03/30 21:28:30 dirkw Exp $ +# $Id: testssl.sh,v 1.487 2016/05/23 20:42:39 dirkw Exp $