From 9094665768fc2bf201234e127a9070108099c822 Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 28 Sep 2020 20:17:11 +0200 Subject: [PATCH 1/8] Start for improving handling of intermediate certs See #1683, #1653, #1004, #1264 * separate code for bad ocsp a bit * output intermediate cert in json/csv * replace sed statements from cert_fingerprint* and -serial by bash funcs --- testssl.sh | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/testssl.sh b/testssl.sh index ccbacf5..3b035a2 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8317,6 +8317,13 @@ certificate_transparency() { return 0 } +determine_certs_fingerprints_serial() { + local cert="$1" + local ossl_command="$2" + +} + + certificate_info() { local proto local -i certificate_number=$1 @@ -8341,7 +8348,7 @@ certificate_info() { local startdate enddate issuer_CN issuer_C issuer_O issuer sans san all_san="" cn local issuer_DC issuerfinding cn_nosni="" local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_serial cert - local -a intermediate_certs=() + local -a intermediate_certs_txt=() local policy_oid local spaces="" local -i trust_sni=0 trust_nosni=0 diffseconds=0 @@ -8364,7 +8371,7 @@ certificate_info() { local yearstart yearend clockstart clockend y m d local gt_398=false gt_398warn=false local gt_825=false gt_825warn=false - local badocsp=1 + local badocsp=1 if [[ $number_of_certificates -gt 1 ]]; then [[ $certificate_number -eq 1 ]] && outln @@ -8700,14 +8707,19 @@ certificate_info() { fi out "$indent"; pr_bold " Serial / Fingerprints " - cert_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')" + cert_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE)" + cert_serial="${cert_serial//serial=}" fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial" - cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g')" - fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}" + cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE)" + cert_fingerprint_sha1="${cert_fingerprint_sha1//Fingerprint=}" + cert_fingerprint_sha1="${cert_fingerprint_sha1//:/}" outln "$cert_serial / $cert_fingerprint_sha1" + fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}" - cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )" + cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE)" + cert_fingerprint_sha2="${cert_fingerprint_sha2//Fingerprint=}" + cert_fingerprint_sha2="${cert_fingerprint_sha2//:/}" fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2//SHA256 /}" outln "$spaces$cert_fingerprint_sha2" @@ -8988,20 +9000,13 @@ certificate_info() { # https://certs.opera.com/03/ev-oids.xml # see #967 - # courtesy Hanno Boeck (see https://github.com/hannob/badocspcert) - out "$indent"; pr_bold " Bad OCSP intermediate" - out " (exp.) " - jsonID="cert_bad_ocsp" # There might be >1 certificate, so we split intermediatecerts.pem e.g. into # intermediatecert1.crt, intermediatecert2.cert. -#FIXME: This is redundant code. We do that elsewhere, e.g. before in extract_certificates() -# and run_hpkp() at least but didn't keep the result -# -#FIXME: We just raise the flag saying the chain is bad w/o naming the intermediate -# cert to blame. +#FIXME: This is somewhat redundant code. We do similar stuff elsewhere, e.g. in extract_certificates() +# and run_hpkp() but don't keep the result - # Store all of the intermediate certificates in an array so that they can + # Store all of the text output of the intermediate certificates in an array so that they can # be used later (e.g., to check their expiration dates). while true; do [[ "$intermediates" =~ \-\-\-\-\-\BEGIN\ CERTIFICATE\-\-\-\-\- ]] || break @@ -9009,14 +9014,24 @@ certificate_info() { cert="${intermediates%%-----END CERTIFICATE-----*}" intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" - intermediate_certs[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" + # we count as humans in the file output here. This needs later to be adjusted in the code + fileout "intermediate_cert $((certificates_provided + 1 ))" "INFO" "$cert" + intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" certificates_provided+=1 done + + # courtesy Hanno Boeck (see https://github.com/hannob/badocspcert) + out "$indent"; pr_bold " Bad OCSP intermediate" + out " (exp.) " + jsonID="cert_bad_ocsp" + certificates_provided+=1 for (( i=0; i < certificates_provided-1; i++ )); do - cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs[i]}")" + cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs_txt[i]}")" [[ "$cert_ext_keyusage" =~ OCSP\ Signing ]] && badocsp=0 && break done + + #FIXME: We only raise the flag saying the chain is bad w/o naming the intermediate cert to blame. if [[ $badocsp -eq 0 ]]; then prln_svrty_medium "NOT ok" fileout "${jsonID}${json_postfix}" "MEDIUM" "NOT ok is/are intermediate certificate(s)" From b625df87c1b2340460b691d6e5a054ddf0318428 Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 28 Sep 2020 20:38:37 +0200 Subject: [PATCH 2/8] Move determination of fingerprint and serial to determine_cert_fingerprint_serial() .. so that it can be used for other certificates than the host certificate --- testssl.sh | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/testssl.sh b/testssl.sh index 3b035a2..330f158 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8317,10 +8317,20 @@ certificate_transparency() { return 0 } -determine_certs_fingerprints_serial() { +# replacement for inline $OPENSSL x509 -noout -in $HOSTCERT -serial +# and $OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256/-sha1 +# +determine_cert_fingerprint_serial() { local cert="$1" local ossl_command="$2" + local result="" + result="$($OPENSSL x509 -noout -in $1 $2 2>>$ERRFILE)" + # remove strings in text output, colon only appear in fingerprints + result="${result//Fingerprint=}" + result="${result//serial=}" + result="${result//:/}" + safe_echo "$result" } @@ -8707,23 +8717,18 @@ certificate_info() { fi out "$indent"; pr_bold " Serial / Fingerprints " - cert_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE)" - cert_serial="${cert_serial//serial=}" + cert_serial="$(determine_cert_fingerprint_serial "$HOSTCERT" "-serial")" fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial" - cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE)" - cert_fingerprint_sha1="${cert_fingerprint_sha1//Fingerprint=}" - cert_fingerprint_sha1="${cert_fingerprint_sha1//:/}" + cert_fingerprint_sha1="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha1")" outln "$cert_serial / $cert_fingerprint_sha1" fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}" - cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE)" - cert_fingerprint_sha2="${cert_fingerprint_sha2//Fingerprint=}" - cert_fingerprint_sha2="${cert_fingerprint_sha2//:/}" + cert_fingerprint_sha2="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha256")" fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2//SHA256 /}" outln "$spaces$cert_fingerprint_sha2" - # " " needs to be converted back to lf in JSON/CSV output + # " " needs to be converted back to lf in JSON/CSV output. watch out leading/ending line containting "CERTIFICATE" fileout "cert${json_postfix}" "INFO" "$(< $HOSTCERT)" [[ -z $CERT_FINGERPRINT_SHA2 ]] && \ From 5eee67291e4bb5d3a4348d2337e376471e995444 Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 30 Sep 2020 15:44:23 +0200 Subject: [PATCH 3/8] Outsourcing of certificate date properties determination determine_dates_certificate() is now determining the important dates of a certificate passed via argument. It works of course for host and any other certificates. Returning multiple parameters is being done via CSV and passed to a read statement which seemed the best choice for bash. ToDo: * $expok is not set properly for intermediate certificates * check if expired at least in the UI (JSON+CSV: echo the dates so far) * for multiple host certificates the naming scheme (jsonID + intermediate certnumnber kind of sucks: "id" : "intermediate_cert_fingerprintSHA256 1", "id" : "intermediate_cert_notAfter 1", The whole thing is kind of hackish as the code has been historically grown. At some certian point we may want to reconsider how we determine properties of certificates in certificate_info() --- testssl.sh | 119 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/testssl.sh b/testssl.sh index 330f158..26cb7d5 100755 --- a/testssl.sh +++ b/testssl.sh @@ -3567,7 +3567,7 @@ run_cipher_match(){ hexc="${TLS_CIPHER_HEXCODE[i]}" if [[ ${#hexc} -eq 9 ]]; then hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}" - if [[ "${hexc:2:2}" == "00" ]]; then + if [[ "${hexc:2:2}" == 00 ]]; then normalized_hexcode[nr_ciphers]="x${hexc:7:2}" else normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}" @@ -8321,11 +8321,16 @@ certificate_transparency() { # and $OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256/-sha1 # determine_cert_fingerprint_serial() { - local cert="$1" + local cert_file="$1" local ossl_command="$2" local result="" - result="$($OPENSSL x509 -noout -in $1 $2 2>>$ERRFILE)" + if [[ -r "$cert_file" ]]; then + result="$($OPENSSL x509 -noout -in $cert_file $ossl_command 2>>$ERRFILE)" + else + # not really a file but the variable which holds the certificate + result="$($OPENSSL x509 -noout $ossl_command 2>>$ERRFILE <<< "$cert_file")" + fi # remove strings in text output, colon only appear in fingerprints result="${result//Fingerprint=}" result="${result//serial=}" @@ -8333,6 +8338,56 @@ determine_cert_fingerprint_serial() { safe_echo "$result" } +# Returns startdate, enddate, diffseconds, days2expire as CSVs as strings +# arg1: human readable text string for certificate (openssl x509 -text -noout) +# +determine_dates_certificate () { + local cert_txt="$1" + local startdate enddate yearnow y m d yearstart clockstart yearend clockend + local diffseconds=0 days2expire=0 + + startdate="${cert_txt#*Validity*Not Before: }" + # FreeBSD + OSX can't swallow the leading blank: + startdate="${startdate%%GMT*}GMT" + enddate="${cert_txt#*Validity*Not Before: *Not After : }" + enddate="${enddate%%GMT*}GMT" + debugme echo "$enddate - $startdate" + # Now we have a normalized enddate and startdate like "Feb 27 10:03:20 2017 GMT" -- also for OpenBSD + if "$HAS_OPENBSDDATE"; then + # Best we want to do under old versions of OpenBSD, first just remove the GMT and keep start/endate for later output + startdate="$(parse_date "$startdate" "+%s")" + enddate="$(parse_date "$enddate" "+%s")" + # Now we extract a date block and a time block which we need for later output + startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" + enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" + read yearstart clockstart <<< "$startdate" + read yearend clockend <<< "$enddate" + debugme echo "$yearstart, $clockstart" + debugme echo "$yearend, $clockend" + y=$(( ${yearend:0:4} - ${yearstart:0:4} )) + m=$(( ${yearend:5:1} - ${yearstart:5:1} + ${yearend:6:1} - ${yearstart:6:1} )) + d=$(( ${yearend:8:2} - ${yearstart:8:2} )) + # We take the year, month, days here as old OpenBSD's date is too difficult for real conversion + # see comment in parse_date(). In diffseconds then we have the estimated absolute validity period + diffseconds=$(( d + ((m*30)) + ((y*365)) )) + diffseconds=$((diffseconds * 3600 * 24)) + # Now we estimate the days left plus length of month/year: + yearnow="$(date -juz GMT "+%Y-%m-%d %H:%M")" + y=$(( ${yearend:0:4} - ${yearnow:0:4} )) + m=$(( ${yearend:5:1} - ${yearnow:5:1} + ${yearend:6:1} - ${yearnow:6:1} )) + d=$(( ${yearend:8:2} - ${yearnow:8:2} )) + days2expire=$(( d + ((m*30)) + ((y*365)) )) + else + startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" + enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" + days2expire=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(LC_ALL=C date "+%s") )) # first in seconds + days2expire=$((days2expire / 3600 / 24 )) + diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') )) + fi + safe_echo "$startdate, $enddate, $diffseconds, $days2expire, $yearstart" +} + + certificate_info() { local proto @@ -8378,7 +8433,7 @@ certificate_info() { local provides_stapling=false local caa_node="" all_caa="" caa_property_name="" caa_property_value="" local response="" - local yearstart yearend clockstart clockend y m d + local yearstart local gt_398=false gt_398warn=false local gt_825=false gt_825warn=false local badocsp=1 @@ -8735,7 +8790,7 @@ certificate_info() { CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" || CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2 $CERT_FINGERPRINT_SHA2" [[ -z $RSA_CERT_FINGERPRINT_SHA2 ]] && \ - ( [[ $cert_key_algo = *RSA* ]] || [[ $cert_key_algo = *rsa* ]] ) && + ( [[ $cert_key_algo =~ RSA ]] || [[ $cert_key_algo =~ rsa ]] ) && RSA_CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" out "$indent"; pr_bold " Common Name (CN) " @@ -8812,7 +8867,7 @@ certificate_info() { out "$indent"; pr_bold " Issuer " jsonID="cert_caIssuers" #FIXME: oid would be better maybe (see above) - issuer="$($OPENSSL x509 -in $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" + issuer="$($OPENSSL x509 -in $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")" issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")" issuer_C="$(awk -F'=' '/ C=/ { print $2 }' <<< "$issuer")" @@ -9019,9 +9074,19 @@ certificate_info() { cert="${intermediates%%-----END CERTIFICATE-----*}" intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" + # we count as humans in the file output here. This needs later to be adjusted in the code - fileout "intermediate_cert $((certificates_provided + 1 ))" "INFO" "$cert" + fileout "intermediate_cert${json_postfix} $((certificates_provided + 1 ))" "INFO" "$cert" + + fileout "intermediate_cert_fingerprintSHA256${json_postfix} $((certificates_provided + 1 ))" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" + intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" + + # We don't need every value here. For the sake of consistency we add the rest + IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "${intermediate_certs_txt[certificates_provided]}") + fileout "intermediate_cert_notBefore${json_postfix} $((certificates_provided + 1))" "INFO" "$startdate" + expok="OK" #FIXME! + fileout "intermediate_cert_notAfter${json_postfix} $((certificates_provided + 1))" "$expok" "$enddate" certificates_provided+=1 done @@ -9054,44 +9119,8 @@ certificate_info() { # For now we leave this here. We may want to change that later or add infos to other sections (FS & vulnerability) out "$indent"; pr_bold " Certificate Validity (UTC) " - # FreeBSD + OSX can't swallow the leading blank: - startdate="${cert_txt#*Validity*Not Before: }" - startdate="${startdate%%GMT*}GMT" - enddate="${cert_txt#*Validity*Not Before: *Not After : }" - enddate="${enddate%%GMT*}GMT" - debugme echo "$enddate - $startdate" - # Now we have a normalized enddate and startdate like "Feb 27 10:03:20 2017 GMT" -- also for OpenBSD - if "$HAS_OPENBSDDATE"; then - # Best we want to do under old versions of OpenBSD, first just remove the GMT and keep start/endate for later output - startdate="$(parse_date "$startdate" "+%s")" - enddate="$(parse_date "$enddate" "+%s")" - # Now we extract a date block and a time block which we need for later output - startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" - enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" - read yearstart clockstart <<< "$startdate" - read yearend clockend <<< "$enddate" - debugme echo "$yearstart, $clockstart" - debugme echo "$yearend, $clockend" - y=$(( ${yearend:0:4} - ${yearstart:0:4} )) - m=$(( ${yearend:5:1} - ${yearstart:5:1} + ${yearend:6:1} - ${yearstart:6:1} )) - d=$(( ${yearend:8:2} - ${yearstart:8:2} )) - # We take the year, month, days here as old OpenBSD's date is too difficult for real conversion - # see comment in parse_date(). In diffseconds then we have the estimated absolute validity period - diffseconds=$(( d + ((m*30)) + ((y*365)) )) - diffseconds=$((diffseconds * 3600 * 24)) - # Now we estimate the days left plus length of month/year: - yearnow="$(date -juz GMT "+%Y-%m-%d %H:%M")" - y=$(( ${yearend:0:4} - ${yearnow:0:4} )) - m=$(( ${yearend:5:1} - ${yearnow:5:1} + ${yearend:6:1} - ${yearnow:6:1} )) - d=$(( ${yearend:8:2} - ${yearnow:8:2} )) - days2expire=$(( d + ((m*30)) + ((y*365)) )) - else - startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" - enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" - days2expire=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(LC_ALL=C date "+%s") )) # first in seconds - days2expire=$((days2expire / 3600 / 24 )) - diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') )) - fi + IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "$cert_txt") + # We adjust the thresholds by %50 for LE certificates, relaxing warnings for those certificates. # . instead of \' because it does not break syntax highlighting in vim if [[ "$issuer_CN" =~ ^Let.s\ Encrypt\ Authority ]] ; then From 67afa6c372ef9b1b7c6ddadc2b36fd4a9914e697 Mon Sep 17 00:00:00 2001 From: Dirk Date: Thu, 1 Oct 2020 00:13:31 +0200 Subject: [PATCH 4/8] MOre points added to complete intermediate cert section * UI feed back for expiration date of intermediates: 20 days: HIGH, 40 days: MEDIUM * also in JSON/CSV * list the end date of validity * works for >1 intermediates too * section moved to the end of certificate_info() * renamed --> to avoid coinfusion with intermediate certs * removed blanks in return values of determine_dates_certificate --- testssl.sh | 160 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 66 deletions(-) diff --git a/testssl.sh b/testssl.sh index 26cb7d5..59b5bd0 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6468,9 +6468,8 @@ run_server_preference() { local cipher1="" cipher2="" tls13_cipher1="" tls13_cipher2="" default_proto="" local default_cipher="" local limitedsense="" supported_sslv2_ciphers - local -a offered_cipher offered_proto local proto_ossl proto_txt proto_hex cipherlist i - local -i ret=0 j sclient_success str_len + local -i ret=0 j sclient_success local list_fwd="DHE-RSA-SEED-SHA:SEED-SHA:DES-CBC3-SHA:RC4-MD5:DES-CBC-SHA:RC4-SHA:AES128-SHA:AES128-SHA256:AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:AES256-SHA256:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:ADH-AES256-GCM-SHA384:AECDH-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-AES128-SHA" local list_reverse="ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:AECDH-AES128-SHA:ADH-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-DES-CBC3-SHA:AES256-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA256:AES128-SHA:RC4-SHA:DES-CBC-SHA:RC4-MD5:DES-CBC3-SHA:SEED-SHA:DHE-RSA-SEED-SHA" tls_list_fwd="c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, 00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a, 00,04, 00,05, 00,09, 00,0a, 00,9a, 00,96, @@ -8335,13 +8334,15 @@ determine_cert_fingerprint_serial() { result="${result//Fingerprint=}" result="${result//serial=}" result="${result//:/}" + result="${result//SHA1 /}" + result="${result//SHA256 /}" safe_echo "$result" } # Returns startdate, enddate, diffseconds, days2expire as CSVs as strings # arg1: human readable text string for certificate (openssl x509 -text -noout) # -determine_dates_certificate () { +determine_dates_certificate() { local cert_txt="$1" local startdate enddate yearnow y m d yearstart clockstart yearend clockend local diffseconds=0 days2expire=0 @@ -8360,8 +8361,8 @@ determine_dates_certificate () { # Now we extract a date block and a time block which we need for later output startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" - read yearstart clockstart <<< "$startdate" - read yearend clockend <<< "$enddate" + read -r yearstart clockstart <<< "$startdate" + read -r yearend clockend <<< "$enddate" debugme echo "$yearstart, $clockstart" debugme echo "$yearend, $clockend" y=$(( ${yearend:0:4} - ${yearstart:0:4} )) @@ -8384,7 +8385,7 @@ determine_dates_certificate () { days2expire=$((days2expire / 3600 / 24 )) diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') )) fi - safe_echo "$startdate, $enddate, $diffseconds, $days2expire, $yearstart" + safe_echo "$startdate,$enddate,$diffseconds,$days2expire,$yearstart" } @@ -8436,6 +8437,7 @@ certificate_info() { local yearstart local gt_398=false gt_398warn=false local gt_825=false gt_825warn=false + local first=true local badocsp=1 if [[ $number_of_certificates -gt 1 ]]; then @@ -8445,7 +8447,7 @@ certificate_info() { pr_headline "Server Certificate #$certificate_number" [[ -z "$sni_used" ]] && pr_underline " (in response to request w/o SNI)" outln - json_postfix=" " + json_postfix=" " spaces=" " else spaces=" " @@ -8600,7 +8602,7 @@ certificate_info() { *GOST*|*gost*) short_keyAlgo="GOST";; *dh*|*DH*) short_keyAlgo="DH" ;; *) pr_fixme "don't know $cert_key_algo " - let ret++ ;; + ((ret++)) ;; esac out "$short_keyAlgo " # https://tools.ietf.org/html/rfc4492, https://www.keylength.com/en/compare/ @@ -8776,12 +8778,12 @@ certificate_info() { fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial" cert_fingerprint_sha1="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha1")" - outln "$cert_serial / $cert_fingerprint_sha1" - fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}" + outln "$cert_serial / SHA1 $cert_fingerprint_sha1" + fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1}" cert_fingerprint_sha2="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha256")" - fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2//SHA256 /}" - outln "$spaces$cert_fingerprint_sha2" + fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2}" + outln "${spaces}SHA256 ${cert_fingerprint_sha2}" # " " needs to be converted back to lf in JSON/CSV output. watch out leading/ending line containting "CERTIFICATE" fileout "cert${json_postfix}" "INFO" "$(< $HOSTCERT)" @@ -9060,56 +9062,6 @@ certificate_info() { # https://certs.opera.com/03/ev-oids.xml # see #967 - -# There might be >1 certificate, so we split intermediatecerts.pem e.g. into -# intermediatecert1.crt, intermediatecert2.cert. -#FIXME: This is somewhat redundant code. We do similar stuff elsewhere, e.g. in extract_certificates() -# and run_hpkp() but don't keep the result - - # Store all of the text output of the intermediate certificates in an array so that they can - # be used later (e.g., to check their expiration dates). - while true; do - [[ "$intermediates" =~ \-\-\-\-\-\BEGIN\ CERTIFICATE\-\-\-\-\- ]] || break - intermediates="${intermediates#*-----BEGIN CERTIFICATE-----}" - cert="${intermediates%%-----END CERTIFICATE-----*}" - intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" - cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" - - # we count as humans in the file output here. This needs later to be adjusted in the code - fileout "intermediate_cert${json_postfix} $((certificates_provided + 1 ))" "INFO" "$cert" - - fileout "intermediate_cert_fingerprintSHA256${json_postfix} $((certificates_provided + 1 ))" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" - - intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" - - # We don't need every value here. For the sake of consistency we add the rest - IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "${intermediate_certs_txt[certificates_provided]}") - fileout "intermediate_cert_notBefore${json_postfix} $((certificates_provided + 1))" "INFO" "$startdate" - expok="OK" #FIXME! - fileout "intermediate_cert_notAfter${json_postfix} $((certificates_provided + 1))" "$expok" "$enddate" - certificates_provided+=1 - done - - # courtesy Hanno Boeck (see https://github.com/hannob/badocspcert) - out "$indent"; pr_bold " Bad OCSP intermediate" - out " (exp.) " - jsonID="cert_bad_ocsp" - - certificates_provided+=1 - for (( i=0; i < certificates_provided-1; i++ )); do - cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs_txt[i]}")" - [[ "$cert_ext_keyusage" =~ OCSP\ Signing ]] && badocsp=0 && break - done - - #FIXME: We only raise the flag saying the chain is bad w/o naming the intermediate cert to blame. - if [[ $badocsp -eq 0 ]]; then - prln_svrty_medium "NOT ok" - fileout "${jsonID}${json_postfix}" "MEDIUM" "NOT ok is/are intermediate certificate(s)" - else - prln_svrty_good "Ok" - fileout "${jsonID}${json_postfix}" "OK" "intermediate certificate(s) is/are ok" - fi - out "$indent"; pr_bold " ETS/\"eTLS\"" out ", visibility info " jsonID="cert_eTLS" @@ -9217,7 +9169,10 @@ certificate_info() { fileout "cert_validityPeriod${json_postfix}" "INFO" "No finding" fi - out "$indent"; pr_bold " # of certificates provided"; out " $certificates_provided" + out "$indent"; pr_bold " Certificates provided" + certificates_provided="$(grep -ac '\-\-\-\-\-BEGIN\ CERTIFICATE\-\-\-\-\-' <<< "$intermediates")" + ((certificates_provided++)) # plus host certificate + out " $certificates_provided" fileout "certs_countServer${json_postfix}" "INFO" "${certificates_provided}" if "$certificate_list_ordering_problem"; then prln_svrty_low " (certificate list ordering problem)" @@ -9281,7 +9236,7 @@ certificate_info() { else if [[ $(count_lines "$ocsp_uri") -eq 1 ]]; then out "$ocsp_uri" - if [[ "$expfinding" != "expired" ]]; then + if [[ "$expfinding" != expired ]]; then check_revocation_ocsp "$ocsp_uri" "" "cert_ocspRevoked${json_postfix}" fi ret=$((ret +$?)) @@ -9295,7 +9250,7 @@ certificate_info() { out "$spaces" fi out "$line" - if [[ "$expfinding" != "expired" ]]; then + if [[ "$expfinding" != expired ]]; then check_revocation_ocsp "$line" "" "cert_ocspRevoked${json_postfix}" ret=$((ret +$?)) fi @@ -9339,7 +9294,7 @@ certificate_info() { else out "(response status unknown)" fileout "${jsonID}${json_postfix}" "OK" " not sure what's going on here, '$ocsp_response'" - debugme grep -a -A20 -B2 "OCSP response" <<<"$ocsp_response" + debugme grep -a -A20 -B2 "OCSP response" <<< "$ocsp_response" ((ret++)) fi fi @@ -9391,6 +9346,79 @@ certificate_info() { outln "$ct" fileout "${jsonID}${json_postfix}" "INFO" "$ct" fi + +# Now we take care of the intermediate certificates. We basically (should) have them on disk +# as "intermediatecerts.pem" (which could be split into intermediatecert1.crt, intermediatecert2.crt, ..) +# However we do this in RAM which is better as it was passed to this function. +# We should keep in mind though this is somewhat redundant code. We do similar stuff elsewhere, +# e.g. in extract_certificates() and run_hpkp() but don't keep the certificates + +#FIXME: output +# intermediate CN / (what about issuer. moving it?) +# fix the numbering schem of certificates_provided below + + # Store all of the text output of the intermediate certificates in an array so that they can + # be used later (e.g., to check their expiration dates). + certificates_provided=0 + while true; do + [[ "$intermediates" =~ \-\-\-\-\-BEGIN\ CERTIFICATE\-\-\-\-\- ]] || break + intermediates="${intermediates#*-----BEGIN CERTIFICATE-----}" + cert="${intermediates%%-----END CERTIFICATE-----*}" + intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" + cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" + + # we count as humans in the file output here. This needs later to be adjusted in the code + fileout "intermediate_cert <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$cert" + + fileout "intermediate_cert_fingerprintSHA256 <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" + + intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" + + # We don't need every value here. For the sake of being consistent here we add the rest + IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "${intermediate_certs_txt[certificates_provided]}") + fileout "intermediate_cert_notBefore <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$startdate" + + if $first; then + out "$indent"; pr_bold " Intermediate cert validity " + first=false + else + out "$indent$spaces" + fi + if ! $OPENSSL x509 -checkend $((24*3600*20)) 2>>$ERRFILE <<< "$cert" | grep -qw not; then + out "#$((certificates_provided+1)): less then "; pr_svrty_high "20 days" + outln " at $enddate" + expok="HIGH" + elif ! $OPENSSL x509 -checkend $((24*3600*40)) 2>>$ERRFILE <<< "$cert" | grep -qw not; then + out "#$((certificates_provided+1)): less then "; pr_svrty_medium "40 days" + outln " at $enddate" + expok="MEDIUM" + else + outln "#$((certificates_provided+1)): longer than 40 days ($enddate)" + fi + fileout "intermediate_cert_notAfter <#$((certificates_provided + 1))>${json_postfix}" "$expok" "$enddate" + certificates_provided+=1 + done + + # Courtesy Hanno Böck (see https://github.com/hannob/badocspcert) + out "$indent"; pr_bold " Intermediate Bad OCSP" + out " (exp.) " + jsonID="intermediate_cert_badOCSP" + + certificates_provided+=1 + for (( i=0; i < certificates_provided-1; i++ )); do + cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs_txt[i]}")" + [[ "$cert_ext_keyusage" =~ OCSP\ Signing ]] && badocsp=0 && break + done + + #FIXME: We only raise the flag saying the chain is bad w/o naming the intermediate cert to blame. + if [[ $badocsp -eq 0 ]]; then + prln_svrty_medium "NOT ok" + fileout "${jsonID}${json_postfix}" "MEDIUM" "NOT ok is/are intermediate certificate(s)" + else + prln_svrty_good "Ok" + fileout "${jsonID}${json_postfix}" "OK" "intermediate certificate(s) is/are ok" + fi + outln return $ret } From a7bcf9ec7f4ead4e25c02a075af453f59c5819b8 Mon Sep 17 00:00:00 2001 From: Dirk Date: Thu, 1 Oct 2020 17:49:14 +0200 Subject: [PATCH 5/8] Further improvements to certificate_info() * add cn and issuer_CN to the output both on screen and file * the severity rating for intermediates are just a shot (20/40 days) and deserve a second thought * replace the expiry check by one test statement and make grep futile * replace at some places "$openssl x509 -in $filename" by "$openssl x509 <<< $var" * the thing with 25*60*60 was fie readability. When it's used >20 times it maybe is not (and maybe costs to much time) --> replaced by $secsaday * adjusted the loop for bad ocsp check for readability --- testssl.sh | 126 +++++++++++++++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/testssl.sh b/testssl.sh index 59b5bd0..c31e1f7 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8043,7 +8043,7 @@ etsi_ets_visibility_info() { # external functions to obtain the DER encoded certficate. if [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\ ]] || \ [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\ 0.4.0.3523.3.1 ]]; then - dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')" + dercert="$($OPENSSL x509 -outform DER 2>>$ERRFILE <<< "$cert" | hexdump -v -e '16/1 "%02X"')" if [[ "$dercert" =~ 0603551D110101FF04[0-9A-F]*060604009B430301 ]] || \ [[ "$dercert" =~ 0603551D1104[0-9A-F]*060604009B430301 ]]; then # Look for the beginning of the subjectAltName extension. It @@ -8346,6 +8346,7 @@ determine_dates_certificate() { local cert_txt="$1" local startdate enddate yearnow y m d yearstart clockstart yearend clockend local diffseconds=0 days2expire=0 + local -i secsaday=86400 startdate="${cert_txt#*Validity*Not Before: }" # FreeBSD + OSX can't swallow the leading blank: @@ -8371,7 +8372,7 @@ determine_dates_certificate() { # We take the year, month, days here as old OpenBSD's date is too difficult for real conversion # see comment in parse_date(). In diffseconds then we have the estimated absolute validity period diffseconds=$(( d + ((m*30)) + ((y*365)) )) - diffseconds=$((diffseconds * 3600 * 24)) + diffseconds=$((diffseconds * secsaday)) # Now we estimate the days left plus length of month/year: yearnow="$(date -juz GMT "+%Y-%m-%d %H:%M")" y=$(( ${yearend:0:4} - ${yearnow:0:4} )) @@ -8382,7 +8383,7 @@ determine_dates_certificate() { startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")" enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")" days2expire=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(LC_ALL=C date "+%s") )) # first in seconds - days2expire=$((days2expire / 3600 / 24 )) + days2expire=$((days2expire / secsaday)) diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') )) fi safe_echo "$startdate,$enddate,$diffseconds,$days2expire,$yearstart" @@ -8406,11 +8407,12 @@ certificate_info() { local ct="${12}" local certificate_list_ordering_problem="${13}" local cert_sig_algo cert_sig_hash_algo cert_key_algo cert_spki_info + local hostcert="" local common_primes_file="$TESTSSL_INSTALL_DIR/etc/common-primes.txt" local -i lineno_matched=0 local cert_keyusage cert_ext_keyusage short_keyAlgo local outok=true - local expire days2expire secs2warn ocsp_uri crl + local days2expire secs2warn ocsp_uri crl local startdate enddate issuer_CN issuer_C issuer_O issuer sans san all_san="" cn local issuer_DC issuerfinding cn_nosni="" local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_serial cert @@ -8421,7 +8423,7 @@ certificate_info() { local has_dns_sans has_dns_sans_nosni local trust_sni_finding local -i i certificates_provided=0 - local cnfinding trustfinding trustfinding_nosni + local cn_finding trustfinding trustfinding_nosni local cnok="OK" local expfinding expok="OK" local -i ret=0 @@ -8437,6 +8439,7 @@ certificate_info() { local yearstart local gt_398=false gt_398warn=false local gt_825=false gt_825warn=false + local -i secsaday=86400 local first=true local badocsp=1 @@ -8773,20 +8776,22 @@ certificate_info() { fileout "${jsonID}${json_postfix}" "INFO" "$cert_ext_keyusage" fi + hostcert="$(<$HOSTCERT)" + out "$indent"; pr_bold " Serial / Fingerprints " - cert_serial="$(determine_cert_fingerprint_serial "$HOSTCERT" "-serial")" + cert_serial="$(determine_cert_fingerprint_serial "$hostcert" "-serial")" fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial" - cert_fingerprint_sha1="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha1")" + cert_fingerprint_sha1="$(determine_cert_fingerprint_serial "$hostcert" "-fingerprint -sha1")" outln "$cert_serial / SHA1 $cert_fingerprint_sha1" fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1}" - cert_fingerprint_sha2="$(determine_cert_fingerprint_serial "$HOSTCERT" "-fingerprint -sha256")" + cert_fingerprint_sha2="$(determine_cert_fingerprint_serial "$hostcert" "-fingerprint -sha256")" fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2}" outln "${spaces}SHA256 ${cert_fingerprint_sha2}" # " " needs to be converted back to lf in JSON/CSV output. watch out leading/ending line containting "CERTIFICATE" - fileout "cert${json_postfix}" "INFO" "$(< $HOSTCERT)" + fileout "cert${json_postfix}" "INFO" "$hostcert" [[ -z $CERT_FINGERPRINT_SHA2 ]] && \ CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" || @@ -8796,19 +8801,19 @@ certificate_info() { RSA_CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" out "$indent"; pr_bold " Common Name (CN) " - cnfinding="Common Name (CN) : " + cn_finding="Common Name (CN) : " cn="$(get_cn_from_cert $HOSTCERT)" if [[ -n "$cn" ]]; then pr_italic "$cn" - cnfinding="$cn" + cn_finding="$cn" else cn="no CN field in subject" out "($cn)" - cnfinding="$cn" + cn_finding="$cn" cnok="INFO" fi - fileout "cert_commonName${json_postfix}" "$cnok" "$cnfinding" - cnfinding="" + fileout "cert_commonName${json_postfix}" "$cnok" "$cn_finding" + cn_finding="" if [[ -n "$sni_used" ]]; then if grep -q "\-\-\-\-\-BEGIN" "$HOSTCERT.nosni"; then @@ -8822,24 +8827,24 @@ certificate_info() { if [[ -z "$sni_used" ]] || [[ "$(toupper "$cn_nosni")" == "$(toupper "$cn")" ]]; then outln - cnfinding="$cn" + cn_finding="$cn" elif [[ -z "$cn_nosni" ]]; then out " (request w/o SNI didn't succeed"; - cnfinding+="request w/o SNI didn't succeed" + cn_finding+="request w/o SNI didn't succeed" if [[ $cert_sig_algo =~ ecdsa ]]; then out ", usual for EC certificates" - cnfinding+=", usual for EC certificates" + cn_finding+=", usual for EC certificates" fi outln ")" - cnfinding+="" + cn_finding+="" elif [[ "$cn_nosni" == *"no CN field"* ]]; then outln ", (request w/o SNI: $cn_nosni)" - cnfinding="$cn_nosni" + cn_finding="$cn_nosni" else out " (CN in response to request w/o SNI: "; pr_italic "$cn_nosni"; outln ")" - cnfinding="$cn_nosni" + cn_finding="$cn_nosni" fi - fileout "cert_commonName_wo_SNI${json_postfix}" "INFO" "$cnfinding" + fileout "cert_commonName_wo_SNI${json_postfix}" "INFO" "$cn_finding" sans=$(grep -A2 "Subject Alternative Name" <<< "$cert_txt" | \ grep -E "DNS:|IP Address:|email:|URI:|DirName:|Registered ID:" | tr ',' '\n' | \ @@ -8868,14 +8873,14 @@ certificate_info() { out "$indent"; pr_bold " Issuer " jsonID="cert_caIssuers" - #FIXME: oid would be better maybe (see above) - issuer="$($OPENSSL x509 -in $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" + #FIXME: oid would be better maybe (see above). And the line by line input could be done w/o awk + issuer="$($OPENSSL x509 -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE <<< "$hostcert")" issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")" issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")" issuer_C="$(awk -F'=' '/ C=/ { print $2 }' <<< "$issuer")" issuer_DC="$(awk -F'=' '/DC=/ { print $2 }' <<< "$issuer")" - if [[ "$issuer_O" == "issuer=" ]] || [[ "$issuer_O" == "issuer= " ]] || [[ "$issuer_CN" == "$cn" ]]; then + if [[ "$issuer_O" == issuer= ]] || [[ "$issuer_O" == issuer=\ ]] || [[ "$issuer_CN" == "$cn" ]]; then prln_svrty_critical "self-signed (NOT ok)" fileout "${jsonID}${json_postfix}" "CRITICAL" "selfsigned" set_grade_cap "T" "Self-signed certificate" @@ -9065,7 +9070,7 @@ certificate_info() { out "$indent"; pr_bold " ETS/\"eTLS\"" out ", visibility info " jsonID="cert_eTLS" - etsi_ets_visibility_info "${jsonID}${json_postfix}" "$spaces" "$HOSTCERT" "$cert_txt" + etsi_ets_visibility_info "${jsonID}${json_postfix}" "$spaces" "$hostcert" "$cert_txt" # *Currently* this is even listed as a vulnerability (CWE-310, CVE-2019-919), see # https://nvd.nist.gov/vuln/detail/CVE-2019-9191, https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9191 # For now we leave this here. We may want to change that later or add infos to other sections (FS & vulnerability) @@ -9081,19 +9086,16 @@ certificate_info() { fi debugme echo -n "diffseconds: $diffseconds" - expire=$($OPENSSL x509 -in $HOSTCERT -checkend 1 2>>$ERRFILE) - if ! grep -qw not <<< "$expire" ; then + if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then pr_svrty_critical "expired" expfinding="expired" expok="CRITICAL" set_grade_cap "T" "Certificate expired" else - secs2warn=$((24 * 60 * 60 * days2warn2)) # low threshold first - expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) - if grep -qw not <<< "$expire"; then - secs2warn=$((24 * 60 * 60 * days2warn1)) # high threshold - expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE) - if grep -qw not <<< "$expire"; then + # low threshold first + if ! [[ "$($OPENSSL x509 -checkend $((secsaday*days2warn2)) 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then + # high threshold + if ! [[ "$($OPENSSL x509 -checkend $((secsaday*days2warn1)) 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then pr_svrty_good "$days2expire >= $days2warn1 days" expfinding+="$days2expire >= $days2warn1 days" else @@ -9114,15 +9116,15 @@ certificate_info() { # Internal certificates or those from appliances often have too high validity periods. # We check for ~10 years and >~ 5 years - if [[ $diffseconds -ge $((3600 * 24 * 365 * 10)) ]]; then + if [[ $diffseconds -ge $((secsaday*365*10)) ]]; then out "$spaces" prln_svrty_high ">= 10 years is way too long" - fileout "cert_validityPeriod${json_postfix}" "HIGH" "$((diffseconds / (3600 * 24) )) days" - elif [[ $diffseconds -ge $((3600 * 24 * 365 * 5)) ]]; then + fileout "cert_validityPeriod${json_postfix}" "HIGH" "$((diffseconds / secsaday)) days" + elif [[ $diffseconds -ge $((secsaday*365*5)) ]]; then out "$spaces" prln_svrty_medium ">= 5 years is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) days" - elif [[ $diffseconds -ge $((3600 * 24 * 398 + 1)) ]]; then + fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) days" + elif [[ $diffseconds -ge $((secsaday*398 + 1)) ]]; then # Also "official" certificates issued from september 1st 2020 (1598918400) aren't supposed # to be valid longer than 398 days which is 34387200 in epoch seconds gt_398=true @@ -9137,12 +9139,12 @@ certificate_info() { out "$spaces" if "$gt_398warn" && "$gt_398"; then prln_svrty_medium "> 398 days issued after 2020/09/01 is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) > 398 days" + fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 398 days" elif "$gt_398"; then outln ">= 398 days certificate life time but issued before 2020/09/01" - fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / (3600 * 24) )) =< 398 days" + fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 398 days" fi - elif [[ $diffseconds -ge $((3600 * 24 * 825 + 1)) ]]; then + elif [[ $diffseconds -ge $((secsaday*825 + 1)) ]]; then # Also "official" certificates issued from March 1st, 2018 (1517353200) aren't supposed # to be valid longer than 825 days which is 1517353200 in epoch seconds gt_825=true @@ -9157,10 +9159,10 @@ certificate_info() { out "$spaces" if "$gt_825warn" && "$gt_825"; then prln_svrty_medium "> 825 days issued after 2018/03/01 is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) > 825 days" + fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 825 days" elif "$gt_825"; then outln ">= 825 days certificate life time but issued before 2018/03/01" - fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / (3600 * 24) )) =< 825 days" + fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 825 days" fi else # All is fine with valididy period @@ -9229,7 +9231,7 @@ certificate_info() { out "$indent"; pr_bold " OCSP URI " jsonID="cert_ocspURL" - ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE) + ocsp_uri="$($OPENSSL x509 -noout -ocsp_uri 2>>$ERRFILE <<< "$hostcert")" if [[ -z "$ocsp_uri" ]]; then outln "--" fileout "${jsonID}${json_postfix}" "INFO" "--" @@ -9353,9 +9355,7 @@ certificate_info() { # We should keep in mind though this is somewhat redundant code. We do similar stuff elsewhere, # e.g. in extract_certificates() and run_hpkp() but don't keep the certificates -#FIXME: output -# intermediate CN / (what about issuer. moving it?) -# fix the numbering schem of certificates_provided below +#FIXME: the numbering scheme of certificates_provided below: we start @ 0 and always add 1. Careful with Intermediate Bad OCSP # Store all of the text output of the intermediate certificates in an array so that they can # be used later (e.g., to check their expiration dates). @@ -9367,9 +9367,7 @@ certificate_info() { intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" - # we count as humans in the file output here. This needs later to be adjusted in the code fileout "intermediate_cert <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$cert" - fileout "intermediate_cert_fingerprintSHA256 <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" @@ -9384,18 +9382,31 @@ certificate_info() { else out "$indent$spaces" fi - if ! $OPENSSL x509 -checkend $((24*3600*20)) 2>>$ERRFILE <<< "$cert" | grep -qw not; then - out "#$((certificates_provided+1)): less then "; pr_svrty_high "20 days" - outln " at $enddate" + out "#$((certificates_provided+1)): " + if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then + cn_finding="expired!" + pr_svrty_critical "$cn_finding" + expok="CRITICAL" + elif ! [[ "$($OPENSSL x509 -checkend $((secsaday*20)) 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then + cn_finding="expires <= 20 days" + pr_svrty_high "$cn_finding" expok="HIGH" - elif ! $OPENSSL x509 -checkend $((24*3600*40)) 2>>$ERRFILE <<< "$cert" | grep -qw not; then - out "#$((certificates_provided+1)): less then "; pr_svrty_medium "40 days" - outln " at $enddate" + elif ! [[ "$($OPENSSL x509 -checkend $((secsaday*40)) 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then + cn_finding="expires <= 40 days" + pr_svrty_medium "$cn_finding" expok="MEDIUM" else - outln "#$((certificates_provided+1)): longer than 40 days ($enddate)" + cn_finding="valid > 40 days" + pr_svrty_good "$cn_finding" + expok="OK" fi + out " ($enddate). " + cn="$(awk -F= '/Subject:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[certificates_provided]}")" + issuer_CN="$(awk -F= '/Issuer:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[certificates_provided]}")" + pr_italic "$cn"; out " <-- "; prln_italic "$issuer_CN" fileout "intermediate_cert_notAfter <#$((certificates_provided + 1))>${json_postfix}" "$expok" "$enddate" + fileout "intermediate_cert_expiration <#$((certificates_provided + 1))>${json_postfix}" "$expok" "$cn_finding" + fileout "intermediate_cert_chain <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$cn <-- $issuer_CN" certificates_provided+=1 done @@ -9404,8 +9415,7 @@ certificate_info() { out " (exp.) " jsonID="intermediate_cert_badOCSP" - certificates_provided+=1 - for (( i=0; i < certificates_provided-1; i++ )); do + for (( i=1; i < certificates_provided; i++ )); do cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs_txt[i]}")" [[ "$cert_ext_keyusage" =~ OCSP\ Signing ]] && badocsp=0 && break done From d5a64ff4b60fba59d3641f9bc19f207282ef8608 Mon Sep 17 00:00:00 2001 From: Dirk Date: Fri, 2 Oct 2020 08:43:17 +0200 Subject: [PATCH 6/8] Further improvements to intermediate certs * reorder sequence of checks in certificate info so that the chain relevant points are closer together * determine_cert_fingerprint_serial() doesn't need fil input anymore, thus removed that part * cert_validityPeriod in JSON/CSV may lead to misunderstandings, thus renamed to cert_extlifeSpan * reorganized loop for the intermediate certificate checks, so that also i is used and not the variable which defines the number of certificates, i.e. certificates_provided. In addition made the counting more hiuma friendly, which starts now at 1 instead of 0 --- testssl.sh | 99 ++++++++++++++++++++++++------------------------------ 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/testssl.sh b/testssl.sh index c31e1f7..1b197bb 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8320,16 +8320,11 @@ certificate_transparency() { # and $OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256/-sha1 # determine_cert_fingerprint_serial() { - local cert_file="$1" + local cert="$1" local ossl_command="$2" local result="" - if [[ -r "$cert_file" ]]; then - result="$($OPENSSL x509 -noout -in $cert_file $ossl_command 2>>$ERRFILE)" - else - # not really a file but the variable which holds the certificate - result="$($OPENSSL x509 -noout $ossl_command 2>>$ERRFILE <<< "$cert_file")" - fi + result="$($OPENSSL x509 -noout $ossl_command 2>>$ERRFILE <<< "$cert")" # remove strings in text output, colon only appear in fingerprints result="${result//Fingerprint=}" result="${result//serial=}" @@ -9067,14 +9062,6 @@ certificate_info() { # https://certs.opera.com/03/ev-oids.xml # see #967 - out "$indent"; pr_bold " ETS/\"eTLS\"" - out ", visibility info " - jsonID="cert_eTLS" - etsi_ets_visibility_info "${jsonID}${json_postfix}" "$spaces" "$hostcert" "$cert_txt" - # *Currently* this is even listed as a vulnerability (CWE-310, CVE-2019-919), see - # https://nvd.nist.gov/vuln/detail/CVE-2019-9191, https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9191 - # For now we leave this here. We may want to change that later or add infos to other sections (FS & vulnerability) - out "$indent"; pr_bold " Certificate Validity (UTC) " IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "$cert_txt") @@ -9119,11 +9106,11 @@ certificate_info() { if [[ $diffseconds -ge $((secsaday*365*10)) ]]; then out "$spaces" prln_svrty_high ">= 10 years is way too long" - fileout "cert_validityPeriod${json_postfix}" "HIGH" "$((diffseconds / secsaday)) days" + fileout "cert_extlifeSpan${json_postfix}" "HIGH" "$((diffseconds / secsaday)) days" elif [[ $diffseconds -ge $((secsaday*365*5)) ]]; then out "$spaces" prln_svrty_medium ">= 5 years is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) days" + fileout "cert_extlifeSpan${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) days" elif [[ $diffseconds -ge $((secsaday*398 + 1)) ]]; then # Also "official" certificates issued from september 1st 2020 (1598918400) aren't supposed # to be valid longer than 398 days which is 34387200 in epoch seconds @@ -9139,10 +9126,10 @@ certificate_info() { out "$spaces" if "$gt_398warn" && "$gt_398"; then prln_svrty_medium "> 398 days issued after 2020/09/01 is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 398 days" + fileout "cert_extlifeSpan${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 398 days" elif "$gt_398"; then outln ">= 398 days certificate life time but issued before 2020/09/01" - fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 398 days" + fileout "cert_extlifeSpan${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 398 days" fi elif [[ $diffseconds -ge $((secsaday*825 + 1)) ]]; then # Also "official" certificates issued from March 1st, 2018 (1517353200) aren't supposed @@ -9159,30 +9146,25 @@ certificate_info() { out "$spaces" if "$gt_825warn" && "$gt_825"; then prln_svrty_medium "> 825 days issued after 2018/03/01 is too long" - fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 825 days" + fileout "cert_extlifeSpan${json_postfix}" "MEDIUM" "$((diffseconds / secsaday)) > 825 days" elif "$gt_825"; then outln ">= 825 days certificate life time but issued before 2018/03/01" - fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 825 days" + fileout "cert_extlifeSpan${json_postfix}" "INFO" "$((diffseconds / secsaday)) =< 825 days" fi else - # All is fine with valididy period + # All is fine with validity period # We ignore for now certificates < 2018/03/01. On the screen we only show debug info - [[ "$DEBUG" -ge 1 ]] && outln "${spaces}DEBUG: all is fine with total certificate life time" - fileout "cert_validityPeriod${json_postfix}" "INFO" "No finding" + debugme1 outln "${spaces}DEBUG: all is fine with total certificate life time" + fileout "cert_extlifeSpan${json_postfix}" "OK" "certificate has no extended life time according to browser forum" fi - out "$indent"; pr_bold " Certificates provided" - certificates_provided="$(grep -ac '\-\-\-\-\-BEGIN\ CERTIFICATE\-\-\-\-\-' <<< "$intermediates")" - ((certificates_provided++)) # plus host certificate - out " $certificates_provided" - fileout "certs_countServer${json_postfix}" "INFO" "${certificates_provided}" - if "$certificate_list_ordering_problem"; then - prln_svrty_low " (certificate list ordering problem)" - fileout "certs_list_ordering_problem${json_postfix}" "LOW" "yes" - else - fileout "certs_list_ordering_problem${json_postfix}" "INFO" "no" - outln - fi + out "$indent"; pr_bold " ETS/\"eTLS\"" + out ", visibility info " + jsonID="cert_eTLS" + etsi_ets_visibility_info "${jsonID}${json_postfix}" "$spaces" "$hostcert" "$cert_txt" + # *Currently* this is even listed as a vulnerability (CWE-310, CVE-2019-919), see + # https://nvd.nist.gov/vuln/detail/CVE-2019-9191, https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9191 + # For now we leave this here. We may want to change that later or add infos to other sections (FS & vulnerability) if "$PHONE_OUT"; then out "$indent"; pr_bold " In pwnedkeys.com DB " @@ -9349,32 +9331,42 @@ certificate_info() { fileout "${jsonID}${json_postfix}" "INFO" "$ct" fi + out "$indent"; pr_bold " Certificates provided" + certificates_provided="$(grep -ac '\-\-\-\-\-BEGIN\ CERTIFICATE\-\-\-\-\-' <<< "$intermediates")" + ((certificates_provided++)) # plus host certificate + out " $certificates_provided" + fileout "certs_countServer${json_postfix}" "INFO" "${certificates_provided}" + if "$certificate_list_ordering_problem"; then + prln_svrty_low " (certificate list ordering problem)" + fileout "certs_list_ordering_problem${json_postfix}" "LOW" "yes" + else + fileout "certs_list_ordering_problem${json_postfix}" "INFO" "no" + outln + fi + # Now we take care of the intermediate certificates. We basically (should) have them on disk # as "intermediatecerts.pem" (which could be split into intermediatecert1.crt, intermediatecert2.crt, ..) # However we do this in RAM which is better as it was passed to this function. # We should keep in mind though this is somewhat redundant code. We do similar stuff elsewhere, # e.g. in extract_certificates() and run_hpkp() but don't keep the certificates -#FIXME: the numbering scheme of certificates_provided below: we start @ 0 and always add 1. Careful with Intermediate Bad OCSP - # Store all of the text output of the intermediate certificates in an array so that they can # be used later (e.g., to check their expiration dates). - certificates_provided=0 - while true; do + for (( i=1; i < certificates_provided; i++ )); do [[ "$intermediates" =~ \-\-\-\-\-BEGIN\ CERTIFICATE\-\-\-\-\- ]] || break intermediates="${intermediates#*-----BEGIN CERTIFICATE-----}" cert="${intermediates%%-----END CERTIFICATE-----*}" intermediates="${intermediates#${cert}-----END CERTIFICATE-----}" cert="-----BEGIN CERTIFICATE-----${cert}-----END CERTIFICATE-----" - fileout "intermediate_cert <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$cert" - fileout "intermediate_cert_fingerprintSHA256 <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" + fileout "intermediate_cert <#${i}>${json_postfix}" "INFO" "$cert" + fileout "intermediate_cert_fingerprintSHA256 <#${i}>${json_postfix}" "INFO" "$(determine_cert_fingerprint_serial "$cert" "-fingerprint -sha256")" - intermediate_certs_txt[certificates_provided]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" + intermediate_certs_txt[i]="$($OPENSSL x509 -text -noout 2>/dev/null <<< "$cert")" # We don't need every value here. For the sake of being consistent here we add the rest - IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "${intermediate_certs_txt[certificates_provided]}") - fileout "intermediate_cert_notBefore <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$startdate" + IFS=',' read -r startdate enddate diffseconds days2expire yearstart < <(determine_dates_certificate "${intermediate_certs_txt[i]}") + fileout "intermediate_cert_notBefore <#${i}>${json_postfix}" "INFO" "$startdate" if $first; then out "$indent"; pr_bold " Intermediate cert validity " @@ -9382,7 +9374,7 @@ certificate_info() { else out "$indent$spaces" fi - out "#$((certificates_provided+1)): " + out "#${i}: " if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$cert")" =~ \ not\ ]]; then cn_finding="expired!" pr_svrty_critical "$cn_finding" @@ -9401,13 +9393,12 @@ certificate_info() { expok="OK" fi out " ($enddate). " - cn="$(awk -F= '/Subject:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[certificates_provided]}")" - issuer_CN="$(awk -F= '/Issuer:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[certificates_provided]}")" + cn="$(awk -F= '/Subject:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[i]}")" + issuer_CN="$(awk -F= '/Issuer:.*CN/ { print $NF }' <<< "${intermediate_certs_txt[i]}")" pr_italic "$cn"; out " <-- "; prln_italic "$issuer_CN" - fileout "intermediate_cert_notAfter <#$((certificates_provided + 1))>${json_postfix}" "$expok" "$enddate" - fileout "intermediate_cert_expiration <#$((certificates_provided + 1))>${json_postfix}" "$expok" "$cn_finding" - fileout "intermediate_cert_chain <#$((certificates_provided + 1))>${json_postfix}" "INFO" "$cn <-- $issuer_CN" - certificates_provided+=1 + fileout "intermediate_cert_notAfter <#${i}>${json_postfix}" "$expok" "$enddate" + fileout "intermediate_cert_expiration <#${i}>${json_postfix}" "$expok" "$cn_finding" + fileout "intermediate_cert_chain <#${i}>${json_postfix}" "INFO" "$cn <-- $issuer_CN" done # Courtesy Hanno Böck (see https://github.com/hannob/badocspcert) @@ -9419,11 +9410,9 @@ certificate_info() { cert_ext_keyusage="$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "${intermediate_certs_txt[i]}")" [[ "$cert_ext_keyusage" =~ OCSP\ Signing ]] && badocsp=0 && break done - - #FIXME: We only raise the flag saying the chain is bad w/o naming the intermediate cert to blame. if [[ $badocsp -eq 0 ]]; then prln_svrty_medium "NOT ok" - fileout "${jsonID}${json_postfix}" "MEDIUM" "NOT ok is/are intermediate certificate(s)" + fileout "${jsonID}${json_postfix}" "MEDIUM" "NOT ok is intermediate certificate ${i}" else prln_svrty_good "Ok" fileout "${jsonID}${json_postfix}" "OK" "intermediate certificate(s) is/are ok" From c3f8207d938c0168c8ad5caf01aa42521c193127 Mon Sep 17 00:00:00 2001 From: Dirk Date: Fri, 2 Oct 2020 13:00:21 +0200 Subject: [PATCH 7/8] Fix Travis + mv issuer line down Travis failure was due to debug output in function which return a string. The debug statement was removed, (stderr would have been choice \#2). Issuer is heading now the intermediate certificate section, not sure whethe this is redundant info. --- testssl.sh | 100 +++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/testssl.sh b/testssl.sh index 1b197bb..adb5401 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1992,10 +1992,10 @@ elif "$HAS_FREEBSDDATE"; then # FreeBSD, OS X and newer (~6.6) OpenBSD vers LC_ALL=C TZ=GMT date -j -f "$3" "$2" "$1" } elif "$HAS_OPENBSDDATE"; then -# We bascially echo it as a conversion as we want it is too difficult. Approach for that would be: -# printf '%s\n' "$1" | awk '{ printf "%04d%02d%02d\n", $4, $2, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3}' -# 4: year, 1: month, 2: day, $3: time (e.g. "Dec 8 10:16:13 2016") -# This way we could also kind of convert args to epoch but as newer OpenBSDs "date" behave like FreeBSD + # We bascially echo it as a conversion as we want it is too difficult. Approach for that would be: + # printf '%s\n' "$1" | awk '{ printf "%04d%02d%02d\n", $4, $2, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3}' + # 4: year, 1: month, 2: day, $3: time (e.g. "Dec 8 10:16:13 2016") + # This way we could also kind of convert args to epoch but as newer OpenBSDs "date" behave like FreeBSD parse_date() { local tmp="" if [[ $2 == +%s* ]]; then @@ -2011,8 +2011,9 @@ else } fi -# arg1: An ASCII-HEX string -# Print $arg1 in binary format + +# Print $arg1 in binary format. arg1: An ASCII-HEX string +# asciihex_to_binary() { local string="$1" local -i len @@ -8348,7 +8349,6 @@ determine_dates_certificate() { startdate="${startdate%%GMT*}GMT" enddate="${cert_txt#*Validity*Not Before: *Not After : }" enddate="${enddate%%GMT*}GMT" - debugme echo "$enddate - $startdate" # Now we have a normalized enddate and startdate like "Feb 27 10:03:20 2017 GMT" -- also for OpenBSD if "$HAS_OPENBSDDATE"; then # Best we want to do under old versions of OpenBSD, first just remove the GMT and keep start/endate for later output @@ -8866,49 +8866,13 @@ certificate_info() { fi fi - out "$indent"; pr_bold " Issuer " - jsonID="cert_caIssuers" - #FIXME: oid would be better maybe (see above). And the line by line input could be done w/o awk + # Determine the issuer now as we need them for host certificate warning issuer="$($OPENSSL x509 -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE <<< "$hostcert")" issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")" issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")" issuer_C="$(awk -F'=' '/ C=/ { print $2 }' <<< "$issuer")" issuer_DC="$(awk -F'=' '/DC=/ { print $2 }' <<< "$issuer")" - if [[ "$issuer_O" == issuer= ]] || [[ "$issuer_O" == issuer=\ ]] || [[ "$issuer_CN" == "$cn" ]]; then - prln_svrty_critical "self-signed (NOT ok)" - fileout "${jsonID}${json_postfix}" "CRITICAL" "selfsigned" - set_grade_cap "T" "Self-signed certificate" - else - issuerfinding="$issuer_CN" - pr_italic "$issuer_CN" - if [[ -z "$issuer_O" ]] && [[ -n "$issuer_DC" ]]; then - for san in $issuer_DC; do - if [[ -z "$issuer_O" ]]; then - issuer_O="${san}" - else - issuer_O="${san}.${issuer_O}" - fi - done - fi - if [[ -n "$issuer_O" ]]; then - issuerfinding+=" (" - out " (" - issuerfinding+="$issuer_O" - pr_italic "$issuer_O" - if [[ -n "$issuer_C" ]]; then - issuerfinding+=" from " - out " from " - issuerfinding+="$issuer_C" - pr_italic "$issuer_C" - fi - issuerfinding+=")" - out ")" - fi - outln - fileout "${jsonID}${json_postfix}" "INFO" "$issuerfinding" - fi - out "$indent"; pr_bold " Trust (hostname) " compare_server_name_to_cert "$HOSTCERT" trust_sni=$? @@ -9054,7 +9018,7 @@ certificate_info() { out "no " fileout "${jsonID}${json_postfix}" "INFO" "no" fi - debugme echo "($(newline_to_spaces "$policy_oid"))" + debugme1 echo -n "($(newline_to_spaces "$policy_oid"))" outln #TODO: check browser OIDs: # https://dxr.mozilla.org/mozilla-central/source/security/certverifier/ExtendedValidation.cpp @@ -9072,7 +9036,7 @@ certificate_info() { days2warn1=$((days2warn1 / 2)) fi - debugme echo -n "diffseconds: $diffseconds" + debugme echo -n "(diffseconds: $diffseconds)" if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then pr_svrty_critical "expired" expfinding="expired" @@ -9154,7 +9118,7 @@ certificate_info() { else # All is fine with validity period # We ignore for now certificates < 2018/03/01. On the screen we only show debug info - debugme1 outln "${spaces}DEBUG: all is fine with total certificate life time" + debugme1 echo "${spaces}DEBUG: all is fine with total certificate life time" fileout "cert_extlifeSpan${json_postfix}" "OK" "certificate has no extended life time according to browser forum" fi @@ -9298,7 +9262,7 @@ certificate_info() { caa_node=${caa_node#*.} done if [[ -n "$caa" ]]; then - pr_svrty_good "available"; out " - please check for match with \"Issuer\" above" + pr_svrty_good "available"; out " - please check for match with \"Issuer\" below" if [[ $(count_lines "$caa") -eq 1 ]]; then out ": " else @@ -9344,6 +9308,44 @@ certificate_info() { outln fi + out "$indent"; pr_bold " Issuer " + jsonID="cert_caIssuers" + + if [[ "$issuer_O" == issuer= ]] || [[ "$issuer_O" == issuer=\ ]] || [[ "$issuer_CN" == "$cn" ]]; then + prln_svrty_critical "self-signed (NOT ok)" + fileout "${jsonID}${json_postfix}" "CRITICAL" "selfsigned" + set_grade_cap "T" "Self-signed certificate" + else + issuerfinding="$issuer_CN" + pr_italic "$issuer_CN" + if [[ -z "$issuer_O" ]] && [[ -n "$issuer_DC" ]]; then + for san in $issuer_DC; do + if [[ -z "$issuer_O" ]]; then + issuer_O="${san}" + else + issuer_O="${san}.${issuer_O}" + fi + done + fi + if [[ -n "$issuer_O" ]]; then + issuerfinding+=" (" + out " (" + issuerfinding+="$issuer_O" + pr_italic "$issuer_O" + if [[ -n "$issuer_C" ]]; then + issuerfinding+=" from " + out " from " + issuerfinding+="$issuer_C" + pr_italic "$issuer_C" + fi + issuerfinding+=")" + out ")" + fi + outln + fileout "${jsonID}${json_postfix}" "INFO" "$issuerfinding" + fi + + # Now we take care of the intermediate certificates. We basically (should) have them on disk # as "intermediatecerts.pem" (which could be split into intermediatecert1.crt, intermediatecert2.crt, ..) # However we do this in RAM which is better as it was passed to this function. @@ -9388,7 +9390,7 @@ certificate_info() { pr_svrty_medium "$cn_finding" expok="MEDIUM" else - cn_finding="valid > 40 days" + cn_finding="ok > 40 days" pr_svrty_good "$cn_finding" expok="OK" fi From 4ca4e075a287dfafb35b919d8d43565ef8a12a4e Mon Sep 17 00:00:00 2001 From: Dirk Date: Fri, 2 Oct 2020 13:07:13 +0200 Subject: [PATCH 8/8] Use test::diff so that errors are spotted better --- t/08_isHTML_valid.t | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/t/08_isHTML_valid.t b/t/08_isHTML_valid.t index 48139b7..294661a 100755 --- a/t/08_isHTML_valid.t +++ b/t/08_isHTML_valid.t @@ -6,6 +6,7 @@ use strict; use Test::More; use Data::Dumper; +use Text::Diff; my $tests = 0; my $prg="./testssl.sh"; @@ -15,7 +16,7 @@ my $html=""; my $debughtml=""; my $edited_html=""; my $check2run="--ip=one --color 0 --htmlfile tmp.html"; - +my $diff=""; die "Unable to open $prg" unless -f $prg; printf "\n%s\n", "Doing HTML output checks"; @@ -47,6 +48,9 @@ $edited_html =~ s/'/'/g; cmp_ok($edited_html, "eq", $out, "HTML file matches terminal output"); $tests++; +$diff = diff \$edited_html, \$out; +printf "\n%s\n", "$diff"; + #2 printf "\n%s\n", " .. running again $prg against \"$uri\", now with --debug 4 to create HTML output (may take another ~2 minutes)"; # Redirect stderr to /dev/null in order to avoid some unexplained "date: invalid date" error messages @@ -72,5 +76,10 @@ $debughtml =~ s/.*DEBUG:.*\n//g; cmp_ok($debughtml, "eq", $html, "HTML file created with --debug 4 matches HTML file created without --debug"); $tests++; +$diff = diff \$debughtml, \$html; +printf "\n%s\n", "$diff"; + + + printf "\n"; done_testing($tests);