Merge branch 'magnuslarsen-grading_dev' into 3.1dev

This commit is contained in:
Dirk 2020-05-01 17:37:13 +02:00
commit f5a1884439
6 changed files with 624 additions and 55 deletions

View File

@ -13,7 +13,7 @@
* Several display/output fixes
* Security fix: DNS input
* Don't use external pwd anymore
* Rating (pending)
* Rating (SSL Labs, not complete)
### Features implemented / improvements in 3.0

View File

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

View File

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "TESTSSL" "1" "January 2020" "" ""
.TH "TESTSSL" "1" "May 2020" "" ""
.
.SH "NAME"
\fBtestssl\fR
@ -43,10 +43,10 @@ Any OpenSSL or LibreSSL version is needed as a helper\. Unlike previous versions
1) SSL/TLS protocol check
.
.P
2) standard cipher categories to give you upfront an idea for the ciphers supported
2) standard cipher categories
.
.P
3) server's cipher preferences (server order)
3) server\'s cipher preferences (server order?)
.
.P
4) forward secrecy: ciphers and elliptical curves
@ -61,8 +61,14 @@ Any OpenSSL or LibreSSL version is needed as a helper\. Unlike previous versions
7) vulnerabilities
.
.P
8) testing each of 370 preconfigured ciphers
.
.P
8) client simulation
.
.P
9) rating
.
.SH "OPTIONS AND PARAMETERS"
Options are either short or long options\. Any long or short option requiring a value can be called with or without an equal sign\. E\.g\. \fBtestssl\.sh \-t=smtp \-\-wide \-\-openssl=/usr/bin/openssl <URI>\fR (short options with equal sign) is equivalent to \fBtestssl\.sh \-\-starttls smtp \-\-wide \-\-openssl /usr/bin/openssl <URI>\fR (long option without equal sign)\. Some command line options can also be preset via ENV variables\. \fBWIDE=true OPENSSL=/usr/bin/openssl testssl\.sh \-\-starttls=smtp <URI>\fR would be the equivalent to the aforementioned examples\. Preference has the command line over any environment variables\.
.
@ -235,8 +241,7 @@ session resumption capabilities,
Time skew relative to localhost (most server implementations return random values)\.
.
.IP "\(bu" 4
Several certificate information
.RS
.
.IP "\(bu" 4
signature algorithm,
.
@ -272,7 +277,7 @@ displaying DNS Certification Authority Authorization resource record
.
.IP "\(bu" 4
Certificate Transparency info (if provided by server)\.
.RE
.
.IP "" 0
.
@ -406,7 +411,7 @@ Security headers (X\-Frame\-Options, X\-XSS\-Protection, Expect\-CT,\.\.\. , CSP
.IP "" 0
.
.P
Please note that in testssl\.sh 3,0 you can still use \fBrfc\fR instead of \fBiana\fR and \fBno\-rfc\fR instead of \fBno\-iana\fR but it\'ll disappear after 3\.0\.
Please note that in testssl\.sh 3\.0 you can still use \fBrfc\fR instead of \fBiana\fR and \fBno\-rfc\fR instead of \fBno\-iana\fR but it\'ll disappear after 3\.0\.
.
.P
\fB\-\-show\-each\fR This is an option for all wide modes only: it displays all ciphers tested \-\- not only succeeded ones\. \fBSHOW_EACH_C\fR is your friend if you prefer to set this via the shell environment\.
@ -440,6 +445,9 @@ whole 9 yards
.
.IP "" 0
.
.P
\fB\-\-disable\-rating\fR disables rating\. Rating 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)\.
.
.SS "FILE OUTPUT OPTIONS"
\fB\-\-log, \-\-logging\fR Logs stdout also to \fB${NODE}\-p${port}${YYYYMMDD\-HHMM}\.log\fR in current working directory of the shell\. Depending on the color output option (see above) the output file will contain color and other markup escape codes, unless you specify \fB\-\-color 0\fR too\. \fBcat\fR and \-\- if properly configured \fBless\fR \-\- will show the output properly formatted on your terminal\. The output shows a banner with the almost the same information as on the screen\. In addition it shows the command line of the testssl\.sh instance\. Please note that the resulting log file is formatted according to the width of your screen while running testssl\.sh\. You can override the width with the environment variable TERM_WIDTH\.
.
@ -623,6 +631,30 @@ MAX_HEADER_FAIL: A number which tells testssl\.sh how often a HTTP GET request o
.
.IP "" 0
.
.SS "RATING"
This program has a near\-complete implementation of SSL Labs\'s \'SSL Server Rating Guide \fIhttps://github\.com/ssllabs/research/wiki/SSL\-Server\-Rating\-Guide\fR\'\.
.
.P
This is \fInot\fR a 100% reimplementation of the SSL Lab\'s SSL Server Test \fIhttps://www\.ssllabs\.com/ssltest/analyze\.html\fR, 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\.
.
.P
Disclaimer: Having a good grade is \fBNOT\fR 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\.
.
.P
As of writing, these checks are missing: * GOLDENDOODLE \- should be graded \fBF\fR if vulnerable * Insecure renegotiation \- should be graded \fBF\fR if vulnerable * Padding oracle in AES\-NI CBC MAC check (CVE\-2016\-2107) \- should be graded \fBF\fR if vulnerable * Sleeping POODLE \- should be graded \fBF\fR if vulnerable * Zero Length Padding Oracle (CVE\-2019\-1559) \- should be graded \fBF\fR if vulnerable * Zombie POODLE \- should be graded \fBF\fR if vulnerable * All remaining old Symantec PKI certificates are distrusted \- should be graded \fBT\fR * Symantec certificates issued before June 2016 are distrusted \- should be graded \fBT\fR * ! A reading of DH params \- should give correct points in \fBset_key_str_score()\fR * Anonymous key exchange \- should give \fB0\fR points in \fBset_key_str_score()\fR * Exportable key exchange \- should give \fB40\fR points in \fBset_key_str_score()\fR * Weak key (Debian OpenSSL Flaw) \- should give \fB0\fR points in \fBset_key_str_score()\fR
.
.P
To implement a new grading cap, simply call the \fBset_grade_cap()\fR function, with the grade and a reason: \fBbash set_grade_cap "D" "Vulnerable to documentation"\fR To implement a new grade warning, simply call the \fBset_grade_warning()\fR function, with a message: \fBbash set_grade_warning "Documentation is always right"\fR
.
.P
When implementing a new check (be it vulnerability or not) that sets grade caps, the \fBset_rating_state()\fR has to be updated (i\.e\. the \fB$do_mycheck\fR variable\-name has to be added to the loop, and \fB$nr_enabled\fR if\-statement has to be incremented)
.
.P
The \fBset_rating_state()\fR automatically disables ratinng, if all the required checks are \fInot\fR enabled\. This is to prevent giving out a misleading or wrong grade\.
.
.P
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 \fIi\.\fR is not possible) * New grade warnings has to be added wherever relevant * The revision output in \fBrun_rating()\fR function has to updated
.
.SH "EXAMPLES"
.
.nf
@ -632,7 +664,7 @@ MAX_HEADER_FAIL: A number which tells testssl\.sh how often a HTTP GET request o
.fi
.
.P
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, server\'s cipher preferences, forward secrecy, server defaults, vulnerabilities, client simulation, and rating\.
.
.IP "" 4
.

View File

@ -121,9 +121,9 @@ linked OpenSSL binaries for major operating systems are supplied in <code>./bin/
<p>1) SSL/TLS protocol check</p>
<p>2) standard cipher categories to give you upfront an idea for the ciphers supported</p>
<p>2) standard cipher categories</p>
<p>3) server's cipher preferences (server order)</p>
<p>3) server's cipher preferences (server order?)</p>
<p>4) forward secrecy: ciphers and elliptical curves</p>
@ -135,6 +135,8 @@ linked OpenSSL binaries for major operating systems are supplied in <code>./bin/
<p>8) client simulation</p>
<p>9) rating</p>
<h2 id="OPTIONS-AND-PARAMETERS">OPTIONS AND PARAMETERS</h2>
<p>Options are either short or long options. Any long or short option requiring a value can be called with or without an equal sign. E.g. <code>testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl &lt;URI></code> (short options with equal sign) is equivalent to <code>testssl.sh --starttls smtp --wide --openssl /usr/bin/openssl &lt;URI></code> (long option without equal sign). Some command line options can also be preset via ENV variables. <code>WIDE=true OPENSSL=/usr/bin/openssl testssl.sh --starttls=smtp &lt;URI></code> would be the equivalent to the aforementioned examples. Preference has the command line over any environment variables.</p>
@ -183,7 +185,7 @@ The same can be achieved by setting the environment variable <code>WARNINGS</cod
<p><code>--openssl-timeout &lt;seconds></code> This is especially useful for all connects using openssl and practically useful for mass testing. It avoids the openssl connect to hang for ~2 minutes. The expected parameter <code>seconds</code> instructs testssl.sh to wait before the openssl connect will be terminated. The option is only available if your OS has a timeout binary installed. As there are different implementations of <code>timeout</code>: It automatically calls the binary with the right parameters. OPENSSL_TIMEOUT is the equivalent environment variable.</p>
<p><code>--basicauth &lt;user:pass></code> This can be set to provide HTTP basic auth credentials which are used during checks for security headers. BASICAUTH is the ENV variable you can use instead.</p>
<p><code>--basicauth &lt;user:pass&gt;</code> This can be set to provide HTTP basic auth credentials which are used during checks for security headers. BASICAUTH is the ENV variable you can use instead.</p>
<h3 id="SPECIAL-INVOCATIONS">SPECIAL INVOCATIONS</h3>
@ -355,7 +357,7 @@ Also for multiple server certificates are being checked for as well as for the c
</ul>
<p>Please note that in testssl.sh 3,0 you can still use <code>rfc</code> instead of <code>iana</code> and <code>no-rfc</code> instead of <code>no-iana</code> but it'll disappear after 3.0.</p>
<p>Please note that in testssl.sh 3.0 you can still use <code>rfc</code> instead of <code>iana</code> and <code>no-rfc</code> instead of <code>no-iana</code> but it'll disappear after 3.0.</p>
<p><code>--show-each</code> This is an option for all wide modes only: it displays all ciphers tested -- not only succeeded ones. <code>SHOW_EACH_C</code> is your friend if you prefer to set this via the shell environment.</p>
@ -375,6 +377,9 @@ Also for multiple server certificates are being checked for as well as for the c
</ol>
<p><code>--disable-rating</code> disables rating.
Rating 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).</p>
<h3 id="FILE-OUTPUT-OPTIONS">FILE OUTPUT OPTIONS</h3>
<p><code>--log, --logging</code> Logs stdout also to <code>${NODE}-p${port}${YYYYMMDD-HHMM}.log</code> in current working directory of the shell. Depending on the color output option (see above) the output file will contain color and other markup escape codes, unless you specify <code>--color 0</code> too. <code>cat</code> and -- if properly configured <code>less</code> -- will show the output properly formatted on your terminal. The output shows a banner with the almost the same information as on the screen. In addition it shows the command line of the testssl.sh instance. Please note that the resulting log file is formatted according to the width of your screen while running testssl.sh. You can override the width with the environment variable TERM_WIDTH.</p>
@ -473,12 +478,61 @@ Also for multiple server certificates are being checked for as well as for the c
</ul>
<h3 id="RATING">RATING</h3>
<p>This program has a near-complete implementation of SSL Labs's '<a href="https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide">SSL Server Rating Guide</a>'.</p>
<p>This is <em>not</em> a 100% reimplementation of the <a href="https://www.ssllabs.com/ssltest/analyze.html">SSL Lab's SSL Server Test</a>, 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.</p>
<p>Disclaimer: Having a good grade is <strong>NOT</strong> 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.</p>
<p>As of writing, these checks are missing:
* GOLDENDOODLE - should be graded <strong>F</strong> if vulnerable
* Insecure renegotiation - should be graded <strong>F</strong> if vulnerable
* Padding oracle in AES-NI CBC MAC check (CVE-2016-2107) - should be graded <strong>F</strong> if vulnerable
* Sleeping POODLE - should be graded <strong>F</strong> if vulnerable
* Zero Length Padding Oracle (CVE-2019-1559) - should be graded <strong>F</strong> if vulnerable
* Zombie POODLE - should be graded <strong>F</strong> if vulnerable
* All remaining old Symantec PKI certificates are distrusted - should be graded <strong>T</strong>
* Symantec certificates issued before June 2016 are distrusted - should be graded <strong>T</strong>
* ! A reading of DH params - should give correct points in <code>set_key_str_score()</code>
* Anonymous key exchange - should give <strong>0</strong> points in <code>set_key_str_score()</code>
* Exportable key exchange - should give <strong>40</strong> points in <code>set_key_str_score()</code>
* Weak key (Debian OpenSSL Flaw) - should give <strong>0</strong> points in <code>set_key_str_score()</code></p>
<h4 id="Implementing-new-grades-caps-or-warnings">Implementing new grades caps or -warnings</h4>
<p>To implement a new grading cap, simply call the <code>set_grade_cap()</code> function, with the grade and a reason:
<code>bash
set_grade_cap "D" "Vulnerable to documentation"
</code>
To implement a new grade warning, simply call the <code>set_grade_warning()</code> function, with a message:
<code>bash
set_grade_warning "Documentation is always right"
</code></p>
<h4 id="Implementing-a-new-check-which-contains-grade-caps">Implementing a new check which contains grade caps</h4>
<p>When implementing a new check (be it vulnerability or not) that sets grade caps, the <code>set_rating_state()</code> has to be updated (i.e. the <code>$do_mycheck</code> variable-name has to be added to the loop, and <code>$nr_enabled</code> if-statement has to be incremented)</p>
<p>The <code>set_rating_state()</code> automatically disables ratinng, if all the required checks are <em>not</em> enabled.
This is to prevent giving out a misleading or wrong grade.</p>
<h4 id="Implementing-a-new-revision">Implementing a new revision</h4>
<p>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 <em>i.</em> is not possible)
* New grade warnings has to be added wherever relevant
* The revision output in <code>run_rating()</code> function has to updated</p>
<h2 id="EXAMPLES">EXAMPLES</h2>
<pre><code> testssl.sh testssl.sh
</code></pre>
<p>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.</p>
<p>does a default run on https://testssl.sh (protocols, standard cipher lists, server's cipher preferences, forward secrecy, server defaults, vulnerabilities, client simulation, and rating.</p>
<pre><code> testssl.sh testssl.net:443
</code></pre>
@ -604,7 +658,7 @@ where to get this program from.</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>January 2020</li>
<li class='tc'>May 2020</li>
<li class='tr'>testssl(1)</li>
</ol>

View File

@ -40,7 +40,7 @@ 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) standard cipher categories
3) server's cipher preferences (server order?)
@ -52,8 +52,13 @@ linked OpenSSL binaries for major operating systems are supplied in `./bin/`.
7) vulnerabilities
8) testing each of 370 preconfigured ciphers
8) client simulation
9) rating
## OPTIONS AND PARAMETERS
@ -265,7 +270,7 @@ Also for multiple server certificates are being checked for as well as for the c
* `no-openssl`: don't display the OpenSSL cipher suite name, display IANA names only.
* `no-iana`: don't display the IANA cipher suite name, display OpenSSL names only.
Please note that in testssl.sh 3,0 you can still use `rfc` instead of `iana` and `no-rfc` instead of `no-iana` but it'll disappear after 3.0.
Please note that in testssl.sh 3.0 you can still use `rfc` instead of `iana` and `no-rfc` instead of `no-iana` but it'll disappear after 3.0.
`--show-each` This is an option for all wide modes only: it displays all ciphers tested -- not only succeeded ones. `SHOW_EACH_C` is your friend if you prefer to set this via the shell environment.
@ -284,6 +289,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-rating` disables rating.
Rating 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
@ -381,13 +388,55 @@ 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*.
### 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 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 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
* 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 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"
```
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_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_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 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_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.
does a default run on https://testssl.sh (protocols, standard cipher lists, server's cipher preferences, forward secrecy, server defaults, vulnerabilities, client simulation, and rating.
testssl.sh testssl.net:443
@ -506,4 +555,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

@ -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
@ -374,6 +375,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
#
@ -984,10 +992,102 @@ f5_port_decode() {
echo $((16#${tmp:2:2}${tmp:0:2})) # reverse order and convert it from hex to dec
}
###### START ServerHello/OpenSSL/F5 function definitions ######
###### END helper function definitions ######
###### 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_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
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_rating" || 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_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...
if [[ $type == EC || $type == DH ]]; then
if [[ $size -lt 110 ]]; then
let KEY_EXCH_SCORE=20
set_grade_cap "F" "Using an insecure key"
elif [[ $size -lt 123 ]]; then
let KEY_EXCH_SCORE=40
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"
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 an insecure key"
fi
else
if [[ $size -lt 512 ]]; then
let KEY_EXCH_SCORE=20
set_grade_cap "F" "Using an insecure key"
elif [[ $size -lt 1024 ]]; then
let KEY_EXCH_SCORE=40
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"
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 an 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_rating" || return 0
[[ $size -gt $CIPH_STR_BEST ]] && let CIPH_STR_BEST=$size
[[ $size -lt $CIPH_STR_WORST ]] && let CIPH_STR_WORST=$size
return 0
}
###### END scoring function definitions ######
##################### START output file formatting functions #########################
#################### START JSON file functions ####################
@ -1024,6 +1124,7 @@ fileout_json_section() {
9) echo -e ",\n \"vulnerabilities\" : [" ;;
10) echo -e ",\n \"cipherTests\" : [" ;;
11) echo -e ",\n \"browserSimulations\": [" ;;
12) echo -e ",\n \"rating\" : [" ;;
*) echo "invalid section" ;;
esac
}
@ -1758,12 +1859,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"
@ -1825,6 +1928,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"
@ -2444,15 +2548,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"
@ -2470,6 +2577,7 @@ run_hsts() {
else
pr_svrty_low "not offered"
fileout "$jsonID" "LOW" "not offered"
set_grade_cap "A" "HSTS is not offered"
fi
outln
@ -2505,6 +2613,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"
@ -2531,6 +2640,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, "
@ -2551,6 +2661,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
@ -2708,11 +2819,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 "--"
@ -3329,6 +3442,9 @@ neat_list(){
enc="${enc//POLY1305/}" # remove POLY1305
enc="${enc//\//}" # remove "/"
# For rating set bit size
set_ciph_str_score $strength
[[ "$export" =~ export ]] && strength="$strength,exp"
[[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && tls_cipher="$(show_rfc_style "$hexcode")"
@ -5034,6 +5150,7 @@ run_protocols() {
if [[ "$lines" -gt 1 ]]; then
nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
add_proto_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"
@ -5055,6 +5172,7 @@ run_protocols() {
0) prln_svrty_critical "offered (NOT ok)"
fileout "$jsonID" "CRITICAL" "offered"
add_proto_offered ssl2 yes
set_grade_cap "F" "SSLv2 is offered"
;;
1) prln_svrty_best "not offered (OK)"
fileout "$jsonID" "OK" "not offered"
@ -5063,6 +5181,8 @@ run_protocols() {
5) prln_svrty_high "CVE-2015-3197: $supported_no_ciph2";
fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310"
add_proto_offered ssl2 yes
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"
@ -5090,6 +5210,8 @@ run_protocols() {
latest_supported_string="SSLv3"
fi
add_proto_offered ssl3 yes
add_tls_offered ssl3 yes
set_grade_cap "B" "SSLv3 is offered"
;;
1) prln_svrty_best "not offered (OK)"
fileout "$jsonID" "OK" "not offered"
@ -5125,6 +5247,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_proto_offered ssl3 yes
set_grade_cap "B" "SSLv3 is offered"
;;
7) if "$using_sockets" ; then
# can only happen in debug mode
@ -5156,6 +5279,7 @@ run_protocols() {
latest_supported="0301"
latest_supported_string="TLSv1.0"
add_proto_offered tls1 yes
set_grade_cap "B" "TLS 1.0 offered"
;; # nothing wrong with it -- per se
1) out "not offered"
add_proto_offered tls1 no
@ -5202,6 +5326,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_proto_offered tls1 yes
set_grade_cap "B" "TLS 1.0 offered"
;;
7) if "$using_sockets" ; then
# can only happen in debug mode
@ -5234,6 +5359,7 @@ run_protocols() {
latest_supported="0302"
latest_supported_string="TLSv1.1"
add_proto_offered tls1_1 yes
set_grade_cap "B" "TLS 1.1 offered"
;; # nothing wrong with it
1) out "not offered"
add_proto_offered tls1_1 no
@ -5283,6 +5409,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_proto_offered tls1_1 yes
set_grade_cap "B" "TLS 1.1 offered"
;;
7) if "$using_sockets" ; then
# can only happen in debug mode
@ -5358,6 +5485,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"
@ -5365,6 +5493,7 @@ run_protocols() {
fi
;;
2) add_proto_offered tls1_2 no
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"
@ -5393,12 +5522,14 @@ run_protocols() {
3) out "not offered, "
fileout "$jsonID" "INFO" "not offered"
add_proto_offered tls1_2 no
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"
;;
4) out "likely "; pr_svrty_medium "not offered, "
fileout "$jsonID" "MEDIUM" "not offered"
add_proto_offered tls1_2 no
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}"
;;
@ -5765,6 +5896,10 @@ sub_cipherlists() {
((ret++))
;;
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"
@ -6952,17 +7087,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
@ -7058,6 +7193,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
@ -7076,6 +7212,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 "
@ -8193,6 +8330,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"
@ -8213,6 +8351,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"
@ -8233,6 +8372,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"
@ -8248,6 +8388,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"
@ -8274,6 +8415,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"
@ -8282,6 +8424,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 ("
@ -8336,6 +8479,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
@ -8362,6 +8507,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"
@ -8528,6 +8675,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"
@ -8571,7 +8719,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
@ -8676,6 +8826,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++))
@ -8768,6 +8919,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)
@ -9562,8 +9714,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"
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
@ -14920,6 +15073,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)"
@ -15078,6 +15232,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 "
@ -15378,6 +15533,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"
@ -15435,6 +15591,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"
@ -15618,6 +15775,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
@ -15750,6 +15908,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_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// /, }) "
@ -15810,6 +15969,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_vulnerable=true
[[ $sclient_success -eq 0 ]] && break
done
if "$HAS_SSL2"; then
@ -15829,9 +15989,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_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_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"
@ -15913,6 +16075,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)";
@ -15943,6 +16106,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
}
@ -16090,15 +16255,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"
@ -16441,6 +16609,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"
@ -16456,6 +16625,7 @@ run_logjam() {
else
if [[ $subret -eq 1 ]]; then
out_common_prime "$jsonID2" "$cve" "$cwe"
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}"
@ -16553,6 +16723,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 "
@ -16866,6 +17037,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)"
@ -17113,6 +17285,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 TLS 1.1"
fi
done
done
@ -17193,6 +17370,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)"
@ -17837,6 +18015,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"
@ -18340,6 +18519,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-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)
@ -20315,6 +20495,224 @@ run_mass_testing_parallel() {
return $?
}
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
local c3_worst c3_best c3_worst_cb c3_best_cb
local old_ifs=$IFS sorted_reasons sorted_warnings reason_nr=0 warning_nr=0
outln "\n";
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"
fileout "grade_cap_reasons" "INFO" "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[*]}"))
IFS=$old_ifs
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 "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
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 " Overall Grade "; prln_svrty_critical "$GRADE_CAP"
fileout "grade" "CRITICAL" "$GRADE_CAP"
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 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
# 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 [[ -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 [[ -z "$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
pr_bold " Overall Grade "
case "$final_grade" in
A*) prln_svrty_best $final_grade
fileout "grade" "OK" "$final_grade"
;;
B) prln_svrty_medium $final_grade
fileout "grade" "MEDIUM" "$final_grade"
;;
C) prln_svrty_medium $final_grade
fileout "grade" "MEDIUM" "$final_grade"
;;
D) prln_svrty_high $final_grade
fileout "grade" "HIGH" "$final_grade"
;;
E) prln_svrty_high $final_grade
fileout "grade" "HIGH" "$final_grade"
;;
F) 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_nr -eq 0 ]]; then
pr_bold " Grade cap reasons "; outln "$reason"
else
outln " $reason"
fi
let reason_nr++
fileout "grade_cap_reason_${reason_nr}" "INFO" "$reason"
done
for warning in "${sorted_warnings[@]}"; do
if [[ $warning_nr -eq 0 ]]; then
pr_bold " Grade warning "; prln_svrty_medium "$warning"
else
prln_svrty_medium " $warning"
fi
let warning_nr++
fileout "grade_cap_warning_${warning_nr}" "INFO" "$warning"
done
return 0
}
# 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
# 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}" && let nr_enabled++
done
# ... atleast one of these has to be set
[[ "$do_allciphers" || "$do_cipher_per_proto" ]] && let nr_enabled++
# ... else we can't do rating
if [[ $nr_enabled -lt 18 ]]; then
do_rating=false
return 1
fi
return 0
}
# This initializes boolean global do_* variables. They keep track of what to do
@ -20358,6 +20756,7 @@ initialize_globals() {
do_client_simulation=false
do_display_only=false
do_starttls=false
do_rating=false
}
@ -20393,6 +20792,7 @@ set_scanning_defaults() {
else
VULN_COUNT=12
fi
do_rating=true
}
# returns number of $do variables set = number of run_funcs() to perform
@ -20403,8 +20803,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
[[ "${!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
}
@ -20416,13 +20816,31 @@ 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_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_<variables> 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
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() {
@ -20494,7 +20912,7 @@ parse_cmd_line() {
;;
esac
# initializing
# set all do_* globals to false
initialize_globals
while [[ $# -gt 0 ]]; do
@ -20680,6 +21098,11 @@ parse_cmd_line() {
-g|--grease)
do_grease=true
;;
--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
do_allciphers=false
@ -20690,18 +21113,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 <host which supports SSLv3>
# DEBUG=3 ./testssl.sh --devel 22 <host which still supports SSLv2>
TLS_LOW_BYTE="$2";
HEX_CIPHER="$TLS12_CIPHER"
TLS_LOW_BYTE="$2"
if [[ $# -eq 4 ]]; then # protocol AND ciphers specified
HEX_CIPHER="$3"
shift
@ -20963,9 +21386,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
;;
@ -20999,10 +21422,15 @@ 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
set_skip_tests
[[ "$DEBUG" -ge 5 ]] && debug_globals
# Unless explicit disabled, check if rating can be enabled
# Should be called after set_scanning_defaults
! "$do_rating" && set_rating_state
CMDLINE_PARSED=true
}
@ -21171,6 +21599,10 @@ lets_roll() {
fileout_section_header $section_number true && ((section_number++))
"$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; }
fileout_section_header $section_number true && ((section_number++))
"$do_rating" && { run_rating; ret=$(($? + ret)); stopwatch run_rating; }
fi
fileout_section_footer true
fi