mirror of
https://github.com/drwetter/testssl.sh.git
synced 2024-12-29 04:49:44 +01:00
Added grading based on ssllabs
This commit is contained in:
parent
8ce781c71d
commit
e4cef5438d
@ -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/ .
|
||||
|
||||
|
446
testssl.sh
446
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
|
||||
|
Loading…
Reference in New Issue
Block a user