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 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. 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? TLS13_ONLY=false # Does the server support TLS 1.3 ONLY?
TLS_EXTENSIONS="" declare -a TLS_EXTENSIONS=()
TLS13_CERT_COMPRESS_METHODS="" TLS13_CERT_COMPRESS_METHODS=""
CERTIFICATE_TRANSPARENCY_SOURCE="" CERTIFICATE_TRANSPARENCY_SOURCE=""
V2_HELLO_CIPHERSPEC_LENGTH=0 V2_HELLO_CIPHERSPEC_LENGTH=0
@ -7884,6 +7884,7 @@ sclient_connect_successful() {
extract_new_tls_extensions() { extract_new_tls_extensions() {
local tls_extensions local tls_extensions
local -i i
# this is not beautiful (grep+sed) # this is not beautiful (grep+sed)
# but maybe we should just get the ids and do a private matching, according to # 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 if [[ -n "$tls_extensions" ]]; then
# check to see if any new TLS extensions were returned and add any new ones to TLS_EXTENSIONS # check to see if any new TLS extensions were returned and add any new ones to TLS_EXTENSIONS
while read -d "\"" -r line; do while read -d "\"" -r line; do
if [[ $line != "" ]] && [[ ! "$TLS_EXTENSIONS" =~ "$line" ]]; then 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 i=${#TLS_EXTENSIONS[*]}
TLS_EXTENSIONS+=" \"${line}\"" 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 fi
done <<<$tls_extensions done <<<$tls_extensions
[[ "${TLS_EXTENSIONS:0:1}" == " " ]] && TLS_EXTENSIONS="${TLS_EXTENSIONS:1}"
fi fi
} }
@ -7918,7 +7922,7 @@ extract_new_tls_extensions() {
determine_tls_extensions() { determine_tls_extensions() {
local addcmd local addcmd
local -i success=1 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 alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex
local -i alpn_list_len alpn_extn_len 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" 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) 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" 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 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" tls_sockets "03" "$cbc_cipher_list_hex, 00,ff" "all" "$tls_extensions"
success=$? success=$?
fi fi
@ -7965,7 +7969,7 @@ determine_tls_extensions() {
else else
addcmd="$SNI" addcmd="$SNI"
fi 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 $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 sclient_connect_successful $? $TMPFILE
success=$? success=$?
@ -7980,7 +7984,13 @@ determine_tls_extensions() {
fi fi
# Keep it "on file" for debugging purposes # 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 return $success
} }
@ -8865,7 +8875,7 @@ certificate_transparency() {
# determine_tls_extensions() discovered an SCT TLS extension. If the server has more than # 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 # one certificate, then it is possible that an SCT TLS extension is returned for some
# certificates, but not for all of them. # 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" CERTIFICATE_TRANSPARENCY_SOURCE="TLS extension"
return 0 return 0
fi 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 ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct
local -a ciphers_to_test certificate_type local -a ciphers_to_test certificate_type
local -a -i success 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 local using_sockets=true
"$SSL_NATIVE" && using_sockets=false "$SSL_NATIVE" && using_sockets=false
@ -10328,7 +10338,7 @@ run_server_defaults() {
outln outln
pr_bold " TLS extensions (standard) " pr_bold " TLS extensions (standard) "
if [[ -z "$TLS_EXTENSIONS" ]]; then if [[ ${#TLS_EXTENSIONS[*]} -eq 0 ]]; then
outln "(none)" outln "(none)"
fileout "TLS_extensions" "INFO" "(none)" fileout "TLS_extensions" "INFO" "(none)"
else else
@ -10339,12 +10349,17 @@ run_server_defaults() {
# across lines, temporarily replace space characters within the text # across lines, temporarily replace space characters within the text
# of an extension with "}", and then convert the "}" back to space in # of an extension with "}", and then convert the "}" back to space in
# the output of out_row_aligned_max_width(). # 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="${tls_extensions//\"{\"/\" \"}"
tls_extensions="$(out_row_aligned_max_width "$tls_extensions" " " $TERM_WIDTH)" tls_extensions="$(out_row_aligned_max_width "$tls_extensions" " " $TERM_WIDTH)"
tls_extensions="${tls_extensions//{/ }" tls_extensions="${tls_extensions//{/ }"
outln "$tls_extensions" outln "$tls_extensions"
fileout "TLS_extensions" "INFO" "$TLS_EXTENSIONS"
fi fi
pr_bold " Session Ticket RFC 5077 hint " pr_bold " Session Ticket RFC 5077 hint "
@ -16712,8 +16727,8 @@ run_heartbleed(){
return 1 return 1
fi fi
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions [[ ${#TLS_EXTENSIONS[*]} -eq 0 ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS}" =~ heartbeat ]]; then if [[ ! "${TLS_EXTENSIONS[*]}" =~ heartbeat ]]; then
pr_svrty_best "not vulnerable (OK)" pr_svrty_best "not vulnerable (OK)"
outln ", no heartbeat extension" outln ", no heartbeat extension"
fileout "$jsonID" "OK" "not vulnerable, no heartbeat extension" "$cve" "$cwe" fileout "$jsonID" "OK" "not vulnerable, no heartbeat extension" "$cve" "$cwe"
@ -17017,8 +17032,8 @@ run_ticketbleed() {
fi fi
# highly unlikely that it is NOT supported. We may loose time here but it's more solid # highly unlikely that it is NOT supported. We may loose time here but it's more solid
[[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions [[ ${#TLS_EXTENSIONS[*]} -eq 0 ]] && determine_tls_extensions
if [[ ! "${TLS_EXTENSIONS}" =~ "session ticket" ]]; then if [[ ! "${TLS_EXTENSIONS[*]}" =~ "session ticket" ]]; then
pr_svrty_best "not vulnerable (OK)" pr_svrty_best "not vulnerable (OK)"
outln ", no session ticket extension" outln ", no session ticket extension"
fileout "$jsonID" "OK" "no session ticket extension" "$cve" "$cwe" 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. # (~ 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 # 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 # 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") 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 # 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. # 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 # 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) # We use the whole array, got to be careful when the array becomes bigger (unintended match)
if [[ ${forbidden_tls_ext[@]} =~ $tls_ext ]]; then if [[ ${forbidden_tls_ext[@]} =~ $tls_ext ]]; then
pr_svrty_best "not vulnerable (OK)"; outln " - TLS extension $tls_ext detected" pr_svrty_best "not vulnerable (OK)"; outln " - TLS extension $tls_ext detected"
@ -24378,7 +24393,7 @@ reset_hostdepended_vars() {
NR_OSSL_FAIL=0 NR_OSSL_FAIL=0
NR_STARTTLS_FAIL=0 NR_STARTTLS_FAIL=0
NR_HEADER_FAIL=0 NR_HEADER_FAIL=0
TLS_EXTENSIONS="" TLS_EXTENSIONS=()
TLS13_CERT_COMPRESS_METHODS="" TLS13_CERT_COMPRESS_METHODS=""
CERTIFICATE_TRANSPARENCY_SOURCE="" CERTIFICATE_TRANSPARENCY_SOURCE=""
PROTOS_OFFERED="" PROTOS_OFFERED=""