Merge pull request #2822 from testssl/quic

First try for QUIC (OpenSSL only and only checking the protocol)
This commit is contained in:
Dirk Wetter
2025-07-05 13:24:25 +02:00
committed by GitHub
9 changed files with 2760 additions and 1263 deletions

View File

@ -12,5 +12,5 @@ jobs:
- uses: actions/checkout@v4
- uses: codespell-project/actions-codespell@master
with:
skip: ca_hashes.txt,tls_data.txt,*.pem,OPENSSL-LICENSE.txt,CREDITS.md,openssl.cnf,fedora-dirk-ipv6.diff
skip: ca_hashes.txt,tls_data.txt,*.pem,OPENSSL-LICENSE.txt,CREDITS.md,openssl.cnf,fedora-dirk-ipv6.diff,testssl.1
ignore_words_list: borken,gost,ciph,ba,bloc,isnt,chello,fo,alle,anull

View File

@ -1,6 +1,10 @@
## Change Log
### Features implemented / improvements in 3.3dev
* QUIC protocol check
### Features implemented / improvements in 3.2
* Rating (SSL Labs)

View File

@ -5,7 +5,7 @@ Contributing / participating is always welcome!
Please note the following:
* Please read the [coding convention](https://github.com/testssl/testssl.sh/blob/3.2/Coding_Convention.md).
* Please read the [coding convention](https://github.com/testssl/testssl.sh/blob/3.3dev/Coding_Convention.md).
* If you have something new and/or bigger which you like to contribute, better open an issue first before you get frustrated.
* Please one pull request per feature or bug fix or improvement. Please do not mix issues.
* Documentation pays off in the long run. So please your document your code and the pull request and/or commit message.

View File

@ -15,3 +15,5 @@ $(NAME).$(MANSECTION): $(NAME).$(MANSECTION).md
$(NAME).$(MANSECTION).html: template.html $(NAME).$(MANSECTION).md
$(PANDOC) --standalone --to html5 --template template.html --metadata title="$(TITLE)" $(NAME).$(MANSECTION).md -o $@
# Source is Markdown

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -175,7 +175,7 @@ Any single check switch supplied as an argument prevents testssl.sh from doing a
`-f, --fs, --nsa, --forward-secrecy` Checks robust forward secrecy key exchange. "Robust" means that ciphers having intrinsic severe weaknesses like Null Authentication or Encryption, 3DES and RC4 won't be considered here. There shouldn't be the wrong impression that a secure key exchange has been taking place and everything is fine when in reality the encryption sucks. Also this section lists the available elliptical curves and Diffie Hellman groups, as well as FFDHE groups (TLS 1.2 and TLS 1.3).
`-p, --protocols` checks TLS/SSL protocols SSLv2, SSLv3, TLS 1.0 through TLS 1.3 and for HTTP: SPDY (NPN) and ALPN, a.k.a. HTTP/2. For TLS 1.3 several drafts (from 18 on) and final are supported and being tested for. Note the supplied openssl-bad version doesn't support TLS 1.3 . As the check for TLS 1.3 will be done in sockets this normally does not pose a problem. However if a TLS-1.3-only host is encountered and to have a complete test coverage (e.g. header checks) `/usr/bin/openssl` (or the content of `OPENSSL2`) is checked for existence and support of TLS 1.3 and if those tests succeeded it will be switched to this binary. A message will notify you.
`-p, --protocols` checks TLS/SSL protocols SSLv2, SSLv3, TLS 1.0 through TLS 1.3. And for HTTP also QUIC (HTTP/3), SPDY (NPN) and ALPN (HTTP/2). For TLS 1.3 the final version and several drafts (from 18 on) are tested. QUIC needs OpenSSL >= 3.2 which can be automatically picked up when in `/usr/bin/openssl` (or when defined environment variable OPENSSL2). If a TLS-1.3-only host is encountered and the openssl-bad version is used testssl.sh will e.g. for HTTP header checks switch to `/usr/bin/openssl` (or when defined via ENV to OPENSSL2). Also this will be tried for the QUIC check.
`-P, --server-preference, --preference` displays the servers preferences: cipher order, with used openssl client: negotiated protocol and cipher. If there's a cipher order enforced by the server it displays it for each protocol (openssl+sockets). If there's not, it displays instead which ciphers from the server were picked with each protocol.
@ -520,6 +520,7 @@ Please note that for plain TLS-encrypted ports you must not specify the protocol
* RFC 8143: Using Transport Layer Security (TLS) with Network News Transfer Protocol (NNTP)
* RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3
* RFC 8701: Applying Generate Random Extensions And Sustain Extensibility (GREASE) to TLS Extensibility
* RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport
* W3C CSP: Content Security Policy Level 1-3
* TLSWG Draft: The Transport Layer Security (TLS) Protocol Version 1.3

View File

@ -8,6 +8,7 @@
"TLS1_1","testssl.sh/81.169.166.184","443","LOW","offered (deprecated)","",""
"TLS1_2","testssl.sh/81.169.166.184","443","OK","offered","",""
"TLS1_3","testssl.sh/81.169.166.184","443","OK","offered with final","",""
"QUIC","testssl.sh/81.169.166.184","443","WARN","not tested due to lack of local OpenSSL support","",""
"NPN","testssl.sh/81.169.166.184","443","INFO","offered with h2, http/1.1 (advertised)","",""
"ALPN_HTTP2","testssl.sh/81.169.166.184","443","OK","h2","",""
"ALPN","testssl.sh/81.169.166.184","443","INFO","http/1.1","",""

View File

@ -122,7 +122,7 @@ trap "child_error" USR1
########### Internal definitions
#
declare -r VERSION="3.2.1"
declare -r VERSION="3.3dev"
declare -r SWCONTACT="dirk aet testssl dot sh"
[[ "$VERSION" =~ dev|rc|beta ]] && \
SWURL="https://testssl.sh/dev/" ||
@ -205,6 +205,7 @@ MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2} # If this many failures for s_client con
MAX_STARTTLS_FAIL=${MAX_STARTTLS_FAIL:-2} # max number of STARTTLS handshake failures in plaintext phase
MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-2} # If this many failures for HTTP GET are encountered we don't try again to get the header
MAX_WAITSOCK=${MAX_WAITSOCK:-10} # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this.
QUIC_WAIT=${QUIC_WAIT:-3} # QUIC is UDP. Thus we run the connect in the background. This is how long to wait
CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this.
HEARTBLEED_MAX_WAITSOCK=${HEARTBLEED_MAX_WAITSOCK:-8} # for the heartbleed payload. There shouldn't be any reason to change this.
STARTTLS_SLEEP=${STARTTLS_SLEEP:-10} # max time wait on a socket for STARTTLS. MySQL has a fixed value of 1 which can't be overwritten (#914)
@ -339,6 +340,8 @@ HAS_TLS1=false
HAS_TLS11=false
HAS_TLS12=false
HAS_TLS13=false
HAS_QUIC=false
HAS2_QUIC=false # for automagically determined second OPENSSL version
HAS_X448=false
HAS_X25519=false
HAS_SIGALGS=false
@ -367,7 +370,7 @@ HAS_AES128_GCM=false
HAS_AES256_GCM=false
HAS_ZLIB=false
HAS_UDS=false
HAS_UDS2=false
HAS2_UDS=false
HAS_ENABLE_PHA=false
HAS_DIG=false
HAS_DIG_R=true
@ -5468,6 +5471,7 @@ add_proto_offered() {
# arg1: protocol string or hex code for TLS protocol
# echos: 0 if proto known being offered, 1: known not being offered, 2: we don't know yet whether proto is being offered
# return value is always zero
#
has_server_protocol() {
local proto
local proto_val_pair
@ -5502,6 +5506,7 @@ has_server_protocol() {
# the protocol check needs to be revamped. It sucks, see above
#
run_protocols() {
local using_sockets=true
local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)"
@ -6125,10 +6130,59 @@ run_protocols() {
[[ $? -ne 0 ]] && exit $ERR_CLUELESS
fi
sub_quic
return $ret
}
# We do QUIC check first purely via OpenSSL, supposed it is supported by openssl
#
sub_quic() {
local alpn=""
local use_openssl=""
local proxy_hint_str=""
local sclient_outfile="$TEMPDIR/$NODEIP.quic_connect.txt"
local sclient_errfile="$TEMPDIR/$NODEIP.quic_connect_err.txt"
local jsonID="QUIC"
[[ $DEBUG -ne 0 ]] && sclient_errfile=/dev/null
pr_bold " QUIC ";
if "$HAS2_QUIC" || "$HAS_QUIC"; then
# Proxying QUIC is not supported
# The s_client call would block if either the remote side doesn't support QUIC or outbound traffic is blocked
if "$HAS2_QUIC"; then
use_openssl="$OPENSSL2"
else
use_openssl="$OPENSSL"
fi
OPENSSL_CONF='' $use_openssl s_client -quic -alpn h3 -connect $NODEIP:$PORT -servername $NODE </dev/null \
2>$sclient_errfile >$sclient_outfile &
wait_kill $! $QUIC_WAIT
if [[ $? -ne 0 ]]; then
if [[ -n "$PROXY" ]]; then
proxy_hint_str="(tried directly, is not proxyable):"
fi
outln "$proxy_hint_str not offered or timed out"
fileout "$jsonID" "INFO" "$proxy_hint_str not offered"
else
pr_svrty_best "offered (OK)"
fileout "$jsonID" "OK" "offered"
alpn="$(awk -F':' '/^ALPN protocol/ { print $2 }' < $sclient_outfile)"
alpn="$(strip_spaces $alpn)"
outln ": $(awk '/^Protocol:/ { print $2 }' < $sclient_outfile) ($alpn)"
fi
else
prln_local_problem "No OpenSSL QUIC support"
fileout "$jsonID" "WARN" "not tested due to lack of local OpenSSL support"
fi
return 0
}
# list ciphers (and makes sure you have them locally configured)
# arg[1]: non-TLSv1.3 cipher list (or anything else)
# arg[2]: TLSv1.3 cipher list
@ -19900,7 +19954,7 @@ run_starttls_injection() {
outln "Need socat for this check"
return 1
fi
if ! "$HAS_UDS2" && ! "$HAS_UDS"; then
if ! "$HAS2_UDS" && ! "$HAS_UDS"; then
fileout "$jsonID" "WARN" "Need OpenSSL with Unix-domain socket s_client support for this check" "$cve" "$cwe" "$hint"
outln "Need an OpenSSL with Unix-domain socket s_client support for this check"
return 1
@ -19926,7 +19980,7 @@ run_starttls_injection() {
if "$HAS_UDS"; then
openssl_bin="$OPENSSL"
elif "$HAS_UDS2"; then
elif "$HAS2_UDS"; then
openssl_bin="$OPENSSL2"
fi
# normally the interesting fallback we grep later for is in fd2 but we'll catch also stdout here
@ -20684,7 +20738,7 @@ find_openssl_binary() {
local s_client_has=$TEMPDIR/s_client_has.txt
local s_client_has2=$TEMPDIR/s_client_has2.txt
local s_client_starttls_has=$TEMPDIR/s_client_starttls_has.txt
local s_client_starttls_has2=$TEMPDIR/s_client_starttls_has2
local s_client2_starttls_has=$TEMPDIR/s_client2_starttls_has
local openssl_location="" cwd=""
local curve="" ossl_tls13_supported_curves
local ossl_line1="" yr=""
@ -20831,7 +20885,7 @@ find_openssl_binary() {
HAS_AES256_GCM=false
HAS_ZLIB=false
HAS_UDS=false
HAS_UDS2=false
HAS2_UDS=false
TRUSTED1ST=""
HAS_ENABLE_PHA=false
@ -20868,16 +20922,20 @@ find_openssl_binary() {
$OPENSSL s_client -tls1_3 -sigalgs PSS+SHA256:PSS+SHA384 $NXCONNECT </dev/null 2>&1 | grep -aiq "unknown option" || HAS_SIGALGS=true
fi
if [[ -x $OPENSSL2 ]] && OPENSSL_CONF='' $OPENSSL2 s_client -quic 2>&1 | grep -qi 'QUIC requires ALPN'; then
HAS2_QUIC="true"
elif OPENSSL_CONF='' $OPENSSL s_client -quic 2>&1 | grep -qi 'QUIC requires ALPN'; then
HAS_QUIC="true"
fi
$OPENSSL s_client -noservername </dev/null 2>&1 | grep -aiq "unknown option" || HAS_NOSERVERNAME=true
$OPENSSL s_client -ciphersuites </dev/null 2>&1 | grep -aiq "unknown option" || HAS_CIPHERSUITES=true
$OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null 2> /dev/null && HAS_SECLEVEL=true
$OPENSSL s_client -comp </dev/null 2>&1 | grep -aiq "unknown option" || HAS_COMP=true
$OPENSSL s_client -no_comp </dev/null 2>&1 | grep -aiq "unknown option" || HAS_NO_COMP=true
OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL')")
$OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null 2> /dev/null && HAS_SECLEVEL=true
OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL')")
if [[ $OPENSSL_NR_CIPHERS -le 140 ]]; then
[[ "$OSSL_NAME" =~ LibreSSL ]] && [[ ${OSSL_VER//./} -ge 210 ]] && HAS_DH_BITS=true
if "$SSL_NATIVE"; then
@ -20981,9 +21039,9 @@ find_openssl_binary() {
# We also check, whether there's $OPENSSL2 which has TLS 1.3
if [[ ! "$OSSL_NAME" =~ LibreSSL ]] && [[ ! $OSSL_VER =~ 1.1.1 ]] && [[ $OSSL_VER_MAJOR -lt 3 ]]; then
OPENSSL_CONF='' $OPENSSL2 s_client -help 2>$s_client_has2
OPENSSL_CONF='' $OPENSSL2 s_client -starttls foo 2>$s_client_starttls_has2
grep -q 'Unix-domain socket' $s_client_has2 && HAS_UDS2=true
grep -q 'xmpp-server' $s_client_starttls_has2 && HAS_XMPP_SERVER2=true
OPENSSL_CONF='' $OPENSSL2 s_client -starttls foo 2>$s_client2_starttls_has
grep -q 'Unix-domain socket' $s_client_has2 && HAS2_UDS=true
grep -q 'xmpp-server' $s_client2_starttls_has && HAS_XMPP_SERVER2=true
# Likely we don't need the following second check here, see 6 lines above
if grep -wq 'tls1_3' $s_client_has2; then
OPENSSL_CONF='' OPENSSL2_HAS_TLS_1_3=true
@ -21179,7 +21237,7 @@ single check as <options> ("$PROG_NAME URI" does everything except -E and -g):
-E, --cipher-per-proto checks those per protocol
-s, --std, --categories tests standard cipher categories by strength
-f, --fs, --forward-secrecy checks forward secrecy settings
-p, --protocols checks TLS/SSL protocols (including SPDY/HTTP2)
-p, --protocols checks TLS/SSL protocols, for HTTP: including QUIC/HTTP/3 and ALPN/HTTP2 (and SPDY)
-g, --grease tests several server implementation bugs like GREASE and size limitations
-S, --server-defaults displays the server's default picks and certificate info
-P, --server-preference displays the server's picks: protocol+cipher
@ -21339,6 +21397,8 @@ HAS_TLS1: $HAS_TLS1
HAS_TLS11: $HAS_TLS11
HAS_TLS12: $HAS_TLS12
HAS_TLS13: $HAS_TLS13
HAS_QUIC: $HAS_QUIC
HAS2_QUIC: $HAS2_QUIC
HAS_X448: $HAS_X448
HAS_X25519: $HAS_X25519
HAS_SIGALGS: $HAS_SIGALGS
@ -21363,7 +21423,7 @@ HAS_SIEVE: $HAS_SIEVE
HAS_NNTP: $HAS_NNTP
HAS_IRC: $HAS_IRC
HAS_UDS: $HAS_UDS
HAS_UDS2: $HAS_UDS2
HAS2_UDS: $HAS2_UDS
HAS_ENABLE_PHA: $HAS_ENABLE_PHA
HAS_DIG: $HAS_DIG