Merge pull request #3104 from ericcgu/feat/3097-short-lived-certs

Improve handling of short-lived certificates (#3097)
This commit is contained in:
Dirk Wetter
2026-07-15 17:13:21 +02:00
committed by GitHub
5 changed files with 45 additions and 1 deletions
+1
View File
@@ -14,6 +14,7 @@
* Detect and show DNS HTTPS RR (RFC 9460) * Detect and show DNS HTTPS RR (RFC 9460)
* Provide an FAQ * Provide an FAQ
* Security fix: HTML-escape URLs in the HTML report to prevent stored XSS from a server-controlled `Location:` header (#3090) * Security fix: HTML-escape URLs in the HTML report to prevent stored XSS from a server-controlled `Location:` header (#3090)
* Detect short-lived certificates (validity period <= `DAYS_VALID_SHORTLIVED`, default 10 days) and no longer flag them red merely for their short lifespan; warn only when less than 24h is left (#3097)
### Features implemented / improvements in 3.2 ### Features implemented / improvements in 3.2
+11 -1
View File
@@ -1,4 +1,4 @@
.\" Automatically generated by Pandoc 3.7.0.2 .\" Automatically generated by Pandoc 3.10
.\" .\"
.TH "" "" "" "" .TH "" "" "" ""
.SS NAME .SS NAME
@@ -1147,6 +1147,16 @@ DAYS2WARN2 is the second threshold when you\(cqll be warning of a
certificate expiration of a host, preset to 30 (days). certificate expiration of a host, preset to 30 (days).
For Let\(cqs Encrypt this value will be divided internally by 2. For Let\(cqs Encrypt this value will be divided internally by 2.
.IP \(bu 2 .IP \(bu 2
DAYS_VALID_SHORTLIVED is preset to 10 (days).
Certificates with a total validity period (notAfter \- notBefore) of
this many days or fewer are treated as intentionally short\-lived (see
\(lqShort\-lived Subscriber Certificate\(rq in the CA/Browser Forum
Baseline Requirements, e.g.\ Let\(cqs Encrypt\(cqs 6\-day profile).
They are not being warned of for their short lifespan.
A warning is issued only when less than 24 hours of validity are left,
and only for certificates whose total validity period is more than 24
hours.
.IP \(bu 2
TESTSSL_INSTALL_DIR is the derived installation directory of testssl.sh. TESTSSL_INSTALL_DIR is the derived installation directory of testssl.sh.
Relatively to that the \f[CR]bin\f[R] and mandatory \f[CR]etc\f[R] Relatively to that the \f[CR]bin\f[R] and mandatory \f[CR]etc\f[R]
directory will be looked for. directory will be looked for.
+9
View File
@@ -988,6 +988,15 @@
<li>DAYS2WARN2 is the second threshold when youll be warning of <li>DAYS2WARN2 is the second threshold when youll be warning of
a certificate expiration of a host, preset to 30 (days). For a certificate expiration of a host, preset to 30 (days). For
Lets Encrypt this value will be divided internally by 2.</li> Lets Encrypt this value will be divided internally by 2.</li>
<li>DAYS_VALID_SHORTLIVED is preset to 10 (days). Certificates
with a total validity period (notAfter - notBefore) of this many
days or fewer are treated as intentionally short-lived (see
“Short-lived Subscriber Certificate” in the CA/Browser Forum
Baseline Requirements, e.g. Lets Encrypts 6-day profile). They
are not being warned of for their short lifespan. A warning is
issued only when less than 24 hours of validity are left, and
only for certificates whose total validity period is more than
24 hours.</li>
<li>TESTSSL_INSTALL_DIR is the derived installation directory of <li>TESTSSL_INSTALL_DIR is the derived installation directory of
testssl.sh. Relatively to that the <code>bin</code> and testssl.sh. Relatively to that the <code>bin</code> and
mandatory <code>etc</code> directory will be looked for.</li> mandatory <code>etc</code> directory will be looked for.</li>
+1
View File
@@ -407,6 +407,7 @@ Except the environment variables mentioned above which can replace command line
* HPKP_MIN is preset to 30 (days). If you want warnings sooner or later for HTTP Public Key Pinning you can change this * HPKP_MIN is preset to 30 (days). If you want warnings sooner or later for HTTP Public Key Pinning you can change this
* DAYS2WARN1 is the first threshold when you'll be warning of a certificate expiration of a host, preset to 60 (days). For Let's Encrypt this value will be divided internally by 2. * DAYS2WARN1 is the first threshold when you'll be warning of a certificate expiration of a host, preset to 60 (days). For Let's Encrypt this value will be divided internally by 2.
* DAYS2WARN2 is the second threshold when you'll be warning of a certificate expiration of a host, preset to 30 (days). For Let's Encrypt this value will be divided internally by 2. * DAYS2WARN2 is the second threshold when you'll be warning of a certificate expiration of a host, preset to 30 (days). For Let's Encrypt this value will be divided internally by 2.
* DAYS_VALID_SHORTLIVED is preset to 10 (days). Certificates with a total validity period (notAfter - notBefore) of this many days or fewer are treated as intentionally short-lived (see "Short-lived Subscriber Certificate" in the CA/Browser Forum Baseline Requirements, e.g. Let's Encrypt's 6-day profile). They are not being warned of for their short lifespan. A warning is issued only when less than 24 hours of validity are left, and only for certificates whose total validity period is more than 24 hours.
* TESTSSL_INSTALL_DIR is the derived installation directory of testssl.sh. Relatively to that the `bin` and mandatory `etc` directory will be looked for. * TESTSSL_INSTALL_DIR is the derived installation directory of testssl.sh. Relatively to that the `bin` and mandatory `etc` directory will be looked for.
* CA_BUNDLES_PATH: If you have an own set of CA bundles or you want to point testssl.sh to a specific location of a CA bundle, you can use this variable to set the directory which testssl.sh will use. Please note that it overrides completely the builtin path of testssl.sh which means that you will only test against the bundles you point to. Also you might want to use `~/utils/create_ca_hashes.sh` to create the hashes for HPKP. * CA_BUNDLES_PATH: If you have an own set of CA bundles or you want to point testssl.sh to a specific location of a CA bundle, you can use this variable to set the directory which testssl.sh will use. Please note that it overrides completely the builtin path of testssl.sh which means that you will only test against the bundles you point to. Also you might want to use `~/utils/create_ca_hashes.sh` to create the hashes for HPKP.
* MAX_SOCKET_FAIL: A number which tells testssl.sh how often a TCP socket connection 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_SOCKET_FAIL: A number which tells testssl.sh how often a TCP socket connection 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*.
+23
View File
@@ -220,6 +220,7 @@ HPKP_MIN=${HPKP_MIN:-30} # >=30 days should be ok for HPKP_MIN, p
HPKP_MIN=$((HPKP_MIN * 86400)) # correct to seconds HPKP_MIN=$((HPKP_MIN * 86400)) # correct to seconds
DAYS2WARN1=${DAYS2WARN1:-60} # days to warn before cert expires, threshold 1 DAYS2WARN1=${DAYS2WARN1:-60} # days to warn before cert expires, threshold 1
DAYS2WARN2=${DAYS2WARN2:-30} # days to warn before cert expires, threshold 2 DAYS2WARN2=${DAYS2WARN2:-30} # days to warn before cert expires, threshold 2
DAYS_VALID_SHORTLIVED=${DAYS_VALID_SHORTLIVED:-10} # validity period (notAfter-notBefore) <= this many days => "short-lived", see CA/Browser Forum BR 1.6.1 and #3097
UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses
NO_ENGINE=${NO_ENGINE:-false} # if there are problems finding the (external) openssl engine set this to true NO_ENGINE=${NO_ENGINE:-false} # if there are problems finding the (external) openssl engine set this to true
declare -r CLIENT_MIN_FS=5 # number of ciphers needed to run a test for FS declare -r CLIENT_MIN_FS=5 # number of ciphers needed to run a test for FS
@@ -9590,6 +9591,7 @@ certificate_info() {
local indent="" local indent=""
local days2warn2=$DAYS2WARN2 local days2warn2=$DAYS2WARN2
local days2warn1=$DAYS2WARN1 local days2warn1=$DAYS2WARN1
local cert_is_shortlived=false
local provides_stapling=false local provides_stapling=false
local caa_node="" all_caa="" caa_property_name="" caa_property_value="" local caa_node="" all_caa="" caa_property_name="" caa_property_value=""
local response="" local response=""
@@ -10257,12 +10259,32 @@ certificate_info() {
days2warn1=$((days2warn1 / 2)) days2warn1=$((days2warn1 / 2))
fi fi
# A short-lived certificate has a validity period (notAfter - notBefore) at or below
# DAYS_VALID_SHORTLIVED. These (e.g. Let's Encrypt's 6-day "shortlived" profile) are
# intentionally short, so the normal days2warn thresholds would always flag them red.
# For those we only warn when the cert is nearly expired (< 24h left), see #3097.
[[ $diffseconds -gt 0 ]] && [[ $diffseconds -le $((secsaday*DAYS_VALID_SHORTLIVED)) ]] && cert_is_shortlived=true
debugme echo -n "(diffseconds: $diffseconds)" debugme echo -n "(diffseconds: $diffseconds)"
if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then if ! [[ "$($OPENSSL x509 -checkend 1 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then
pr_svrty_critical "expired" pr_svrty_critical "expired"
expfinding="expired" expfinding="expired"
expok="CRITICAL" expok="CRITICAL"
set_grade_cap "T" "Certificate expired" set_grade_cap "T" "Certificate expired"
elif "$cert_is_shortlived"; then
# An intentionally short-lived cert (e.g. Let's Encrypt's 6-day profile) shouldn't be
# flagged red just for its short lifespan. Warn only when it is about to expire (< 24h
# left) and only if its total lifetime is more than 24h -- otherwise the 24h rule would
# flag such a cert red for its whole life. The "short-lived cert" remark signals intent.
if [[ $diffseconds -gt $secsaday ]] && \
! [[ "$($OPENSSL x509 -checkend $secsaday 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then
pr_svrty_high "short-lived cert, expires < 24h"
expfinding+="short-lived cert, expires < 24h"
expok="HIGH"
else
pr_svrty_good "short-lived cert ($days2expire days)"
expfinding+="short-lived cert ($days2expire days)"
fi
else else
# low threshold first # low threshold first
if [[ "$($OPENSSL x509 -checkend $((secsaday*days2warn2)) 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then if [[ "$($OPENSSL x509 -checkend $((secsaday*days2warn2)) 2>>$ERRFILE <<< "$hostcert")" =~ \ not\ ]]; then
@@ -22101,6 +22123,7 @@ HPKP_MIN: $HPKP_MIN
CLIENT_MIN_FS: $CLIENT_MIN_FS CLIENT_MIN_FS: $CLIENT_MIN_FS
DAYS2WARN1: $DAYS2WARN1 DAYS2WARN1: $DAYS2WARN1
DAYS2WARN2: $DAYS2WARN2 DAYS2WARN2: $DAYS2WARN2
DAYS_VALID_SHORTLIVED: $DAYS_VALID_SHORTLIVED
IPv6_OK: $IPv6_OK IPv6_OK: $IPv6_OK
MAX_WAITSOCK: $MAX_WAITSOCK MAX_WAITSOCK: $MAX_WAITSOCK