Sort TLS extensions

This commit modifies testssl.sh so that run_server_defaults() prints the server's supported TLS extensions sorted by extension number rather than listing them in the order in which they were found.

In order to simplify the sorting of the extensions, this commit changes $TLS_EXTENSIONS from a string to an array. In February 2017 comments were added (925e1061b2) saying that it would be $TLS_EXTENSIONS were an array. So, this commit addresses those comments. However, it is possible that the reason for those comments no longer apply.
This commit is contained in:
David Cooper 2025-03-10 15:38:24 -07:00 committed by GitHub
parent f34b81ed8f
commit 75b78bc21a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -277,7 +277,7 @@ KNOWN_OSSL_PROB=false # We need OpenSSL a few times. This vari
DETECTED_TLS_VERSION="" # .. as hex string, e.g. 0300 or 0303
APP_TRAF_KEY_INFO="" # Information about the application traffic keys for a TLS 1.3 connection.
TLS13_ONLY=false # Does the server support TLS 1.3 ONLY?
TLS_EXTENSIONS=""
declare -a TLS_EXTENSIONS=()
TLS13_CERT_COMPRESS_METHODS=""
CERTIFICATE_TRANSPARENCY_SOURCE=""
V2_HELLO_CIPHERSPEC_LENGTH=0
@ -7884,6 +7884,7 @@ sclient_connect_successful() {
extract_new_tls_extensions() {
local tls_extensions
local -i i
# this is not beautiful (grep+sed)
# but maybe we should just get the ids and do a private matching, according to
@ -7897,12 +7898,15 @@ extract_new_tls_extensions() {
if [[ -n "$tls_extensions" ]]; then
# check to see if any new TLS extensions were returned and add any new ones to TLS_EXTENSIONS
while read -d "\"" -r line; do
if [[ $line != "" ]] && [[ ! "$TLS_EXTENSIONS" =~ "$line" ]]; then
#FIXME: This is a string of quoted strings, so this seems to determine the output format already. Better e.g. would be an array
TLS_EXTENSIONS+=" \"${line}\""
if [[ $line != "" ]] && [[ ! "${TLS_EXTENSIONS[*]}" =~ "$line" ]]; then
i=${#TLS_EXTENSIONS[*]}
while [[ $i -gt 0 ]] && [[ ${TLS_EXTENSIONS[i-1]#*/#} -gt ${line#*/#} ]]; do
TLS_EXTENSIONS[i]="${TLS_EXTENSIONS[i-1]}"
i=$((i-1))
done
TLS_EXTENSIONS[i]="$line"
fi
done <<<$tls_extensions
[[ "${TLS_EXTENSIONS:0:1}" == " " ]] && TLS_EXTENSIONS="${TLS_EXTENSIONS:1}"
fi
}
@ -7918,7 +7922,7 @@ extract_new_tls_extensions() {
determine_tls_extensions() {
local addcmd
local -i success=1
local line params="" tls_extensions=""
local line params="" tls_extensions="" extn
local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex
local -i alpn_list_len alpn_extn_len
local cbc_cipher_list="ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DH-RSA-CAMELLIA256-SHA256:DH-DSS-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:ECDH-RSA-CAMELLIA256-SHA384:ECDH-ECDSA-CAMELLIA256-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DH-RSA-CAMELLIA128-SHA256:DH-DSS-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-CAMELLIA128-SHA256:ECDH-ECDSA-CAMELLIA128-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA"
@ -7940,7 +7944,7 @@ determine_tls_extensions() {
alpn_extn_len_hex=$(printf "%04x" $alpn_extn_len)
tls_extensions+=", 00,10,${alpn_extn_len_hex:0:2},${alpn_extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn"
fi
if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then
if [[ ! "${TLS_EXTENSIONS[*]}" =~ encrypt-then-mac ]]; then
tls_sockets "03" "$cbc_cipher_list_hex, 00,ff" "all" "$tls_extensions"
success=$?
fi
@ -7965,7 +7969,7 @@ determine_tls_extensions() {
else
addcmd="$SNI"
fi
if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then
if [[ ! "${TLS_EXTENSIONS[*]}" =~ encrypt-then-mac ]]; then
$OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params -cipher $cbc_cipher_list") </dev/null 2>$ERRFILE >$TMPFILE
sclient_connect_successful $? $TMPFILE
success=$?
@ -7980,7 +7984,13 @@ determine_tls_extensions() {
fi
# Keep it "on file" for debugging purposes
[[ "$DEBUG" -ge 1 ]] && safe_echo "$TLS_EXTENSIONS" >"$TEMPDIR/$NODE.$NODEIP.tls_extensions.txt"
if [[ "$DEBUG" -ge 1 ]]; then
tls_extensions=""
for extn in "${TLS_EXTENSIONS[@]}"; do
tls_extensions+=" \"${extn}\""
done
safe_echo "${tls_extensions:1}" >"$TEMPDIR/$NODE.$NODEIP.tls_extensions.txt"
fi
return $success
}
@ -8865,7 +8875,7 @@ certificate_transparency() {
# determine_tls_extensions() discovered an SCT TLS extension. If the server has more than
# one certificate, then it is possible that an SCT TLS extension is returned for some
# certificates, but not for all of them.
if [[ $number_of_certificates -eq 1 ]] && [[ "$TLS_EXTENSIONS" =~ signed\ certificate\ timestamps ]]; then
if [[ $number_of_certificates -eq 1 ]] && [[ "${TLS_EXTENSIONS[*]}" =~ signed\ certificate\ timestamps ]]; then
CERTIFICATE_TRANSPARENCY_SOURCE="TLS extension"
return 0
fi
@ -10076,7 +10086,7 @@ run_server_defaults() {
local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct
local -a ciphers_to_test certificate_type
local -a -i success
local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions client_auth_ca
local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions extn client_auth_ca
local using_sockets=true
"$SSL_NATIVE" && using_sockets=false
@ -10328,7 +10338,7 @@ run_server_defaults() {
outln
pr_bold " TLS extensions (standard) "
if [[ -z "$TLS_EXTENSIONS" ]]; then
if [[ ${#TLS_EXTENSIONS[*]} -eq 0 ]]; then
outln "(none)"
fileout "TLS_extensions" "INFO" "(none)"
else
@ -10339,12 +10349,17 @@ run_server_defaults() {
# across lines, temporarily replace space characters within the text
# of an extension with "}", and then convert the "}" back to space in
# the output of out_row_aligned_max_width().
tls_extensions="${TLS_EXTENSIONS// /{}"
tls_extensions=""
for extn in "${TLS_EXTENSIONS[@]}"; do
tls_extensions+=" \"${extn}\""
done
tls_extensions="${tls_extensions:1}"
fileout "TLS_extensions" "INFO" "$tls_extensions"
tls_extensions="${tls_extensions// /{}"
tls_extensions="${tls_extensions//\"{\"/\" \"}"
tls_extensions="$(out_row_aligned_max_width "$tls_extensions" " " $TERM_WIDTH)"
tls_extensions="${tls_extensions//{/ }"
outln "$tls_extensions"
fileout "TLS_extensions" "INFO" "$TLS_EXTENSIONS"
fi
pr_bold " Session Ticket RFC 5077 hint "
@ -16712,8 +16727,8 @@ run_heartbleed(){
return 1
fi
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS}" =~ heartbeat ]]; then
[[ ${#TLS_EXTENSIONS[*]} -eq 0 ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS[*]}" =~ heartbeat ]]; then
pr_svrty_best "not vulnerable (OK)"
outln ", no heartbeat extension"
fileout "$jsonID" "OK" "not vulnerable, no heartbeat extension" "$cve" "$cwe"
@ -17017,8 +17032,8 @@ run_ticketbleed() {
fi
# highly unlikely that it is NOT supported. We may loose time here but it's more solid
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS}" =~ "session ticket" ]]; then
[[ ${#TLS_EXTENSIONS[*]} -eq 0 ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS[*]}" =~ "session ticket" ]]; then
pr_svrty_best "not vulnerable (OK)"
outln ", no session ticket extension"
fileout "$jsonID" "OK" "no session ticket extension" "$cve" "$cwe"
@ -19120,7 +19135,7 @@ run_winshock() {
# (~ sub_check_curves) which is some work. But also for the sake of clean code this needs to be done.
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
[[ ${#TLS_EXTENSIONS[*]} -eq 0 ]] && determine_tls_extensions
# Basis of the following https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations#Extensions
# Our standard: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
@ -19134,9 +19149,9 @@ run_winshock() {
local -a forbidden_tls_ext=("encrypt-then-mac" "max fragment length")
# Open whether ec_point_formats, supported_groups(=elliptic_curves), heartbeat are supported under windows <=2012
# key_share and supported_versions are extensions which came with TLS 1.3. We checked the protocol before.
if [[ -n "$TLS_EXTENSIONS" ]]; then
if [[ ${#TLS_EXTENSIONS[*]} -gt 0 ]]; then
# Check whether there are any TLS extension which should not be available under <= Windows 2012 R2
for tls_ext in $TLS_EXTENSIONS; do
for tls_ext in "${TLS_EXTENSIONS[@]}"; do
# We use the whole array, got to be careful when the array becomes bigger (unintended match)
if [[ ${forbidden_tls_ext[@]} =~ $tls_ext ]]; then
pr_svrty_best "not vulnerable (OK)"; outln " - TLS extension $tls_ext detected"
@ -24378,7 +24393,7 @@ reset_hostdepended_vars() {
NR_OSSL_FAIL=0
NR_STARTTLS_FAIL=0
NR_HEADER_FAIL=0
TLS_EXTENSIONS=""
TLS_EXTENSIONS=()
TLS13_CERT_COMPRESS_METHODS=""
CERTIFICATE_TRANSPARENCY_SOURCE=""
PROTOS_OFFERED=""