Added grading based on ssllabs

This commit is contained in:
Magnus Larsen 2020-04-15 15:06:08 +02:00
parent 8ce781c71d
commit e4cef5438d
2 changed files with 479 additions and 20 deletions

View File

@ -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 <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.
`--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.
### 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/ .

View File

@ -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