From e4cef5438dfa2ed242b4faceccc6002fab87a492 Mon Sep 17 00:00:00 2001 From: Magnus Larsen <[]> Date: Wed, 15 Apr 2020 15:06:08 +0200 Subject: [PATCH 01/22] Added grading based on ssllabs --- doc/testssl.1.md | 53 +++++- testssl.sh | 446 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 479 insertions(+), 20 deletions(-) diff --git a/doc/testssl.1.md b/doc/testssl.1.md index f347c20..696d9f3 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -56,6 +56,8 @@ linked OpenSSL binaries for major operating systems are supplied in `./bin/`. 9) client simulation +10) Result of script in form of a grade + ## OPTIONS AND PARAMETERS @@ -142,8 +144,7 @@ in `/etc/hosts`. The use of the switch is only useful if you either can't or ar `--phone-out` Checking for revoked certificates via CRL and OCSP is not done per default. This switch instructs testssl.sh to query external -- in a sense of the current run -- URIs. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl.sh doesn't handle. PHONE_OUT is the environment variable for this which needs to be set to true if you want this. -`--add-ca ` enables you to add your own CA(s) for trust chain checks. `cafile` can be a single path or multiple paths as a comma separated list of root CA files. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates is issued by internal CAs. Alternatively -ADDITIONAL_CA_FILES is the environment variable for this. +`--add-ca ` enables you to add your own CA(s) for trust chain checks. `cafile` can be a single path or multiple paths as a comma separated list of root CA files. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates is issued by internal CAs. Alternatively ADDITIONAL_CA_FILES is the environment variable for this. ### SINGLE CHECK OPTIONS @@ -286,6 +287,8 @@ Please note that in testssl.sh 3,0 you can still use `rfc` instead of `iana` and 5. display bytes received via sockets 6. whole 9 yards +`--disable-grading` disables grading explicitly. +Grading automatically gets disabled, to not give a wrong or misleading grade, when not all required functions are executed (e.g when checking for a single vulnerabilities). `DISABLE_GRADING` is the according environment variable which you can use. ### FILE OUTPUT OPTIONS @@ -383,13 +386,56 @@ Except the environment variables mentioned above which can replace command line * MAX_OSSL_FAIL: A number which tells testssl.sh how often an OpenSSL s_client connect may fail before the program gives up and terminates. The default is 2. You can increase it to a higher value if you frequently see a message like *Fatal error: repeated TCP connect problems, giving up*. * MAX_HEADER_FAIL: A number which tells testssl.sh how often a HTTP GET request over OpenSSL may return an empty file before the program gives up and terminates. The default is 3. Also here you can incerase the threshold when you spot messages like *Fatal error: repeated HTTP header connect problems, doesn't make sense to continue*. +### GRADING +This script has a near-complete implementation of SSLLabs's '[SSL Server Rating Guide](https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide)'. +This is *not* a reimplementation of the [SSLLab's SSL Server Test](https://www.ssllabs.com/ssltest/analyze.html), but a implementation of the above grading specification, slight discrepancies might occur! + +Disclaimer: Having a good grade does **NOT** necessary equal to having good security! Never rely solely on a good grade! + +As of writing, these checks are missing: +* Authenticated encryption (AEAD) - should be graded **B** if not supported +* GOLDENDOODLE - should be graded **F** if vulnerable +* Insecure renegotiation - should be graded **F** if vulnerable +* Padding oracle in AES-NI CBC MAC check (CVE-2016-2107) - should be graded **F** if vulnerable +* Sleeping POODLE - should be graded **F** if vulnerable +* Zero Length Padding Oracle (CVE-2019-1559) - should be graded **F** if vulnerable +* Zombie POODLE - should be graded **F** if vulnerable +* All remaining old Symantec PKI certificates are distrusted - should be graded **T** +* Symantec certificates issued before June 2016 are distrusted - should be graded **T** +* ! A reading of DH params - should give correct points in `set_key_str_score()` +* Anonymous key exchange - should give **0** points in `set_key_str_score()` +* Exportable key exchange - should give **40** points in `set_key_str_score()` +* Weak key (Debian OpenSSL Flaw) - should give **0** points in `set_key_str_score()` + +#### Implementing new grades caps or -warnings +To implement at new grading cap, simply call the `set_grade_cap()` function, with the grade and a reason: +```bash +set_grade_cap "D" "Vulnerable to documentation" +``` +To implement a new grade warning, simply call the `set_grade_warning()` function, with a message: +```bash +set_grade_warning "Documentation is always right" +``` +#### Implementing a new check which contains grade caps +When implementing a new check (be it vulnerability or not) that sets grade caps, the `set_grading_state()` has to be updated (i.e. the `$do_mycheck` variable-name has to be added to the loop, and `$nr_enabled` if-statement has to be incremented) + +The `set_grading_state()` automatically disables grading, if all the required checks are *not* enabled. +This is to prevent giving out a misleading or wrong grade. + +#### Implementing a new revision +When a new revision of the grading specification comes around, the following has to be done: +* New grade caps has to be either: + 1. Added to the script wherever relevant, or + 2. Added to the above list of missing checks (if *i.* is not possible) +* New grade warnings has to be added wherever relevant +* The revision output in `run_grading()` function has to updated ## EXAMPLES testssl.sh testssl.sh -does a default run on https://testssl.sh (protocols, standard cipher lists, FS, server preferences, server defaults, vulnerabilities, testing all known 370 ciphers, client simulation. +does a default run on https://testssl.sh (protocols, standard cipher lists, FS, server preferences, server defaults, vulnerabilities, testing all known 370 ciphers, client simulation, and grading. testssl.sh testssl.net:443 @@ -508,4 +554,3 @@ Probably. Current known ones and interface for filing new ones: https://testssl. ## SEE ALSO `ciphers`(1), `openssl`(1), `s_client`(1), `x509`(1), `verify`(1), `ocsp`(1), `crl`(1), `bash`(1) and the websites https://testssl.sh/ and https://github.com/drwetter/testssl.sh/ . - diff --git a/testssl.sh b/testssl.sh index d5d7ee7..c540e33 100755 --- a/testssl.sh +++ b/testssl.sh @@ -227,6 +227,7 @@ fi DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) declare -r UA_STD="TLS tester from $SWURL" declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" +DISABLE_GRADING=${DISABLE_GRADING:-false} # Whether to disable grading, or not ########### Initialization part, further global vars just being declared here # @@ -372,6 +373,13 @@ SERVER_COUNTER=0 # Counter for multiple servers TLS_LOW_BYTE="" # For "secret" development stuff, see -q below HEX_CIPHER="" # " +GRADE_CAP="" # Keeps track of the current grading cap +GRADE_CAP_REASONS=() # Keeps track of all the reasons why grades are capped +GRADE_WARNINGS=() # Keeps track of all the grade warnings +KEY_EXCH_SCORE=0 # Keeps track of the score for category 2 "Key Exchange Strength" +CIPH_STR_BEST=0 # Keeps track of the best bit size for category 3 "Cipher Strength" +CIPH_STR_WORST=100000 # Keeps track of the worst bit size for category 3 "Cipher Strength" + # Intentionally set very high, so it can be set to 0, if necessary ########### Global variables for parallel mass testing # @@ -982,6 +990,105 @@ f5_port_decode() { echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec } +# Sets the grade cap to ARG1 +# arg1: A grade to set ("A", "B", "C", "D", "E", "F", "M", or "T") +# arg2: A reason why (e.g. "Vulnerable to CRIME") +set_grade_cap() { + # Do nothing if disabled + "$DISABLE_GRADING" && return 0 + + GRADE_CAP_REASONS+=("Grade capped to $1. $2") + + # Always set special attributes. These are hard caps, due to name mismatch or cert being invalid + if [[ "$1" == "T" || "$1" == "M" ]]; then + GRADE_CAP=$1 + # Only keep track of the lowest grade cap, since a higher grade cap wont do anything (F = lowest, A = highest) + elif [[ ! "$GRADE_CAP" > "$1" ]]; then + GRADE_CAP=$1 + fi + + return 0 +} + +# Sets a grade warning, as specified by the grade specification +# arg1: A warning message +set_grade_warning() { + # Do nothing if disabled + "$DISABLE_GRADING" && return 0 + + GRADE_WARNINGS+=("$1") + + return 0 +} + +# Sets the score for Category 2 (Key Exchange Strength) +# arg1: Short key algorithm ("EC", "DH", "RSA", ...) # Can die, when we get DH_PARAMs +# arg2: key size (number of bits) +set_key_str_score() { + local type=$1 + local size=$2 + + # Do nothing if disabled + "$DISABLE_GRADING" && return 0 + + # TODO: We need to get the size of DH params (follows the same table as the "else" clause) + # For now, verifying the key size will do... + if [[ $type == "EC" || $type == "DH" ]]; then + if [[ $size -lt 110 ]]; then + let KEY_EXCH_SCORE=20 + set_grade_cap "F" "Using a insecure key" + elif [[ $size -lt 123 ]]; then + let KEY_EXCH_SCORE=40 + set_grade_cap "F" "Using a insecure key" + elif [[ $size -lt 163 ]]; then + let KEY_EXCH_SCORE=80 + set_grade_cap "B" "Using a weak key" + elif [[ $size -lt 225 ]]; then + let KEY_EXCH_SCORE=90 + elif [[ $size -ge 225 ]]; then + let KEY_EXCH_SCORE=100 + else + let KEY_EXCH_SCORE=0 + set_grade_cap "F" "Using a insecure key" + fi + else + if [[ $size -lt 512 ]]; then + let KEY_EXCH_SCORE=20 + set_grade_cap "F" "Using a insecure key" + elif [[ $size -lt 1024 ]]; then + let KEY_EXCH_SCORE=40 + set_grade_cap "F" "Using a insecure key" + elif [[ $size -lt 2048 ]]; then + let KEY_EXCH_SCORE=80 + set_grade_cap "B" "Using a weak key" + elif [[ $size -lt 4096 ]]; then + let KEY_EXCH_SCORE=90 + elif [[ $size -ge 4096 ]]; then + let KEY_EXCH_SCORE=100 + else + let KEY_EXCH_SCORE=0 + set_grade_cap "F" "Using a insecure key" + fi + fi + + return 0 +} + +# Sets the best and worst bit size key, used to grade Category 3 (Cipher Strength) +# This function itself doesn't actually set a score; its just in the name to keep it logical (score == grading function) +# arg1: a bit size +set_ciph_str_score() { + local size=$1 + + # Do nothing if disabled + "$DISABLE_GRADING" && return 0 + + [[ $size -gt $CIPH_STR_BEST ]] && let CIPH_STR_BEST=$size + [[ $size -lt $CIPH_STR_WORST ]] && let CIPH_STR_WORST=$size + + return 0 +} + ###### START ServerHello/OpenSSL/F5 function definitions ###### ###### END helper function definitions ###### @@ -1756,12 +1863,14 @@ check_revocation_crl() { out ", " pr_svrty_critical "revoked" fileout "$jsonID" "CRITICAL" "revoked" + set_grade_cap "T" "Certificate revoked" else retcode="$(verify_retcode_helper "$retcode")" out " $retcode" retcode="${retcode#(}" retcode="${retcode%)}" fileout "$jsonID" "WARN" "$retcode" + set_grade_cap "T" "Issues with certificate $retcode" if [[ $DEBUG -ge 2 ]]; then outln cat "${tmpfile%%.crl}.err" @@ -1823,6 +1932,7 @@ check_revocation_ocsp() { out ", " pr_svrty_critical "revoked" fileout "$jsonID" "CRITICAL" "revoked" + set_grade_cap "T" "Certificate revoked" else out ", " pr_warning "error querying OCSP responder" @@ -2441,15 +2551,18 @@ run_hsts() { if [[ $hsts_age_days -eq -1 ]]; then pr_svrty_medium "misconfiguration: HSTS max-age (recommended > $HSTS_MIN seconds = $((HSTS_MIN/86400)) days ) is required but missing" fileout "${jsonID}_time" "MEDIUM" "misconfiguration, parameter max-age (recommended > $HSTS_MIN seconds = $((HSTS_MIN/86400)) days) missing" + set_grade_cap "A" "HSTS max-age is misconfigured" elif [[ $hsts_age_sec -eq 0 ]]; then pr_svrty_low "HSTS max-age is set to 0. HSTS is disabled" fileout "${jsonID}_time" "LOW" "0. HSTS is disabled" + set_grade_cap "A" "HSTS is disabled" elif [[ $hsts_age_sec -gt $HSTS_MIN ]]; then pr_svrty_good "$hsts_age_days days" ; out "=$hsts_age_sec s" fileout "${jsonID}_time" "OK" "$hsts_age_days days (=$hsts_age_sec seconds) > $HSTS_MIN seconds" else pr_svrty_medium "$hsts_age_sec s = $hsts_age_days days is too short ( > $HSTS_MIN seconds recommended)" fileout "${jsonID}_time" "MEDIUM" "max-age too short. $hsts_age_days days (=$hsts_age_sec seconds) <= $HSTS_MIN seconds" + set_grade_cap "A" "HSTS max-age is too short" fi if includeSubDomains "$TMPFILE"; then fileout "${jsonID}_subdomains" "OK" "includes subdomains" @@ -2467,6 +2580,7 @@ run_hsts() { else pr_svrty_low "not offered" fileout "$jsonID" "LOW" "not offered" + set_grade_cap "A" "HSTS is not offered" fi outln @@ -2502,6 +2616,7 @@ run_hpkp() { first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE | head -1)" # we only evaluate the keys here, unless they a not present out "$spaces " + set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)" elif [[ $(grep -aci '^Public-Key-Pins-Report-Only:' $TMPFILE) -gt 1 ]]; then outln "Multiple HPKP headers (Report-Only), taking first line" fileout "HPKP_notice" "INFO" "multiple Public-Key-Pins-Report-Only in header" @@ -2528,6 +2643,7 @@ run_hpkp() { if [[ $hpkp_nr_keys -eq 1 ]]; then 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" + set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)" else pr_svrty_good "$hpkp_nr_keys" out " keys, " @@ -2548,6 +2664,7 @@ run_hpkp() { 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" "age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough." + set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)" fi if includeSubDomains "$TMPFILE"; then @@ -2705,11 +2822,13 @@ run_hpkp() { "$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. Bricked site?" + set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)" fi if ! "$has_backup_spki"; then prln_svrty_high " No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site. " fileout "HPKP_backup" "HIGH" "No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site." + set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)" fi else outln "--" @@ -3326,6 +3445,9 @@ neat_list(){ enc="${enc//POLY1305/}" # remove POLY1305 enc="${enc//\//}" # remove "/" + # For grading, set bits size + set_ciph_str_score $strength + [[ "$export" =~ export ]] && strength="$strength,exp" [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && tls_cipher="$(show_rfc_style "$hexcode")" @@ -4986,6 +5108,7 @@ run_protocols() { if [[ "$lines" -gt 1 ]]; then nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3)) add_tls_offered ssl2 yes + set_grade_cap "F" "SSLv2 is offered" if [[ 0 -eq "$nr_ciphers_detected" ]]; then prln_svrty_high "supported but couldn't detect a cipher and vulnerable to CVE-2015-3197 "; fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310" @@ -5007,6 +5130,7 @@ run_protocols() { 0) prln_svrty_critical "offered (NOT ok)" fileout "$jsonID" "CRITICAL" "offered" add_tls_offered ssl2 yes + set_grade_cap "F" "SSLv2 is offered" ;; 1) prln_svrty_best "not offered (OK)" fileout "$jsonID" "OK" "not offered" @@ -5015,6 +5139,7 @@ run_protocols() { 5) prln_svrty_high "CVE-2015-3197: $supported_no_ciph2"; fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310" add_tls_offered ssl2 yes + set_grade_cap "F" "SSLv2 is offered" ;; 7) prln_local_problem "$OPENSSL doesn't support \"s_client -ssl2\"" fileout "$jsonID" "INFO" "not tested due to lack of local support" @@ -5042,6 +5167,7 @@ run_protocols() { latest_supported_string="SSLv3" fi add_tls_offered ssl3 yes + set_grade_cap "B" "SSLv3 is offered" ;; 1) prln_svrty_best "not offered (OK)" fileout "$jsonID" "OK" "not offered" @@ -5077,6 +5203,7 @@ run_protocols() { 5) pr_svrty_high "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl fileout "$jsonID" "HIGH" "$supported_no_ciph1" add_tls_offered ssl3 yes + set_grade_cap "B" "SSLv3 is offered" ;; 7) if "$using_sockets" ; then # can only happen in debug mode @@ -5108,6 +5235,7 @@ run_protocols() { latest_supported="0301" latest_supported_string="TLSv1.0" add_tls_offered tls1 yes + set_grade_cap "B" "TLS1.0 offered" ;; # nothing wrong with it -- per se 1) out "not offered" add_tls_offered tls1 no @@ -5154,6 +5282,7 @@ run_protocols() { 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl fileout "$jsonID" "INFO" "$supported_no_ciph1" add_tls_offered tls1 yes + set_grade_cap "B" "TLS1.0 offered" ;; 7) if "$using_sockets" ; then # can only happen in debug mode @@ -5186,6 +5315,7 @@ run_protocols() { latest_supported="0302" latest_supported_string="TLSv1.1" add_tls_offered tls1_1 yes + set_grade_cap "B" "TLS1.1 offered" ;; # nothing wrong with it 1) out "not offered" add_tls_offered tls1_1 no @@ -5235,6 +5365,7 @@ run_protocols() { 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl fileout "$jsonID" "INFO" "$supported_no_ciph1" add_tls_offered tls1_1 yes + set_grade_cap "B" "TLS1.1 offered" ;; 7) if "$using_sockets" ; then # can only happen in debug mode @@ -5299,6 +5430,7 @@ run_protocols() { add_tls_offered tls1_2 yes ;; # GCM cipher in TLS 1.2: very good! 1) add_tls_offered tls1_2 no + set_grade_cap "C" "TLS1.2 is not offered" if "$offers_tls13"; then out "not offered" else @@ -5317,6 +5449,7 @@ run_protocols() { fi ;; 2) add_tls_offered tls1_2 no + set_grade_cap "C" "TLS1.2 is not offered" pr_svrty_medium "not offered and downgraded to a weaker protocol" if [[ "$tls12_detected_version" == 0300 ]]; then detected_version_string="SSLv3" @@ -5345,12 +5478,15 @@ run_protocols() { 3) out "not offered, " fileout "$jsonID" "INFO" "not offered" add_tls_offered tls1_2 no + set_grade_cap "C" "TLS1.2 is not offered" pr_warning "TLS downgraded to STARTTLS plaintext"; outln fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" + set_grade_cap "C" "TLS1.2 is not offered" ;; 4) out "likely "; pr_svrty_medium "not offered, " fileout "$jsonID" "MEDIUM" "not offered" add_tls_offered tls1_2 no + set_grade_cap "C" "TLS1.2 is not offered" pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" ;; @@ -5717,6 +5853,8 @@ sub_cipherlists() { ((ret++)) ;; esac + + [[ $sclient_success -eq 0 && "$1" =~ (^|:)EXPORT(:|$) ]] && set_grade_cap "F" "Export suite offered" fi tmpfile_handle ${FUNCNAME[0]}.${5}.txt [[ $DEBUG -ge 1 ]] && tm_out " -- $1" @@ -6991,17 +7129,17 @@ verify_retcode_helper() { case $retcode in # codes from ./doc/apps/verify.pod | verify(1ssl) - 44) tm_out "(different CRL scope)" ;; # X509_V_ERR_DIFFERENT_CRL_SCOPE - 26) tm_out "(unsupported certificate purpose)" ;; # X509_V_ERR_INVALID_PURPOSE - 24) tm_out "(certificate unreadable)" ;; # X509_V_ERR_INVALID_CA - 23) tm_out "(certificate revoked)" ;; # X509_V_ERR_CERT_REVOKED - 21) tm_out "(chain incomplete, only 1 cert provided)" ;; # X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE - 20) tm_out "(chain incomplete)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY - 19) tm_out "(self signed CA in chain)" ;; # X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN - 18) tm_out "(self signed)" ;; # X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT - 10) tm_out "(expired)" ;; # X509_V_ERR_CERT_HAS_EXPIRED - 9) tm_out "(not yet valid)" ;; # X509_V_ERR_CERT_NOT_YET_VALID - 2) tm_out "(issuer cert missing)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT + 44) tm_out "(different CRL scope)" ;; # X509_V_ERR_DIFFERENT_CRL_SCOPE + 26) tm_out "(unsupported certificate purpose)" ;; # X509_V_ERR_INVALID_PURPOSE + 24) tm_out "(certificate unreadable)" ;; # X509_V_ERR_INVALID_CA + 23) tm_out "(certificate revoked)" ;; # X509_V_ERR_CERT_REVOKED + 21) tm_out "(chain incomplete, only 1 cert provided)" ;; # X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE + 20) tm_out "(chain incomplete)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY + 19) tm_out "(self signed CA in chain)" ;; # X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN + 18) tm_out "(self signed)" ;; # X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT + 10) tm_out "(expired)" ;; # X509_V_ERR_CERT_HAS_EXPIRED + 9) tm_out "(not yet valid)" ;; # X509_V_ERR_CERT_NOT_YET_VALID + 2) tm_out "(issuer cert missing)" ;; # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT *) ret=1 ; tm_out " (unknown, pls report) $1" ;; esac return $ret @@ -7097,6 +7235,7 @@ determine_trust() { out "$code" fi fileout "${jsonID}${json_postfix}" "CRITICAL" "failed $code. $addtl_warning" + set_grade_cap "T" "Issues with certificate $code" else # is one ok and the others not ==> display the culprit store if "$some_ok"; then @@ -7115,6 +7254,7 @@ determine_trust() { out "$code" fi notok_was="${certificate_file[i]} $code $notok_was" + set_grade_cap "T" "Issues with certificate $code" fi done #pr_svrty_high "$notok_was " @@ -8232,6 +8372,7 @@ certificate_info() { fi outln fileout "${jsonID}${json_postfix}" "MEDIUM" "SHA1 with RSA" + set_grade_cap "T" "Uses SHA1 algorithm" ;; sha224WithRSAEncryption) outln "SHA224 with RSA" @@ -8252,6 +8393,7 @@ certificate_info() { ecdsa-with-SHA1) prln_svrty_medium "ECDSA with SHA1" fileout "${jsonID}${json_postfix}" "MEDIUM" "ECDSA with SHA1" + set_grade_cap "T" "Uses SHA1 algorithm" ;; ecdsa-with-SHA224) outln "ECDSA with SHA224" @@ -8272,6 +8414,7 @@ certificate_info() { dsaWithSHA1) prln_svrty_medium "DSA with SHA1" fileout "${jsonID}${json_postfix}" "MEDIUM" "DSA with SHA1" + set_grade_cap "T" "Uses SHA1 algorithm" ;; dsa_with_SHA224) outln "DSA with SHA224" @@ -8287,6 +8430,7 @@ certificate_info() { sha1) prln_svrty_medium "RSASSA-PSS with SHA1" fileout "${jsonID}${json_postfix}" "MEDIUM" "RSASSA-PSS with SHA1" + set_grade_cap "T" "Uses SHA1 algorithm" ;; sha224) outln "RSASSA-PSS with SHA224" @@ -8313,6 +8457,7 @@ certificate_info() { md2*) prln_svrty_critical "MD2" fileout "${jsonID}${json_postfix}" "CRITICAL" "MD2" + set_grade_cap "F" "Supports a insecure signature (MD2)" ;; md4*) prln_svrty_critical "MD4" @@ -8321,6 +8466,7 @@ certificate_info() { md5*) prln_svrty_critical "MD5" fileout "${jsonID}${json_postfix}" "CRITICAL" "MD5" + set_grade_cap "F" "Supports a insecure signature (MD5)" ;; *) out "$cert_sig_algo (" @@ -8375,6 +8521,8 @@ certificate_info() { ((ret++)) fi outln " bits" + + set_key_str_score "$short_keyAlgo" "$cert_keysize" # TODO: should be $dh_param_size elif [[ $cert_key_algo =~ RSA ]] || [[ $cert_key_algo =~ rsa ]] || [[ $cert_key_algo =~ dsa ]] || \ [[ $cert_key_algo =~ dhKeyAgreement ]] || [[ $cert_key_algo == X9.42\ DH ]]; then if [[ "$cert_keysize" -le 512 ]]; then @@ -8401,6 +8549,8 @@ certificate_info() { fileout "${jsonID}${json_postfix}" "WARN" "$cert_keysize bits (Odd)" ((ret++)) fi + + set_key_str_score "$short_keyAlgo" "$cert_keysize" else out "$cert_key_algo + $cert_keysize bits (" pr_warning "FIXME: can't tell whether this is good or not" @@ -8567,6 +8717,7 @@ certificate_info() { 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" @@ -8610,7 +8761,9 @@ certificate_info() { has_dns_sans=$HAS_DNS_SANS case $trust_sni in - 0) trustfinding="certificate does not match supplied URI" ;; + 0) trustfinding="certificate does not match supplied URI" + set_grade_cap "M" "Domain name mismatch" + ;; 1) trustfinding="Ok via SAN" ;; 2) trustfinding="Ok via SAN wildcard" ;; 4) if "$has_dns_sans"; then @@ -8715,6 +8868,7 @@ certificate_info() { # Shortcut for this special case here. pr_italic "WoSign/StartCom"; out " are " ; prln_svrty_critical "not trusted anymore (NOT ok)" fileout "${jsonID}${json_postfix}" "CRITICAL" "Issuer not trusted anymore (WoSign/StartCom)" + set_grade_cap "T" "Untrusted certificate chain" else # Also handles fileout, keep error if happened determine_trust "$jsonID" "$json_postfix" || ((ret++)) @@ -8807,6 +8961,7 @@ certificate_info() { 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) @@ -9603,6 +9758,7 @@ run_fs() { outln prln_svrty_medium " No ciphers supporting Forward Secrecy offered" fileout "$jsonID" "MEDIUM" "No ciphers supporting (P)FS offered" + set_grade_cap "B" "Forward Security (PFS) is not supported" else outln fs_offered=true @@ -14973,6 +15129,7 @@ run_heartbleed(){ else pr_svrty_critical "VULNERABLE (NOT ok)" fileout "$jsonID" "CRITICAL" "VULNERABLE $cve" "$cwe" "$hint" + set_grade_cap "F" "Vulnerable to Heartbleed" fi else pr_svrty_best "not vulnerable (OK)" @@ -15131,6 +15288,7 @@ run_ccs_injection(){ # decryption failed received pr_svrty_critical "VULNERABLE (NOT ok)" fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" + set_grade_cap "F" "Vulnerable to CCS injection" elif [[ "$byte6" == "0A" ]] || [[ "$byte6" == "28" ]]; then # Unexpected message / Handshake failure received pr_warning "likely " @@ -15428,6 +15586,7 @@ run_ticketbleed() { if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then pr_svrty_critical "VULNERABLE (NOT ok)" fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" + set_grade_cap "F" "Vulnerable to Ticketbleed" else pr_svrty_best "not vulnerable (OK)" out ", session IDs were returned but potential memory fragments do not differ" @@ -15485,6 +15644,7 @@ run_renego() { case $sec_renego in 0) prln_svrty_critical "Not supported / VULNERABLE (NOT ok)" fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint" + set_grade_warning "Secure renegotiation is not supported" ;; 1) prln_svrty_best "supported (OK)" fileout "$jsonID" "OK" "supported" "$cve" "$cwe" @@ -15668,6 +15828,7 @@ run_crime() { # not clear whether a protocol != HTTP offers the ability to repeatedly modify the input # which is done e.g. via javascript in the context of HTTP fi + set_grade_cap "C" "Vulnerable to CRIME" fi outln @@ -15800,6 +15961,7 @@ run_sweet32() { local -i nr_sweet32_ciphers=0 nr_supported_ciphers=0 nr_ssl2_sweet32_ciphers=0 nr_ssl2_supported_ciphers=0 local ssl2_sweet=false local using_sockets=true + local tls1_1_vulnable=false [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SWEET32 (Birthday Attacks on 64-bit Block Ciphers) " && outln pr_bold " SWEET32"; out " (${cve// /, }) " @@ -15860,6 +16022,7 @@ run_sweet32() { sclient_connect_successful $? $TMPFILE sclient_success=$? [[ $DEBUG -ge 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" + [[ $proto == -tls1_1 && $sclient_success -eq 0 ]] && tls1_1_vulnable=true [[ $sclient_success -eq 0 ]] && break done if "$HAS_SSL2"; then @@ -15879,9 +16042,11 @@ run_sweet32() { if [[ $sclient_success -eq 0 ]] && "$ssl2_sweet" ; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers for SSLv2 and above" fileout "SWEET32" "LOW" "uses 64 bit block ciphers for SSLv2 and above" "$cve" "$cwe" "$hint" + "$tls1_1_vulnable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS1.1+ (vulnerable to SWEET32)" elif [[ $sclient_success -eq 0 ]]; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers" fileout "SWEET32" "LOW" "uses 64 bit block ciphers" "$cve" "$cwe" "$hint" + "$tls1_1_vulnable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS1.1+ (vulnerable to SWEET32)" elif "$ssl2_sweet"; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers wth SSLv2 only" fileout "SWEET32" "LOW" "uses 64 bit block ciphers with SSLv2 only" "$cve" "$cwe" "$hint" @@ -15963,6 +16128,7 @@ run_ssl_poodle() { POODLE=0 pr_svrty_high "VULNERABLE (NOT ok)"; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)" fileout "$jsonID" "HIGH" "VULNERABLE, uses SSLv3+CBC" "$cve" "$cwe" "$hint" + set_grade_cap "C" "Vulnerable to POODLE" else POODLE=1 pr_svrty_best "not vulnerable (OK)"; @@ -15993,6 +16159,8 @@ run_tls_poodle() { #FIXME prln_warning "#FIXME" fileout "$jsonID" "WARN" "Not yet implemented #FIXME" "$cve" "$cwe" + # set_grade_cap "F" "Vulnerable to POODLE TLS" + return 0 } @@ -16021,6 +16189,7 @@ run_tls_fallback_scsv() { if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then prln_svrty_critical "No fallback possible, SSLv2 is the only protocol" fileout "$jsonID" "CRITICAL" "SSLv2 is the only protocol" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 fi for p in tls1_2 tls1_1 tls1 ssl3; do @@ -16049,6 +16218,7 @@ run_tls_fallback_scsv() { "ssl3") prln_svrty_high "No fallback possible, SSLv3 is the only protocol" fileout "$jsonID" "HIGH" "only SSLv3 supported" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 ;; *) if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then @@ -16056,6 +16226,7 @@ run_tls_fallback_scsv() { # then assume it does not support SSLv3, even if SSLv3 cannot be tested. pr_svrty_good "No fallback possible (OK)"; outln ", TLS 1.3 is the only protocol" fileout "$jsonID" "OK" "only TLS 1.3 supported" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" elif [[ $(has_server_protocol tls1_3) -eq 1 ]] && \ ( [[ $(has_server_protocol ssl3) -eq 1 ]] || "$HAS_SSL3" ); then # TLS 1.3, TLS 1.2, TLS 1.1, TLS 1, and SSLv3 are all not supported. @@ -16069,6 +16240,7 @@ run_tls_fallback_scsv() { # it is very likely that SSLv3 is the only supported protocol. pr_svrty_high "NOT ok, no fallback possible"; outln ", TLS 1.3, 1.2, 1.1 and 1.0 not supported" fileout "$jsonID" "HIGH" "TLS 1.3, 1.2, 1.1, 1.0 not supported" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" else # TLS 1.2, TLS 1.1, and TLS 1 are not supported, but can't tell whether TLS 1.3 is supported. # This could be a TLS 1.3 only server, an SSLv3 only server (if SSLv3 support cannot be tested), @@ -16076,6 +16248,7 @@ run_tls_fallback_scsv() { # since this could either be good or bad. outln "No fallback possible, TLS 1.2, TLS 1.1, and TLS 1 not supported" fileout "$jsonID" "INFO" "TLS 1.2, TLS 1.1, and TLS 1 not supported" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" fi return 0 esac @@ -16120,6 +16293,7 @@ run_tls_fallback_scsv() { ;; esac fileout "$jsonID" "OK" "no protocol below $high_proto_str offered" + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 fi case "$low_proto" in @@ -16140,15 +16314,18 @@ run_tls_fallback_scsv() { if [[ -z "$POODLE" ]]; then pr_warning "Rerun including POODLE SSL check. " pr_svrty_medium "Downgrade attack prevention NOT supported" - fileout "$jsonID" "WARN" "NOT supported. Pls rerun wity POODLE SSL check" + fileout "$jsonID" "WARN" "NOT supported. Pls rerun with POODLE SSL check" ret=1 elif [[ "$POODLE" -eq 0 ]]; then pr_svrty_high "Downgrade attack prevention NOT supported and vulnerable to POODLE SSL" fileout "$jsonID" "HIGH" "NOT supported and vulnerable to POODLE SSL" + set_grade_cap "C" "Vulnerable to POODLE" else pr_svrty_medium "Downgrade attack prevention NOT supported" fileout "$jsonID" "MEDIUM" "NOT supported" fi + set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" + elif grep -qa "alert inappropriate fallback" "$TMPFILE"; then pr_svrty_good "Downgrade attack prevention supported (OK)" fileout "$jsonID" "OK" "supported" @@ -16493,6 +16670,7 @@ run_logjam() { if "$vuln_exportdh_ciphers"; then pr_svrty_high "VULNERABLE (NOT ok):"; out " uses DH EXPORT ciphers" fileout "$jsonID" "HIGH" "VULNERABLE, uses DH EXPORT ciphers" "$cve" "$cwe" "$hint" + set_grade_cap "B" "Uses weak DH key exchange parameters (vulnerable to LOGJAM)" if [[ $subret -eq 3 ]]; then out ", no DH key detected with <= TLS 1.2" fileout "$jsonID2" "OK" "no DH key detected with <= TLS 1.2" @@ -16508,6 +16686,7 @@ run_logjam() { else if [[ $subret -eq 1 ]]; then out_common_prime "$jsonID2" "$cve" "$cwe" + set_grade_cap "B" "Uses weak DH key exchange parameters (vulnerable to LOGJAM)" if ! "$openssl_no_expdhciphers"; then outln "," out "${spaces}but no DH EXPORT ciphers${addtl_warning}" @@ -16605,6 +16784,7 @@ run_drown() { else prln_svrty_critical "VULNERABLE (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers"; 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" + set_grade_cap "F" "Vulnerable to DROWN" fi outln "$spaces Make sure you don't use this certificate elsewhere, see:" out "$spaces " @@ -16918,6 +17098,7 @@ run_beast(){ pr_svrty_medium "VULNERABLE" outln " -- and no higher protocols as mitigation supported" fileout "$jsonID" "MEDIUM" "VULNERABLE -- and no higher protocols as mitigation supported" "$cve" "$cwe" "$hint" + set_grade_cap "B" "Vulnerable to BEAST" fi fi "$first" && ! "$vuln_beast" && prln_svrty_good "no CBC ciphers found for any protocol (OK)" @@ -17165,6 +17346,11 @@ run_rc4() { fi "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \ sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" + + # If you use RC4 with newer protocols, you are punished harder + if [[ "$proto" == "-tls1_1" ]]; then + set_grade_cap "C" "RC4 ciphers offered on TLS1.1" + fi done done @@ -17245,6 +17431,7 @@ run_rc4() { outln "$WIDE" && out " " && prln_svrty_high "VULNERABLE (NOT ok)" fileout "$jsonID" "HIGH" "VULNERABLE, Detected ciphers: $rc4_detected" "$cve" "$cwe" "$hint" + set_grade_cap "B" "RC4 ciphers offered" elif [[ $nr_ciphers -eq 0 ]]; then prln_local_problem "No RC4 Ciphers configured in $OPENSSL" fileout "$jsonID" "WARN" "RC4 ciphers not supported by local OpenSSL ($OPENSSL)" @@ -17889,6 +18076,7 @@ run_robot() { prln_svrty_critical "VULNERABLE (NOT ok)" fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" fi + set_grade_cap "F" "Vulnerable to ROBOT" else prln_svrty_best "not vulnerable (OK)" fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe" @@ -18383,6 +18571,7 @@ output options (can also be preset via environment variables): --color <0|1|2|3> 0: no escape or other codes, 1: b/w escape codes, 2: color (default), 3: extra color (color all ciphers) --colorblind swap green and blue in the output --debug <0-6> 1: screen output normal but keeps debug output in /tmp/. 2-6: see "grep -A 5 '^DEBUG=' testssl.sh" + --disable-grading Explicitly disables the grading output file output options (can also be preset via environment variables) --log, --logging logs stdout to '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log' in current working directory (cwd) @@ -20357,6 +20546,215 @@ run_mass_testing_parallel() { return $? } +run_grading() { + local final_score pre_cap_grade final_grade + local c1_score c2_score c3_score c1_wscore c2_wscore c3_wscore + local c1_worst c1_best + local c3_worst c3_best c3_worst_cb c3_best_cb + local old_ifs=$IFS sorted_reasons sorted_warnings reason_loop=0 warning_loop=0 + + # Sort the reasons. This is just nicer to read in genereal + IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}")) + IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}")) + IFS=$old_ifs + fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' (revision 2009q) (near complete)" + pr_bold " Grading specification "; out "SSLLabs's 'SSL Server Rating Guide' (revision 2009q)"; prln_warning " (near complete)" + pr_bold " Specification documentation "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide" + outln + + # No point in calculating a score, if a cap of "F", "T", or "M" has been set + if [[ $GRADE_CAP == "F" || $GRADE_CAP == "T" || $GRADE_CAP == "M" ]]; then + pr_bold " Protocol Support "; out "(weighted) "; outln "0 (0)" + pr_bold " Key Exchange "; out " (weighted) "; outln "0 (0)" + pr_bold " Cipher Stregth "; out " (weighted) "; outln "0 (0)" + pr_bold " Final Score "; outln "0" + pr_bold " Grade "; prln_svrty_critical "$GRADE_CAP" + fileout "grade" "CRITICAL" "$GRADE_CAP" + outln + else + ## Category 1 + # get best score, by searching for the best protocol, until a hit occurs + if [[ $(has_server_protocol "tls1_3") -eq 0 || $(has_server_protocol "tls1_2") -eq 0 ]]; then + c1_best=100 + elif [[ $(has_server_protocol "tls1_1") -eq 0 ]]; then + c1_best=95 + elif [[ $(has_server_protocol "tls1") -eq 0 ]]; then + c1_best=90 + elif [[ $(has_server_protocol "ssl3") -eq 0 ]]; then + c1_best=80 + # If the best protocol offered is SSLv3, cap to F. It is easier done here + set_grade_cap "F" "SSLv3 is the best protocol offered" + else # SSLv2 gives 0 points + c1_best=0 + fi + + # get worst score, by searching for the worst protcol, until a hit occurs + if [[ $(has_server_protocol "ssl2") -eq 0 ]]; then + c1_worst=0 + elif [[ $(has_server_protocol "ssl3") -eq 0 ]]; then + c1_worst=80 + elif [[ $(has_server_protocol "tls1") -eq 0 ]]; then + c1_worst=90 + elif [[ $(has_server_protocol "tls1_1") -eq 0 ]]; then + c1_worst=95 + else # TLS1.2 and TLS1.3 both give 100 points + c1_worst=100 + fi + + let c1_score="($c1_best+$c1_worst)/2" # Gets the category score + let c1_wscore=$c1_score*30/100 # Gets the weighted score for category (30%) + + pr_bold " Protocol Support "; out "(weighted) "; outln "$c1_score ($c1_wscore)" + + ## Category 2 + let c2_score=$KEY_EXCH_SCORE + let c2_wscore=$c2_score*30/100 + + pr_bold " Key Exchange "; out " (weighted) "; outln "$c2_score ($c2_wscore)" + + + ## Category 3 + # Get the cipher bits sizes for the best cipher, and the worst cipher + c3_best_cb=$CIPH_STR_BEST + c3_worst_cb=$CIPH_STR_WORST + + # Determine score for the best key + if [[ $c3_best_cb -ge 256 ]]; then + c3_best=100 + elif [[ $c3_best_cb -ge 128 ]]; then + c3_best=80 + elif [[ $c3_best_cb -ge 0 ]]; then + c3_best=20 + else + c3_best=0 + fi + + # Determine the score for the worst key + if [[ $c3_worst_cb -gt 0 && $c3_worst_cb -lt 128 ]]; then + c3_worst=20 + elif [[ $c3_worst_cb -lt 256 ]]; then + c3_worst=80 + elif [[ $c3_worst_cb -ge 256 ]]; then + c3_worst=100 + else + c3_worst=0 + fi + let c3_score="($c3_best+$c3_worst)/2" # Gets the category score + let c3_wscore=$c3_score*40/100 # Gets the weighted score for category (40%) + + pr_bold " Cipher Stregth "; out " (weighted) "; outln "$c3_score ($c3_wscore)" + + ## Calculate final score and grade + let final_score=$c1_wscore+$c2_wscore+$c3_wscore + + pr_bold " Final Score "; outln $final_score + + # get score, and somehow do something about the GRADE_CAP + if [[ $final_score -ge 80 ]]; then + pre_cap_grade="A" + elif [[ $final_score -ge 65 ]]; then + pre_cap_grade="B" + elif [[ $final_score -ge 50 ]]; then + pre_cap_grade="C" + elif [[ $final_score -ge 35 ]]; then + pre_cap_grade="D" + elif [[ $final_score -ge 20 ]]; then + pre_cap_grade="E" + elif [[ $final_score -lt 20 ]]; then + pre_cap_grade="F" + fi + + # If the calculated grade is bigger than the grade cap, then set grade as the cap + if [[ $GRADE_CAP != "" && ! $pre_cap_grade > $GRADE_CAP ]]; then + final_grade=$GRADE_CAP + # For "exceptional" config, an "A+" is awarded, or "A-" for slightly less "exceptional" + elif [[ $GRADE_CAP == "" && $pre_cap_grade == "A" ]]; then + if [[ ${#sorted_warnings[@]} -eq 0 ]]; then + final_grade="A+" + else + final_grade="A-" + fi + else + final_grade=$pre_cap_grade + fi + + case "$final_grade" in + "A"*) pr_bold " Grade " + prln_svrty_best $final_grade + fileout "grade" "OK" "$final_grade" + ;; + "B") pr_bold " Grade " + prln_svrty_medium $final_grade + fileout "grade" "MEDIUM" "$final_grade" + ;; + "C") pr_bold " Grade " + prln_svrty_medium $final_grade + fileout "grade" "MEDIUM" "$final_grade" + ;; + "D") pr_bold " Grade " + prln_svrty_high $final_grade + fileout "grade" "HIGH" "$final_grade" + ;; + "E") pr_bold " Grade " + prln_svrty_high $final_grade + fileout "grade" "HIGH" "$final_grade" + ;; + "F") pr_bold " Grade " + prln_svrty_critical $final_grade + fileout "grade" "CRITICAL" "$final_grade" + ;; + esac + fi + + # Pretty print - again, it's just nicer to read + for reason in "${sorted_reasons[@]}"; do + if [[ $reason_loop -eq 0 ]]; then + pr_bold " Grade cap reasons "; outln "$reason" + let reason_loop++ + else + outln " $reason" + fi + done + + for warning in "${sorted_warnings[@]}"; do + if [[ $warning_loop -eq 0 ]]; then + pr_bold " Grade warning "; prln_svrty_medium "$warning" + let warning_loop++ + else + prln_svrty_medium " $warning" + fi + done + + return 0 +} + +# Checks whether grading can be done or not. +# Grading needs a mix of certificate and vulnerabilities checks, in order to give out a proper grade. +# This function disables grading, if not all required checks are enabled +# Returns "0" if grading is enabled, and "1" if grading is disabled +set_grading_state() { + local gbl + local nr_enabled=0 + + # All of these should be enabled + for gbl in do_protocols do_cipherlists do_fs do_server_defaults do_header \ + do_heartbleed do_ccs_injection do_ticketbleed do_robot do_renego \ + do_crime do_ssl_poodle do_tls_fallback_scsv do_drown do_beast \ + do_rc4 do_logjam; do + [[ "${!gbl}" == true ]] && let nr_enabled++ + done + + # ... atleast one of these has to be set + [[ $do_allciphers == true || $do_cipher_per_proto == true ]] && let nr_enabled++ + + # ... else we can't grade + if [[ $nr_enabled -lt 18 ]]; then + DISABLE_GRADING=true + return 1 + fi + + return 0 +} # This initializes boolean global do_* variables. They keep track of what to do @@ -20722,6 +21120,9 @@ parse_cmd_line() { -g|--grease) do_grease=true ;; + --disable-grading) + DISABLE_GRADING=true + ;; -9|--full) set_scanning_defaults do_allciphers=false @@ -21005,9 +21406,9 @@ parse_cmd_line() { SSL_NATIVE=true ;; --basicauth|--basicauth=*) - BASICAUTH="$(parse_opt_equal_sign "$1" "$2")" - [[ $? -eq 0 ]] && shift - ;; + BASICAUTH="$(parse_opt_equal_sign "$1" "$2")" + [[ $? -eq 0 ]] && shift + ;; (--) shift break ;; @@ -21045,6 +21446,11 @@ parse_cmd_line() { count_do_variables [[ $? -eq 0 ]] && set_scanning_defaults + + # Unless explicit disabled, check if grading can be enabled + # Should be called after set_scanning_defaults + "$DISABLE_GRADING" || set_grading_state + CMDLINE_PARSED=true } @@ -21213,6 +21619,14 @@ lets_roll() { fileout_section_header $section_number true && ((section_number++)) "$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; } + + if ! "$DISABLE_GRADING"; then + outln; pr_headlineln " Calculating grade " + outln + + fileout_section_header $section_number true && ((section_number++)) + { run_grading; ret=$(($? + ret)); stopwatch run_grading; } + fi fi fileout_section_footer true fi From 64735d0241fe1fffa8eb0e3168dd8b7edbf68926 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 17 Apr 2020 13:22:30 +0200 Subject: [PATCH 02/22] Remove env variable DISABLE_GRADING as for run_* functions we currntly don't have that. Also AEAD as WIP we can remove that from the doc --- doc/testssl.1.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 696d9f3..170d426 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -288,7 +288,7 @@ Please note that in testssl.sh 3,0 you can still use `rfc` instead of `iana` and 6. whole 9 yards `--disable-grading` disables grading explicitly. -Grading automatically gets disabled, to not give a wrong or misleading grade, when not all required functions are executed (e.g when checking for a single vulnerabilities). `DISABLE_GRADING` is the according environment variable which you can use. +Grading automatically gets disabled, to not give a wrong or misleading grade, when not all required functions are executed (e.g when checking for a single vulnerabilities). ### FILE OUTPUT OPTIONS @@ -394,7 +394,6 @@ This is *not* a reimplementation of the [SSLLab's SSL Server Test](https://www.s Disclaimer: Having a good grade does **NOT** necessary equal to having good security! Never rely solely on a good grade! As of writing, these checks are missing: -* Authenticated encryption (AEAD) - should be graded **B** if not supported * GOLDENDOODLE - should be graded **F** if vulnerable * Insecure renegotiation - should be graded **F** if vulnerable * Padding oracle in AES-NI CBC MAC check (CVE-2016-2107) - should be graded **F** if vulnerable @@ -409,7 +408,7 @@ As of writing, these checks are missing: * Weak key (Debian OpenSSL Flaw) - should give **0** points in `set_key_str_score()` #### Implementing new grades caps or -warnings -To implement at new grading cap, simply call the `set_grade_cap()` function, with the grade and a reason: +To implement a new grading cap, simply call the `set_grade_cap()` function, with the grade and a reason: ```bash set_grade_cap "D" "Vulnerable to documentation" ``` From 359965dc17b7d111a49d0abc83195d7698944767 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 17 Apr 2020 13:24:32 +0200 Subject: [PATCH 03/22] First round of polishing @magnuslarsen's contribution * instead of DISABLE_GRADING we use do_grading as for run_* functions we currently don't support global variables * Add AEAD cipher set_grade_cap (needs to be tested though) * remove redundant quotes * be to be safe add double quotes at other places * Fix typos * Polishing output Tasks (not complete): * Review whether it is rated as intended * Do we want to mofify SSL Lab's rating? (SSLv3 e.g., T for SHA1 certificate?) * Does JSON output work? * TLS 1.3 only server are not rated properly --> wait for SSLlabs? * SWEET32: rating refers to TLS 1.1 atm. SSLlabs docu doesn't give a hint (is their docu incomplete?) * Rating for STARTTLS at all? --- testssl.sh | 137 ++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/testssl.sh b/testssl.sh index c540e33..bfef981 100755 --- a/testssl.sh +++ b/testssl.sh @@ -227,7 +227,6 @@ fi DISPLAY_CIPHERNAMES="openssl" # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode) declare -r UA_STD="TLS tester from $SWURL" declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" -DISABLE_GRADING=${DISABLE_GRADING:-false} # Whether to disable grading, or not ########### Initialization part, further global vars just being declared here # @@ -990,34 +989,33 @@ f5_port_decode() { echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec } +###### END universal helper function definitions ###### + + +###### START scoring function definitions ###### + # Sets the grade cap to ARG1 # arg1: A grade to set ("A", "B", "C", "D", "E", "F", "M", or "T") # arg2: A reason why (e.g. "Vulnerable to CRIME") set_grade_cap() { - # Do nothing if disabled - "$DISABLE_GRADING" && return 0 - + "$do_grading" || return 0 GRADE_CAP_REASONS+=("Grade capped to $1. $2") # Always set special attributes. These are hard caps, due to name mismatch or cert being invalid - if [[ "$1" == "T" || "$1" == "M" ]]; then - GRADE_CAP=$1 + if [[ "$1" == T || "$1" == M ]]; then + GRADE_CAP="$1" # Only keep track of the lowest grade cap, since a higher grade cap wont do anything (F = lowest, A = highest) elif [[ ! "$GRADE_CAP" > "$1" ]]; then - GRADE_CAP=$1 + GRADE_CAP="$1" fi - return 0 } # Sets a grade warning, as specified by the grade specification # arg1: A warning message set_grade_warning() { - # Do nothing if disabled - "$DISABLE_GRADING" && return 0 - + "$do_grading" || return 0 GRADE_WARNINGS+=("$1") - return 0 } @@ -1028,18 +1026,17 @@ set_key_str_score() { local type=$1 local size=$2 - # Do nothing if disabled - "$DISABLE_GRADING" && return 0 + "$do_grading" || return 0 # TODO: We need to get the size of DH params (follows the same table as the "else" clause) # For now, verifying the key size will do... - if [[ $type == "EC" || $type == "DH" ]]; then + if [[ $type == EC || $type == DH ]]; then if [[ $size -lt 110 ]]; then let KEY_EXCH_SCORE=20 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" elif [[ $size -lt 123 ]]; then let KEY_EXCH_SCORE=40 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" elif [[ $size -lt 163 ]]; then let KEY_EXCH_SCORE=80 set_grade_cap "B" "Using a weak key" @@ -1049,15 +1046,15 @@ set_key_str_score() { let KEY_EXCH_SCORE=100 else let KEY_EXCH_SCORE=0 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" fi else if [[ $size -lt 512 ]]; then let KEY_EXCH_SCORE=20 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" elif [[ $size -lt 1024 ]]; then let KEY_EXCH_SCORE=40 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" elif [[ $size -lt 2048 ]]; then let KEY_EXCH_SCORE=80 set_grade_cap "B" "Using a weak key" @@ -1067,10 +1064,9 @@ set_key_str_score() { let KEY_EXCH_SCORE=100 else let KEY_EXCH_SCORE=0 - set_grade_cap "F" "Using a insecure key" + set_grade_cap "F" "Using an insecure key" fi fi - return 0 } @@ -1080,18 +1076,14 @@ set_key_str_score() { set_ciph_str_score() { local size=$1 - # Do nothing if disabled - "$DISABLE_GRADING" && return 0 + "$do_grading" || return 0 [[ $size -gt $CIPH_STR_BEST ]] && let CIPH_STR_BEST=$size [[ $size -lt $CIPH_STR_WORST ]] && let CIPH_STR_WORST=$size - return 0 } -###### START ServerHello/OpenSSL/F5 function definitions ###### -###### END helper function definitions ###### - +###### END scoring function definitions ###### ##################### START output file formatting functions ######################### #################### START JSON file functions #################### @@ -5235,7 +5227,7 @@ run_protocols() { latest_supported="0301" latest_supported_string="TLSv1.0" add_tls_offered tls1 yes - set_grade_cap "B" "TLS1.0 offered" + set_grade_cap "B" "TLS 1.0 offered" ;; # nothing wrong with it -- per se 1) out "not offered" add_tls_offered tls1 no @@ -5282,7 +5274,7 @@ run_protocols() { 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl fileout "$jsonID" "INFO" "$supported_no_ciph1" add_tls_offered tls1 yes - set_grade_cap "B" "TLS1.0 offered" + set_grade_cap "B" "TLS 1.0 offered" ;; 7) if "$using_sockets" ; then # can only happen in debug mode @@ -5315,7 +5307,7 @@ run_protocols() { latest_supported="0302" latest_supported_string="TLSv1.1" add_tls_offered tls1_1 yes - set_grade_cap "B" "TLS1.1 offered" + set_grade_cap "B" "TLS 1.1 offered" ;; # nothing wrong with it 1) out "not offered" add_tls_offered tls1_1 no @@ -5365,7 +5357,7 @@ run_protocols() { 5) outln "$supported_no_ciph1" # protocol detected but no cipher --> comes from run_prototest_openssl fileout "$jsonID" "INFO" "$supported_no_ciph1" add_tls_offered tls1_1 yes - set_grade_cap "B" "TLS1.1 offered" + set_grade_cap "B" "TLS 1.1 offered" ;; 7) if "$using_sockets" ; then # can only happen in debug mode @@ -5430,7 +5422,7 @@ run_protocols() { add_tls_offered tls1_2 yes ;; # GCM cipher in TLS 1.2: very good! 1) add_tls_offered tls1_2 no - set_grade_cap "C" "TLS1.2 is not offered" + set_grade_cap "C" "TLS 1.2 is not offered" if "$offers_tls13"; then out "not offered" else @@ -5449,7 +5441,7 @@ run_protocols() { fi ;; 2) add_tls_offered tls1_2 no - set_grade_cap "C" "TLS1.2 is not offered" + set_grade_cap "C" "TLS 1.2 is not offered" pr_svrty_medium "not offered and downgraded to a weaker protocol" if [[ "$tls12_detected_version" == 0300 ]]; then detected_version_string="SSLv3" @@ -5478,15 +5470,15 @@ run_protocols() { 3) out "not offered, " fileout "$jsonID" "INFO" "not offered" add_tls_offered tls1_2 no - set_grade_cap "C" "TLS1.2 is not offered" + set_grade_cap "C" "TLS 1.2 is not offered" pr_warning "TLS downgraded to STARTTLS plaintext"; outln fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext" - set_grade_cap "C" "TLS1.2 is not offered" + set_grade_cap "C" "TLS 1.2 is not offered" ;; 4) out "likely "; pr_svrty_medium "not offered, " fileout "$jsonID" "MEDIUM" "not offered" add_tls_offered tls1_2 no - set_grade_cap "C" "TLS1.2 is not offered" + set_grade_cap "C" "TLS 1.2 is not offered" pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm" fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}" ;; @@ -5854,7 +5846,9 @@ sub_cipherlists() { ;; esac + # Not a perfect place here. A new one should be picked in the future [[ $sclient_success -eq 0 && "$1" =~ (^|:)EXPORT(:|$) ]] && set_grade_cap "F" "Export suite offered" + [[ $sclient_success -eq 0 && "$1" =~ AEAD ]] && set_grade_cap "B" "No AEAD ciphers offered" fi tmpfile_handle ${FUNCNAME[0]}.${5}.txt [[ $DEBUG -ge 1 ]] && tm_out " -- $1" @@ -5959,7 +5953,7 @@ run_cipherlists() { ret=$((ret + $?)) sub_cipherlists "$ossl_good_ciphers" "" " non-FS Strong encryption (AEAD ciphers) " 6 "GOOD" "$good_ciphers" "" "$using_sockets" "" "" ret=$((ret + $?)) - sub_cipherlists "$ossl_strong_ciphers" 'ALL' " Forward Secure Strong encryption (AEAD ciphers)" 7 "STRONG" "$strong_ciphers" "" "$using_sockets" "" "" + sub_cipherlists "$ossl_strong_ciphers" ALL " FS + Strong encryption (AEAD ciphers) " 7 "STRONG" "$strong_ciphers" "" "$using_sockets" "" "" ret=$((ret + $?)) outln @@ -9756,9 +9750,9 @@ run_fs() { if [[ $sclient_success -ne 0 ]]; then outln - prln_svrty_medium " No ciphers supporting Forward Secrecy offered" - fileout "$jsonID" "MEDIUM" "No ciphers supporting (P)FS offered" - set_grade_cap "B" "Forward Security (PFS) is not supported" + prln_svrty_medium " No ciphers supporting Forward Secrecy (FS) offered" + fileout "$jsonID" "MEDIUM" "No ciphers supporting Forward Secrecy offered" + set_grade_cap "B" "Forward Secrecy (FS) is not supported" else outln fs_offered=true @@ -15961,7 +15955,7 @@ run_sweet32() { local -i nr_sweet32_ciphers=0 nr_supported_ciphers=0 nr_ssl2_sweet32_ciphers=0 nr_ssl2_supported_ciphers=0 local ssl2_sweet=false local using_sockets=true - local tls1_1_vulnable=false + local tls1_1_vulnerable=false [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SWEET32 (Birthday Attacks on 64-bit Block Ciphers) " && outln pr_bold " SWEET32"; out " (${cve// /, }) " @@ -16022,7 +16016,7 @@ run_sweet32() { sclient_connect_successful $? $TMPFILE sclient_success=$? [[ $DEBUG -ge 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error" - [[ $proto == -tls1_1 && $sclient_success -eq 0 ]] && tls1_1_vulnable=true + [[ $proto == -tls1_1 && $sclient_success -eq 0 ]] && tls1_1_vulnerable=true [[ $sclient_success -eq 0 ]] && break done if "$HAS_SSL2"; then @@ -16042,11 +16036,11 @@ run_sweet32() { if [[ $sclient_success -eq 0 ]] && "$ssl2_sweet" ; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers for SSLv2 and above" fileout "SWEET32" "LOW" "uses 64 bit block ciphers for SSLv2 and above" "$cve" "$cwe" "$hint" - "$tls1_1_vulnable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS1.1+ (vulnerable to SWEET32)" + "$tls1_1_vulnerable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS 1.1 (vulnerable to SWEET32)" elif [[ $sclient_success -eq 0 ]]; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers" fileout "SWEET32" "LOW" "uses 64 bit block ciphers" "$cve" "$cwe" "$hint" - "$tls1_1_vulnable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS1.1+ (vulnerable to SWEET32)" + "$tls1_1_vulnerable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS 1.1 (vulnerable to SWEET32)" elif "$ssl2_sweet"; then pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers wth SSLv2 only" fileout "SWEET32" "LOW" "uses 64 bit block ciphers with SSLv2 only" "$cve" "$cwe" "$hint" @@ -17348,8 +17342,8 @@ run_rc4() { sigalg[i]="$(read_sigalg_from_file "$TMPFILE")" # If you use RC4 with newer protocols, you are punished harder - if [[ "$proto" == "-tls1_1" ]]; then - set_grade_cap "C" "RC4 ciphers offered on TLS1.1" + if [[ "$proto" == -tls1_1 ]]; then + set_grade_cap "C" "RC4 ciphers offered on TLS 1.1" fi done done @@ -20553,24 +20547,27 @@ run_grading() { local c3_worst c3_best c3_worst_cb c3_best_cb local old_ifs=$IFS sorted_reasons sorted_warnings reason_loop=0 warning_loop=0 + outln "\n"; + pr_headlineln " Calculating grades (experimental)" + outln + # Sort the reasons. This is just nicer to read in genereal IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}")) IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}")) IFS=$old_ifs - fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' (revision 2009q) (near complete)" - pr_bold " Grading specification "; out "SSLLabs's 'SSL Server Rating Guide' (revision 2009q)"; prln_warning " (near complete)" + fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' version 2009q from 2020-01-30 (near complete)" + pr_bold " Grading specification "; out "SSL Labs's 'SSL Server Rating Guide' version 2009q from 2020-01-30"; prln_warning " (near complete)" pr_bold " Specification documentation "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide" outln # No point in calculating a score, if a cap of "F", "T", or "M" has been set - if [[ $GRADE_CAP == "F" || $GRADE_CAP == "T" || $GRADE_CAP == "M" ]]; then + if [[ $GRADE_CAP == F || $GRADE_CAP == T || $GRADE_CAP == M ]]; then pr_bold " Protocol Support "; out "(weighted) "; outln "0 (0)" pr_bold " Key Exchange "; out " (weighted) "; outln "0 (0)" - pr_bold " Cipher Stregth "; out " (weighted) "; outln "0 (0)" + pr_bold " Cipher Strength "; out " (weighted) "; outln "0 (0)" pr_bold " Final Score "; outln "0" pr_bold " Grade "; prln_svrty_critical "$GRADE_CAP" fileout "grade" "CRITICAL" "$GRADE_CAP" - outln else ## Category 1 # get best score, by searching for the best protocol, until a hit occurs @@ -20642,7 +20639,7 @@ run_grading() { let c3_score="($c3_best+$c3_worst)/2" # Gets the category score let c3_wscore=$c3_score*40/100 # Gets the weighted score for category (40%) - pr_bold " Cipher Stregth "; out " (weighted) "; outln "$c3_score ($c3_wscore)" + pr_bold " Cipher Strength "; out " (weighted) "; outln "$c3_score ($c3_wscore)" ## Calculate final score and grade let final_score=$c1_wscore+$c2_wscore+$c3_wscore @@ -20668,7 +20665,7 @@ run_grading() { if [[ $GRADE_CAP != "" && ! $pre_cap_grade > $GRADE_CAP ]]; then final_grade=$GRADE_CAP # For "exceptional" config, an "A+" is awarded, or "A-" for slightly less "exceptional" - elif [[ $GRADE_CAP == "" && $pre_cap_grade == "A" ]]; then + elif [[ $GRADE_CAP == "" && $pre_cap_grade == A ]]; then if [[ ${#sorted_warnings[@]} -eq 0 ]]; then final_grade="A+" else @@ -20679,27 +20676,27 @@ run_grading() { fi case "$final_grade" in - "A"*) pr_bold " Grade " + A*) pr_bold " Grade " prln_svrty_best $final_grade fileout "grade" "OK" "$final_grade" ;; - "B") pr_bold " Grade " + B) pr_bold " Grade " prln_svrty_medium $final_grade fileout "grade" "MEDIUM" "$final_grade" ;; - "C") pr_bold " Grade " + C) pr_bold " Grade " prln_svrty_medium $final_grade fileout "grade" "MEDIUM" "$final_grade" ;; - "D") pr_bold " Grade " + D) pr_bold " Grade " prln_svrty_high $final_grade fileout "grade" "HIGH" "$final_grade" ;; - "E") pr_bold " Grade " + E) pr_bold " Grade " prln_svrty_high $final_grade fileout "grade" "HIGH" "$final_grade" ;; - "F") pr_bold " Grade " + F) pr_bold " Grade " prln_svrty_critical $final_grade fileout "grade" "CRITICAL" "$final_grade" ;; @@ -20749,7 +20746,7 @@ set_grading_state() { # ... else we can't grade if [[ $nr_enabled -lt 18 ]]; then - DISABLE_GRADING=true + do_grading=false return 1 fi @@ -20798,6 +20795,7 @@ initialize_globals() { do_client_simulation=false do_display_only=false do_starttls=false + do_grading=false } @@ -20833,6 +20831,7 @@ set_scanning_defaults() { else VULN_COUNT=12 fi + do_grading=true } # returns number of $do variables set = number of run_funcs() to perform @@ -20843,7 +20842,7 @@ count_do_variables() { for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do + do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_grading; do [[ "${!gbl}" == true ]] && let true_nr++ done return $true_nr @@ -20856,7 +20855,7 @@ debug_globals() { for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only; do + do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_grading; do printf "%-22s = %s\n" $gbl "${!gbl}" done printf "%-22s : %s\n" URI: "$URI" @@ -21121,7 +21120,7 @@ parse_cmd_line() { do_grease=true ;; --disable-grading) - DISABLE_GRADING=true + do_grading=false ;; -9|--full) set_scanning_defaults @@ -21449,7 +21448,7 @@ parse_cmd_line() { # Unless explicit disabled, check if grading can be enabled # Should be called after set_scanning_defaults - "$DISABLE_GRADING" || set_grading_state + "$do_grading" || set_grading_state CMDLINE_PARSED=true } @@ -21620,13 +21619,9 @@ lets_roll() { fileout_section_header $section_number true && ((section_number++)) "$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; } - if ! "$DISABLE_GRADING"; then - outln; pr_headlineln " Calculating grade " - outln + fileout_section_header $section_number true && ((section_number++)) + "$do_grading" && { run_grading; ret=$(($? + ret)); stopwatch run_grading; } - fileout_section_header $section_number true && ((section_number++)) - { run_grading; ret=$(($? + ret)); stopwatch run_grading; } - fi fi fileout_section_footer true fi From 2c10676e036416466b5f5fe6769008a79330ab1e Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 17 Apr 2020 14:49:35 +0200 Subject: [PATCH 04/22] Output polish, minor code polish to grading ... and squash the TLS 1.2 grading cap for TLS 1.3 only server --- testssl.sh | 69 +++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/testssl.sh b/testssl.sh index bfef981..9522fef 100755 --- a/testssl.sh +++ b/testssl.sh @@ -5422,7 +5422,6 @@ run_protocols() { add_tls_offered tls1_2 yes ;; # GCM cipher in TLS 1.2: very good! 1) add_tls_offered tls1_2 no - set_grade_cap "C" "TLS 1.2 is not offered" if "$offers_tls13"; then out "not offered" else @@ -5434,6 +5433,7 @@ run_protocols() { fileout "$jsonID" "INFO" "not offered" else fileout "$jsonID" "MEDIUM" "not offered" # TLS 1.3, no TLS 1.2 --> no GCM, penalty + set_grade_cap "C" "TLS 1.2 or TLS 1.3 are not offered" fi else prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string" @@ -20555,18 +20555,18 @@ run_grading() { IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}")) IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}")) IFS=$old_ifs - fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' version 2009q from 2020-01-30 (near complete)" - pr_bold " Grading specification "; out "SSL Labs's 'SSL Server Rating Guide' version 2009q from 2020-01-30"; prln_warning " (near complete)" - pr_bold " Specification documentation "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide" + pr_bold " Grading specs"; out ", not complete "; outln "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" + pr_bold " Specification documentation "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide" outln + fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" # No point in calculating a score, if a cap of "F", "T", or "M" has been set if [[ $GRADE_CAP == F || $GRADE_CAP == T || $GRADE_CAP == M ]]; then - pr_bold " Protocol Support "; out "(weighted) "; outln "0 (0)" - pr_bold " Key Exchange "; out " (weighted) "; outln "0 (0)" - pr_bold " Cipher Strength "; out " (weighted) "; outln "0 (0)" - pr_bold " Final Score "; outln "0" - pr_bold " Grade "; prln_svrty_critical "$GRADE_CAP" + pr_bold " Protocol Support"; out " (weighted) "; outln "0 (0)" + pr_bold " Key Exchange"; out " (weighted) "; outln "0 (0)" + pr_bold " Cipher Strength"; out " (weighted) "; outln "0 (0)" + pr_bold " Final Score "; outln "0" + pr_bold " Grade "; prln_svrty_critical "$GRADE_CAP" fileout "grade" "CRITICAL" "$GRADE_CAP" else ## Category 1 @@ -20601,13 +20601,13 @@ run_grading() { let c1_score="($c1_best+$c1_worst)/2" # Gets the category score let c1_wscore=$c1_score*30/100 # Gets the weighted score for category (30%) - pr_bold " Protocol Support "; out "(weighted) "; outln "$c1_score ($c1_wscore)" + pr_bold " Protocol Support "; out "(weighted) "; outln "$c1_score ($c1_wscore)" ## Category 2 let c2_score=$KEY_EXCH_SCORE let c2_wscore=$c2_score*30/100 - pr_bold " Key Exchange "; out " (weighted) "; outln "$c2_score ($c2_wscore)" + pr_bold " Key Exchange "; out " (weighted) "; outln "$c2_score ($c2_wscore)" ## Category 3 @@ -20639,12 +20639,12 @@ run_grading() { let c3_score="($c3_best+$c3_worst)/2" # Gets the category score let c3_wscore=$c3_score*40/100 # Gets the weighted score for category (40%) - pr_bold " Cipher Strength "; out " (weighted) "; outln "$c3_score ($c3_wscore)" + pr_bold " Cipher Strength "; out " (weighted) "; outln "$c3_score ($c3_wscore)" ## Calculate final score and grade let final_score=$c1_wscore+$c2_wscore+$c3_wscore - pr_bold " Final Score "; outln $final_score + pr_bold " Final Score "; outln $final_score # get score, and somehow do something about the GRADE_CAP if [[ $final_score -ge 80 ]]; then @@ -20662,10 +20662,10 @@ run_grading() { fi # If the calculated grade is bigger than the grade cap, then set grade as the cap - if [[ $GRADE_CAP != "" && ! $pre_cap_grade > $GRADE_CAP ]]; then + if [[ -n "$GRADE_CAP" && ! $pre_cap_grade > $GRADE_CAP ]]; then final_grade=$GRADE_CAP # For "exceptional" config, an "A+" is awarded, or "A-" for slightly less "exceptional" - elif [[ $GRADE_CAP == "" && $pre_cap_grade == A ]]; then + elif [[ -z "$GRADE_CAP" && $pre_cap_grade == A ]]; then if [[ ${#sorted_warnings[@]} -eq 0 ]]; then final_grade="A+" else @@ -20675,30 +20675,25 @@ run_grading() { final_grade=$pre_cap_grade fi + pr_bold " Grade " case "$final_grade" in - A*) pr_bold " Grade " - prln_svrty_best $final_grade - fileout "grade" "OK" "$final_grade" + A*) prln_svrty_best $final_grade + fileout "grade" "OK" "$final_grade" ;; - B) pr_bold " Grade " - prln_svrty_medium $final_grade - fileout "grade" "MEDIUM" "$final_grade" + B) prln_svrty_medium $final_grade + fileout "grade" "MEDIUM" "$final_grade" ;; - C) pr_bold " Grade " - prln_svrty_medium $final_grade - fileout "grade" "MEDIUM" "$final_grade" + C) prln_svrty_medium $final_grade + fileout "grade" "MEDIUM" "$final_grade" ;; - D) pr_bold " Grade " - prln_svrty_high $final_grade - fileout "grade" "HIGH" "$final_grade" + D) prln_svrty_high $final_grade + fileout "grade" "HIGH" "$final_grade" ;; - E) pr_bold " Grade " - prln_svrty_high $final_grade - fileout "grade" "HIGH" "$final_grade" + E) prln_svrty_high $final_grade + fileout "grade" "HIGH" "$final_grade" ;; - F) pr_bold " Grade " - prln_svrty_critical $final_grade - fileout "grade" "CRITICAL" "$final_grade" + F) prln_svrty_critical $final_grade + fileout "grade" "CRITICAL" "$final_grade" ;; esac fi @@ -20706,10 +20701,10 @@ run_grading() { # Pretty print - again, it's just nicer to read for reason in "${sorted_reasons[@]}"; do if [[ $reason_loop -eq 0 ]]; then - pr_bold " Grade cap reasons "; outln "$reason" + pr_bold " Grade cap reasons "; outln "$reason" let reason_loop++ else - outln " $reason" + outln " $reason" fi done @@ -20738,11 +20733,11 @@ set_grading_state() { do_heartbleed do_ccs_injection do_ticketbleed do_robot do_renego \ do_crime do_ssl_poodle do_tls_fallback_scsv do_drown do_beast \ do_rc4 do_logjam; do - [[ "${!gbl}" == true ]] && let nr_enabled++ + "${!gbl}" && let nr_enabled++ done # ... atleast one of these has to be set - [[ $do_allciphers == true || $do_cipher_per_proto == true ]] && let nr_enabled++ + "$do_allciphers" || "$do_cipher_per_proto" && let nr_enabled++ # ... else we can't grade if [[ $nr_enabled -lt 18 ]]; then From d9f2ca80d6c0fd3b766526775a387e9fe443fd9d Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Fri, 17 Apr 2020 14:54:11 +0200 Subject: [PATCH 05/22] fix conditional statement (regression) --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 9522fef..316269a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20737,7 +20737,7 @@ set_grading_state() { done # ... atleast one of these has to be set - "$do_allciphers" || "$do_cipher_per_proto" && let nr_enabled++ + [[ "$do_allciphers" || "$do_cipher_per_proto" ]] && let nr_enabled++ # ... else we can't grade if [[ $nr_enabled -lt 18 ]]; then From b4ad0d2425b2f977526545445eaa40631dff336a Mon Sep 17 00:00:00 2001 From: Magnus Larsen <[]> Date: Fri, 17 Apr 2020 15:31:29 +0200 Subject: [PATCH 06/22] Less aggresive TLS_FALLBACK_SCVS checks --- testssl.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/testssl.sh b/testssl.sh index 316269a..4c468fa 100755 --- a/testssl.sh +++ b/testssl.sh @@ -16183,7 +16183,6 @@ run_tls_fallback_scsv() { if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then prln_svrty_critical "No fallback possible, SSLv2 is the only protocol" fileout "$jsonID" "CRITICAL" "SSLv2 is the only protocol" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 fi for p in tls1_2 tls1_1 tls1 ssl3; do @@ -16212,7 +16211,6 @@ run_tls_fallback_scsv() { "ssl3") prln_svrty_high "No fallback possible, SSLv3 is the only protocol" fileout "$jsonID" "HIGH" "only SSLv3 supported" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 ;; *) if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then @@ -16220,7 +16218,6 @@ run_tls_fallback_scsv() { # then assume it does not support SSLv3, even if SSLv3 cannot be tested. pr_svrty_good "No fallback possible (OK)"; outln ", TLS 1.3 is the only protocol" fileout "$jsonID" "OK" "only TLS 1.3 supported" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" elif [[ $(has_server_protocol tls1_3) -eq 1 ]] && \ ( [[ $(has_server_protocol ssl3) -eq 1 ]] || "$HAS_SSL3" ); then # TLS 1.3, TLS 1.2, TLS 1.1, TLS 1, and SSLv3 are all not supported. @@ -16234,7 +16231,6 @@ run_tls_fallback_scsv() { # it is very likely that SSLv3 is the only supported protocol. pr_svrty_high "NOT ok, no fallback possible"; outln ", TLS 1.3, 1.2, 1.1 and 1.0 not supported" fileout "$jsonID" "HIGH" "TLS 1.3, 1.2, 1.1, 1.0 not supported" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" else # TLS 1.2, TLS 1.1, and TLS 1 are not supported, but can't tell whether TLS 1.3 is supported. # This could be a TLS 1.3 only server, an SSLv3 only server (if SSLv3 support cannot be tested), @@ -16242,7 +16238,6 @@ run_tls_fallback_scsv() { # since this could either be good or bad. outln "No fallback possible, TLS 1.2, TLS 1.1, and TLS 1 not supported" fileout "$jsonID" "INFO" "TLS 1.2, TLS 1.1, and TLS 1 not supported" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" fi return 0 esac @@ -16287,7 +16282,6 @@ run_tls_fallback_scsv() { ;; esac fileout "$jsonID" "OK" "no protocol below $high_proto_str offered" - set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV" return 0 fi case "$low_proto" in From 49608294338adb6a20038b32c47905f2e806d302 Mon Sep 17 00:00:00 2001 From: Dirk Date: Sun, 19 Apr 2020 23:54:42 +0200 Subject: [PATCH 07/22] Fix JSON for grading / rating --- testssl.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/testssl.sh b/testssl.sh index 316269a..bd62c1a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1121,6 +1121,7 @@ fileout_json_section() { 9) echo -e ",\n \"vulnerabilities\" : [" ;; 10) echo -e ",\n \"cipherTests\" : [" ;; 11) echo -e ",\n \"browserSimulations\": [" ;; + 12) echo -e ",\n \"grading\" : [" ;; *) echo "invalid section" ;; esac } From 127cf95e2266384c505d541a79b727b701f160f6 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 12:26:33 +0200 Subject: [PATCH 08/22] Address rating for STARTTLS tests STARTTLS tests should always give a bad rating because of the missing trust 1) . That's why we don't provide more details as "T". Maybe we decide later to provide an environment variable which still shows this warning but divulges more details. TBC. Documentation is missing for STARTTLS + grades. 1) There might be cases also for STARTTLS where encryption is enforced and e.g. the certificate fingerprint is validated. As this is highly protcol specific we won't test that. --- testssl.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testssl.sh b/testssl.sh index be47c72..74b03ac 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20546,6 +20546,14 @@ run_grading() { pr_headlineln " Calculating grades (experimental)" outln + if [[ -n "$STARTTLS_PROTOCOL" ]]; then + pr_bold " Grade "; pr_svrty_critical "T" + outln " - STARTTLS encryption is opportunistic" + outln " (Further details would lead to a false sense of security)" + fileout "grade" "CRITICAL" "T, No more details shown as it would lead to a false sense of security" + return 0 + fi + # Sort the reasons. This is just nicer to read in genereal IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}")) IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}")) From c3f09f56f7e92a7358135d5ff1898257f31c3dfe Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 22:41:14 +0200 Subject: [PATCH 09/22] Grading --> Rating but we still hand out grades --- doc/testssl.1.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 170d426..1006684 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -287,7 +287,7 @@ Please note that in testssl.sh 3,0 you can still use `rfc` instead of `iana` and 5. display bytes received via sockets 6. whole 9 yards -`--disable-grading` disables grading explicitly. +`--disable-rating` disables rating explicitly. Grading automatically gets disabled, to not give a wrong or misleading grade, when not all required functions are executed (e.g when checking for a single vulnerabilities). @@ -386,12 +386,12 @@ Except the environment variables mentioned above which can replace command line * MAX_OSSL_FAIL: A number which tells testssl.sh how often an OpenSSL s_client connect may fail before the program gives up and terminates. The default is 2. You can increase it to a higher value if you frequently see a message like *Fatal error: repeated TCP connect problems, giving up*. * MAX_HEADER_FAIL: A number which tells testssl.sh how often a HTTP GET request over OpenSSL may return an empty file before the program gives up and terminates. The default is 3. Also here you can incerase the threshold when you spot messages like *Fatal error: repeated HTTP header connect problems, doesn't make sense to continue*. -### GRADING -This script has a near-complete implementation of SSLLabs's '[SSL Server Rating Guide](https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide)'. +### RATING +This program has a near-complete implementation of SSL Labs's '[SSL Server Rating Guide](https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide)'. -This is *not* a reimplementation of the [SSLLab's SSL Server Test](https://www.ssllabs.com/ssltest/analyze.html), but a implementation of the above grading specification, slight discrepancies might occur! +This is *not* a reimplementation of the [SS LLab's SSL Server Test](https://www.ssllabs.com/ssltest/analyze.html), but a implementation of the above rating specification, slight discrepancies might occur! -Disclaimer: Having a good grade does **NOT** necessary equal to having good security! Never rely solely on a good grade! +Disclaimer: Having a good grade does **NOT** necessary equal to having good security! Never rely solely on a good rating! As of writing, these checks are missing: * GOLDENDOODLE - should be graded **F** if vulnerable @@ -417,24 +417,24 @@ To implement a new grade warning, simply call the `set_grade_warning()` function set_grade_warning "Documentation is always right" ``` #### Implementing a new check which contains grade caps -When implementing a new check (be it vulnerability or not) that sets grade caps, the `set_grading_state()` has to be updated (i.e. the `$do_mycheck` variable-name has to be added to the loop, and `$nr_enabled` if-statement has to be incremented) +When implementing a new check (be it vulnerability or not) that sets grade caps, the `set_rating_state()` has to be updated (i.e. the `$do_mycheck` variable-name has to be added to the loop, and `$nr_enabled` if-statement has to be incremented) -The `set_grading_state()` automatically disables grading, if all the required checks are *not* enabled. +The `set_rating_state()` automatically disables ratinng, if all the required checks are *not* enabled. This is to prevent giving out a misleading or wrong grade. #### Implementing a new revision -When a new revision of the grading specification comes around, the following has to be done: +When a new revision of the rating specification comes around, the following has to be done: * New grade caps has to be either: 1. Added to the script wherever relevant, or 2. Added to the above list of missing checks (if *i.* is not possible) * New grade warnings has to be added wherever relevant -* The revision output in `run_grading()` function has to updated +* The revision output in `run_rating()` function has to updated ## EXAMPLES testssl.sh testssl.sh -does a default run on https://testssl.sh (protocols, standard cipher lists, FS, server preferences, server defaults, vulnerabilities, testing all known 370 ciphers, client simulation, and grading. +does a default run on https://testssl.sh (protocols, standard cipher lists, FS, server preferences, server defaults, vulnerabilities, testing all known 370 ciphers, client simulation, and rating. testssl.sh testssl.net:443 From e9e11e213afc3df7bcc4e78c91d014ef345830e1 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 22:45:58 +0200 Subject: [PATCH 10/22] * Grading --> Rating. But we still hand out grades --- testssl.sh | 82 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/testssl.sh b/testssl.sh index 74b03ac..befcc21 100755 --- a/testssl.sh +++ b/testssl.sh @@ -998,7 +998,7 @@ f5_port_decode() { # arg1: A grade to set ("A", "B", "C", "D", "E", "F", "M", or "T") # arg2: A reason why (e.g. "Vulnerable to CRIME") set_grade_cap() { - "$do_grading" || return 0 + "$do_rating" || return 0 GRADE_CAP_REASONS+=("Grade capped to $1. $2") # Always set special attributes. These are hard caps, due to name mismatch or cert being invalid @@ -1014,7 +1014,7 @@ set_grade_cap() { # Sets a grade warning, as specified by the grade specification # arg1: A warning message set_grade_warning() { - "$do_grading" || return 0 + "$do_rating" || return 0 GRADE_WARNINGS+=("$1") return 0 } @@ -1026,7 +1026,7 @@ set_key_str_score() { local type=$1 local size=$2 - "$do_grading" || return 0 + "$do_rating" || return 0 # TODO: We need to get the size of DH params (follows the same table as the "else" clause) # For now, verifying the key size will do... @@ -1076,7 +1076,7 @@ set_key_str_score() { set_ciph_str_score() { local size=$1 - "$do_grading" || return 0 + "$do_rating" || return 0 [[ $size -gt $CIPH_STR_BEST ]] && let CIPH_STR_BEST=$size [[ $size -lt $CIPH_STR_WORST ]] && let CIPH_STR_WORST=$size @@ -1121,7 +1121,7 @@ fileout_json_section() { 9) echo -e ",\n \"vulnerabilities\" : [" ;; 10) echo -e ",\n \"cipherTests\" : [" ;; 11) echo -e ",\n \"browserSimulations\": [" ;; - 12) echo -e ",\n \"grading\" : [" ;; + 12) echo -e ",\n \"rating\" : [" ;; *) echo "invalid section" ;; esac } @@ -3438,7 +3438,7 @@ neat_list(){ enc="${enc//POLY1305/}" # remove POLY1305 enc="${enc//\//}" # remove "/" - # For grading, set bits size + # For rating set bit size set_ciph_str_score $strength [[ "$export" =~ export ]] && strength="$strength,exp" @@ -18560,7 +18560,7 @@ output options (can also be preset via environment variables): --color <0|1|2|3> 0: no escape or other codes, 1: b/w escape codes, 2: color (default), 3: extra color (color all ciphers) --colorblind swap green and blue in the output --debug <0-6> 1: screen output normal but keeps debug output in /tmp/. 2-6: see "grep -A 5 '^DEBUG=' testssl.sh" - --disable-grading Explicitly disables the grading output + --disable-rating Explicitly disables the rating output file output options (can also be preset via environment variables) --log, --logging logs stdout to '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log' in current working directory (cwd) @@ -20535,7 +20535,7 @@ run_mass_testing_parallel() { return $? } -run_grading() { +run_rating() { local final_score pre_cap_grade final_grade local c1_score c2_score c3_score c1_wscore c2_wscore c3_wscore local c1_worst c1_best @@ -20543,14 +20543,15 @@ run_grading() { local old_ifs=$IFS sorted_reasons sorted_warnings reason_loop=0 warning_loop=0 outln "\n"; - pr_headlineln " Calculating grades (experimental)" + pr_headlineln " Rating (experimental) " outln if [[ -n "$STARTTLS_PROTOCOL" ]]; then pr_bold " Grade "; pr_svrty_critical "T" outln " - STARTTLS encryption is opportunistic" outln " (Further details would lead to a false sense of security)" - fileout "grade" "CRITICAL" "T, No more details shown as it would lead to a false sense of security" + fileout "grade" "CRITICAL" "T" + fileout "grade_cap_reasons" "INFO" "No more details shown as it would lead to a false sense of security" return 0 fi @@ -20558,10 +20559,10 @@ run_grading() { IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}")) IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}")) IFS=$old_ifs - pr_bold " Grading specs"; out ", not complete "; outln "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" + pr_bold " Rating specs"; out " (not complete) "; outln "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" pr_bold " Specification documentation "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide" outln - fileout "grading_spec" "INFO" "SSLLabs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" + fileout "rating_spec" "INFO" "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)" # No point in calculating a score, if a cap of "F", "T", or "M" has been set if [[ $GRADE_CAP == F || $GRADE_CAP == T || $GRADE_CAP == M ]]; then @@ -20569,7 +20570,7 @@ run_grading() { pr_bold " Key Exchange"; out " (weighted) "; outln "0 (0)" pr_bold " Cipher Strength"; out " (weighted) "; outln "0 (0)" pr_bold " Final Score "; outln "0" - pr_bold " Grade "; prln_svrty_critical "$GRADE_CAP" + pr_bold " Overall Grade "; prln_svrty_critical "$GRADE_CAP" fileout "grade" "CRITICAL" "$GRADE_CAP" else ## Category 1 @@ -20640,7 +20641,7 @@ run_grading() { c3_worst=0 fi let c3_score="($c3_best+$c3_worst)/2" # Gets the category score - let c3_wscore=$c3_score*40/100 # Gets the weighted score for category (40%) + let c3_wscore=$c3_score*40/100 # Gets the weighted score for category (40%) pr_bold " Cipher Strength "; out " (weighted) "; outln "$c3_score ($c3_wscore)" @@ -20678,7 +20679,7 @@ run_grading() { final_grade=$pre_cap_grade fi - pr_bold " Grade " + pr_bold " Overall Grade " case "$final_grade" in A*) prln_svrty_best $final_grade fileout "grade" "OK" "$final_grade" @@ -20720,14 +20721,26 @@ run_grading() { fi done + case $GRADE_CAP in + # A-E: WIP + A) fileout "grade_cap_reasons" "INFO" "" ;; + B) fileout "grade_cap_reasons" "INFO" "" ;; + C) fileout "grade_cap_reasons" "INFO" "" ;; + D) fileout "grade_cap_reasons" "INFO" "" ;; + E) fileout "grade_cap_reasons" "INFO" "" ;; + M) fileout "grade_cap_reasons" "INFO" "SAN / CN mismatch" ;; + F) fileout "grade_cap_reasons" "INFO" "Severe vulnerability or cryptographic problem" ;; + T) fileout "grade_cap_reasons" "INFO" "Issue with certificate" ;; + esac + return 0 } -# Checks whether grading can be done or not. -# Grading needs a mix of certificate and vulnerabilities checks, in order to give out a proper grade. -# This function disables grading, if not all required checks are enabled -# Returns "0" if grading is enabled, and "1" if grading is disabled -set_grading_state() { +# Checks whether rating can be done or not. +# Rating needs a mix of certificate and vulnerabilities checks, in order to give out proper grades. +# This function disables rating, if not all required checks are enabled +# Returns "0" if rating is enabled, and "1" if rating is disabled +set_rating_state() { local gbl local nr_enabled=0 @@ -20742,9 +20755,9 @@ set_grading_state() { # ... atleast one of these has to be set [[ "$do_allciphers" || "$do_cipher_per_proto" ]] && let nr_enabled++ - # ... else we can't grade + # ... else we can't do rating if [[ $nr_enabled -lt 18 ]]; then - do_grading=false + do_rating=false return 1 fi @@ -20793,7 +20806,7 @@ initialize_globals() { do_client_simulation=false do_display_only=false do_starttls=false - do_grading=false + do_rating=false } @@ -20829,7 +20842,7 @@ set_scanning_defaults() { else VULN_COUNT=12 fi - do_grading=true + do_rating=true } # returns number of $do variables set = number of run_funcs() to perform @@ -20840,8 +20853,8 @@ count_do_variables() { for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_grading; do - [[ "${!gbl}" == true ]] && let true_nr++ + do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do + "${!gbl}" && let true_nr++ done return $true_nr } @@ -20853,7 +20866,7 @@ debug_globals() { for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \ do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \ do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \ - do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_grading; do + do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do printf "%-22s = %s\n" $gbl "${!gbl}" done printf "%-22s : %s\n" URI: "$URI" @@ -20931,7 +20944,7 @@ parse_cmd_line() { ;; esac - # initializing + # set all globals to false initialize_globals while [[ $# -gt 0 ]]; do @@ -21117,8 +21130,8 @@ parse_cmd_line() { -g|--grease) do_grease=true ;; - --disable-grading) - do_grading=false + --disable-rating) + do_rating=false ;; -9|--full) set_scanning_defaults @@ -21439,14 +21452,13 @@ parse_cmd_line() { grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE done - [[ "$DEBUG" -ge 5 ]] && debug_globals - count_do_variables [[ $? -eq 0 ]] && set_scanning_defaults + [[ "$DEBUG" -ge 5 ]] && debug_globals - # Unless explicit disabled, check if grading can be enabled + # Unless explicit disabled, check if rating can be enabled # Should be called after set_scanning_defaults - "$do_grading" || set_grading_state + ! "$do_rating" && set_rating_state CMDLINE_PARSED=true } @@ -21618,7 +21630,7 @@ lets_roll() { "$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; } fileout_section_header $section_number true && ((section_number++)) - "$do_grading" && { run_grading; ret=$(($? + ret)); stopwatch run_grading; } + "$do_rating" && { run_rating; ret=$(($? + ret)); stopwatch run_rating; } fi fileout_section_footer true From b1ef3a020f5f3c65e612af10e2b0ca17bd9db07a Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 22:48:31 +0200 Subject: [PATCH 11/22] add single blank for pretty JSON --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index befcc21..12f7ae8 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1121,7 +1121,7 @@ fileout_json_section() { 9) echo -e ",\n \"vulnerabilities\" : [" ;; 10) echo -e ",\n \"cipherTests\" : [" ;; 11) echo -e ",\n \"browserSimulations\": [" ;; - 12) echo -e ",\n \"rating\" : [" ;; + 12) echo -e ",\n \"rating\" : [" ;; *) echo "invalid section" ;; esac } From 577370a27223a3f7032483a535684099c3288e11 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 22:49:31 +0200 Subject: [PATCH 12/22] Add rating --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 289f81a..26c1e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ## Change Log +### Features implemented / improvements in 3.1 +* Renamed PFS into FS +* Improved mass testing +* Security fix: DNS input +* Rating + ### Features implemented / improvements in 3.0 * Full support of TLS 1.3, shows also drafts supported From 7da2e90083de33b289b6497c0cdef0c8d9027389 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Mon, 20 Apr 2020 22:49:48 +0200 Subject: [PATCH 13/22] Honor Magnus --- CREDITS.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 38ccced..884d8dd 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -46,9 +46,9 @@ Full contribution, see git log. - Client simulations - CI integration, some test cases for it - * Steven Danneman - - Postgres and MySQL STARTTLS support - - MongoDB support +* Steven Danneman + - Postgres and MySQL STARTTLS support + - MongoDB support * Christian Dresen - Dockerfile @@ -78,6 +78,9 @@ Full contribution, see git log. * Hubert Kario - helped with avoiding accidental TCP fragmentation +* Magnus Larsen + - SSL Labs Rating + * Jacco de Leeuw - skip checks which might trigger an IDS ($OFFENSIVE / --ids-friendly) From d6a9360f2ce1e27bcdcf928090e2a8554517955e Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 22 Apr 2020 14:08:58 +0200 Subject: [PATCH 14/22] Fix known DH but not weak keys to be capped @ A not B --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 12f7ae8..f6f5656 100755 --- a/testssl.sh +++ b/testssl.sh @@ -16675,7 +16675,7 @@ run_logjam() { else if [[ $subret -eq 1 ]]; then out_common_prime "$jsonID2" "$cve" "$cwe" - set_grade_cap "B" "Uses weak DH key exchange parameters (vulnerable to LOGJAM)" + set_grade_cap "A" "Uses known DH key exchange parameters" if ! "$openssl_no_expdhciphers"; then outln "," out "${spaces}but no DH EXPORT ciphers${addtl_warning}" From 32eab3ead9c988e4574776987b09cd0df27176ed Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 22 Apr 2020 17:14:05 +0200 Subject: [PATCH 15/22] Fix problem with --disable-rating by introducing framework for tests to be skipped, see also #1502. As a first example for the development branch should serve --disable-rating / --no-rating. The latter is for now undocumented. Also the big case statement in parse_cmd_line() may use a general --disable-* or --no-* clause where all --disable-* / --no-* are being parsed/ A new function set_skip_tests() is being introduced which sets do_ according to the new array SKIP_TESTS . Any new test do be skipped needs to be added to that array. The changes in the --devel part come from the tries to fix the syntax highlight in vim -- which in the end difn't work --- testssl.sh | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/testssl.sh b/testssl.sh index f6f5656..5990768 100755 --- a/testssl.sh +++ b/testssl.sh @@ -140,6 +140,7 @@ declare CMDLINE CMDLINE_PARSED="" # This makes sure we don't let early fatal() write into files when files aren't created yet declare -r -a CMDLINE_ARRAY=("$@") # When performing mass testing, the child processes need to be sent the declare -a MASS_TESTING_CMDLINE # command line in the form of an array (see #702 and https://mywiki.wooledge.org/BashFAQ/050). +declare -a SKIP_TESTS=() # This array hold the checks to be skipped ########### Defining (and presetting) variables which can be changed @@ -20842,7 +20843,6 @@ set_scanning_defaults() { else VULN_COUNT=12 fi - do_rating=true } # returns number of $do variables set = number of run_funcs() to perform @@ -20869,10 +20869,26 @@ debug_globals() { do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do printf "%-22s = %s\n" $gbl "${!gbl}" done + # ${!var} is an indirect expansion, see https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + # Example: https://stackoverflow.com/questions/8515411/what-is-indirect-expansion-what-does-var-mean#8515492 printf "%-22s : %s\n" URI: "$URI" } +# This is determining the tests which should be skipped by --no-* or --disable-* a a cmdline arg. +# It achieves that by setting the do_ according to the global array $SKIP_TESTS +# +set_skip_tests() { + for t in ${SKIP_TESTS[@]} ; do + t="do_${t}" + # declare won't do it here --> local scope + eval "$t"=false + debugme printf '%s\n' "set $t: ${!t}" + done +} + + + # arg1: either switch+value (=) or switch # arg2: value (if no = provided) parse_opt_equal_sign() { @@ -20944,7 +20960,7 @@ parse_cmd_line() { ;; esac - # set all globals to false + # set all do_* globals to false initialize_globals while [[ $# -gt 0 ]]; do @@ -21130,8 +21146,10 @@ parse_cmd_line() { -g|--grease) do_grease=true ;; - --disable-rating) - do_rating=false + --disable-rating|--no-rating) + SKIP_TESTS+=("rating") + # TODO: a generic thing would be --disable-* / --no-* , + # catch $1 and add it to the array ( #1502 ) ;; -9|--full) set_scanning_defaults @@ -21143,18 +21161,18 @@ parse_cmd_line() { ADDTL_CA_FILES="$(parse_opt_equal_sign "$1" "$2")" [[ $? -eq 0 ]] && shift ;; - --devel) ### this development feature will soon disappear + --devel) echo -e "\nthis is a development feature and may disappear at any time" # arg1: SSL/TLS protocol (SSLv2=22) # arg2: list of cipher suites / hostname/ip # arg3: hostname/ip - HEX_CIPHER="$TLS12_CIPHER" - # DEBUG=3 ./testssl.sh --devel 04 "13,02, 13,01" google.com --> TLS 1.3 - # DEBUG=3 ./testssl.sh --devel 03 "cc, 13, c0, 13" google.de --> TLS 1.2, old CHACHA/POLY - # DEBUG=3 ./testssl.sh --devel 03 "cc,a8, cc,a9, cc,aa, cc,ab, cc,ac" blog.cloudflare.com --> new CHACHA/POLY - # DEBUG=3 ./testssl.sh --devel 01 yandex.ru --> TLS 1.0 + # DEBUG=3 ./testssl.sh --devel 04 "13,02, 13,01" google.com --> TLS 1.3 + # DEBUG=3 ./testssl.sh --devel 03 "cc, 13, c0, 13" google.de --> TLS 1.2, old CHACHA/POLY + # DEBUG=3 ./testssl.sh --devel 03 "cc,a8, cc,a9, cc,aa, cc,ab, cc,ac" blog.cloudflare.com --> new CHACHA/POLY + # DEBUG=3 ./testssl.sh --devel 01 yandex.ru --> TLS 1.0 # DEBUG=3 ./testssl.sh --devel 00 # DEBUG=3 ./testssl.sh --devel 22 - TLS_LOW_BYTE="$2"; + HEX_CIPHER="$TLS12_CIPHER" + TLS_LOW_BYTE="$2" if [[ $# -eq 4 ]]; then # protocol AND ciphers specified HEX_CIPHER="$3" shift @@ -21454,6 +21472,7 @@ parse_cmd_line() { count_do_variables [[ $? -eq 0 ]] && set_scanning_defaults + set_skip_tests [[ "$DEBUG" -ge 5 ]] && debug_globals # Unless explicit disabled, check if rating can be enabled From 07c06e0f9442b1f9ae4414769c305a616a316632 Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Wed, 22 Apr 2020 17:19:36 +0200 Subject: [PATCH 16/22] declare t variable in set_skip_tests() --- testssl.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testssl.sh b/testssl.sh index 5990768..7358c34 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20879,6 +20879,8 @@ debug_globals() { # It achieves that by setting the do_ according to the global array $SKIP_TESTS # set_skip_tests() { + local t + for t in ${SKIP_TESTS[@]} ; do t="do_${t}" # declare won't do it here --> local scope From 8566ca80bcf8a18676ba038c34cdbddabd2bbc64 Mon Sep 17 00:00:00 2001 From: Dirk Date: Thu, 23 Apr 2020 09:23:21 +0200 Subject: [PATCH 17/22] Enable rating again was per default disabled by accident previously --- testssl.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/testssl.sh b/testssl.sh index 7358c34..75fbd7a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20843,6 +20843,7 @@ set_scanning_defaults() { else VULN_COUNT=12 fi + do_rating=true } # returns number of $do variables set = number of run_funcs() to perform From 13a76bc7198577f72cfb8f91306ef745489ac9c1 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 28 Apr 2020 13:35:24 +0200 Subject: [PATCH 18/22] (try to) resolve merge conflict --- CHANGELOG.md | 14 +++++++++++--- doc/testssl.1.md | 8 ++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c1e23..2b2a34b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,19 @@ ## Change Log -### Features implemented / improvements in 3.1 -* Renamed PFS into FS +### Features implemented / improvements in 3.1dev + +* Extend Server (cipher) preference: always now in wide mode instead of running all ciphers in the end (per default) +* Improved compatibility with OpenSSL 3.0 +* Renamed PFS/perfect forward secrecy --> FS/forward secrecy * Improved mass testing +* Align better colors of ciphers with standard cipherlists +* Added several ciphers to colored ciphers +* Percent output char problem fixed +* Several display/output fixes * Security fix: DNS input -* Rating +* Don't use external pwd anymore +* Rating (via SSL Labs) ### Features implemented / improvements in 3.0 diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 1006684..e81ae78 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -40,9 +40,9 @@ linked OpenSSL binaries for major operating systems are supplied in `./bin/`. 1) SSL/TLS protocol check -2) standard cipher categories to give you upfront an idea for the ciphers supported +2) checks forward secrecy: ciphers and elliptical curves -3) checks forward secrecy: ciphers and elliptical curves +3) standard cipher categories to give you upfront an idea for the ciphers supported 4) server preferences (server order) @@ -54,9 +54,9 @@ linked OpenSSL binaries for major operating systems are supplied in `./bin/`. 8) testing each of 370 preconfigured ciphers -9) client simulation +8) client simulation -10) Result of script in form of a grade +9) Result of script in form of a grade ## OPTIONS AND PARAMETERS From db84e5c87c853758e7af7ac1689f94d423952692 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 28 Apr 2020 13:38:23 +0200 Subject: [PATCH 19/22] Add grade cap reasons and warnings to JSON/CSV --- testssl.sh | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/testssl.sh b/testssl.sh index 75fbd7a..3971364 100755 --- a/testssl.sh +++ b/testssl.sh @@ -20541,7 +20541,7 @@ run_rating() { local c1_score c2_score c3_score c1_wscore c2_wscore c3_wscore local c1_worst c1_best local c3_worst c3_best c3_worst_cb c3_best_cb - local old_ifs=$IFS sorted_reasons sorted_warnings reason_loop=0 warning_loop=0 + local old_ifs=$IFS sorted_reasons sorted_warnings reason_nr=0 warning_nr=0 outln "\n"; pr_headlineln " Rating (experimental) " @@ -20705,35 +20705,25 @@ run_rating() { # Pretty print - again, it's just nicer to read for reason in "${sorted_reasons[@]}"; do - if [[ $reason_loop -eq 0 ]]; then + if [[ $reason_nr -eq 0 ]]; then pr_bold " Grade cap reasons "; outln "$reason" - let reason_loop++ else outln " $reason" fi + let reason_nr++ + fileout "grade_cap_reason_${reason_nr}" "INFO" "$reason" done for warning in "${sorted_warnings[@]}"; do - if [[ $warning_loop -eq 0 ]]; then - pr_bold " Grade warning "; prln_svrty_medium "$warning" - let warning_loop++ + if [[ $warning_nr -eq 0 ]]; then + pr_bold " Grade warning "; prln_svrty_medium "$warning" else - prln_svrty_medium " $warning" + prln_svrty_medium " $warning" fi + let warning_nr++ + fileout "grade_cap_warning_${warning_nr}" "INFO" "$warning" done - case $GRADE_CAP in - # A-E: WIP - A) fileout "grade_cap_reasons" "INFO" "" ;; - B) fileout "grade_cap_reasons" "INFO" "" ;; - C) fileout "grade_cap_reasons" "INFO" "" ;; - D) fileout "grade_cap_reasons" "INFO" "" ;; - E) fileout "grade_cap_reasons" "INFO" "" ;; - M) fileout "grade_cap_reasons" "INFO" "SAN / CN mismatch" ;; - F) fileout "grade_cap_reasons" "INFO" "Severe vulnerability or cryptographic problem" ;; - T) fileout "grade_cap_reasons" "INFO" "Issue with certificate" ;; - esac - return 0 } From 97ac4c452e441b2e7f34bc573f19c60d8b8b0b5d Mon Sep 17 00:00:00 2001 From: Dirk Wetter Date: Thu, 23 Apr 2020 11:20:46 +0200 Subject: [PATCH 21/22] Update documentation (ADDITIONAL_CA_FILES -> ADDTL_CA_FILES) which happened in d44a643fab6be6755a917f85ed491c38990d15ae in testssl.sh . This fixes it in the related files. See also #1581 --- Coding_Convention.md | 2 +- doc/testssl.1 | 4 ++-- doc/testssl.1.html | 4 ++-- doc/testssl.1.md | 5 +++-- etc/README.md | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Coding_Convention.md b/Coding_Convention.md index 933b30c..03122d1 100644 --- a/Coding_Convention.md +++ b/Coding_Convention.md @@ -65,7 +65,7 @@ Bash is actually quite powerful -- not only with respect to sockets. It's not as ### Misc -* If you're implementing a new feature a cmd line switch, there has to be also a global ENV variable which can be used without the switch (see e.g. `SNEAKY`, `ASSUME_HTTP` or `ADDITIONAL_CA_FILES`) +* If you're implementing a new feature a cmd line switch, there has to be also a global ENV variable which can be used without the switch (see e.g. `SNEAKY`, `ASSUME_HTTP` or `ADDTL_CA_FILES`) * Test before doing a PR! Best if you check with two bad and two good examples which should then work as expected. Maybe compare results e.g. with SSLlabs. * Unit tests are done automatically done with Perl using Travis. The trigger is `~/.travis.yml`. The general documentation for [Test::More](https://perldoc.perl.org/Test/More.html) is a good start. You are encouraged to write own checks. You can use e.g. `t/20_baseline_ipv4_http.t` as an example. * If it's an OpenSSL feature you want to use and it could be not available for older OpenSSL versions testssl.sh needs to find out whether OpenSSL has that feature. Best do this with OpenSSL itself and not by checking the version as some vendors do backports. See the examples for `HAS_SSL2` or proxy option check of OpenSSL in `check_proxy()`. diff --git a/doc/testssl.1 b/doc/testssl.1 index 9c0f684..a8e8626 100644 --- a/doc/testssl.1 +++ b/doc/testssl.1 @@ -176,7 +176,7 @@ Please note that \fBfname\fR has to be in Unix format\. DOS carriage returns won \fB\-\-phone\-out\fR Checking for revoked certificates via CRL and OCSP is not done per default\. This switch instructs testssl\.sh to query external \-\- in a sense of the current run \-\- URIs\. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl\.sh doesn\'t handle\. PHONE_OUT is the environment variable for this which needs to be set to true if you want this\. . .P -\fB\-\-add\-ca \fR enables you to add your own CA(s) for trust chain checks\. \fBcafile\fR can be a single path or multiple paths as a comma separated list of root CA files\. Internally they will be added during runtime to all CA stores\. This is (only) useful for internal hosts whose certificates is issued by internal CAs\. Alternatively ADDITIONAL_CA_FILES is the environment variable for this\. +\fB\-\-add\-ca \fR enables you to add your own CA(s) for trust chain checks\. \fBcafile\fR can be a single path or multiple paths as a comma separated list of root CA files\. Internally they will be added during runtime to all CA stores\. This is (only) useful for internal hosts whose certificates is issued by internal CAs\. Alternatively ADDTL_CA_FILES is the environment variable for this\. . .SS "SINGLE CHECK OPTIONS" Any single check switch supplied as an argument prevents testssl\.sh from doing a default run\. It just takes this and if supplied other options and runs them \- in the order they would also appear in the default run\. @@ -282,7 +282,7 @@ Certificate Transparency info (if provided by server)\. .IP "" 0 . .P -For the trust chain check 5 certificate stores are provided\. If the test against one of the trust stores failed, the one is being identified and the reason for the failure is displayed \- in addition the ones which succeeded are displayed too\. You can configure your own CA via ADDITIONAL_CA_FILES, see section \fBFILES\fR below\. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated\. Also for multiple server certificates are being checked for as well as for the certificate reply to a non\-SNI (Server Name Indication) client hello to the IP address\. Regarding the TLS clock skew: it displays the time difference to the client\. Only a few TLS stacks nowadays still support this and return the local clock \fBgmt_unix_time\fR, e\.g\. IIS, openssl < 1\.0\.1f\. In addition to the HTTP date you could e\.g\. derive that there are different hosts where your TLS and your HTTP request ended \-\- if the time deltas differ significantly\. +For the trust chain check 5 certificate stores are provided\. If the test against one of the trust stores failed, the one is being identified and the reason for the failure is displayed \- in addition the ones which succeeded are displayed too\. You can configure your own CA via ADDTL_CA_FILES, see section \fBFILES\fR below\. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated\. Also for multiple server certificates are being checked for as well as for the certificate reply to a non\-SNI (Server Name Indication) client hello to the IP address\. Regarding the TLS clock skew: it displays the time difference to the client\. Only a few TLS stacks nowadays still support this and return the local clock \fBgmt_unix_time\fR, e\.g\. IIS, openssl < 1\.0\.1f\. In addition to the HTTP date you could e\.g\. derive that there are different hosts where your TLS and your HTTP request ended \-\- if the time deltas differ significantly\. . .P \fB\-x , \-\-single\-cipher \fR tests matched \fBpattern\fR of ciphers against a server\. Patterns are similar to \fB\-V pattern , \-\-local pattern\fR, see above about matching\. diff --git a/doc/testssl.1.html b/doc/testssl.1.html index e7f3c34..5a6c392 100644 --- a/doc/testssl.1.html +++ b/doc/testssl.1.html @@ -221,7 +221,7 @@ in /etc/hosts. The use of the switch is only useful if you either

--phone-out Checking for revoked certificates via CRL and OCSP is not done per default. This switch instructs testssl.sh to query external -- in a sense of the current run -- URIs. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl.sh doesn't handle. PHONE_OUT is the environment variable for this which needs to be set to true if you want this.

--add-ca <cafile> enables you to add your own CA(s) for trust chain checks. cafile can be a single path or multiple paths as a comma separated list of root CA files. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates is issued by internal CAs. Alternatively -ADDITIONAL_CA_FILES is the environment variable for this.

+ADDTL_CA_FILES is the environment variable for this.

SINGLE CHECK OPTIONS

@@ -278,7 +278,7 @@ ADDITIONAL_CA_FILES is the environment variable for this.

For the trust chain check 5 certificate stores are provided. If the test against one of the trust stores failed, the one is being identified and the reason for the failure is displayed - in addition the ones which succeeded are displayed too. -You can configure your own CA via ADDITIONAL_CA_FILES, see section FILES below. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated. +You can configure your own CA via ADDTL_CA_FILES, see section FILES below. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated. Also for multiple server certificates are being checked for as well as for the certificate reply to a non-SNI (Server Name Indication) client hello to the IP address. Regarding the TLS clock skew: it displays the time difference to the client. Only a few TLS stacks nowadays still support this and return the local clock gmt_unix_time, e.g. IIS, openssl < 1.0.1f. In addition to the HTTP date you could e.g. derive that there are different hosts where your TLS and your HTTP request ended -- if the time deltas differ significantly.

-x <pattern>, --single-cipher <pattern> tests matched pattern of ciphers against a server. Patterns are similar to -V pattern , --local pattern, see above about matching.

diff --git a/doc/testssl.1.md b/doc/testssl.1.md index e81ae78..96a774c 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -144,7 +144,8 @@ in `/etc/hosts`. The use of the switch is only useful if you either can't or ar `--phone-out` Checking for revoked certificates via CRL and OCSP is not done per default. This switch instructs testssl.sh to query external -- in a sense of the current run -- URIs. By using this switch you acknowledge that the check might have privacy issues, a download of several megabytes (CRL file) may happen and there may be network connectivity problems while contacting the endpoint which testssl.sh doesn't handle. PHONE_OUT is the environment variable for this which needs to be set to true if you want this. -`--add-ca ` enables you to add your own CA(s) for trust chain checks. `cafile` can be a single path or multiple paths as a comma separated list of root CA files. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates is issued by internal CAs. Alternatively ADDITIONAL_CA_FILES is the environment variable for this. +`--add-ca ` enables you to add your own CA(s) for trust chain checks. `cafile` can be a single path or multiple paths as a comma separated list of root CA files. Internally they will be added during runtime to all CA stores. This is (only) useful for internal hosts whose certificates is issued by internal CAs. Alternatively +ADDTL_CA_FILES is the environment variable for this. ### SINGLE CHECK OPTIONS @@ -192,7 +193,7 @@ Any single check switch supplied as an argument prevents testssl.sh from doing a - Certificate Transparency info (if provided by server). For the trust chain check 5 certificate stores are provided. If the test against one of the trust stores failed, the one is being identified and the reason for the failure is displayed - in addition the ones which succeeded are displayed too. -You can configure your own CA via ADDITIONAL_CA_FILES, see section `FILES` below. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated. +You can configure your own CA via ADDTL_CA_FILES, see section `FILES` below. If the server provides no matching record in Subject Alternative Name (SAN) but in Common Name (CN), it will be indicated as this is deprecated. Also for multiple server certificates are being checked for as well as for the certificate reply to a non-SNI (Server Name Indication) client hello to the IP address. Regarding the TLS clock skew: it displays the time difference to the client. Only a few TLS stacks nowadays still support this and return the local clock `gmt_unix_time`, e.g. IIS, openssl < 1.0.1f. In addition to the HTTP date you could e.g. derive that there are different hosts where your TLS and your HTTP request ended -- if the time deltas differ significantly. `-x , --single-cipher ` tests matched `pattern` of ciphers against a server. Patterns are similar to `-V pattern , --local pattern`, see above about matching. diff --git a/etc/README.md b/etc/README.md index 3437195..fc619cc 100644 --- a/etc/README.md +++ b/etc/README.md @@ -18,7 +18,7 @@ The certificate trust stores were retrieved from Google Chromium uses basically the trust stores above, see https://www.chromium.org/Home/chromium-security/root-ca-policy. -If you want to check trust against e.g. a company internal CA you need to use ``./testssl.sh --add-ca companyCA1.pem,companyCA2.pem `` or ``ADDITIONAL_CA_FILES=companyCA1.pem,companyCA2.pem ./testssl.sh ``. +If you want to check trust against e.g. a company internal CA you need to use ``./testssl.sh --add-ca companyCA1.pem,companyCA2.pem `` or ``ADDTL_CA_FILES=companyCA1.pem,companyCA2.pem ./testssl.sh ``. #### Further files From a9d28949fe27605e2bb365b2dd855d9cf4c8eb08 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 28 Apr 2020 21:13:36 +0200 Subject: [PATCH 22/22] Clarify responsilility for rating --- doc/testssl.1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/testssl.1.md b/doc/testssl.1.md index 96a774c..1f5be5d 100644 --- a/doc/testssl.1.md +++ b/doc/testssl.1.md @@ -390,9 +390,9 @@ Except the environment variables mentioned above which can replace command line ### RATING This program has a near-complete implementation of SSL Labs's '[SSL Server Rating Guide](https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide)'. -This is *not* a reimplementation of the [SS LLab's SSL Server Test](https://www.ssllabs.com/ssltest/analyze.html), but a implementation of the above rating specification, slight discrepancies might occur! +This is *not* a 100% reimplementation of the [SSL Lab's SSL Server Test](https://www.ssllabs.com/ssltest/analyze.html), but an implementation of the above rating specification, slight discrepancies may occur. Please note that for now we stick to the SSL Labs rating as good as possible. We are not responsible for their rating. Before filing issues please inspect their Rating Guide. -Disclaimer: Having a good grade does **NOT** necessary equal to having good security! Never rely solely on a good rating! +Disclaimer: Having a good grade is **NOT** necessarily equal to having good security! Don't start a competition for the best grade, at least not without monitoring the client handshakes and not without adding a portion of good sense to it. As of writing, these checks are missing: * GOLDENDOODLE - should be graded **F** if vulnerable @@ -435,7 +435,7 @@ When a new revision of the rating specification comes around, the following has testssl.sh testssl.sh -does a default run on https://testssl.sh (protocols, standard cipher lists, FS, server preferences, server defaults, vulnerabilities, testing all known 370 ciphers, client simulation, and rating. +does a default run on https://testssl.sh (protocols, standard cipher lists, server's cipher preferences, FS, server defaults, vulnerabilities, client simulation, and rating. testssl.sh testssl.net:443