merge upstream

This commit is contained in:
Magnus Larsen 2020-05-28 20:20:32 +02:00
commit 985e647cdf
2 changed files with 316 additions and 74 deletions

View File

@ -11,10 +11,12 @@
* Added several ciphers to colored ciphers * Added several ciphers to colored ciphers
* Percent output char problem fixed * Percent output char problem fixed
* Several display/output fixes * Several display/output fixes
* BREACH check: list all compression methods and add brotli
* Security fix: DNS input * Security fix: DNS input
* Don't use external pwd anymore * Don't use external pwd anymore
* STARTTLS: XMPP server support * STARTTLS: XMPP server support
* Rating (SSL Labs, not complete) * Rating (SSL Labs, not complete)
* Added support for certificates with EdDSA signatures and pubilc keys
### Features implemented / improvements in 3.0 ### Features implemented / improvements in 3.0

View File

@ -1038,8 +1038,18 @@ set_key_str_score() {
"$do_rating" || return 0 "$do_rating" || return 0
<<<<<<< HEAD
if [[ $type == EC ]]; then if [[ $type == EC ]]; then
if [[ $size -lt 123 ]] && [[ $KEY_EXCH_SCORE -gt 40 ]]; then if [[ $size -lt 123 ]] && [[ $KEY_EXCH_SCORE -gt 40 ]]; then
=======
# 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 == EdDSA ]]; then
if [[ $size -lt 110 ]] && [[ $KEY_EXCH_SCORE -gt 20 ]]; then
let KEY_EXCH_SCORE=20
set_grade_cap "F" "Using an insecure key"
elif [[ $size -lt 123 ]] && [[ $KEY_EXCH_SCORE -gt 40 ]]; then
>>>>>>> upstream/3.1dev
let KEY_EXCH_SCORE=40 let KEY_EXCH_SCORE=40
set_grade_cap "F" "Using an insecure key" set_grade_cap "F" "Using an insecure key"
elif [[ $size -lt 163 ]] && [[ $KEY_EXCH_SCORE -gt 80 ]]; then elif [[ $size -lt 163 ]] && [[ $KEY_EXCH_SCORE -gt 80 ]]; then
@ -6241,7 +6251,15 @@ read_dhtype_from_file() {
# arg1: certificate file # arg1: certificate file
read_sigalg_from_file() { read_sigalg_from_file() {
$OPENSSL x509 -noout -text -in "$1" 2>/dev/null | awk -F':' '/Signature Algorithm/ { print $2; exit; }' local sig_alg
sig_alg="$(strip_leading_space "$($OPENSSL x509 -noout -text -in "$1" 2>/dev/null | awk -F':' '/Signature Algorithm/ { print $2; exit; }')")"
case "$sig_alg" in
1.3.101.112|ED25519) tm_out "Ed25519" ;;
1.3.101.113|ED448) tm_out "Ed448" ;;
*) tm_out "$sig_alg" ;;
esac
} }
@ -6985,7 +7003,11 @@ cipher_pref_check() {
! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}" ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}"
done done
[[ -z "$ciphers_to_test" ]] && break [[ -z "$ciphers_to_test" ]] && break
tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" if "$wide" && "$SHOW_SIGALGO"; then
tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all"
else
tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
fi
[[ $? -ne 0 ]] && break [[ $? -ne 0 ]] && break
cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt") cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
for (( i=0; i < nr_ciphers; i++ )); do for (( i=0; i < nr_ciphers; i++ )); do
@ -7533,12 +7555,12 @@ get_server_certificate() {
"$SSL_NATIVE" && using_sockets=false "$SSL_NATIVE" && using_sockets=false
CERTIFICATE_LIST_ORDERING_PROBLEM=false CERTIFICATE_LIST_ORDERING_PROBLEM=false
if [[ "$1" =~ "tls1_3" ]]; then if [[ "$1" =~ tls1_3 ]]; then
[[ $(has_server_protocol "tls1_3") -eq 1 ]] && return 1 [[ $(has_server_protocol "tls1_3") -eq 1 ]] && return 1
if "$HAS_TLS13" && "$HAS_SIGALGS"; then if "$HAS_TLS13" && "$HAS_SIGALGS" && [[ ! "$1" =~ tls1_3_EdDSA ]]; then
if [[ "$1" =~ "tls1_3_RSA" ]]; then if [[ "$1" =~ tls1_3_RSA ]]; then
$OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs PSS+SHA256:PSS+SHA384") </dev/null 2>$ERRFILE >$TMPFILE $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs PSS+SHA256:PSS+SHA384") </dev/null 2>$ERRFILE >$TMPFILE
elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then elif [[ "$1" =~ tls1_3_ECDSA ]]; then
$OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs ECDSA+SHA256:ECDSA+SHA384") </dev/null 2>$ERRFILE >$TMPFILE $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs ECDSA+SHA256:ECDSA+SHA384") </dev/null 2>$ERRFILE >$TMPFILE
else else
return 1 return 1
@ -7552,10 +7574,12 @@ get_server_certificate() {
# For STARTTLS protcols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP) # For STARTTLS protcols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP)
if ( [[ "$STARTTLS" =~ ldap ]] || [[ "$STARTTLS" =~ irc ]] ); then if ( [[ "$STARTTLS" =~ ldap ]] || [[ "$STARTTLS" =~ irc ]] ); then
return 1 return 1
elif [[ "$1" =~ "tls1_3_RSA" ]]; then elif [[ "$1" =~ tls1_3_RSA ]]; then
tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01" tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01"
elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then elif [[ "$1" =~ tls1_3_ECDSA ]]; then
tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03" tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03"
elif [[ "$1" =~ tls1_3_EdDSA ]]; then
tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,06,00,04,08,07,08,08"
else else
return 1 return 1
fi fi
@ -8320,8 +8344,16 @@ certificate_info() {
GOOD_CA_BUNDLE="" GOOD_CA_BUNDLE=""
cert_sig_algo="$(awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")" cert_sig_algo="$(awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")"
cert_sig_algo="${cert_sig_algo// /}" cert_sig_algo="${cert_sig_algo// /}"
case "$cert_sig_algo" in
1.3.101.112|ED25519) cert_sig_algo="Ed25519" ;;
1.3.101.113|ED448) cert_sig_algo="Ed448" ;;
esac
cert_key_algo="$(awk -F':' '/Public Key Algorithm:/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")" cert_key_algo="$(awk -F':' '/Public Key Algorithm:/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")"
cert_key_algo="${cert_key_algo// /}" cert_key_algo="${cert_key_algo// /}"
case "$cert_key_algo" in
1.3.101.112|E[Dd]25519) cert_key_algo="Ed25519"; cert_keysize=253 ;;
1.3.101.113|E[Dd]448) cert_key_algo="Ed448"; cert_keysize=456 ;;
esac
out "$indent" ; pr_bold " Signature Algorithm " out "$indent" ; pr_bold " Signature Algorithm "
jsonID="cert_signatureAlgorithm" jsonID="cert_signatureAlgorithm"
@ -8429,6 +8461,10 @@ certificate_info() {
fileout "${jsonID}${json_postfix}" "CRITICAL" "MD5" fileout "${jsonID}${json_postfix}" "CRITICAL" "MD5"
set_grade_cap "F" "Supports a insecure signature (MD5)" set_grade_cap "F" "Supports a insecure signature (MD5)"
;; ;;
Ed25519|Ed448)
prln_svrty_good "$cert_sig_algo"
fileout "${jsonID}${json_postfix}" "OK" "$cert_sig_algo"
;;
*) *)
out "$cert_sig_algo (" out "$cert_sig_algo ("
pr_warning "FIXME: can't tell whether this is good or not" pr_warning "FIXME: can't tell whether this is good or not"
@ -8449,6 +8485,7 @@ certificate_info() {
case $cert_key_algo in case $cert_key_algo in
*RSA*|*rsa*) short_keyAlgo="RSA";; *RSA*|*rsa*) short_keyAlgo="RSA";;
*ecdsa*|*ecPublicKey) short_keyAlgo="EC";; *ecdsa*|*ecPublicKey) short_keyAlgo="EC";;
*Ed25519*|*Ed448*) short_keyAlgo="EdDSA";;
*DSA*|*dsa*) short_keyAlgo="DSA";; *DSA*|*dsa*) short_keyAlgo="DSA";;
*GOST*|*gost*) short_keyAlgo="GOST";; *GOST*|*gost*) short_keyAlgo="GOST";;
*dh*|*DH*) short_keyAlgo="DH" ;; *dh*|*DH*) short_keyAlgo="DH" ;;
@ -8511,6 +8548,10 @@ certificate_info() {
((ret++)) ((ret++))
fi fi
set_key_str_score "$short_keyAlgo" "$cert_keysize"
elif [[ $cert_key_algo == Ed* ]]; then
pr_svrty_good "$cert_key_algo"
json_rating="OK"; json_msg="$short_keyAlgo $cert_key_algo"
set_key_str_score "$short_keyAlgo" "$cert_keysize" set_key_str_score "$short_keyAlgo" "$cert_keysize"
else else
out "$cert_key_algo + $cert_keysize bits (" out "$cert_key_algo + $cert_keysize bits ("
@ -8574,19 +8615,19 @@ certificate_info() {
cert_keyusage="$(strip_leading_space "$(awk '/X509v3 Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")" cert_keyusage="$(strip_leading_space "$(awk '/X509v3 Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")"
if [[ -n "$cert_keyusage" ]]; then if [[ -n "$cert_keyusage" ]]; then
outln "$cert_keyusage" outln "$cert_keyusage"
if ( [[ " $cert_type " =~ " RSASig " ]] || [[ " $cert_type " =~ " DSA " ]] || [[ " $cert_type " =~ " ECDSA " ]] ) && \ if ( [[ " $cert_type " =~ \ RSASig\ ]] || [[ " $cert_type " =~ \ DSA\ ]] || [[ " $cert_type " =~ \ ECDSA\ ]] || [[ " $cert_type " =~ \ EdDSA\ ]] ) && \
[[ ! "$cert_keyusage" =~ "Digital Signature" ]]; then [[ ! "$cert_keyusage" =~ Digital\ Signature ]]; then
prln_svrty_high "$indent Certificate incorrectly used for digital signatures" prln_svrty_high "$indent Certificate incorrectly used for digital signatures"
fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for digital signatures: \"$cert_keyusage\"" fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for digital signatures: \"$cert_keyusage\""
outok=false outok=false
fi fi
if [[ " $cert_type " =~ " RSAKMK " ]] && [[ ! "$cert_keyusage" =~ "Key Encipherment" ]]; then if [[ " $cert_type " =~ \ RSAKMK\ ]] && [[ ! "$cert_keyusage" =~ Key\ Encipherment ]]; then
prln_svrty_high "$indent Certificate incorrectly used for key encipherment" prln_svrty_high "$indent Certificate incorrectly used for key encipherment"
fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key encipherment: \"$cert_keyusage\"" fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key encipherment: \"$cert_keyusage\""
outok=false outok=false
fi fi
if ( [[ " $cert_type " =~ " DH " ]] || [[ " $cert_type " =~ " ECDH " ]] ) && \ if ( [[ " $cert_type " =~ \ DH\ ]] || [[ " $cert_type " =~ \ ECDH\ ]] ) && \
[[ ! "$cert_keyusage" =~ "Key Agreement" ]]; then [[ ! "$cert_keyusage" =~ Key\ Agreement ]]; then
prln_svrty_high "$indent Certificate incorrectly used for key agreement" prln_svrty_high "$indent Certificate incorrectly used for key agreement"
fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key agreement: \"$cert_keyusage\"" fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key agreement: \"$cert_keyusage\""
outok=false outok=false
@ -9245,27 +9286,28 @@ run_server_defaults() {
ciphers_to_test[7]="" ciphers_to_test[7]=""
ciphers_to_test[8]="tls1_3_RSA" ciphers_to_test[8]="tls1_3_RSA"
ciphers_to_test[9]="tls1_3_ECDSA" ciphers_to_test[9]="tls1_3_ECDSA"
ciphers_to_test[10]="tls1_3_EdDSA"
certificate_type[1]="" ; certificate_type[2]="" certificate_type[1]="" ; certificate_type[2]=""
certificate_type[3]=""; certificate_type[4]="" certificate_type[3]=""; certificate_type[4]=""
certificate_type[5]="" ; certificate_type[6]="" certificate_type[5]="" ; certificate_type[6]=""
certificate_type[7]="" ; certificate_type[8]="RSASig" certificate_type[7]="" ; certificate_type[8]="RSASig"
certificate_type[9]="ECDSA" certificate_type[9]="ECDSA" ; certificate_type[10]="EdDSA"
for (( n=1; n <= 16 ; n++ )); do for (( n=1; n <= 17 ; n++ )); do
# Some servers use a different certificate if the ClientHello # Some servers use a different certificate if the ClientHello
# specifies TLSv1.1 and doesn't include a server name extension. # specifies TLSv1.1 and doesn't include a server name extension.
# So, for each public key type for which a certificate was found, # So, for each public key type for which a certificate was found,
# try again, but only with TLSv1.1 and without SNI. # try again, but only with TLSv1.1 and without SNI.
if [[ $n -ne 1 ]] && [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then if [[ $n -ne 1 ]] && [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
ciphers_to_test[n]="" ciphers_to_test[n]=""
elif [[ $n -ge 10 ]]; then elif [[ $n -ge 11 ]]; then
ciphers_to_test[n]="" ciphers_to_test[n]=""
[[ ${success[n-9]} -eq 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 1 ]] && \ [[ ${success[n-10]} -eq 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 1 ]] && \
ciphers_to_test[n]="${ciphers_to_test[n-9]}" && certificate_type[n]="${certificate_type[n-9]}" ciphers_to_test[n]="${ciphers_to_test[n-10]}" && certificate_type[n]="${certificate_type[n-10]}"
fi fi
if [[ -n "${ciphers_to_test[n]}" ]]; then if [[ -n "${ciphers_to_test[n]}" ]]; then
if [[ $n -ge 10 ]]; then if [[ $n -ge 11 ]]; then
sni="$SNI" sni="$SNI"
SNI="" SNI=""
get_server_certificate "${ciphers_to_test[n]}" "tls1_1" get_server_certificate "${ciphers_to_test[n]}" "tls1_1"
@ -9276,7 +9318,7 @@ run_server_defaults() {
success[n]=$? success[n]=$?
fi fi
if [[ ${success[n]} -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then if [[ ${success[n]} -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then
[[ $n -ge 10 ]] && [[ ! -e $HOSTCERT.nosni ]] && cp $HOSTCERT $HOSTCERT.nosni [[ $n -ge 11 ]] && [[ ! -e $HOSTCERT.nosni ]] && cp $HOSTCERT $HOSTCERT.nosni
cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE
>$ERRFILE >$ERRFILE
if [[ -z "$sessticket_lifetime_hint" ]]; then if [[ -z "$sessticket_lifetime_hint" ]]; then
@ -9358,7 +9400,7 @@ run_server_defaults() {
fi fi
i=$((i + 1)) i=$((i + 1))
done done
if ! "$match_found" && [[ $n -ge 10 ]] && [[ $certs_found -ne 0 ]]; then if ! "$match_found" && [[ $n -ge 11 ]] && [[ $certs_found -ne 0 ]]; then
# A new certificate was found using TLSv1.1 without SNI. # A new certificate was found using TLSv1.1 without SNI.
# Check to see if the new certificate should be displayed. # Check to see if the new certificate should be displayed.
# It should be displayed if it is either a match for the # It should be displayed if it is either a match for the
@ -9415,7 +9457,7 @@ run_server_defaults() {
[[ -n "${previous_intermediates[certs_found]}" ]] && [[ -r $TEMPDIR/hostcert_issuer.pem ]] && \ [[ -n "${previous_intermediates[certs_found]}" ]] && [[ -r $TEMPDIR/hostcert_issuer.pem ]] && \
previous_hostcert_issuer[certs_found]=$(cat $TEMPDIR/hostcert_issuer.pem) previous_hostcert_issuer[certs_found]=$(cat $TEMPDIR/hostcert_issuer.pem)
previous_ordering_problem[certs_found]=$CERTIFICATE_LIST_ORDERING_PROBLEM previous_ordering_problem[certs_found]=$CERTIFICATE_LIST_ORDERING_PROBLEM
[[ $n -ge 10 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI" [[ $n -ge 11 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI"
tls_version[certs_found]="$DETECTED_TLS_VERSION" tls_version[certs_found]="$DETECTED_TLS_VERSION"
previous_hostcert_type[certs_found]=" ${certificate_type[n]}" previous_hostcert_type[certs_found]=" ${certificate_type[n]}"
if [[ $DEBUG -ge 1 ]]; then if [[ $DEBUG -ge 1 ]]; then
@ -10726,7 +10768,15 @@ get_pub_key_size() {
"$HAS_PKEY" || return 1 "$HAS_PKEY" || return 1
# OpenSSL displays the number of bits for RSA and ECC # OpenSSL displays the number of bits for RSA and ECC
pubkeybits=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -text 2>>$ERRFILE | awk -F'(' '/Public-Key/ { print $2 }') pubkeybits=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -text 2>>$ERRFILE)
if [[ "$pubkeybits" =~ E[Dd]25519 ]]; then
echo "Server public key is 253 bit" >> $TMPFILE
return 0
elif [[ "$pubkeybits" =~ E[Dd]448 ]]; then
echo "Server public key is 456 bit" >> $TMPFILE
return 0
fi
pubkeybits=$(awk -F'(' '/Public-Key/ { print $2 }' <<< "$pubkeybits")
if [[ -n $pubkeybits ]]; then if [[ -n $pubkeybits ]]; then
# remainder e.g. "256 bit)" # remainder e.g. "256 bit)"
pubkeybits="${pubkeybits//\)/}" pubkeybits="${pubkeybits//\)/}"
@ -12788,6 +12838,7 @@ parse_tls_serverhello() {
local len1 len2 len3 key_bitstring="" pem_certificate local len1 len2 len3 key_bitstring="" pem_certificate
local dh_p dh_param ephemeral_param rfc7919_param local dh_p dh_param ephemeral_param rfc7919_param
local -i dh_p_len dh_param_len local -i dh_p_len dh_param_len
local peering_signing_digest=0 peer_signature_type=0
DETECTED_TLS_VERSION="" DETECTED_TLS_VERSION=""
[[ $DEBUG -ge 1 ]] && echo > $TMPFILE [[ $DEBUG -ge 1 ]] && echo > $TMPFILE
@ -13021,6 +13072,14 @@ parse_tls_serverhello() {
fi fi
tls_serverkeyexchange_ascii="${tls_handshake_ascii:i:msg_len}" tls_serverkeyexchange_ascii="${tls_handshake_ascii:i:msg_len}"
tls_serverkeyexchange_ascii_len=$msg_len tls_serverkeyexchange_ascii_len=$msg_len
elif [[ "$tls_msg_type" == 0F ]]; then
if [[ $msg_len -lt 4 ]]; then
debugme tmln_warning "Response contained malformed certificate_verify message."
return 1
fi
# Extract just the SignatureAndHashAlgorithm from the CertificateVerify message.
peering_signing_digest="${tls_handshake_ascii:i:2}"
peer_signature_type="${tls_handshake_ascii:$((i+2)):2}"
elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 16 ]]; then elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 16 ]]; then
if [[ -n "$tls_certificate_status_ascii" ]]; then if [[ -n "$tls_certificate_status_ascii" ]]; then
debugme tmln_warning "Response contained more than one certificate_status handshake message." debugme tmln_warning "Response contained more than one certificate_status handshake message."
@ -13799,12 +13858,24 @@ parse_tls_serverhello() {
29) dh_bits=253 ; named_curve_str="X25519" ;; 29) dh_bits=253 ; named_curve_str="X25519" ;;
30) dh_bits=448 ; named_curve_str="X448" ;; 30) dh_bits=448 ; named_curve_str="X448" ;;
esac esac
if [[ "$DETECTED_TLS_VERSION" == 0303 ]]; then
# Skip over the public key to get to the SignatureAndHashAlgorithm
# This is TLS 1.2-only, as this field does not appear in earlier versions.
len1=2*$(hex2dec "${tls_serverkeyexchange_ascii:6:2}")
offset=$((len1+8))
if [[ $tls_serverkeyexchange_ascii_len -ge $((offset+4)) ]]; then
# The SignatureAndHashAlgorithm won't be present in an anonymous
# key exhange.
peering_signing_digest="${tls_serverkeyexchange_ascii:offset:2}"
peer_signature_type="${tls_serverkeyexchange_ascii:$((offset+2)):2}"
fi
fi
fi fi
if [[ $dh_bits -ne 0 ]] && [[ $named_curve -ne 29 ]] && [[ $named_curve -ne 30 ]]; then if [[ $dh_bits -ne 0 ]] && [[ $named_curve -ne 29 ]] && [[ $named_curve -ne 30 ]]; then
[[ $DEBUG -ge 3 ]] && echo -e " dh_bits: ECDH, $named_curve_str, $dh_bits bits\n" [[ $DEBUG -ge 3 ]] && echo -e " dh_bits: ECDH, $named_curve_str, $dh_bits bits"
echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE
elif [[ $dh_bits -ne 0 ]]; then elif [[ $dh_bits -ne 0 ]]; then
[[ $DEBUG -ge 3 ]] && echo -e " dh_bits: $named_curve_str, $dh_bits bits\n" [[ $DEBUG -ge 3 ]] && echo -e " dh_bits: $named_curve_str, $dh_bits bits"
echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE
fi fi
elif [[ $rfc_cipher_suite =~ TLS_DHE_ ]] || [[ $rfc_cipher_suite =~ TLS_DH_anon ]] || \ elif [[ $rfc_cipher_suite =~ TLS_DHE_ ]] || [[ $rfc_cipher_suite =~ TLS_DH_anon ]] || \
@ -13863,10 +13934,73 @@ parse_tls_serverhello() {
[[ "$ephemeral_param" != "$rfc7919_param" ]] && named_curve_str="" [[ "$ephemeral_param" != "$rfc7919_param" ]] && named_curve_str=""
fi fi
[[ $DEBUG -ge 3 ]] && [[ $dh_bits -ne 0 ]] && echo -e " dh_bits: DH,$named_curve_str $dh_bits bits\n" [[ $DEBUG -ge 3 ]] && [[ $dh_bits -ne 0 ]] && echo -e " dh_bits: DH,$named_curve_str $dh_bits bits"
[[ $dh_bits -ne 0 ]] && echo "Server Temp Key: DH,$named_curve_str $dh_bits bits" >> $TMPFILE [[ $dh_bits -ne 0 ]] && echo "Server Temp Key: DH,$named_curve_str $dh_bits bits" >> $TMPFILE
if [[ "$DETECTED_TLS_VERSION" == 0303 ]]; then
# Skip over the public key (P, G, Y) to get to the SignatureAndHashAlgorithm
# This is TLS 1.2-only, as this field does not appear in earlier versions.
offset=$((dh_p_len+4))
if [[ $tls_serverkeyexchange_ascii_len -lt $((offset+4)) ]]; then
debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
tmpfile_handle ${FUNCNAME[0]}.txt
return 1
fi
len1=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}")
offset+=$((len1+4))
if [[ $tls_serverkeyexchange_ascii_len -lt $((offset+4)) ]]; then
debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
tmpfile_handle ${FUNCNAME[0]}.txt
return 1
fi
len1=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}")
offset+=$((len1+4))
if [[ $tls_serverkeyexchange_ascii_len -ge $((offset+4)) ]]; then
# The SignatureAndHashAlgorithm won't be present in an anonymous
# key exhange.
peering_signing_digest="${tls_serverkeyexchange_ascii:offset:2}"
peer_signature_type="${tls_serverkeyexchange_ascii:$((offset+2)):2}"
fi
fi
fi fi
fi fi
if [[ 0x$peering_signing_digest -eq 8 ]] && \
[[ 0x$peer_signature_type -ge 4 ]] && [[ 0x$peer_signature_type -le 11 ]]; then
case $peer_signature_type in
04) peering_signing_digest="SHA256"; peer_signature_type="RSA-PSS" ;;
05) peering_signing_digest="SHA384"; peer_signature_type="RSA-PSS" ;;
06) peering_signing_digest="SHA512"; peer_signature_type="RSA-PSS" ;;
07) peering_signing_digest=""; peer_signature_type="Ed25519" ;;
08) peering_signing_digest=""; peer_signature_type="Ed448" ;;
09) peering_signing_digest="SHA256"; peer_signature_type="RSA-PSS" ;;
0A) peering_signing_digest="SHA384"; peer_signature_type="RSA-PSS" ;;
0B) peering_signing_digest="SHA512"; peer_signature_type="RSA-PSS" ;;
esac
if [[ -n "$peering_signing_digest" ]]; then
echo "Peer signing digest: $peering_signing_digest" >> $TMPFILE
[[ $DEBUG -ge 3 ]] && echo -e " Peer signing digest: $peering_signing_digest"
fi
echo "Peer signature type: $peer_signature_type" >> $TMPFILE
[[ $DEBUG -ge 3 ]] && echo -e " Peer signature type: $peer_signature_type\n"
elif [[ 0x$peering_signing_digest -ge 1 ]] && [[ 0x$peering_signing_digest -le 6 ]] && \
[[ 0x$peer_signature_type -ge 1 ]] && [[ 0x$peer_signature_type -le 3 ]]; then
case $peering_signing_digest in
01) peering_signing_digest="MD5" ;;
02) peering_signing_digest="SHA1" ;;
03) peering_signing_digest="SHA224" ;;
04) peering_signing_digest="SHA256" ;;
05) peering_signing_digest="SHA384" ;;
06) peering_signing_digest="SHA512" ;;
esac
case $peer_signature_type in
01) peer_signature_type="RSA" ;;
02) peer_signature_type="DSA" ;;
03) peer_signature_type="ECDSA" ;;
esac
echo "Peer signing digest: $peering_signing_digest" >> $TMPFILE
[[ $DEBUG -ge 3 ]] && echo -e " Peer signing digest: $peering_signing_digest"
echo "Peer signature type: $peer_signature_type" >> $TMPFILE
[[ $DEBUG -ge 3 ]] && echo -e " Peer signature type: $peer_signature_type\n"
fi
tmpfile_handle ${FUNCNAME[0]}.txt tmpfile_handle ${FUNCNAME[0]}.txt
TLS_SERVER_HELLO="02$(printf "%06x" $(( tls_serverhello_ascii_len/2)) )${tls_serverhello_ascii}" TLS_SERVER_HELLO="02$(printf "%06x" $(( tls_serverhello_ascii_len/2)) )${tls_serverhello_ascii}"
@ -14183,10 +14317,10 @@ prepare_tls_clienthello() {
if [[ 0x$tls_low_byte -le 0x03 ]]; then if [[ 0x$tls_low_byte -le 0x03 ]]; then
extension_signature_algorithms=" extension_signature_algorithms="
00, 0d, # Type: signature_algorithms , see RFC 5246 00, 0d, # Type: signature_algorithms , see RFC 5246 and RFC 8422
00, 20, 00,1e, # lengths 00, 24, 00,22, # lengths
06,01, 06,02, 06,03, 05,01, 05,02, 05,03, 04,01, 04,02, 04,03, 06,01, 06,02, 06,03, 05,01, 05,02, 05,03, 04,01, 04,02, 04,03,
03,01, 03,02, 03,03, 02,01, 02,02, 02,03" 03,01, 03,02, 03,03, 02,01, 02,02, 02,03, 08,07, 08,08"
else else
extension_signature_algorithms=" extension_signature_algorithms="
00, 0d, # Type: signature_algorithms , see RFC 8446 00, 0d, # Type: signature_algorithms , see RFC 8446
@ -15876,14 +16010,48 @@ run_crime() {
} }
# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic
# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here! # As the name says. It expects as arg1 a GET command string. It returns 1
# when GET command was stalled or killed (which is no not always used)
# and echos "warn_*". It return 0 when everything went ok and echos the
# compression if any.
sub_breach_helper() {
local get_command="$1"
local detected_compression=""
local -i was_killed=0
safe_echo "$get_command" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP
was_killed=$? # !=0 when it was killed
detected_compression=$(grep -ia ^Content-Encoding: $TMPFILE)
detected_compression="$(strip_lf "$detected_compression")"
detected_compression="${detected_compression#*:}"
detected_compression="$(strip_spaces "$detected_compression")"
if [[ ! -s $TMPFILE ]]; then
if [[ $was_killed -eq 0 ]]; then
echo "warn_stalled"
else
echo "warn_killed"
fi
return 1
elif [[ -z $detected_compression ]]; then
echo "no_compression"
else
echo "$detected_compression"
fi
return 0
}
# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic to the
# version of TLS/SSL, more: http://www.breachattack.com/ . External referrers are the important thing here!
# Mitigation: see https://community.qualys.com/message/20360 # Mitigation: see https://community.qualys.com/message/20360
# Any URL can be vulnerable. Here only the given URL is tested. See also $when_makesense
# #
run_breach() { run_breach() {
local header local header
local -i ret=0 local -i ret=0
local -i was_killed=0
local referer useragent local referer useragent
local url="$1" local url="$1"
local spaces=" " local spaces=" "
@ -15891,8 +16059,12 @@ run_breach() {
local when_makesense=" Can be ignored for static pages or if no secrets in the page" local when_makesense=" Can be ignored for static pages or if no secrets in the page"
local cve="CVE-2013-3587" local cve="CVE-2013-3587"
local cwe="CWE-310" local cwe="CWE-310"
local hint="" local hint="" c=""
local jsonID="BREACH" local jsonID="BREACH"
local compressions="gzip deflate compress br"
local has_compression=()
local detected_compression=""
local get_command=""
[[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7 [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7
@ -15903,13 +16075,6 @@ run_breach() {
fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe" fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe"
fi fi
# if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
# pr_warning "Retrieving HTTP header failed before. Skipping."
# fileout "$jsonID" "WARN" "HTTP response was wampty before" "$cve" "$cwe"
# outln
# return 1
# fi
[[ -z "$url" ]] && url="/" [[ -z "$url" ]] && url="/"
disclaimer=" - only supplied \"$url\" tested" disclaimer=" - only supplied \"$url\" tested"
@ -15917,33 +16082,80 @@ run_breach() {
[[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com [[ "$NODE" =~ google ]] && referer="https://yandex.ru/" # otherwise we have a false positive for google.com
useragent="$UA_STD" useragent="$UA_STD"
$SNEAKY && useragent="$UA_SNEAKY" $SNEAKY && useragent="$UA_SNEAKY"
tm_out "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
wait_kill $! $HEADER_MAXSLEEP # Assemble the GET command with all available compressions and send them all, initially.
was_killed=$? # !=0 was killed # If the result is negative: we can just tell the finding and return. If it's
result=$(awk '/^Content-Encoding/ { print $2 }' $TMPFILE) # positive: We already have identified 1x compression
result=$(strip_lf "$result") get_command="GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: ${compressions// /,}\r\nAccept: text/*\r\n\r\n"
debugme grep '^Content-Encoding' $TMPFILE detected_compression=$(sub_breach_helper "$get_command")
if [[ ! -s $TMPFILE ]]; then case "$detected_compression" in
pr_warning "failed (HTTP header request stalled or empty return" warn_stalled)
if [[ $was_killed -ne 0 ]]; then pr_warning "First request failed (HTTP header request stalled and was terminated)"
pr_warning " and was terminated" fileout "$jsonID" "WARN" "Test failed as first HTTP request stalled and was terminated" "$cve" "$cwe"
fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe" ret=1
else ;;
fileout "$jsonID" "WARN" "Test failed as HTTP response was empty" "$cve" "$cwe" warn_failed)
fi pr_warning "First request failed (HTTP header request was empty)"
prln_warning ") " fileout "$jsonID" "WARN" "Test failed as first HTTP response was empty" "$cve" "$cwe"
ret=1 ret=1
elif [[ -z $result ]]; then ;;
pr_svrty_best "no HTTP compression (OK) " no_compression)
outln "$disclaimer" pr_svrty_best "no gzip/deflate/compress/br HTTP compression (OK) "
fileout "$jsonID" "OK" "not vulnerable, no HTTP compression $disclaimer" "$cve" "$cwe" outln "$disclaimer"
else fileout "$jsonID" "OK" "not vulnerable, no gzip/deflate/compress/br HTTP compression $disclaimer" "$cve" "$cwe"
pr_svrty_high "potentially NOT ok, uses $result HTTP compression." ret=0
outln "$disclaimer" ;;
outln "$spaces$when_makesense" *) # Now assemble the remaining compressions in $compressions and loop through them
fileout "$jsonID" "HIGH" "potentially VULNERABLE, uses $result HTTP compression $disclaimer" "$cve" "$cwe" "$hint" has_compression+=("$detected_compression:yes")
fi compressions="${compressions//$detected_compression/}"
# Any URL can be vulnerable. I am testing now only the given URL! for c in $compressions; do
get_command="GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: ${c}\r\nAccept: text/*\r\n\r\n"
detected_compression=$(sub_breach_helper "$get_command")
if [[ $? -ne 0 ]]; then
# This failure unlikely here. The initial request must have succeeded and this one then
# failed but we'd rather treat this correctly (e.d. IDS which triggers later). Not also
# we exit on the first stalled request. So if the first one with all compressions failed,
# we don't get here. It seems very unlikely the first failed and subsequent will succeed.
has_compression+=("$c:$compressions")
elif [[ "$detected_compression" =~ no_compression ]]; then
has_compression+=("$c:no")
debugme echo "has_compression: $c: no"
elif [[ -n "detected_compression" ]]; then
has_compression+=("$c:yes")
debugme echo "has_compression: $c: yes"
else
prln_fixme "strange reply around line $((LINENO)) from sub_breach_helper()"
fi
done
# Final verdict (if not happened preemptively before). We reuse $detected_compression here
detected_compression=""
if [[ ${has_compression[@]} =~ warn ]]; then
# warn_empty / warn_stalled
if [[ ${has_compression[@]} =~ warn_empty ]]; then
pr_warning "At least 1/4 checks failed (HTTP header request was empty, debug: ${has_compression[@]}"
out ", debug: ${has_compression[@]})"
fileout "$jsonID" "WARN" "Test failed as HTTP response was empty, debug: ${has_compression[@]}" "$cve" "$cwe"
else # warn_stalled
pr_warning "At least 1/4 checks failed (HTTP header request stalled and was terminated"
out ", debug: ${has_compression[@]})"
fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe"
fi
else
for c in ${has_compression[@]}; do
if [[ $c =~ yes ]]; then
detected_compression+="${c%:*} "
fi
done
detected_compression="$(strip_trailing_space "$detected_compression")"
pr_svrty_high "potentially NOT ok, \"$detected_compression\" HTTP compression detected."
outln "$disclaimer"
outln "${spaces}${when_makesense}"
fileout "$jsonID" "HIGH" "potentially VULNERABLE, $detected_compression HTTP compression detected $disclaimer" "$cve" "$cwe" "$hint"
fi
debugme outln "${spaces}has_compression: ${has_compression[@]}"
;;
esac
tmpfile_handle ${FUNCNAME[0]}.txt tmpfile_handle ${FUNCNAME[0]}.txt
return $ret return $ret
@ -16998,7 +17210,7 @@ run_beast(){
! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}" ! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}"
done done
[[ -z "$ciphers_to_test" ]] && break [[ -z "$ciphers_to_test" ]] && break
if "$SHOW_SIGALGO"; then if "$WIDE" && "$SHOW_SIGALGO"; then
tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all" tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all"
else else
tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey" tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
@ -19347,7 +19559,7 @@ get_caa_rr_record() {
return 0 return 0
} }
# watch out: $1 can also be a cname! --> all checked # arg1: domain to check for. Returned will be the MX record as a string
get_mx_record() { get_mx_record() {
local mx="" local mx=""
local saved_openssl_conf="$OPENSSL_CONF" local saved_openssl_conf="$OPENSSL_CONF"
@ -19357,21 +19569,49 @@ get_mx_record() {
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
# we need the last two columns here # we need the last two columns here
if "$HAS_HOST"; then if "$HAS_HOST"; then
mxs="$(host -t MX "$1" 2>/dev/null | awk '/is handled by/ { print $(NF-1), $NF }')" mx="$(host -t MX "$1" 2>/dev/null | awk '/is handled by/ { print $(NF-1), $NF }')"
elif "$HAS_DIG"; then elif "$HAS_DIG"; then
mxs="$(dig +short $noidnout -t MX "$1" 2>/dev/null | awk '/^[0-9]/ { print $1" "$2 }')" mx="$(dig +short $noidnout -t MX "$1" 2>/dev/null | awk '/^[0-9]/ { print $1" "$2 }')"
elif "$HAS_DRILL"; then elif "$HAS_DRILL"; then
mxs="$(drill mx $1 | awk '/IN[ \t]MX[ \t]+/ { print $(NF-1), $NF }')" mx="$(drill mx $1 | awk '/IN[ \t]MX[ \t]+/ { print $(NF-1), $NF }')"
elif "$HAS_NSLOOKUP"; then elif "$HAS_NSLOOKUP"; then
mxs="$(strip_lf "$(nslookup -type=MX "$1" 2>/dev/null | awk '/mail exchanger/ { print $(NF-1), $NF }')")" mx="$(strip_lf "$(nslookup -type=MX "$1" 2>/dev/null | awk '/mail exchanger/ { print $(NF-1), $NF }')")"
else else
# shouldn't reach this, as we checked in the top # shouldn't reach this, as we checked in the top
fatal "No dig, host, drill or nslookup" $ERR_DNSBIN fatal "No dig, host, drill or nslookup" $ERR_DNSBIN
fi fi
OPENSSL_CONF="$saved_openssl_conf" OPENSSL_CONF="$saved_openssl_conf"
echo "$mxs" echo "$mx"
} }
# arg1: domain / hostname. Returned will be the TXT record as a string which can be multilined
# (one entry per line), for e.g. non-MTA-STS records.
# Is supposed to be used by MTA STS in the future like get_txt_record _mta-sts.DOMAIN.TLD
get_txt_record() {
local record=""
local saved_openssl_conf="$OPENSSL_CONF"
local noidnout=""
"$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134
# we need the last two columns here and strip any remaining double quotes later
if "$HAS_HOST"; then
record="$(host -t TXT "$1" 2>/dev/null | awk -F\" '/descriptive text/ { print $(NF-1) }')"
elif "$HAS_DIG"; then
record="$(dig +short $noidnout -t TXT "$1" 2>/dev/null)"
elif "$HAS_DRILL"; then
record="$(drill txt $1 | awk -F\" '/^[a-z0-9].*TXT/ { print $(NF-1) }')"
elif "$HAS_NSLOOKUP"; then
record="$(strip_lf "$(nslookup -type=MX "$1" 2>/dev/null | awk -F= '/text/ { print $(NF-1), $NF }')")"
else
# shouldn't reach this, as we checked in the top
fatal "No dig, host, drill or nslookup" $ERR_DNSBIN
fi
OPENSSL_CONF="$saved_openssl_conf"
echo "${record//\"/}"
}
# set IPADDRs and IP46ADDRs # set IPADDRs and IP46ADDRs
# #