Support EdDSA

This commit adds support for EdDSA (Ed25519 and Ed448). In particular:

* It modifies prepare_tls_clienthello() to include Ed25519 and Ed448 in the signature_algorithms extension of the TLS 1.2 and earlier ClientHello (RFC 8422).

* It modifies run_server_defaults() and get_server_certificate() to check whether the server offers EdDSA certificates with TLS 1.3.

* It modifies certificate_info() to handle certificates signed with EdDSA or with EdDSA public keys, even if $OPENSSL does not support pretty printing such keys and signatures.

* It modifies read_sigalg_from_file() to recognize EdDSA signatures even if $OPENSSL does not.
This commit is contained in:
David Cooper 2020-05-14 14:55:48 -04:00
parent 42386e512b
commit 3ae48931fb
2 changed files with 54 additions and 17 deletions

View File

@ -16,6 +16,7 @@
* 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

@ -1040,7 +1040,7 @@ set_key_str_score() {
# TODO: We need to get the size of DH params (follows the same table as the "else" clause) # 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... # For now, verifying the key size will do...
if [[ $type == EC ]]; then if [[ $type == EC || $type == EdDSA ]]; then
if [[ $size -lt 110 ]] && [[ $KEY_EXCH_SCORE -gt 20 ]]; then if [[ $size -lt 110 ]] && [[ $KEY_EXCH_SCORE -gt 20 ]]; then
let KEY_EXCH_SCORE=20 let KEY_EXCH_SCORE=20
set_grade_cap "F" "Using an insecure key" set_grade_cap "F" "Using an insecure key"
@ -6249,7 +6249,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
} }
@ -7547,7 +7555,7 @@ get_server_certificate() {
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
@ -7568,6 +7576,8 @@ get_server_certificate() {
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
@ -8332,8 +8342,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"
@ -8441,6 +8459,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"
@ -8461,6 +8483,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" ;;
@ -8523,6 +8546,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 ("
@ -8586,7 +8613,7 @@ 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\""
@ -9257,27 +9284,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"
@ -9288,7 +9316,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
@ -9370,7 +9398,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
@ -9427,7 +9455,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
@ -10738,7 +10766,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//\)/}"
@ -14279,10 +14315,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