From 815e21e9b5012729af41795a09adc4ea24aac9e8 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 21 Dec 2016 13:16:10 -0500 Subject: [PATCH 01/15] Use sockets for run_freak() This PR changes `run_freak()` to use sockets. I added two ciphers to `$exportrsa_cipher_list`: EXP1024-RC4-MD5 (0x00,0x60) and EXP1024-RC2-CBC-MD5 (0x00,0x61). So, the list is now as follows: ``` 0x00,0x62 - EXP1024-DES-CBC-SHA TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA SSLv3 Kx=RSA(1024) Au=RSA Enc=DES(56) Mac=SHA1 export 0x00,0x61 - EXP1024-RC2-CBC-MD5 TLS_RSA_EXPORT1024_WITH_RC2_56_MD5 SSLv3 Kx=RSA(1024) Au=RSA Enc=RC2(56) Mac=MD5 export 0x00,0x64 - EXP1024-RC4-SHA TLS_RSA_EXPORT1024_WITH_RC4_56_SHA SSLv3 Kx=RSA(1024) Au=RSA Enc=RC4(56) Mac=SHA1 export 0x00,0x60 - EXP1024-RC4-MD5 TLS_RSA_EXPORT1024_WITH_RC4_56_MD5 SSLv3 Kx=RSA(1024) Au=RSA Enc=RC4(56) Mac=MD5 export 0x00,0x14 - EXP-EDH-RSA-DES-CBC-SHA TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH(512) Au=RSA Enc=DES(40) Mac=SHA1 export 0x00,0x08 - EXP-DES-CBC-SHA TLS_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=RSA(512) Au=RSA Enc=DES(40) Mac=SHA1 export 0x00,0x06 - EXP-RC2-CBC-MD5 TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 SSLv3 Kx=RSA(512) Au=RSA Enc=RC2(40) Mac=MD5 export 0x04,0x00,0x80 - EXP-RC2-CBC-MD5 SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 SSLv2 Kx=RSA(512) Au=RSA Enc=RC2(40) Mac=MD5 export 0x00,0x0E - EXP-DH-RSA-DES-CBC-SHA TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA SSLv3 Kx=DH/RSA Au=DH Enc=DES(40) Mac=SHA1 export 0x00,0x03 - EXP-RC4-MD5 TLS_RSA_EXPORT_WITH_RC4_40_MD5 SSLv3 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export 0x02,0x00,0x80 - EXP-RC4-MD5 SSL_CK_RC4_128_EXPORT40_WITH_MD5 SSLv2 Kx=RSA(512) Au=RSA Enc=RC4(40) Mac=MD5 export ``` --- testssl.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/testssl.sh b/testssl.sh index 405e138..2de6de8 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8811,18 +8811,27 @@ run_tls_fallback_scsv() { # Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/ run_freak() { local -i sclient_success=0 - local -i nr_supported_ciphers=0 - # with correct build it should list these 7 ciphers (plus the two latter as SSLv2 ciphers): - local exportrsa_cipher_list="EXP1024-DES-CBC-SHA:EXP1024-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-RC4-MD5:EXP-RC4-MD5" - local addcmd="" addtl_warning="" + local -i i nr_supported_ciphers=0 + # with correct build it should list these 9 ciphers (plus the two latter as SSLv2 ciphers): + local exportrsa_cipher_list="EXP1024-DES-CBC-SHA:EXP1024-RC2-CBC-MD5:EXP1024-RC4-SHA:EXP1024-RC4-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5" + local exportrsa_tls_cipher_list_hex="00,62, 00,61, 00,64, 00,60, 00,14, 00,0E, 00,08, 00,06, 00,03" + local exportrsa_ssl2_cipher_list_hex="04,00,80, 02,00,80" + local addcmd="" addtl_warning="" hexc local cve="CVE-2015-0204" local cwe="CWE-310" local hint="" + local using_sockets=true + + "$SSL_NATIVE" && using_sockets=false [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for FREAK attack " && outln pr_bold " FREAK"; out " ($cve) " - nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportrsa_cipher_list)) + if "$using_sockets"; then + nr_supported_ciphers=$(count_words "$exportrsa_tls_cipher_list_hex")+$(count_words "$exportrsa_ssl2_cipher_list_hex") + else + nr_supported_ciphers=$(count_ciphers $(actually_supported_ciphers $exportrsa_cipher_list)) + fi #echo "========= ${PIPESTATUS[*]} case $nr_supported_ciphers in @@ -8838,12 +8847,26 @@ run_freak() { 4|5|6|7) addtl_warning=" (tested with $nr_supported_ciphers/9 ciphers)" ;; esac - [[ "$OPTIMAL_PROTO" == "-ssl2" ]] && addcmd="$OPTIMAL_PROTO" - [[ ! "$OPTIMAL_PROTO" =~ ssl ]] && addcmd="$SNI" - $OPENSSL s_client $STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY $addcmd >$TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE $TMPFILE 2>$ERRFILE Date: Thu, 29 Dec 2016 16:31:42 -0500 Subject: [PATCH 02/15] Common primes test phase 1 In response to your request in #572, this PR provides a starting point for addressing #120. It adds code to `run_logjam()` to try connecting to the server using any cipher that uses an ephemeral DH key. If successful, it gets the server's ephemeral key (in OpenSSL's PEM format) and then extracts the prime from the key and places it in `$dh_p`. So, all that needs to be done at this point is to compare `$dh_p` against a set of "bad" primes. I'm not sure if I'll be able to work on that part soon, so if someone else has the time, that would be great. I actually found the `-msg` option easy to use. I moved the code in `parse_tls_serverhello()` that extracts the DH ephemeral public key from the ServerKeyExchange message into a separate function. Then, if using OpenSSL with the `-msg` option, I extract the ServerKeyExchange message from `$TMPFILE` and call this new function to extract the key and convert it to PEM format. That way the new code in `run_logjam()` can use either `$OPENSSL` or `tls_sockets()`. --- testssl.sh | 320 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 188 insertions(+), 132 deletions(-) diff --git a/testssl.sh b/testssl.sh index 16b78de..68b1b73 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6830,6 +6830,159 @@ get_pub_key_size() { return 0 } +# Extract the DH ephemeral key from the ServerKeyExchange message +get_dh_ephemeralkey() { + local tls_serverkeyexchange_ascii="$1" + local -i tls_serverkeyexchange_ascii_len offset + local dh_p dh_g dh_y dh_param len1 key_bitstring tmp_der_key_file + local -i i dh_p_len dh_g_len dh_y_len dh_param_len + + tls_serverkeyexchange_ascii_len=${#tls_serverkeyexchange_ascii} + dh_p_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:0:4}") + offset=4+$dh_p_len + if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + + # Subtract any leading 0 bytes + for (( i=4; i < offset; i=i+2 )); do + [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break + dh_p_len=$dh_p_len-2 + done + if [[ $i -ge $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + dh_p="${tls_serverkeyexchange_ascii:i:dh_p_len}" + + dh_g_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") + i=4+$offset + offset+=4+$dh_g_len + if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + # Subtract any leading 0 bytes + for (( 1; i < offset; i=i+2 )); do + [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break + dh_g_len=$dh_g_len-2 + done + if [[ $i -ge $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + dh_g="${tls_serverkeyexchange_ascii:i:dh_g_len}" + + dh_y_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") + i=4+$offset + offset+=4+$dh_y_len + if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + # Subtract any leading 0 bytes + for (( 1; i < offset; i=i+2 )); do + [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break + dh_y_len=$dh_y_len-2 + done + if [[ $i -ge $offset ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + dh_y="${tls_serverkeyexchange_ascii:i:dh_y_len}" + + # The following code assumes that all lengths can be encoded using at most 2 bytes, + # which just means that the encoded length of the public key must be less than + # 65,536 bytes. If the length is anywhere close to that, it is almost certainly an + # encoding error. + if [[ $dh_p_len+$dh_g_len+$dh_y_len -ge 131000 ]]; then + debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." + return 1 + fi + # make ASN.1 INTEGER of p, g, and Y + [[ "0x${dh_p:0:1}" -ge 8 ]] && dh_p_len+=2 && dh_p="00$dh_p" + if [[ $dh_p_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_p_len/2)))" + elif [[ $dh_p_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_p_len/2)))" + else + len1="82$(printf "%04x" $((dh_p_len/2)))" + fi + dh_p="02${len1}$dh_p" + + [[ "0x${dh_g:0:1}" -ge 8 ]] && dh_g_len+=2 && dh_g="00$dh_g" + if [[ $dh_g_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_g_len/2)))" + elif [[ $dh_g_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_g_len/2)))" + else + len1="82$(printf "%04x" $((dh_g_len/2)))" + fi + dh_g="02${len1}$dh_g" + + [[ "0x${dh_y:0:1}" -ge 8 ]] && dh_y_len+=2 && dh_y="00$dh_y" + if [[ $dh_y_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_y_len/2)))" + elif [[ $dh_y_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_y_len/2)))" + else + len1="82$(printf "%04x" $((dh_y_len/2)))" + fi + dh_y="02${len1}$dh_y" + + # Make a SEQUENCE of p and g + dh_param_len=${#dh_p}+${#dh_g} + if [[ $dh_param_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_param_len/2)))" + elif [[ $dh_param_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_param_len/2)))" + else + len1="82$(printf "%04x" $((dh_param_len/2)))" + fi + dh_param="30${len1}${dh_p}${dh_g}" + + # Make a SEQUENCE of the paramters SEQUENCE and the OID + dh_param_len=22+${#dh_param} + if [[ $dh_param_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_param_len/2)))" + elif [[ $dh_param_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_param_len/2)))" + else + len1="82$(printf "%04x" $((dh_param_len/2)))" + fi + dh_param="30${len1}06092A864886F70D010301${dh_param}" + + # Encapsulate public key, y, in a BIT STRING + dh_y_len=${#dh_y}+2 + if [[ $dh_y_len -lt 256 ]]; then + len1="$(printf "%02x" $((dh_y_len/2)))" + elif [[ $dh_y_len -lt 512 ]]; then + len1="81$(printf "%02x" $((dh_y_len/2)))" + else + len1="82$(printf "%04x" $((dh_y_len/2)))" + fi + dh_y="03${len1}00$dh_y" + + # Create the public key SEQUENCE + i=${#dh_param}+${#dh_y} + if [[ $i -lt 256 ]]; then + len1="$(printf "%02x" $((i/2)))" + elif [[ $i -lt 512 ]]; then + len1="81$(printf "%02x" $((i/2)))" + else + len1="82$(printf "%04x" $((i/2)))" + fi + key_bitstring="30${len1}${dh_param}${dh_y}" + tmp_der_key_file=$(mktemp $TEMPDIR/pub_key_der.XXXXXX) || return 1 + asciihex_to_binary_file "$key_bitstring" "$tmp_der_key_file" + key_bitstring="$($OPENSSL pkey -pubin -in $tmp_der_key_file -inform DER 2> $ERRFILE)" + rm $tmp_der_key_file + [[ -z "$key_bitstring" ]] && return 1 + out "$key_bitstring" + return 0 +} + # arg1: name of file with socket reply # arg2: true if entire server hello should be parsed parse_sslv2_serverhello() { @@ -7032,9 +7185,9 @@ parse_tls_serverhello() { local -i curve_type named_curve local -i dh_bits=0 msb mask local tmp_der_certfile tmp_pem_certfile hostcert_issuer="" ocsp_response="" - local len1 key_bitstring="" tmp_der_key_file - local dh_p dh_g dh_y dh_param ephemeral_param rfc7919_param - local -i dh_p_len dh_g_len dh_y_len dh_param_len + local key_bitstring="" + local dh_p ephemeral_param rfc7919_param + local -i dh_p_len TLS_TIME="" DETECTED_TLS_VERSION="" @@ -7735,134 +7888,8 @@ parse_tls_serverhello() { dh_bits=$dh_bits-1 done - dh_g_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") - i=4+$offset - offset+=4+$dh_g_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle $FUNCNAME.txt - return 1 - fi - # Subtract any leading 0 bytes - for (( 1; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_g_len=$dh_g_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle $FUNCNAME.txt - return 1 - fi - dh_g="${tls_serverkeyexchange_ascii:i:dh_g_len}" - - dh_y_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}") - i=4+$offset - offset+=4+$dh_y_len - if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle $FUNCNAME.txt - return 1 - fi - # Subtract any leading 0 bytes - for (( 1; i < offset; i=i+2 )); do - [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break - dh_y_len=$dh_y_len-2 - done - if [[ $i -ge $offset ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle $FUNCNAME.txt - return 1 - fi - dh_y="${tls_serverkeyexchange_ascii:i:dh_y_len}" - - # The following code assumes that all lengths can be encoded using at most 2 bytes, - # which just means that the encoded length of the public key must be less than - # 65,536 bytes. If the length is anywhere close to that, it is almost certainly an - # encoding error. - if [[ $dh_p_len+$dh_g_len+$dh_y_len -ge 131000 ]]; then - debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello." - tmpfile_handle $FUNCNAME.txt - return 1 - fi - # make ASN.1 INTEGER of p, g, and Y - [[ "0x${dh_p:0:1}" -ge 8 ]] && dh_p_len+=2 && dh_p="00$dh_p" - if [[ $dh_p_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_p_len/2)))" - elif [[ $dh_p_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_p_len/2)))" - else - len1="82$(printf "%04x" $((dh_p_len/2)))" - fi - dh_p="02${len1}$dh_p" - - [[ "0x${dh_g:0:1}" -ge 8 ]] && dh_g_len+=2 && dh_g="00$dh_g" - if [[ $dh_g_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_g_len/2)))" - elif [[ $dh_g_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_g_len/2)))" - else - len1="82$(printf "%04x" $((dh_g_len/2)))" - fi - dh_g="02${len1}$dh_g" - - [[ "0x${dh_y:0:1}" -ge 8 ]] && dh_y_len+=2 && dh_y="00$dh_y" - if [[ $dh_y_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_y_len/2)))" - elif [[ $dh_y_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_y_len/2)))" - else - len1="82$(printf "%04x" $((dh_y_len/2)))" - fi - dh_y="02${len1}$dh_y" - - # Make a SEQUENCE of p and g - dh_param_len=${#dh_p}+${#dh_g} - if [[ $dh_param_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_param_len/2)))" - elif [[ $dh_param_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_param_len/2)))" - else - len1="82$(printf "%04x" $((dh_param_len/2)))" - fi - dh_param="30${len1}${dh_p}${dh_g}" - - # Make a SEQUENCE of the paramters SEQUENCE and the OID - dh_param_len=22+${#dh_param} - if [[ $dh_param_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_param_len/2)))" - elif [[ $dh_param_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_param_len/2)))" - else - len1="82$(printf "%04x" $((dh_param_len/2)))" - fi - dh_param="30${len1}06092A864886F70D010301${dh_param}" - - # Encapsulate public key, y, in a BIT STRING - dh_y_len=${#dh_y}+2 - if [[ $dh_y_len -lt 256 ]]; then - len1="$(printf "%02x" $((dh_y_len/2)))" - elif [[ $dh_y_len -lt 512 ]]; then - len1="81$(printf "%02x" $((dh_y_len/2)))" - else - len1="82$(printf "%04x" $((dh_y_len/2)))" - fi - dh_y="03${len1}00$dh_y" - - # Create the public key SEQUENCE - i=${#dh_param}+${#dh_y} - if [[ $i -lt 256 ]]; then - len1="$(printf "%02x" $((i/2)))" - elif [[ $i -lt 512 ]]; then - len1="81$(printf "%02x" $((i/2)))" - else - len1="82$(printf "%04x" $((i/2)))" - fi - key_bitstring="30${len1}${dh_param}${dh_y}" - tmp_der_key_file=$(mktemp $TEMPDIR/pub_key_der.XXXXXX) || return 1 - asciihex_to_binary_file "$key_bitstring" "$tmp_der_key_file" - key_bitstring="$($OPENSSL pkey -pubin -in $tmp_der_key_file -inform DER 2> $ERRFILE)" - rm $tmp_der_key_file - [[ -n "$key_bitstring" ]] && echo "$key_bitstring" >> $TMPFILE + key_bitstring="$(get_dh_ephemeralkey "$tls_serverkeyexchange_ascii")" + [[ $? -eq 0 ]] && echo "$key_bitstring" >> $TMPFILE # Check to see whether the ephemeral public key uses one of the groups from # RFC 7919 for parameters @@ -9063,11 +9090,12 @@ run_logjam() { local -i sclient_success=0 local exportdhe_cipher_list="EXP1024-DHE-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA" local exportdhe_cipher_list_hex="00,63, 00,65, 00,14, 00,11" - local -i i nr_supported_ciphers=0 + local -i i nr_supported_ciphers=0 server_key_exchange_len=0 ephemeral_pub_len=0 local addtl_warning="" hexc local cve="CVE-2015-4000" local cwe="CWE-310" local hint="" + local server_key_exchange ephemeral_pub key_bitstring="" dh_p local using_sockets=true "$SSL_NATIVE" && using_sockets=false @@ -9135,6 +9163,34 @@ run_logjam() { fi debugme echo $nr_supported_ciphers + # Try all ciphers that use an ephemeral DH key. If successful, check whether the key uses a weak prime. + if "$using_sockets"; then + tls_sockets "03" "cc,15, 00,b3, 00,91, c0,97, 00,a3, 00,9f, cc,aa, c0,a3, c0,9f, 00,6b, 00,6a, 00,39, 00,38, 00,c4, 00,c3, 00,88, 00,87, 00,a7, 00,6d, 00,3a, 00,c5, 00,89, 00,ab, cc,ad, c0,a7, c0,43, c0,45, c0,47, c0,53, c0,57, c0,5b, c0,67, c0,6d, c0,7d, c0,81, c0,85, c0,91, 00,a2, 00,9e, c0,a2, c0,9e, 00,aa, c0,a6, 00,67, 00,40, 00,33, 00,32, 00,be, 00,bd, 00,9a, 00,99, 00,45, 00,44, 00,a6, 00,6c, 00,34, 00,bf, 00,9b, 00,46, 00,b2, 00,90, c0,96, c0,42, c0,44, c0,46, c0,52, c0,56, c0,5a, c0,66, c0,6c, c0,7c, c0,80, c0,84, c0,90, 00,66, 00,18, 00,8e, 00,16, 00,13, 00,1b, 00,8f, 00,63, 00,15, 00,12, 00,1a, 00,65, 00,14, 00,11, 00,19, 00,17, 00,b5, 00,b4, 00,2d" "ephemeralkey" + sclient_success=$? + if [[ $sclient_success -eq 0 ]] || [[ $sclient_success -eq 2 ]]; then + cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE + key_bitstring="$(awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' $TMPFILE)" + fi + else + $OPENSSL s_client $STARTTLS $BUGS -cipher kEDH -msg -connect $NODEIP:$PORT $PROXY $SNI >$TMPFILE 2>$ERRFILE Date: Thu, 29 Dec 2016 16:45:46 -0500 Subject: [PATCH 03/15] Add extra check --- testssl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 18204e2..4a71070 100755 --- a/testssl.sh +++ b/testssl.sh @@ -9386,7 +9386,7 @@ run_logjam() { server_key_exchange="${server_key_exchange%%[!0-9A-F]*}" server_key_exchange_len=${#server_key_exchange} [[ $server_key_exchange_len -gt 8 ]] && [[ "${server_key_exchange:0:2}" == "0C" ]] && ephemeral_pub_len=$(hex2dec "${server_key_exchange:2:6}") - [[ $ephemeral_pub_len -le $server_key_exchange_len ]] && key_bitstring="$(get_dh_ephemeralkey "${server_key_exchange:8}")" + [[ $ephemeral_pub_len -ne 0 ]] && [[ $ephemeral_pub_len -le $server_key_exchange_len ]] && key_bitstring="$(get_dh_ephemeralkey "${server_key_exchange:8}")" fi fi if [[ -n "$key_bitstring" ]]; then From 62aee8f8467658c5d60f5804cc7a7ff9f4cdb2ca Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 30 Dec 2016 11:32:41 -0500 Subject: [PATCH 04/15] Remove leading "00" byte from prime, if present The primes in https://svn.nmap.org/nmap/scripts/ssl-dh-params.nse do not include a leading "00" byte, so don't include it in `$dh_p`. --- testssl.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testssl.sh b/testssl.sh index 4a71070..93f99ac 100755 --- a/testssl.sh +++ b/testssl.sh @@ -9392,7 +9392,9 @@ run_logjam() { if [[ -n "$key_bitstring" ]]; then dh_p="$($OPENSSL pkey -pubin -text -noout <<< "$key_bitstring" | awk '/prime:/,/generator:/' | tail -n +2 | head -n -1)" dh_p="$(strip_spaces "$(colon_to_spaces "$(newline_to_spaces "$dh_p")")")" + [[ "${dh_p:0:2}" == "00" ]] && dh_p="${dh_p:2}" # At this point the DH key's prime has been extracted into $dh_p. Compare is against known weak primes. + echo "dh_p = $dh_p" fi tmpfile_handle $FUNCNAME.txt From 83472301bc76d7da8cd14d40e7406f4e0c317f55 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 30 Dec 2016 11:33:27 -0500 Subject: [PATCH 05/15] Don't "echo" the prime to the terminal --- testssl.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 93f99ac..aee84cd 100755 --- a/testssl.sh +++ b/testssl.sh @@ -9394,7 +9394,6 @@ run_logjam() { dh_p="$(strip_spaces "$(colon_to_spaces "$(newline_to_spaces "$dh_p")")")" [[ "${dh_p:0:2}" == "00" ]] && dh_p="${dh_p:2}" # At this point the DH key's prime has been extracted into $dh_p. Compare is against known weak primes. - echo "dh_p = $dh_p" fi tmpfile_handle $FUNCNAME.txt From 5270747eb0baf7a728c1d2afd960d5716c45762d Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 4 Jan 2017 10:31:13 -0500 Subject: [PATCH 06/15] Check for matching SSLv2 cipher Some servers respond to an SSLv2 ClientHello with a list of all SSLv2 ciphers that the server supports rather than just a list of ciphers that it supports in common with the client (i.e., that appear in the ClientHello). This PR changes the sockets version of `run_freak()` so that, if `sslv2_sockets()` is successful, it checks whether there are any ciphers in common between the ClientHello and the ServerHello before declaring that the server supports an export RSA cipher. --- testssl.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/testssl.sh b/testssl.sh index 2f1ab04..ff908a1 100755 --- a/testssl.sh +++ b/testssl.sh @@ -9212,11 +9212,12 @@ run_tls_fallback_scsv() { # Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/ run_freak() { local -i sclient_success=0 - local -i i nr_supported_ciphers=0 + local -i i nr_supported_ciphers=0 len # with correct build it should list these 9 ciphers (plus the two latter as SSLv2 ciphers): local exportrsa_cipher_list="EXP1024-DES-CBC-SHA:EXP1024-RC2-CBC-MD5:EXP1024-RC4-SHA:EXP1024-RC4-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5" local exportrsa_tls_cipher_list_hex="00,62, 00,61, 00,64, 00,60, 00,14, 00,0E, 00,08, 00,06, 00,03" local exportrsa_ssl2_cipher_list_hex="04,00,80, 02,00,80" + local detected_ssl2_ciphers local addcmd="" addtl_warning="" hexc local cve="CVE-2015-0204" local cwe="CWE-310" @@ -9253,8 +9254,15 @@ run_freak() { sclient_success=$? [[ $sclient_success -eq 2 ]] && sclient_success=0 if [[ $sclient_success -ne 0 ]]; then - sslv2_sockets "$exportrsa_ssl2_cipher_list_hex" - [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]] && sclient_success=0 + sslv2_sockets "$exportrsa_ssl2_cipher_list_hex" "true" + if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then + exportrsa_ssl2_cipher_list_hex="$(strip_spaces "${exportrsa_ssl2_cipher_list_hex//,/}")" + len=${#exportrsa_ssl2_cipher_list_hex} + detected_ssl2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")" + for (( i=0; i Date: Thu, 5 Jan 2017 14:55:08 -0500 Subject: [PATCH 07/15] Use sockets for run_crime() This PR changes `run_crime()` to use `tls_sockets()` rather than failing if `$OPENSSL` lacks zlib support, unless `$SSL_NATIVE` is `true`. At the moment, the ClientHello created by `socksend_tls_clienthello()` only specifies the NULL compression method. So, this PR adds a new parameter to `socksend_tls_clienthello()` and `tls_sockets()` to allow to caller to request that additional compression methods (DEFLATE and LZS) be specified in the ClientHello. This PR makes another change to `run_crime()`. At the moment, if `$OPENSSL s_client` fails to connect to the server, `run_crime()` will report that the server is not vulnerable, since the output from `$OPENSSL s_client` includes the line "Compression: NONE" (see below). This PR changes that by checking whether the connection was successful, and reporting a "test failed (couldn't connect)" warning if it wasn't successful, rather than reporting "not vulnerable (OK)". ``` CONNECTED(00000003) 140338777061024:error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version:s23_clnt.c:769: --- no peer certificate available --- No client certificate CA names sent --- SSL handshake has read 7 bytes and written 389 bytes --- New, (NONE), Cipher is (NONE) Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated SSL-Session: Protocol : TLSv1 Cipher : 0000 Session-ID: Session-ID-ctx: Master-Key: Key-Arg : None PSK identity: None PSK identity hint: None SRP username: None Start Time: 1483645971 Timeout : 300 (sec) Verify return code: 0 (ok) --- ``` --- testssl.sh | 63 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/testssl.sh b/testssl.sh index 80a8ac8..2019b18 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8159,6 +8159,7 @@ sslv2_sockets() { # ARG1: TLS version low byte (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) # ARG2: CIPHER_SUITES string # ARG3: (optional) additional request extensions +# ARG4: (optional): "true" if ClientHello should advertise compression methods other than "NULL" socksend_tls_clienthello() { local tls_low_byte="$1" local tls_word_reclayer="03, 01" # the first TLS version number is the record layer and always 0301 -- except: SSLv3 @@ -8174,6 +8175,10 @@ socksend_tls_clienthello() { local extension_session_ticket extension_next_protocol extension_padding local extension_supported_groups="" extension_supported_point_formats="" local extra_extensions extra_extensions_list="" + local offer_compression=false compression_metods + + # TLSv1.3 ClientHello messages MUST specify only the NULL compression method. + [[ "$4" == "true" ]] && [[ "0x$tls_low_byte" -le "0x03" ]] && offer_compression=true code2network "$(tolower "$2")" # convert CIPHER_SUITES cipher_suites="$NW_STR" # we don't have the leading \x here so string length is two byte less, see next @@ -8337,6 +8342,7 @@ socksend_tls_clienthello() { # If the length of the Client Hello would be between 256 and 511 bytes, # then add a padding extension (see RFC 7685) len_all=$((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2)) + "$offer_compression" && len_all+=2 if [[ $len_all -ge 256 ]] && [[ $len_all -le 511 ]] && [[ ! "$extra_extensions_list" =~ " 0015 " ]]; then if [[ $len_all -gt 508 ]]; then len_padding_extension=0 @@ -8361,24 +8367,35 @@ socksend_tls_clienthello() { # RFC 3546 doesn't specify SSLv3 to have SNI, openssl just ignores the switch if supplied if [[ "$tls_low_byte" == "00" ]]; then - len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x27))) + len_all=$((0x$len_ciph_suites + 0x27)) else - len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x27 + 0x$len_extension_hex + 0x2))) + len_all=$((0x$len_ciph_suites + 0x27 + 0x$len_extension_hex + 0x2)) fi + "$offer_compression" && len_all+=2 + len2twobytes $(printf "%02x\n" $len_all) len_client_hello_word="$LEN_STR" #[[ $DEBUG -ge 3 ]] && echo $len_client_hello_word if [[ "$tls_low_byte" == "00" ]]; then - len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x2b))) + len_all=$((0x$len_ciph_suites + 0x2b)) else - len2twobytes $(printf "%02x\n" $((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2))) + len_all=$((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2)) fi + "$offer_compression" && len_all+=2 + len2twobytes $(printf "%02x\n" $len_all) len_all_word="$LEN_STR" #[[ $DEBUG -ge 3 ]] && echo $len_all_word # if we have SSLv3, the first occurence of TLS protocol -- record layer -- is SSLv3, otherwise TLS 1.0 [[ $tls_low_byte == "00" ]] && tls_word_reclayer="03, 00" + if "$offer_compression"; then + # See http://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml#comp-meth-ids-2 + compression_metods="03,01,40,00" # Offer NULL, DEFLATE, and LZS compression + else + compression_metods="01,00" # Only offer NULL compression (0x00) + fi + TLS_CLIENT_HELLO=" # TLS header ( 5 bytes) ,16, $tls_word_reclayer # TLS Version: in wireshark this is always 01 for TLS 1.0-1.2 @@ -8395,8 +8412,7 @@ socksend_tls_clienthello() { ,00 # Session ID length ,$len_ciph_suites_word # Cipher suites length ,$cipher_suites - ,01 # Compression methods length - ,00" # Compression method (x00 for NULL) + ,$compression_metods" fd_socket 5 || return 6 @@ -8415,6 +8431,7 @@ socksend_tls_clienthello() { # arg3: (optional): "all" - process full response (including Certificate and certificate_status handshake messages) # "ephemeralkey" - extract the server's ephemeral key (if any) # arg4: (optional) additional request extensions +# arg5: (optional) "true" if ClientHello should advertise compression methods other than "NULL" tls_sockets() { local -i ret=0 local -i save=0 @@ -8423,8 +8440,9 @@ tls_sockets() { local cipher_list_2send local sock_reply_file2 sock_reply_file3 local tls_hello_ascii next_packet hello_done=0 - local process_full="$3" + local process_full="$3" offer_compression=false + [[ "$5" == "true" ]] && offer_compression=true tls_low_byte="$1" if [[ -n "$2" ]]; then # use supplied string in arg2 if there is one cipher_list_2send="$2" @@ -8437,7 +8455,7 @@ tls_sockets() { fi debugme echo "sending client hello..." - socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$4" + socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$4" "$offer_compression" ret=$? # 6 means opening socket didn't succeed, e.g. timeout # if sending didn't succeed we don't bother @@ -8950,7 +8968,7 @@ run_renego() { } run_crime() { - local -i ret=0 + local -i ret=0 sclient_success local addcmd="" local cve="CVE-2012-4929" local cwe="CWE-310" @@ -8968,14 +8986,27 @@ run_crime() { # first we need to test whether OpenSSL binary has zlib support $OPENSSL zlib -e -a -in /dev/stdin &>/dev/stdout $TMPFILE + sclient_connect_successful $? $TMPFILE + sclient_success=$? fi - - [[ "$OSSL_VER" == "0.9.8"* ]] && addcmd="-no_ssl2" - $OPENSSL s_client $OPTIMAL_PROTO $BUGS $addcmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI $TMPFILE - if grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then + if [[ $sclient_success -ne 0 ]]; then + pr_warning "test failed (couldn't connect)" + fileout "crime" "WARN" "CRIME, TLS: Check failed. (couldn't connect)" "$cve" "$cwe" + ret=7 + elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then pr_done_good "not vulnerable (OK)" if [[ $SERVICE != "HTTP" ]] && ! $CLIENT_AUTH; then out " (not using HTTP anyway)" From 95c75f1792837753023e60a9f227d033973d07d6 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Thu, 5 Jan 2017 15:45:18 -0500 Subject: [PATCH 08/15] Add support for OpenSSL 1.1.0 Starting with OpenSSL 1.1.0, s_client will not offer TLS compression methods, even if OpenSSL is compiled with zlib support, unless the `-comp` flag is included in the command line. --- testssl.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testssl.sh b/testssl.sh index 2019b18..64310c4 100755 --- a/testssl.sh +++ b/testssl.sh @@ -8998,6 +8998,9 @@ run_crime() { fi else [[ "$OSSL_VER" == "0.9.8"* ]] && addcmd="-no_ssl2" + if [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.0"* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == "1.1.1"* ]]; then + addcmd="-comp" + fi $OPENSSL s_client $OPTIMAL_PROTO $BUGS $addcmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI $TMPFILE sclient_connect_successful $? $TMPFILE sclient_success=$? From 42da64d601d5059836689ecbb39a8cb6705dd02f Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 13 Jan 2017 10:28:48 -0500 Subject: [PATCH 09/15] Show selected curve This PR changes `read_dhbits_from_file()` so that, when the "quiet" parameter is absent, the selected curve is shown in addition to the number of bits. This PR only affects the output of `run_client_simulation()` and the `Negotiated cipher` in `run_server_preference()`. --- testssl.sh | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9ec0d6e..6349037 100755 --- a/testssl.sh +++ b/testssl.sh @@ -4498,7 +4498,7 @@ run_std_cipherlists() { # arg1: file with input for grepping the bit length for ECDH/DHE # arg2: whether to print warning "old fart" or not (empty: no) read_dhbits_from_file() { - local bits what_dh temp + local bits what_dh temp curve="" local add="" local old_fart=" (openssl cannot show DH bits)" @@ -4506,14 +4506,23 @@ read_dhbits_from_file() { what_dh=$(awk -F',' '{ print $1 }' <<< $temp) bits=$(awk -F',' '{ print $3 }' <<< $temp) # RH's backport has the DH bits in second arg after comma - grep -q bits <<< $bits || bits=$(awk -F',' '{ print $2 }' <<< $temp) + if grep -q bits <<< $bits; then + curve="$(strip_spaces "$(awk -F',' '{ print $2 }' <<< $temp)")" + else + bits=$(awk -F',' '{ print $2 }' <<< $temp) + fi bits=$(tr -d ' bits' <<< $bits) if [[ "$what_dh" == "X25519" ]] || [[ "$what_dh" == "X448" ]]; then + curve="$what_dh" what_dh="ECDH" fi - debugme echo ">$HAS_DH_BITS|$what_dh|$bits<" + if [[ -n "$curve" ]]; then + debugme echo ">$HAS_DH_BITS|$what_dh($curve)|$bits<" + else + debugme echo ">$HAS_DH_BITS|$what_dh|$bits<" + fi [[ -n "$what_dh" ]] && HAS_DH_BITS=true # FIX 190 if [[ -z "$what_dh" ]] && ! "$HAS_DH_BITS"; then @@ -4525,7 +4534,10 @@ read_dhbits_from_file() { [[ -n "$bits" ]] && [[ -z "$2" ]] && out ", " if [[ $what_dh == "DH" ]] || [[ $what_dh == "EDH" ]]; then - [[ -z "$2" ]] && add="bit DH" + if [[ -z "$2" ]]; then + add="bit DH" + [[ -n "$curve" ]] && add+=" ($curve)" + fi if [[ "$bits" -le 600 ]]; then pr_svrty_critical "$bits $add" elif [[ "$bits" -le 800 ]]; then @@ -4539,7 +4551,10 @@ read_dhbits_from_file() { fi # https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography, http://www.keylength.com/en/compare/ elif [[ $what_dh == "ECDH" ]]; then - [[ -z "$2" ]] && add="bit ECDH" + if [[ -z "$2" ]]; then + add="bit ECDH" + [[ -n "$curve" ]] && add+=" ($curve)" + fi if [[ "$bits" -le 80 ]]; then # has that ever existed? pr_svrty_critical "$bits $add" elif [[ "$bits" -le 108 ]]; then # has that ever existed? From 91e0da3485d8ad1ebaf8edeafbaf29241aad9e04 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 13 Jan 2017 12:13:20 -0500 Subject: [PATCH 10/15] Detect support for encrypt-then-mac extension In some cases, the "TLS extensions" line output for the "--server-defaults" option will not show `"encrypt-then-mac/#22"` even if the server supports this extension. The reason is that a server will only include this extension in the ServerHello message if it supports the extension and the selected cipher is a CBC cipher. So, if `determine_tls_extensions()` connects to the server with a non-CBC cipher, then it will not detect if the server supports the encrypt-then-mac extension. It is possible that support for the extension will be detected by `get_server_certificate()`, but only if one of the calls to that function results in a CBC cipher being selected and OpenSSL 1.1.0 is being used (as prior versions did not support the encrypt-then-mac extension). In this PR, if `determine_tls_extensions()` is called and `$TLS_EXTENSIONS` does not already contain `"encrypt-then-mac/#22"`, then an attempt will be made to connect to the server with only CBC ciphers specified in the ClientHello. If the connection is not successful (presumably because the server does not support any CBC ciphers), then a second connection attempt will be made with the "default" ciphers being specified in the ClientHello. en.wikipedia.org is an example of a server that supports the encrypt-then-mac extension, but for which the support is not currently detected (unless OpenSSL 1.1.0 is used) since in the call to `determine_tls_extension()` a non-CBC cipher is selected. --- testssl.sh | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9ec0d6e..92fb2b9 100755 --- a/testssl.sh +++ b/testssl.sh @@ -5165,16 +5165,19 @@ sclient_connect_successful() { # ALPN extensions in the same ServerHello. determine_tls_extensions() { local addcmd - local -i success + local -i success=1 local line params="" tls_extensions="" 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" + local cbc_cipher_list_hex="c0,28, c0,24, c0,14, c0,0a, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,49, c0,4b, c0,4d, c0,4f, c0,27, c0,23, c0,13, c0,09, 00,67, 00,40, 00,3f, 00,3e, 00,33, 00,32, 00,31, 00,30, c0,76, c0,72, 00,be, 00,bd, 00,bc, 00,bb, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,48, c0,4a, c0,4c, c0,4e, c0,12, c0,08, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,62, 00,09, fe,fe, ff,e1, 00,14, 00,11, 00,08, 00,06, 00,0b, 00,0e" local using_sockets=true [[ "$OPTIMAL_PROTO" == "-ssl2" ]] && return 0 "$SSL_NATIVE" && using_sockets=false if "$using_sockets"; then + tls_extensions="00,01,00,01,02, 00,02,00,00, 00,04,00,00, 00,12,00,00, 00,16,00,00, 00,17,00,00" if [[ -z $STARTTLS ]]; then for alpn_proto in $ALPN_PROTOs; do alpn+=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")" @@ -5183,11 +5186,16 @@ determine_tls_extensions() { alpn_list_len_hex=$(printf "%04x" $alpn_list_len) alpn_extn_len=$alpn_list_len+2 alpn_extn_len_hex=$(printf "%04x" $alpn_extn_len) - tls_sockets "03" "$TLS12_CIPHER" "all" "00,01,00,01,02, 00,02,00,00, 00,04,00,00, 00,12,00,00, 00,16,00,00, 00,17,00,00, 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" - else - tls_sockets "03" "$TLS12_CIPHER" "all" "00,01,00,01,02, 00,02,00,00, 00,04,00,00, 00,12,00,00, 00,16,00,00, 00,17,00,00" + 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 + tls_sockets "03" "$cbc_cipher_list_hex, 00,ff" "all" "$tls_extensions" + success=$? + fi + if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then + tls_sockets "03" "$TLS12_CIPHER" "all" "$tls_extensions" + success=$? fi - success=$? [[ $success -eq 2 ]] && success=0 [[ $success -eq 0 ]] && tls_extensions="$(grep -a 'TLS Extensions: ' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | sed 's/TLS Extensions: //' )" if [[ -r "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ]]; then @@ -5200,16 +5208,23 @@ determine_tls_extensions() { elif "$HAS_SPDY" && [[ -z $STARTTLS ]]; then params="-nextprotoneg \"$NPN_PROTOs\"" fi - success=1 addcmd="" if [[ -z "$OPTIMAL_PROTO" ]] && [[ -z "$SNI" ]] && "$HAS_NO_SSL2"; then addcmd="-no_ssl2" elif [[ ! "$OPTIMAL_PROTO" =~ ssl ]]; then addcmd="$SNI" fi - $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params $ERRFILE >$TMPFILE - sclient_connect_successful $? $TMPFILE - if [[ $? -eq 0 ]]; then + if [[ ! "$TLS_EXTENSIONS" =~ "encrypt-then-mac" ]]; then + $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params -cipher $cbc_cipher_list $ERRFILE >$TMPFILE + sclient_connect_successful $? $TMPFILE + success=$? + fi + if [[ $success -ne 0 ]]; then + $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params $ERRFILE >$TMPFILE + sclient_connect_successful $? $TMPFILE + success=$? + fi + if [[ $success -eq 0 ]]; then success=0 tls_extensions=$(grep -a 'TLS server extension ' $TMPFILE | sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' -e 's/,.*$/,/g' -e 's/),$/\"/g') tls_extensions=$(echo $tls_extensions) # into one line From c5dcaf476f20414e44fac32408804a84ef7c3edc Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 13 Jan 2017 12:18:32 -0500 Subject: [PATCH 11/15] Remove redundant setting to success to 0 --- testssl.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 92fb2b9..f110a1d 100755 --- a/testssl.sh +++ b/testssl.sh @@ -5225,7 +5225,6 @@ determine_tls_extensions() { success=$? fi if [[ $success -eq 0 ]]; then - success=0 tls_extensions=$(grep -a 'TLS server extension ' $TMPFILE | sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' -e 's/,.*$/,/g' -e 's/),$/\"/g') tls_extensions=$(echo $tls_extensions) # into one line fi From cdbdc51f5dc032ac6213285e238e5e79bee3a509 Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 16 Jan 2017 14:06:32 +0100 Subject: [PATCH 12/15] fix #587 --- testssl.sh | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/testssl.sh b/testssl.sh index 5d29dd9..9994bb9 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6000,11 +6000,19 @@ certificate_info() { out "$indent"; pr_bold " # of certificates provided"; outln " $certificates_provided" fileout "${json_prefix}certcount" "INFO" "# of certificates provided : $certificates_provided" + # Get both CRL and OCSP URL upfront. If there's none, this is not good. And we need to penalize this in the output + crl="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | awk '/CRL Distribution/,/URI/ { print $0 }' | awk -F'URI:' '/URI/ { print $2 }')" + ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE) + out "$indent"; pr_bold " Certificate Revocation List " - crl="$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A 4 "CRL Distribution" | grep URI | sed 's/^.*URI://')" - if [[ -z "$crl" ]]; then - pr_svrty_highln "--" - fileout "${json_prefix}crl" "HIGH" "No CRL provided" + if [[ -z "$crl" ]] ; then + if [[ -n "$ocsp_uri" ]]; then + outln "--" + fileout "${json_prefix}crl" "INFO" "No CRL provided" + else + pr_svrty_highln "-- (NOT ok)" + fileout "${json_prefix}crl" "HIGH" "Neither CRL nor OCSP URL provided" + fi elif grep -q http <<< "$crl"; then if [[ $(count_lines "$crl") -eq 1 ]]; then outln "$crl" @@ -6019,10 +6027,9 @@ certificate_info() { fi out "$indent"; pr_bold " OCSP URI " - ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE) - if [[ -z "$ocsp_uri" ]]; then - pr_svrty_highln "--" - fileout "${json_prefix}ocsp_uri" "HIGH" "OCSP URI : --" + if [[ -z "$ocsp_uri" ]] && [[ -n "$crl" ]]; then + outln "--" + fileout "${json_prefix}ocsp_uri" "INFO" "OCSP URI : --" else outln "$ocsp_uri" fileout "${json_prefix}ocsp_uri" "INFO" "OCSP URI : $ocsp_uri" From a3a30c7fa5e7dbae46e7804d1b21feb643b1cdb2 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 17 Jan 2017 11:19:57 +0100 Subject: [PATCH 13/15] - CAA RR (expertimental) - replace some sed+grep by awk in get_mx_record() --- testssl.sh | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9994bb9..0bdc5c7 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6056,6 +6056,20 @@ certificate_info() { fi fi fi + outln + + if "$EXPERIMENTAL"; then + out "$indent"; pr_bold " DNS CAA RR record " + caa="$(get_caa_rr_record $NODE)" + if [[ -n "$caa" ]]; then + pr_done_good "OK ($caa)" + fileout "${json_prefix}CAA_record" "OK" "DNS Certification Authority Authorization (CAA) Resource Record / RFC6844 : offered" + else + pr_svrty_minor "--" + fileout "${json_prefix}CAA_record" "LOW" "DNS Certification Authority Authorization (CAA) Resource Record / RFC6844 : not offered" + fi + fi + outln "\n" return $ret @@ -10961,6 +10975,41 @@ determine_rdns() { return 0 } +# RFC6844: DNS Certification Authority Authorization (CAA) Resource Record +# arg1: domain to check for +get_caa_rr_record() { + local caa="" + local saved_openssl_conf="$OPENSSL_CONF" + + OPENSSL_CONF="" + if which dig &> /dev/null; then + caa="$(dig $1 type257 +short | awk '{ print $3 }')" + # empty if no CAA record + elif which host &> /dev/null; then + caa="$(host -t type257 $1)" + if grep -wq issue <<< "$caa" && grep -wvq "has no CAA" <<< "$caa"; then + caa="$(awk '/issue/ { print $NF }' <<< "$caa")" + fi + elif which nslookup &> /dev/null; then + caa="$(nslookup -type=type257 $1)" + if grep -wq issue <<< "$caa" && grep -wvq "No answer" <<< "$caa"; then + caa="$(awk '/issue/ { print $NF }' <<< "$caa")" + fi + else + return 1 + # No dig, host, or nslookup --> complaint was elsewhere already and except for one which has drill only we don't get here + fi + OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 + echo "$caa" + return 0 +# to do: +# 1: check old binaries whether they support this record at all +# 2: check whether hexstring is returned and deal with it +# 3: check more than domainname, see https://tools.ietf.org/html/rfc6844#section-3 +# 4: check whether $1 is a CNAME and take this +# 5: query with drill +} + get_mx_record() { local mx="" local saved_openssl_conf="$OPENSSL_CONF" @@ -10968,13 +11017,13 @@ get_mx_record() { OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 check_resolver_bins if which host &> /dev/null; then - mxs=$(host -t MX "$1" 2>/dev/null | grep 'handled by' | sed -e 's/^.*by //g' -e 's/\.$//') + mxs=$(host -t MX "$1" 2>/dev/null | awk '/is handled by/ { print $(NF-1), $NF }') elif which dig &> /dev/null; then mxs=$(dig +short -t MX "$1" 2>/dev/null) elif which drill &> /dev/null; then mxs=$(drill mx "$1" 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d') elif which nslookup &> /dev/null; then - mxs=$(nslookup -type=MX "$1" 2>/dev/null | grep 'mail exchanger = ' | sed 's/^.*mail exchanger = //g') + mxs=$(nslookup -type=MX "$1" 2>/dev/null | awk '/mail exchanger/ { print $(NF-1), $NF }') else fatal "No dig, host, drill or nslookup" -3 fi From e7a35934ae1bd51e0b2e8872e58c8b0da0cdfae6 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 17 Jan 2017 12:00:18 +0100 Subject: [PATCH 14/15] add lf before -E --- testssl.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/testssl.sh b/testssl.sh index 1683e14..7c0d0fe 100755 --- a/testssl.sh +++ b/testssl.sh @@ -2753,6 +2753,7 @@ run_cipher_per_proto() { "$FAST" && using_sockets=false [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false + outln if "$using_sockets"; then pr_headlineln " Testing per protocol via OpenSSL and sockets against the server, ordered by encryption strength " else From e9916dd1f44e772255a6656e53d4c1e92dc38d50 Mon Sep 17 00:00:00 2001 From: Dirk Date: Tue, 17 Jan 2017 13:57:14 +0100 Subject: [PATCH 15/15] - FIX #566 - reorder get__record() for better overview - move CMDLINE__IP away from main into determine_ip_addresses() where it belongs to --- testssl.sh | 167 +++++++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 77 deletions(-) diff --git a/testssl.sh b/testssl.sh index 7c0d0fe..f7aa549 100755 --- a/testssl.sh +++ b/testssl.sh @@ -10951,79 +10951,6 @@ get_aaaa_record() { echo "$ip6" } - -# now get all IP addresses -determine_ip_addresses() { - local ip4="" - local ip6="" - - if is_ipv4addr "$NODE"; then - ip4="$NODE" # only an IPv4 address was supplied as an argument, no hostname - SNI="" # override Server Name Indication as we test the IP only - else - ip4=$(get_local_a $NODE) # is there a local host entry? - if [[ -z $ip4 ]]; then # empty: no (LOCAL_A is predefined as false) - check_resolver_bins - ip4=$(get_a_record $NODE) - else - LOCAL_A=true # we have the ip4 from local host entry and need to signal this to testssl - fi - # same now for ipv6 - ip6=$(get_local_aaaa $NODE) - if [[ -z $ip6 ]]; then - check_resolver_bins - ip6=$(get_aaaa_record $NODE) - else - LOCAL_AAAA=true # we have a local ipv6 entry and need to signal this to testssl - fi - fi - if [[ -z "$ip4" ]]; then # IPv6 only address - if "$HAS_IPv6"; then - IPADDRs=$(newline_to_spaces "$ip6") - IP46ADDRs="$IPADDRs" # IP46ADDRs are the ones to display, IPADDRs the ones to test - fi - else - if "$HAS_IPv6" && [[ -n "$ip6" ]]; then - IPADDRs=$(newline_to_spaces "$ip4 $ip6") - IP46ADDRs="$IPADDRs" - else - IPADDRs=$(newline_to_spaces "$ip4") - IP46ADDRs=$(newline_to_spaces "$ip4 $ip6") - fi - fi - if [[ -z "$IPADDRs" ]] && [[ -z "$CMDLINE_IP" ]]; then - fatal "No IPv4 address for \"$NODE\" available" -1 - fi - return 0 # IPADDR and IP46ADDR is set now -} - -determine_rdns() { - local saved_openssl_conf="$OPENSSL_CONF" - local nodeip="$(tr -d '[]' <<< $NODEIP)" # for DNS we do not need the square brackets of IPv6 addresses - - "$NODNS" && rDNS="--" && return 0 - OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 - if [[ "$NODE" == *.local ]]; then - if which avahi-resolve &>/dev/null; then - rDNS=$(avahi-resolve -a $nodeip 2>/dev/null | awk '{ print $2 }') - elif which dig &>/dev/null; then - rDNS=$(dig -x $nodeip @224.0.0.251 -p 5353 +notcp +noall +answer | awk '/PTR/ { print $NF }') - fi - elif which dig &> /dev/null; then - rDNS=$(dig -x $nodeip +noall +answer | awk '/PTR/ { print $NF }') # +short returns also CNAME, e.g. openssl.org - elif which host &> /dev/null; then - rDNS=$(host -t PTR $nodeip 2>/dev/null | awk '/pointer/ { print $NF }') - elif which drill &> /dev/null; then - rDNS=$(drill -x ptr $nodeip 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d') - elif which nslookup &> /dev/null; then - rDNS=$(nslookup -type=PTR $nodeip 2>/dev/null | grep -v 'canonical name =' | grep 'name = ' | awk '{ print $NF }' | sed 's/\.$//') - fi - OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 - rDNS="$(echo $rDNS)" - [[ -z "$rDNS" ]] && rDNS="--" - return 0 -} - # RFC6844: DNS Certification Authority Authorization (CAA) Resource Record # arg1: domain to check for get_caa_rr_record() { @@ -11080,6 +11007,94 @@ get_mx_record() { echo "$mxs" } + +# set IPADDRs and IP46ADDRs +# +determine_ip_addresses() { + local ip4="" + local ip6="" + + if [[ -n "$CMDLINE_IP" ]]; then + # command line has supplied an IP address + [[ "$CMDLINE_IP" == "one" ]] && \ + CMDLINE_IP="$(get_a_record $NODE | head -1)" + # use first IPv4 address + NODEIP="$CMDLINE_IP" + if is_ipv4addr "$NODEIP"; then + ip4="$NODEIP" + elif is_ipv6addr "$NODEIP"; then + ip6="$NODEIP" + else + fatal "couldn't identify supplied \"CMDLINE_IP\"" 2 + fi + elif is_ipv4addr "$NODE"; then + ip4="$NODE" # only an IPv4 address was supplied as an argument, no hostname + SNI="" # override Server Name Indication as we test the IP only + else + ip4=$(get_local_a $NODE) # is there a local host entry? + if [[ -z $ip4 ]]; then # empty: no (LOCAL_A is predefined as false) + check_resolver_bins + ip4=$(get_a_record $NODE) + else + LOCAL_A=true # we have the ip4 from local host entry and need to signal this to testssl + fi + # same now for ipv6 + ip6=$(get_local_aaaa $NODE) + if [[ -z $ip6 ]]; then + check_resolver_bins + ip6=$(get_aaaa_record $NODE) + else + LOCAL_AAAA=true # we have a local ipv6 entry and need to signal this to testssl + fi + fi + + if [[ -z "$ip4" ]]; then # IPv6 only address + if "$HAS_IPv6"; then + IPADDRs=$(newline_to_spaces "$ip6") + IP46ADDRs="$IPADDRs" # IP46ADDRs are the ones to display, IPADDRs the ones to test + fi + else + if "$HAS_IPv6" && [[ -n "$ip6" ]]; then + IPADDRs=$(newline_to_spaces "$ip4 $ip6") + IP46ADDRs="$IPADDRs" + else + IPADDRs=$(newline_to_spaces "$ip4") + IP46ADDRs=$(newline_to_spaces "$ip4 $ip6") + fi + fi + if [[ -z "$IPADDRs" ]]; then + fatal "No IPv4 address for \"$NODE\" available" -1 + fi + return 0 # IPADDR and IP46ADDR is set now +} + +determine_rdns() { + local saved_openssl_conf="$OPENSSL_CONF" + local nodeip="$(tr -d '[]' <<< $NODEIP)" # for DNS we do not need the square brackets of IPv6 addresses + + "$NODNS" && rDNS="--" && return 0 + OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 + if [[ "$NODE" == *.local ]]; then + if which avahi-resolve &>/dev/null; then + rDNS=$(avahi-resolve -a $nodeip 2>/dev/null | awk '{ print $2 }') + elif which dig &>/dev/null; then + rDNS=$(dig -x $nodeip @224.0.0.251 -p 5353 +notcp +noall +answer | awk '/PTR/ { print $NF }') + fi + elif which dig &> /dev/null; then + rDNS=$(dig -x $nodeip +noall +answer | awk '/PTR/ { print $NF }') # +short returns also CNAME, e.g. openssl.org + elif which host &> /dev/null; then + rDNS=$(host -t PTR $nodeip 2>/dev/null | awk '/pointer/ { print $NF }') + elif which drill &> /dev/null; then + rDNS=$(drill -x ptr $nodeip 2>/dev/null | awk '/^\;\;\sANSWER\sSECTION\:$/,/\;\;\sAUTHORITY\sSECTION\:$/ { print $5,$6 }' | sed '/^\s$/d') + elif which nslookup &> /dev/null; then + rDNS=$(nslookup -type=PTR $nodeip 2>/dev/null | grep -v 'canonical name =' | grep 'name = ' | awk '{ print $NF }' | sed 's/\.$//') + fi + OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 + rDNS="$(echo $rDNS)" + [[ -z "$rDNS" ]] && rDNS="--" + return 0 +} + # We need to get the IP address of the proxy so we can use it in fd_socket # check_proxy() { @@ -12011,13 +12026,11 @@ if $do_mx_all_ips; then else parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now prepare_logging - if ! determine_ip_addresses && [[ -z "$CMDLINE_IP" ]]; then + if ! determine_ip_addresses; then fatal "No IP address could be determined" 2 fi if [[ -n "$CMDLINE_IP" ]]; then - [[ "$CMDLINE_IP" == "one" ]] && \ - CMDLINE_IP=$(echo -n "$IPADDRs" | awk '{ print $1 }') - NODEIP="$CMDLINE_IP" # specific ip address for NODE was supplied + # we just test the one supplied lets_roll "${STARTTLS_PROTOCOL}" ret=$? else # no --ip was supplied