diff --git a/testssl.sh b/testssl.sh index b4e9650..9fa810b 100755 --- a/testssl.sh +++ b/testssl.sh @@ -250,7 +250,8 @@ TMPFILE="" ERRFILE="" CLIENT_AUTH=false NO_SSL_SESSIONID=false -HOSTCERT="" +HOSTCERT="" # File with host certificate, without intermediate certificate +HOSTCERT_TXT="" # Text output of that HEADERFILE="" HEADERVALUE="" HTTP_STATUS_CODE="" @@ -1635,29 +1636,30 @@ detect_ipv4() { run_http_date() { local now difftime + jsonID="HTTP_clock_skew" + + if [[ $SERVICE != "HTTP" ]] || "$CLIENT_AUTH"; then + return 7 + fi if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 # this is just for the line "Testing HTTP header response" fi pr_bold " HTTP clock skew " - if [[ $SERVICE != "HTTP" ]]; then - out "not tested as we're not targeting HTTP" - else - if [[ -n "$HTTP_TIME" ]]; then - HTTP_TIME=$(parse_date "$HTTP_TIME" "+%s" "%a, %d %b %Y %T %Z" 2>>$ERRFILE) # the trailing \r confuses BSD flavors otherwise + if [[ -n "$HTTP_TIME" ]]; then + HTTP_TIME=$(parse_date "$HTTP_TIME" "+%s" "%a, %d %b %Y %T %Z" 2>>$ERRFILE) # the trailing \r confuses BSD flavors otherwise - difftime=$((HTTP_TIME - NOW_TIME)) - [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" - # 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" "INFO" "HTTP clock skew $difftime sec from localtime" - else - out "Got no HTTP time, maybe try different URL?"; - fileout "HTTP_clock_skew" "INFO" "HTTP clock skew not measured. Got no HTTP time, maybe try different URL?" - fi - debugme tm_out ", epoch: $HTTP_TIME" + difftime=$((HTTP_TIME - NOW_TIME)) + [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" + # process was killed, so we need to add an error: + [[ $HAD_SLEPT -ne 0 ]] && difftime="$difftime (± 1.5)" + out "$difftime sec from localtime"; + fileout "$jsonID" "INFO" "$difftime seconds from localtime" + else + out "Got no HTTP time, maybe try different URL?"; + fileout "$jsonID" "INFO" "Got no HTTP time, maybe try different URL?" fi + debugme tm_out ", epoch: $HTTP_TIME" outln detect_ipv4 } @@ -1754,21 +1756,21 @@ run_hsts() { fileou t "HSTS_time" "MEDIUM" "HSTS timeout too short. $hsts_age_days days (=$hsts_age_sec seconds) < $HSTS_MIN days" fi if includeSubDomains "$TMPFILE"; then - fileout "HSTS_subdomains" "OK" "HSTS includes subdomains" + fileout "HSTS_subdomains" "OK" "includes subdomains" else - fileout "HSTS_subdomains" "INFO" "HSTS only for this domain" + fileout "HSTS_subdomains" "INFO" "only for this domain" fi if preload "$TMPFILE"; then - fileout "HSTS_preload" "OK" "HSTS domain is marked for preloading" + fileout "HSTS_preload" "OK" "domain IS marked for preloading" else - fileout "HSTS_preload" "INFO" "HSTS domain is NOT marked for preloading" + fileout "HSTS_preload" "INFO" "domain is NOT marked for preloading" #FIXME: To be checked against preloading lists, # e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc # https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json fi else out "--" - fileout "HSTS" "HIGH" "No support for HTTP Strict Transport Security" + fileout "HSTS" "HIGH" "not offered" fi outln @@ -1813,7 +1815,7 @@ run_hpkp() { out "\n$spaces Examining first: " first_hpkp_header=$(awk -F':' '/Public-Key-Pins/ { print $1 }' $HEADERFILE | head -1) pr_italic "$first_hpkp_header, " - fileout "HPKP_multiple" "WARN" "Multiple HPKP headers $hpkp_headers. Using first header: $first_hpkp_header" + fileout "HPKP_multiple" "WARN" "Multiple HPKP headers $hpkp_headers. Using 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 @@ -1825,12 +1827,12 @@ run_hpkp() { hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE) if [[ $hpkp_nr_keys -eq 1 ]]; then - pr_svrty_high "1 key (NOT ok), " - fileout "HPKP_SPKIs" "HIGH" "Only one key pinned in HPKP header, this means the site may become unavailable if the key is revoked" + pr_svrty_high "Only one key pinned (NOT ok), means the site may become unavailable in the future, " + fileout "HPKP_SPKIs" "HIGH" "Only one key pinned" else pr_done_good "$hpkp_nr_keys" out " keys, " - fileout "HPKP_SPKIs" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked" + fileout "HPKP_SPKIs" "OK" "$hpkp_nr_keys keys pinned in header" fi # print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk @@ -1846,18 +1848,18 @@ run_hpkp() { else out "$hpkp_age_sec s = " pr_svrty_medium "$hpkp_age_days days (< $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough)" - fileout "HPKP_age" "MEDIUM" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough." + fileout "HPKP_age" "MEDIUM" "age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough." fi if includeSubDomains "$TMPFILE"; then - fileout "HPKP_subdomains" "INFO" "HPKP header is valid for subdomains as well" + fileout "HPKP_subdomains" "INFO" "is valid for subdomains as well" else - fileout "HPKP_subdomains" "INFO" "HPKP header is valid for this domain only" + fileout "HPKP_subdomains" "INFO" "is valid for this domain only" fi if preload "$TMPFILE"; then - fileout "HPKP_preload" "INFO" "HPKP header is marked for browser preloading" + fileout "HPKP_preload" "INFO" "IS marked for browser preloading" else - fileout "HPKP_preload" "INFO" "HPKP header is NOT marked for browser preloading" + fileout "HPKP_preload" "INFO" "NOT marked for browser preloading" fi # Get the SPKIs first @@ -1947,11 +1949,11 @@ run_hpkp() { out "\n$spaces_indented Root CA: " pr_done_good "$hpkp_spki" pr_italic " $ca_cn" - fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned in the HPKP header. (Root CA part of the chain)" + fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root CA part of the chain)" else # not part of chain match_ca="" has_backup_spki=true # Root CA outside the chain --> we save it for unmatched - fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned in the HPKP header. (Root backup SPKI)" + fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root backup SPKI)" backup_spki[i]="$(strip_lf "$hpkp_spki")" # save it for later backup_spki_str[i]="$ca_cn" # also the name=CN of the root CA i=$((i + 1)) @@ -1996,14 +1998,14 @@ run_hpkp() { if [[ ! -f "$ca_hashes" ]] && "$spki_match"; then out "$spaces " prln_warning "Attribution of further hashes couldn't be done as $ca_hashes could not be found" - fileout "HPKP_spkimatch" "WARN" "Attribution of further hashes couldn't be done as $ca_hashes could not be found" + fileout "HPKP_SPKImatch" "WARN" "Attribution of further hashes possible as $ca_hashes could not be found" fi # If all else fails... if ! "$spki_match"; then "$has_backup_spki" && out "$spaces" # we had a few lines with backup SPKIs already prln_svrty_high " No matching key for SPKI found " - fileout "HPKP_spkimatch" "HIGH" "None of the SPKI match your host certificate, intermediate CA or known root CAs. You may have bricked this site" + fileout "HPKP_SPKImatch" "HIGH" "None of the SPKI match your host certificate, intermediate CA or known root CAs. Bricked site?" fi if ! "$has_backup_spki"; then @@ -2360,7 +2362,7 @@ run_more_flags() { fi pr_done_good "$f2t" outln " $(out_row_aligned_max_width "$HEADERVALUE" "$spaces" $TERM_WIDTH)" - fileout "$f2t" "OK" "$f2t: $HEADERVALUE" + fileout "$f2t" "OK" "$HEADERVALUE" fi done @@ -2469,12 +2471,13 @@ listciphers() { # argv[4]: string to be appended for fileout # argv[5]: non-SSLv2 cipher list to test (hexcodes), if using sockets # argv[6]: SSLv2 cipher list to test (hexcodes), if using sockets -std_cipherlists() { +sub_cipherlists() { local -i i len sclient_success=1 local cipherlist sslv2_cipherlist detected_ssl2_ciphers local singlespaces local proto="" local debugname="$(sed -e s'/\!/not/g' -e 's/\:/_/g' <<< "$1")" + local jsonID="cipherlist" [[ "$OPTIMAL_PROTO" == "-ssl2" ]] && proto="$OPTIMAL_PROTO" pr_bold "$2 " # to be indented equal to server preferences @@ -2537,10 +2540,10 @@ std_cipherlists() { # If server failed with a known error, raise it to the user. if [[ $STARTTLS_PROTOCOL == "mysql" ]]; then pr_warning "SERVER_ERROR: test inconclusive due to MySQL Community Edition (yaSSL) bug." - fileout "std_$4" "WARN" "SERVER_ERROR: test inconclusive due to MySQL Community Edition (yaSSL) bug." + fileout "${jsonID}_$4" "WARN" "SERVER_ERROR, test inconclusive due to MySQL Community Edition (yaSSL) bug." else pr_warning "SERVER_ERROR: test inconclusive." - fileout "std_$4" "WARN" "SERVER_ERROR: test inconclusive." + fileout "${jsonID}_$4" "WARN" "SERVER_ERROR, test inconclusive." fi else # Otherwise the error means the server doesn't support that cipher list. @@ -2548,54 +2551,54 @@ std_cipherlists() { 2) if [[ $sclient_success -eq 0 ]]; then # Strong is excellent to offer pr_done_best "offered (OK)" - fileout "std_$4" "OK" "$2 offered" + fileout "${jsonID}_$4" "OK" "offered" else pr_svrty_medium "not offered" - fileout "std_$4" "MEDIUM" "$2 not offered" + fileout "${jsonID}_$4" "MEDIUM" "not offered" fi ;; 1) if [[ $sclient_success -eq 0 ]]; then # High is good to offer pr_done_good "offered (OK)" - fileout "std_$4" "OK" "$2 offered" + fileout "${jsonID}_$4" "OK" "offered" else # FIXME: the rating could be readjusted if we knew the result of STRONG before pr_svrty_medium "not offered" - fileout "std_$4" "MEDIUM" "$2 not offered" + fileout "${jsonID}_$4" "MEDIUM" "not offered" fi ;; 0) if [[ $sclient_success -eq 0 ]]; then # medium is not that bad pr_svrty_medium "offered" - fileout "std_$4" "MEDIUM" "$2 offered - not too bad" + fileout "${jsonID}_$4" "MEDIUM" "offered" else out "not offered (OK)" - fileout "std_$4" "OK" "$2 not offered" + fileout "${jsonID}_$4" "OK" "not offered" fi ;; -1) if [[ $sclient_success -eq 0 ]]; then # bad but there is worse pr_svrty_high "offered (NOT ok)" - fileout "std_$4" "HIGH" "$2 offered - bad" + fileout "${jsonID}_$4" "HIGH" "offered" else # need a check for -eq 1 here pr_done_good "not offered (OK)" - fileout "std_$4" "OK" "$2 not offered" + fileout "${jsonID}_$4" "OK" "not offered" fi ;; -2) if [[ $sclient_success -eq 0 ]]; then # the ugly ones pr_svrty_critical "offered (NOT ok)" - fileout "std_$4" "CRITICAL" "$2 offered - ugly" + fileout "${jsonID}_$4" "CRITICAL" "offered" else pr_done_best "not offered (OK)" - fileout "std_$4" "OK" "$2 not offered" + fileout "${jsonID}_$4" "OK" "not offered" fi ;; *) # we shouldn't reach this pr_warning "?: $3 (please report this)" - fileout "std_$4" "WARN" "return condition $3 unclear" + fileout "${jsonID}_$4" "WARN" "return condition $3 unclear" ;; esac fi @@ -2609,7 +2612,7 @@ std_cipherlists() { else prln_local_problem "No $singlespaces configured in $OPENSSL" fi - fileout "std_$4" "WARN" "Cipher $2 ($1) not supported by local OpenSSL ($OPENSSL)" + fileout "${jsonID}_$4" "WARN" "Cipher $2 ($1) not supported by local OpenSSL ($OPENSSL)" fi } @@ -3970,6 +3973,7 @@ run_client_simulation() { local has_dh_bits using_sockets=true local client_service local options + local jsonID="clientsimulation" # source the external file . "$TESTSSL_INSTALL_DIR/etc/client-simulation.txt" 2>/dev/null @@ -3998,7 +4002,7 @@ run_client_simulation() { else pr_headline " Running client simulations via openssl " prln_warning " -- you shouldn't run this with \"--ssl-native\" as you will get false results" - fileout "client_simulation" "WARN" "You shouldn't run this with \"--ssl-native\" as you will get false results" + fileout "$jsonID" "WARN" "You shouldn't run this with \"--ssl-native\" as you will get false results" fi outln debugme echo @@ -4065,8 +4069,7 @@ run_client_simulation() { fi if [[ $sclient_success -ne 0 ]]; then outln "No connection" - fileout "client_${short[i]}" "INFO" "$(strip_spaces "${names[i]}") client simulation: No connection" -#FIXME: here probably either the id has to be changed or the finding, id seems not verbose enough + fileout "${jsonID}-${short[i]}" "INFO" "No connection" else proto=$(get_protocol $TMPFILE) # hack: @@ -4136,8 +4139,7 @@ run_client_simulation() { out " " outln "${warning[i]}" fi - fileout "client_${short[i]}" "INFO" \ - "$(strip_spaces "${names[i]}") client simulation: $proto $cipher ${warning[i]}" + fileout "${jsonID}-${short[i]}" "INFO" "$proto $cipher ${warning[i]}" debugme cat $TMPFILE fi fi # correct service? @@ -4728,7 +4730,7 @@ run_protocols() { } #TODO: work with fixed lists here --> atm ok, as sockets are preferred. If there would be a single function for testing: yes. -run_std_cipherlists() { +run_cipherlists() { local hexc hexcode strength local using_sockets=true local -i i @@ -4767,7 +4769,7 @@ run_std_cipherlists() { fi outln - pr_headlineln " Testing ~standard cipher categories " + pr_headlineln " Testing cipher categories " outln # argv[1]: cipher list to test in OpenSSL syntax (see ciphers(1ssl) or run 'openssl ciphers -v/-V)' # argv[2]: string on console / HTML or "finding" @@ -4775,17 +4777,17 @@ run_std_cipherlists() { # argv[4]: string to be appended for fileout # argv[5]: non-SSLv2 cipher list to test (hexcodes), if using sockets # argv[6]: SSLv2 cipher list to test (hexcodes), if using sockets - std_cipherlists 'NULL:eNULL' " NULL ciphers (no encryption) " -2 "NULL" "$null_ciphers" "$sslv2_null_ciphers" - std_cipherlists 'aNULL:ADH' " Anonymous NULL Ciphers (no authentication)" -2 "aNULL" "$anon_ciphers" "$sslv2_anon_ciphers" - std_cipherlists 'EXPORT:!ADH:!NULL' " Export ciphers (w/o ADH+NULL) " -2 "EXPORT" "$exp_ciphers" "$sslv2_exp_ciphers" - std_cipherlists 'LOW:DES:!ADH:!EXP:!NULL' " LOW: 64 Bit + DES encryption (w/o export) " -2 "DES+64Bit" "$low_ciphers" "$sslv2_low_ciphers" + sub_cipherlists 'NULL:eNULL' " NULL ciphers (no encryption) " -2 "NULL" "$null_ciphers" "$sslv2_null_ciphers" + sub_cipherlists 'aNULL:ADH' " Anonymous NULL Ciphers (no authentication)" -2 "aNULL" "$anon_ciphers" "$sslv2_anon_ciphers" + sub_cipherlists 'EXPORT:!ADH:!NULL' " Export ciphers (w/o ADH+NULL) " -2 "EXPORT" "$exp_ciphers" "$sslv2_exp_ciphers" + sub_cipherlists 'LOW:DES:!ADH:!EXP:!NULL' " LOW: 64 Bit + DES encryption (w/o export) " -2 "DES+64Bit" "$low_ciphers" "$sslv2_low_ciphers" - std_cipherlists 'MEDIUM:!aNULL:!AES:!CAMELLIA:!ARIA:!CHACHA20:!3DES' \ + sub_cipherlists 'MEDIUM:!aNULL:!AES:!CAMELLIA:!ARIA:!CHACHA20:!3DES' \ " Weak 128 Bit ciphers (SEED, IDEA, RC[2,4])" -1 "128Bit" "$medium_ciphers" "$sslv2_medium_ciphers" - std_cipherlists '3DES:!aNULL:!ADH' " Triple DES Ciphers (Medium) " 0 "3DES" "$tdes_ciphers" "$sslv2_tdes_ciphers" - std_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES:!AESGCM:!CHACHA20:!AESGCM:!CamelliaGCM:!AESCCM8:!AESCCM'\ + sub_cipherlists '3DES:!aNULL:!ADH' " Triple DES Ciphers (Medium) " 0 "3DES" "$tdes_ciphers" "$sslv2_tdes_ciphers" + sub_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES:!AESGCM:!CHACHA20:!AESGCM:!CamelliaGCM:!AESCCM8:!AESCCM'\ " High encryption (AES+Camellia, no AEAD) " 1 "HIGH" "$high_ciphers" "" - std_cipherlists 'AESGCM:CHACHA20:AESGCM:CamelliaGCM:AESCCM8:AESCCM' \ + sub_cipherlists 'AESGCM:CHACHA20:AESGCM:CamelliaGCM:AESCCM8:AESCCM' \ " Strong encryption (AEAD ciphers) " 2 "STRONG" "$strong_ciphers" "" outln return 0 @@ -5115,6 +5117,7 @@ run_server_preference() { local has_cipher_order=false local addcmd="" addcmd2="" local using_sockets=true + local jsonID="cipher_order" "$SSL_NATIVE" && using_sockets=false @@ -5136,7 +5139,7 @@ run_server_preference() { outln "$list_fwd . " tmpfile_handle $FUNCNAME.txt return 6 - fileout "cipher_order" "WARN" "Could not determine server cipher order, no matching cipher in this list found (pls report this): $list_fwd" + fileout "$jsonID" "WARN" "Could not determine server cipher order, no matching cipher in list found (pls report this): $list_fwd" elif [[ -n "$STARTTLS_PROTOCOL" ]]; then # now it still could be that we hit this bug: https://github.com/drwetter/testssl.sh/issues/188 # workaround is to connect with a protocol @@ -5146,7 +5149,7 @@ run_server_preference() { if ! sclient_connect_successful $? $TMPFILE; then pr_warning "no matching cipher in this list found (pls report this): " outln "$list_fwd . " - fileout "cipher_order" "WARN" "Could not determine server cipher order, no matching cipher in this list found (pls report this): $list_fwd" + fileout "$jsonID" "WARN" "Could not determine cipher order, no matching cipher in list found (pls report this): $list_fwd" tmpfile_handle $FUNCNAME.txt return 6 fi @@ -5172,17 +5175,18 @@ run_server_preference() { # server used the different ends (ciphers) from the client hello pr_svrty_high "nope (NOT ok)" limitedsense=" (limited sense as client will pick)" - fileout "cipher_order" "HIGH" "Server does NOT set a cipher order" + fileout "$jsonID" "HIGH" "NOT cipher order configured" else pr_done_best "yes (OK)" has_cipher_order=true limitedsense="" - fileout "cipher_order" "OK" "Server sets a cipher order" + fileout "$jsonID" "OK" "sets cipher order" fi debugme tm_out " $cipher1 | $cipher2" outln pr_bold " Negotiated protocol " + jsonID="protocol_negotiated" sclient_success=1 if "$using_sockets" && ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -ne 1 ]]; then # Send same list of cipher suites as OpenSSL 1.1.1 sends. @@ -5209,44 +5213,45 @@ run_server_preference() { case "$default_proto" in *TLSv1.3) prln_done_best $default_proto - fileout "protocol_negotiated" "OK" "Default protocol TLS1.3" + fileout "$jsonID" "OK" "Default protocol TLS1.3" ;; *TLSv1.2) prln_done_best $default_proto - fileout "protocol_negotiated" "OK" "Default protocol TLS1.2" + fileout "$jsonID" "OK" "Default protocol TLS1.2" ;; *TLSv1.1) prln_done_good $default_proto - fileout "protocol_negotiated" "OK" "Default protocol TLS1.1" + fileout "$jsonID" "OK" "Default protocol TLS1.1" ;; *TLSv1) outln $default_proto - fileout "protocol_negotiated" "INFO" "Default protocol TLS1.0" + fileout "$jsonID" "INFO" "Default protocol TLS1.0" ;; *SSLv2) prln_svrty_critical $default_proto - fileout "protocol_negotiated" "CRITICAL" "Default protocol SSLv2" + fileout "$jsonID" "CRITICAL" "Default protocol SSLv2" ;; *SSLv3) prln_svrty_critical $default_proto - fileout "protocol_negotiated" "CRITICAL" "Default protocol SSLv3" + fileout "$jsonID" "CRITICAL" "Default protocol SSLv3" ;; "") pr_warning "default proto empty" if [[ $OSSL_VER == 1.0.2* ]]; then outln " (Hint: if IIS6 give OpenSSL 1.0.1 a try)" - fileout "protocol_negotiated" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.0.1 a try)" + fileout "$jsonID" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.0.1 a try)" else - fileout "protocol_negotiated" "WARN" "Default protocol empty" + fileout "$jsonID" "WARN" "Default protocol empty" fi ;; *) pr_warning "FIXME line $LINENO: $default_proto" - fileout "protocol_negotiated" "WARN" "FIXME line $LINENO: $default_proto" + fileout "$jsonID" "WARN" "FIXME line $LINENO: $default_proto" ;; esac pr_bold " Negotiated cipher " + jsonID="cipher_negotiated" cipher1=$(get_cipher $TMPFILE) if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && ( [[ "$cipher1" == TLS_* ]] || [[ "$cipher1" == SSL_* ]] ); then default_cipher="$(rfc2openssl "$cipher1")" @@ -5256,25 +5261,25 @@ run_server_preference() { [[ -z "$default_cipher" ]] && default_cipher="$cipher1" pr_cipher_quality "$default_cipher" case $? in - 1) fileout "cipher_negotiated" "CRITICAL" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" + 1) fileout "$jsonID" "CRITICAL" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" ;; - 2) fileout "cipher_negotiated" "HIGH" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" + 2) fileout "$jsonID" "HIGH" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" ;; - 3) fileout "cipher_negotiated" "MEDIUM" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" + 3) fileout "$jsonID" "MEDIUM" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" ;; - 6|7) fileout "cipher_negotiated" "OK" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" + 6|7) fileout "$jsonID" "OK" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" ;; # best ones - 4) fileout "cipher_negotiated" "LOW" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") (cbc) $limitedsense" + 4) fileout "$jsonID" "LOW" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") (cbc) $limitedsense" ;; # it's CBC. --> lucky13 0) pr_warning "default cipher empty" ; if [[ $OSSL_VER == 1.0.2* ]]; then out " (Hint: if IIS6 give OpenSSL 1.0.1 a try)" - fileout "cipher_negotiated" "WARN" "Default cipher empty (if IIS6 give OpenSSL 1.0.1 a try) $limitedsense" + fileout "$jsonID" "WARN" "Default cipher empty (if IIS6 give OpenSSL 1.0.1 a try) $limitedsense" else - fileout "cipher_negotiated" "WARN" "Default cipher empty $limitedsense" + fileout "$jsonID" "WARN" "Default cipher empty $limitedsense" fi ;; - *) fileout "cipher_negotiated" "INFO" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" + *) fileout "$jsonID" "INFO" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense" ;; esac read_dhbits_from_file "$TMPFILE" @@ -5665,7 +5670,7 @@ cipher_pref_check() { else out_row_aligned_max_width_by_entry "$order" " " $TERM_WIDTH pr_cipher_quality fi - fileout "cipher_order_${proto//./_}" "INFO" "$order" + fileout "cipherorder_${proto//./_}" "INFO" "$order" fi done <<< "$(tm_out " ssl3 00 SSLv3\n tls1 01 TLSv1\n tls1_1 02 TLSv1.1\n tls1_2 03 TLSv1.2\n tls1_3 04 TLSv1.3\n")" outln @@ -5858,17 +5863,17 @@ tls_time() { if [[ "${#difftime}" -gt 5 ]]; then # openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint out "Random values, no fingerprinting possible " - fileout "${jsonID}" "INFO" "The server's TLS time seems to be filled with random values to prevent fingerprinting" + fileout "$jsonID" "INFO" "TLS timestamp is random" else [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime" out "$difftime"; out " sec from localtime"; - fileout "${jsonID}" "INFO" "The server's TLS time is skewed from your localtime by $difftime seconds" + fileout "$jsonID" "INFO" "TLS timestamp is off from your localtime by $difftime seconds" fi debugme tm_out "$TLS_TIME" outln else outln "SSLv3 through TLS 1.2 didn't return a timestamp" - fileout "${jsonID}" "INFO" "No TLS timestamp returned by SSLv3 through TLSv1.2" + fileout "$jsonID" "INFO" "No TLS timestamp returned by SSLv3 through TLSv1.2" fi TLS_DIFFTIME_SET=false # reset the switch to save calls to date and friend in tls_sockets() return 0 @@ -6026,6 +6031,8 @@ get_server_certificate() { cat level?.crt > $TEMPDIR/intermediatecerts.pem rm level?.crt fi + # generate file with text output -- we need that at several occasions later + $OPENSSL x509 -noout -text -in $HOSTCERT 2>>$ERRFILE >$HOSTCERT_TXT fi cd "$savedir" fi @@ -6394,7 +6401,7 @@ certificate_info() { spaces=" " fi - cert_sig_algo="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }')" + cert_sig_algo="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE| awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }')" cert_sig_algo="${cert_sig_algo// /}" cert_key_algo="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | awk -F':' '/Public Key Algorithm:/ { print $2; if (++Match >= 1) exit; }')" cert_key_algo="${cert_key_algo// /}" @@ -6512,7 +6519,7 @@ certificate_info() { jsonID="cert_key_size" if [[ -z "$cert_keysize" ]]; then outln "(couldn't determine)" - fileout "${jsonID}${json_postfix}" "Server keys size cannot be determined" + fileout "${jsonID}${json_postfix}" "cannot be determined" else case $cert_key_algo in *RSA*|*rsa*) out "RSA ";; @@ -6529,22 +6536,22 @@ certificate_info() { if [[ $cert_key_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then if [[ "$cert_keysize" -le 110 ]]; then # a guess pr_svrty_critical "$cert_keysize" - fileout "${jsonID}${json_postfix}" "CRITICAL" "Server keys $cert_keysize EC bits" + fileout "${jsonID}${json_postfix}" "CRITICAL" "$cert_keysize EC bits" elif [[ "$cert_keysize" -le 123 ]]; then # a guess pr_svrty_high "$cert_keysize" - fileout "${jsonID}${json_postfix}" "HIGH" "Server keys $cert_keysize EC bits" + fileout "${jsonID}${json_postfix}" "HIGH" "$cert_keysize EC bits" elif [[ "$cert_keysize" -le 163 ]]; then pr_svrty_medium "$cert_keysize" - fileout "${jsonID}${json_postfix}" "MEDIUM" "Server keys $cert_keysize EC bits" + fileout "${jsonID}${json_postfix}" "MEDIUM" "$cert_keysize EC bits" elif [[ "$cert_keysize" -le 224 ]]; then out "$cert_keysize" - fileout "${jsonID}${json_postfix}" "INFO" "Server keys $cert_keysize EC bits" + fileout "${jsonID}${json_postfix}" "INFO" "$cert_keysize EC bits" elif [[ "$cert_keysize" -le 533 ]]; then pr_done_good "$cert_keysize" - fileout "${jsonID}${json_postfix}" "OK" "Server keys $cert_keysize EC bits" + fileout "${jsonID}${json_postfix}" "OK" "$cert_keysize EC bits" else out "keysize: $cert_keysize (not expected, FIXME)" - fileout "${jsonID}${json_postfix}" "DEBUG" "Server keys $cert_keysize bits (not expected)" + fileout "${jsonID}${json_postfix}" "DEBUG" " $cert_keysize bits (not expected)" fi outln " bits" elif [[ $cert_key_algo = *RSA* ]] || [[ $cert_key_algo = *rsa* ]] || [[ $cert_key_algo = *dsa* ]] || \ @@ -6552,25 +6559,25 @@ certificate_info() { if [[ "$cert_keysize" -le 512 ]]; then pr_svrty_critical "$cert_keysize" outln " bits" - fileout "${jsonID}${json_postfix}" "CRITICAL" "Server keys $cert_keysize bits" + fileout "${jsonID}${json_postfix}" "CRITICAL" "$cert_keysize bits" elif [[ "$cert_keysize" -le 768 ]]; then pr_svrty_high "$cert_keysize" outln " bits" - fileout "${jsonID}${json_postfix}" "HIGH" "Server keys $cert_keysize bits" + fileout "${jsonID}${json_postfix}" "HIGH" "$cert_keysize bits" elif [[ "$cert_keysize" -le 1024 ]]; then pr_svrty_medium "$cert_keysize" outln " bits" - fileout "${jsonID}${json_postfix}" "MEDIUM" "Server keys $cert_keysize bits" + fileout "${jsonID}${json_postfix}" "MEDIUM" "$cert_keysize bits" elif [[ "$cert_keysize" -le 2048 ]]; then outln "$cert_keysize bits" - fileout "${jsonID}${json_postfix}" "INFO" "Server keys $cert_keysize bits" + fileout "${jsonID}${json_postfix}" "INFO" "$cert_keysize bits" elif [[ "$cert_keysize" -le 4096 ]]; then pr_done_good "$cert_keysize" - fileout "${jsonID}${json_postfix}" "OK" "Server keys $cert_keysize bits" + fileout "${jsonID}${json_postfix}" "OK" "$cert_keysize bits" outln " bits" else pr_warning "weird key size: $cert_keysize bits"; outln " (could cause compatibility problems)" - fileout "${jsonID}${json_postfix}" "WARN" "Server keys $cert_keysize bits (Odd)" + fileout "${jsonID}${json_postfix}" "WARN" "$cert_keysize bits (Odd)" fi else out "$cert_keysize bits (" @@ -6583,7 +6590,7 @@ certificate_info() { out "$indent"; pr_bold " Server key usage "; outok=true jsonID="cert_key_usage" - cert_keyusage=$($OPENSSL x509 -text -noout -in $HOSTCERT 2>>$ERRFILE | grep -A 1 "X509v3 Key Usage:" | tail -n +2 | sed 's/^[ \t]*//') + cert_keyusage="$(strip_leading_space "$($OPENSSL x509 -noout -text -in $HOSTCERT 2>>$ERRFILE | awk '/X509v3 Key Usage:/ { getline; print $0 }')")" if [[ -n "$cert_keyusage" ]]; then outln "$cert_keyusage" if ( [[ " $cert_type " =~ " RSASig " ]] || [[ " $cert_type " =~ " DSA " ]] || [[ " $cert_type " =~ " ECDSA " ]] ) && \ @@ -6605,17 +6612,18 @@ certificate_info() { fi else outln "--" - fileout "${jsonID}key_usage" "INFO" "No server key usage information" + fileout "${jsonID}${json_postfix}" "INFO" "No server key usage information" outok=false fi if "$outok"; then - fileout "${jsonID}key_usage" "INFO" "$cert_keyusage" + fileout "${jsonID}${json_postfix}" "INFO" "$cert_keyusage" fi out "$indent"; pr_bold " Server extended key usage "; jsonID="cert_extended_key_usage" outok=true - cert_ext_keyusage="$($OPENSSL x509 -noout -text -in $HOSTCERT 2>>$ERRFILE | grep -A 1 "X509v3 Extended Key Usage: " | tail -1 | sed 's/^[ \t]*//')" + cert_ext_keyusage="$(strip_leading_space "$($OPENSSL x509 -noout -text -in $HOSTCERT 2>>$ERRFILE | awk '/X509v3 Extended Key Usage:/ { getline; print $0 }')")" + $OPENSSL x509 -noout -text -in $HOSTCERT 2>>$ERRFILE | awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' | read cert_ext_keyusage if [[ -n "$cert_ext_keyusage" ]]; then outln "$cert_ext_keyusage" if [[ ! "$cert_ext_keyusage" =~ "TLS Web Server Authentication" ]] && [[ ! "$cert_ext_keyusage" =~ "Any Extended Key Usage" ]]; then @@ -6754,7 +6762,7 @@ certificate_info() { if [[ -n "$issuer_C" ]]; then issuerfinding+=" from " out " from " - issuerfinding+="$issuer_C" + # issuerfinding+="$issuer_C" pr_italic "$issuer_C" fi issuerfinding+=")" @@ -6960,7 +6968,7 @@ certificate_info() { certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem) out "$indent"; pr_bold " # of certificates provided"; outln " $certificates_provided" - fileout "certchain_count${json_postfix}" "INFO" "${certificates_provided} certificates provided" + fileout "certchain_count${json_postfix}" "INFO" "${certificates_provided} certificates" # Get both CRL and OCSP URI upfront. If there's none, this is not good. And we need to penalize this in the output crl="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \ @@ -7057,23 +7065,24 @@ certificate_info() { done <<< "$caa" all_caa=${all_caa%, } # strip trailing comma pr_italic "$(out_row_aligned_max_width "$all_caa" "$indent " $TERM_WIDTH)" - fileout "${jsonID}${json_postfix}" "OK" "'DNS Certification Authority Authorization (CAA) Resource Record / RFC6844' \'$all_caa\' " + fileout "${jsonID}${json_postfix}" "OK" "$all_caa" elif "$NODNS"; then pr_warning "(was instructed to not use DNS)" fileout "${jsonID}${json_postfix}" "WARN" "check skipped as instructed" else pr_svrty_low "not offered" - fileout "${jsonID}${json_postfix}" "LOW" "'DNS Certification Authority Authorization (CAA) Resource Record / RFC6844' not offered" + fileout "${jsonID}${json_postfix}" "LOW" "not offered" fi outln out "$indent"; pr_bold " Certificate Transparency "; + jsonID="certificate_transparency" if [[ "$ct" =~ extension ]]; then pr_done_good "yes"; outln " ($ct)" - fileout "certificate_transparency${json_postfix}" "OK" "yes ($ct)" + fileout "${jsonID}${json_postfix}" "OK" "yes ($ct)" else outln "$ct" - fileout "certificate_transparency${json_postfix}" "INFO" "$ct" + fileout "${jsonID}${json_postfix}" "INFO" "$ct" fi outln return $ret @@ -7265,7 +7274,7 @@ run_server_defaults() { jsonID="TLS_session_ticket" if [[ -z "$sessticket_lifetime_hint" ]]; then outln "(no lifetime advertised)" - fileout "${jsonID}" "INFO" "No TLS session ticket RFC 5077 lifetime advertised" + fileout "${jsonID}" "INFO" "No lifetime advertised" # it MAY be given a hint of the lifetime of the ticket, see https://tools.ietf.org/html/rfc5077#section-5.6 . # Sometimes it just does not -- but it then may also support TLS session tickets reuse else @@ -7274,66 +7283,69 @@ run_server_defaults() { out "$lifetime $unit" if [[ $((3600 * 24)) -lt $lifetime ]]; then prln_svrty_low " but: PFS requires session ticket keys to be rotated < daily !" - fileout "${jsonID}" "LOW" "TLS session ticket RFC 5077 valid for $lifetime $unit but PFS requires session ticket keys to be rotated at least daily!" + fileout "$jsonID" "LOW" "valid for $lifetime $unit (>daily)" else outln ", session tickets keys seems to be rotated < daily" - fileout "${jsonID}" "INFO" "TLS session ticket RFC 5077 valid for $lifetime $unit only (PFS requires session ticket keys are rotated at least daily)" + fileout "$jsonID" "INFO" "valid for $lifetime $unit only ($TMPFILE 2>$ERRFILE =2)" [[ $DEBUG -ge 3 ]] && hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" | head -1 ret=7 - fileout "DROWN" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)" "$cve" "$cwe" + fileout "$jsonID" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)" "$cve" "$cwe" ;; 3) # vulnerable, [[ -n "$cert_fingerprint_sha2" ]] test is not needed as we should have RSA certificate here lines=$(count_lines "$(hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" 2>/dev/null)") @@ -12802,10 +12821,10 @@ run_drown() { nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) if [[ 0 -eq "$nr_ciphers_detected" ]]; then prln_svrty_high "CVE-2015-3197: SSLv2 supported but couldn't detect a cipher (NOT ok)"; - fileout "DROWN" "HIGH" "SSLv2 offered, but could not detect a cipher (CVE-2015-3197. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint" + fileout "$jsonID" "HIGH" "SSLv2 offered, but could not detect a cipher (CVE-2015-3197). Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint" else prln_svrty_critical "VULNERABLE (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers"; - fileout "DROWN" "CRITICAL" "VULNERABLE, SSLv2 offered with $nr_ciphers_detected ciphers. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint" + fileout "$jsonID" "CRITICAL" "VULNERABLE, SSLv2 offered with $nr_ciphers_detected ciphers. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint" fi outln "$spaces Make sure you don't use this certificate elsewhere, see:" out "$spaces " @@ -12821,10 +12840,10 @@ run_drown() { out "$spaces " pr_url "https://censys.io/ipv4?q=$cert_fingerprint_sha2" outln " could help you to find out" - fileout "DROWN" "INFO" "make sure you don't use this certificate elsewhere with SSLv2 enabled services, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" + fileout "$jsonID" "INFO" "Make sure you don't use this certificate elsewhere with SSLv2 enabled services, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" else outln "$spaces no RSA certificate, thus certificate can't be used with SSLv2 elsewhere" - fileout "DROWN" "INFO" "no RSA certificate, thus certificate can't be used with SSLv2 elsewhere" + fileout "$jsonID" "INFO" "no RSA certificate, can't be used with SSLv2 elsewhere" fi ret=0 ;; @@ -13064,7 +13083,7 @@ run_beast(){ if ! "$WIDE"; then if [[ -n "$detected_cbc_ciphers" ]]; then - fileout "BEAST_CBC_$(toupper $proto)" "MEDIUM" "CBC ciphers for $(toupper $proto): $detected_cbc_ciphers" "$cve" "$cwe" "$hint" + fileout "BEAST_CBC_$(toupper $proto)" "MEDIUM" "$detected_cbc_ciphers" "$cve" "$cwe" "$hint" ! "$first" && out "$spaces" out "$(toupper $proto): " [[ -n "$higher_proto_supported" ]] && \ @@ -13156,8 +13175,8 @@ run_lucky13() { fi if [[ $sclient_success -eq 0 ]]; then out "potentially " - pr_svrty_low "VULNERABLE"; out ", uses cipher block chaining (CBC) ciphers with TLS" - fileout "LUCKY13" "LOW" "potentially vulnerable to LUCKY13, uses cipher block chaining (CBC) ciphers with TLS. Check patches" "$cve" "$cwe" "$hint" + pr_svrty_low "VULNERABLE"; out ", uses cipher block chaining (CBC) ciphers with TLS. Check patches" + fileout "LUCKY13" "LOW" "potentially vulnerable to LUCKY13, uses TLS CBC ciphers" "$cve" "$cwe" "$hint" # the CBC padding which led to timing differences during MAC processing has been solved in openssl (https://www.openssl.org/news/secadv/20130205.txt) # and other software. However we can't tell with reasonable effort from the outside. Thus we still issue a warning and label it experimental else @@ -13807,17 +13826,18 @@ run_robot() { local -i start_time end_time timeout=$MAX_WAITSOCK local cve="CVE-2017-17382 CVE-2017-17427 CVE-2017-17428 CVE-2017-13098 CVE-2017-1000385 CVE-2017-13099 CVE-2016-6883 CVE-2012-5081" local cwe="" + local jsonID="ROBOT" [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability " && outln pr_bold " ROBOT " if [[ ! "$HAS_PKUTIL" ]]; then prln_local_problem "Your $OPENSSL does not support the pkeyutl utility." - fileout "ROBOT" "WARN" "Your $OPENSSL does not support the pkeyutl utility." + fileout "$jsonID" "WARN" "$OPENSSL does not support the pkeyutl utility." return 7 elif ! "$HAS_PKEY"; then prln_local_problem "Your $OPENSSL does not support the pkey utility." - fileout "ROBOT" "WARN" "Your $OPENSSL does not support the pkey utility." + fileout "$jsonID" "WARN" "$OPENSSL does not support the pkey utility." return 7 fi @@ -13853,7 +13873,7 @@ run_robot() { cipherlist="${cipherlist:2}" elif [[ $ret -ne 0 ]]; then prln_done_best "Server does not support any cipher suites that use RSA key transport" - fileout "ROBOT" "OK" "not vulnerable (server does not support any cipher suites that use RSA key transport)" + fileout "$jsonID" "OK" "not vulnerable, no RSA key transport cipher" return 0 fi fi @@ -13929,7 +13949,7 @@ run_robot() { fi close_socket prln_fixme "Conversion of public key failed around line $((LINENO - 9))" - fileout "ROBOT" "WARN" "Conversion of public key failed around line $((LINENO - 10)) " + fileout "$jsonID" "WARN" "Conversion of public key failed around line $((LINENO - 10)) " return 1 fi @@ -14032,14 +14052,14 @@ run_robot() { if "$vulnerable"; then if [[ "${response[1]}" == "${response[2]}" ]] && [[ "${response[2]}" == "${response[3]}" ]]; then pr_svrty_medium "VULNERABLE (NOT ok)"; outln " - weakly vulnerable as the attack would take too long" - fileout "ROBOT" "MEDIUM" "VULNERABLE, but the attack would take too long" + fileout "$jsonID" "MEDIUM" "VULNERABLE, but the attack would take too long" else prln_svrty_critical "VULNERABLE (NOT ok)" - fileout "ROBOT" "CRITICAL" "VULNERABLE" + fileout "$jsonID" "CRITICAL" "VULNERABLE" fi else prln_done_best "not vulnerable (OK)" - fileout "ROBOT" "OK" "not vulnerable" + fileout "$jsonID" "OK" "not vulnerable" fi return 0 } @@ -14459,7 +14479,8 @@ maketempf() { else ERRFILE=$TEMPDIR/errorfile.txt || exit -6 fi - HOSTCERT=$TEMPDIR/host_certificate.txt + HOSTCERT=$TEMPDIR/host_certificate.pem + HOSTCERT_TXT=$TEMPDIR/host_certificate.txt #FIXME: needs to be used later } prepare_debug() { @@ -16149,7 +16170,7 @@ parse_cmd_line() { do_protocols=true ;; -s|--std|--standard) - do_std_cipherlists=true + do_cipherlists=true ;; -S|--server[-_]defaults) do_server_defaults=true @@ -16572,7 +16593,7 @@ lets_roll() { "$do_grease" && { run_grease; ret=$(($? + ret)); time_right_align run_grease; } fileout_section_header $section_number true && ((section_number++)) - $do_std_cipherlists && { run_std_cipherlists; ret=$(($? + ret)); time_right_align run_std_cipherlists; } + $do_cipherlists && { run_cipherlists; ret=$(($? + ret)); time_right_align run_cipherlists; } fileout_section_header $section_number true && ((section_number++)) $do_pfs && { run_pfs; ret=$(($? + ret)); time_right_align run_pfs; }