From 968b37e5fe2e406a43a3695144c5a15c22898941 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 3 Oct 2017 16:10:09 -0400 Subject: [PATCH] Initial support for TLSv1.3 This PR adds initial support for TLSv1.3 to tls_sockets() and for run_client_simulation(). It does not change any of the other functions test TLSv1.3. So, with the exception of run_client_simulation(), the functionality added by this PR can only be tested using the --devel option. This PR does not include the ability to decrypt the encrypted portions of the server's response. So, it does not support functions that need to see such things as the server's certificate, status information, or extensions (other than key share). --- testssl.sh | 629 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 535 insertions(+), 94 deletions(-) diff --git a/testssl.sh b/testssl.sh index 3980869..4f19ce1 100755 --- a/testssl.sh +++ b/testssl.sh @@ -3644,8 +3644,21 @@ client_simulation_sockets() { tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" + # Check if the response is a HelloRetryRequest. + resend_if_hello_retry_request "$tls_hello_ascii" "$cipher_list_2send" "$4" "$process_full" + ret=$? + if [[ $ret -eq 2 ]]; then + tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") + tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" + elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then + close_socket + TMPFILE=$SOCK_REPLY_FILE + tmpfile_handle $FUNCNAME.dd + return $ret + fi + if [[ "${tls_hello_ascii:0:1}" != "8" ]]; then - check_tls_serverhellodone "$tls_hello_ascii" + check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey" hello_done=$? fi @@ -3674,7 +3687,7 @@ client_simulation_sockets() { cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE" rm "$sock_reply_file3" - check_tls_serverhellodone "$tls_hello_ascii" + check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey" hello_done=$? fi done @@ -7915,15 +7928,21 @@ parse_sslv2_serverhello() { return $ret } -# Return 0 if arg1 contains the entire server response, 1 if it does not, and 2 if the response is malformed. -# Return 3 if the response version is TLS 1.3 and the entire ServerHello has been received, since any remaining -# portion of the response will be encrypted. +# Return: +# 0 if arg1 contains the entire server response. +# 1 if arg1 does not contain the entire server response. +# 2 if the response is malformed. +# 3 if (a) the response version is TLSv1.3; +# (b) arg1 contains the entire ServerHello (and appears to contain the entire response); and +# (c) the entire response is supposed to be parsed # arg1: ASCII-HEX encoded reply +# arg2: whether to process the full request ("all") or just the basic request plus the ephemeral key if any ("ephemeralkey"). check_tls_serverhellodone() { local tls_hello_ascii="$1" + local process_full="$2" local tls_handshake_ascii="" tls_alert_ascii="" local -i i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len - local -i msg_len remaining + local -i msg_len remaining tls_serverhello_ascii_len local tls_content_type tls_protocol tls_handshake_type tls_msg_type local tls_err_level @@ -7943,7 +7962,7 @@ check_tls_serverhellodone() { [[ "$tls_content_type" != "17" ]] && return 2 i=$i+2 tls_protocol="${tls_hello_ascii:i:4}" - [[ -z "$DETECTED_TLS_VERSION" ]] && DETECTED_TLS_VERSION=$tls_protocol + [[ -z "$DETECTED_TLS_VERSION" ]] && DETECTED_TLS_VERSION="$tls_protocol" [[ "${tls_protocol:0:2}" != "03" ]] && return 2 i=$i+4 msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}") @@ -7958,12 +7977,12 @@ check_tls_serverhellodone() { [[ $tls_handshake_ascii_len -ge 2 ]] && [[ "${tls_handshake_ascii:0:2}" != "02" ]] && return 2 if [[ $tls_handshake_ascii_len -ge 12 ]]; then DETECTED_TLS_VERSION="${tls_handshake_ascii:8:4}" - if [[ 0x"$DETECTED_TLS_VERSION" -ge "0x0304" ]]; then - tls_handshake_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}") - if [[ $tls_handshake_ascii_len+8 -gt $remaining ]]; then - return 1 # Not all of the ServerHello message has been received - else - return 3 + # A version of {0x7F, xx} represents an implementation of a draft version of TLS 1.3 + [[ "${DETECTED_TLS_VERSION:0:2}" == "7F" ]] && DETECTED_TLS_VERSION="0304" + if [[ 0x$DETECTED_TLS_VERSION -ge 0x0304 ]] && [[ "$process_full" == "ephemeralkey" ]]; then + tls_serverhello_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}") + if [[ $tls_handshake_ascii_len -ge $tls_serverhello_ascii_len+8 ]]; then + return 0 # The entire ServerHello message has been received (and the rest isn't needed) fi fi fi @@ -7998,7 +8017,12 @@ check_tls_serverhellodone() { [[ $tls_msg_type == "0E" ]] && return 0 [[ $tls_msg_type == "14" ]] && return 0 done - + # If the response is TLSv1.3 and the full response is to be processed, + # then return 3 if the entire ServerHello has been received. + if [[ "$DETECTED_TLS_VERSION" == "0304" ]] && [[ "$process_full" == "all" ]] && \ + [[ $tls_handshake_ascii_len -gt 0 ]]; then + return 3 + fi # If we haven't encoountered a fatal alert or a server hello done, # then there must be more data to retrieve. return 1 @@ -8024,15 +8048,15 @@ parse_tls_serverhello() { local -i tls_sid_len offset extns_offset nr_certs=0 local tls_msg_type tls_content_type tls_protocol tls_protocol2 tls_hello_time local tls_err_level tls_err_descr tls_cipher_suite rfc_cipher_suite tls_compression_method - local tls_extensions="" extension_type named_curve_str="" + local tls_extensions="" extension_type named_curve_str="" named_curve_oid local -i i j extension_len tls_extensions_len ocsp_response_len ocsp_response_list_len local -i certificate_list_len certificate_len cipherlist_len 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 key_bitstring="" - local dh_p ephemeral_param rfc7919_param - local -i dh_p_len + local len1 len2 len3 key_bitstring="" tmp_der_key_file + local dh_p dh_param ephemeral_param rfc7919_param + local -i dh_p_len dh_param_len TLS_TIME="" DETECTED_TLS_VERSION="" @@ -8125,7 +8149,6 @@ parse_tls_serverhello() { debugme tm_out " tls_err_descr: 0x${tls_err_descr} / = $(hex2dec ${tls_err_descr})" case $tls_err_descr in 00) tls_alert_descrip="close notify" ;; - 01) tls_alert_descrip="end of early data" ;; 0A) tls_alert_descrip="unexpected message" ;; 14) tls_alert_descrip="bad record mac" ;; 15) tls_alert_descrip="decryption failed" ;; @@ -8215,7 +8238,8 @@ parse_tls_serverhello() { 01) tmln_out " (client_hello)" ;; 02) tmln_out " (server_hello)" ;; 03) tmln_out " (hello_verify_request)" ;; - 04) tmln_out " (NewSessionTicket)" ;; + 04) tmln_out " (new_session_ticket)" ;; + 05) tmln_out " (end_of_early_data)" ;; 06) tmln_out " (hello_retry_request)" ;; 08) tmln_out " (encrypted_extensions)" ;; 0B) tmln_out " (certificate)" ;; @@ -8229,6 +8253,7 @@ parse_tls_serverhello() { 16) tmln_out " (certificate_status)" ;; 17) tmln_out " (supplemental_data)" ;; 18) tmln_out " (key_update)" ;; + FE) tmln_out " (message_hash)" ;; *) tmln_out ;; esac echo " msg_len: $((msg_len/2))" @@ -8302,6 +8327,7 @@ parse_tls_serverhello() { # byte 37+sid-len: compression method: 00: none, 01: deflate, 64: LZS # byte 38+39+sid-len: extension length tls_protocol2="${tls_serverhello_ascii:0:4}" + [[ "${tls_protocol2:0:2}" == "7F" ]] && tls_protocol2="0304" if [[ "${tls_protocol2:0:2}" != "03" ]]; then debugme tmln_warning "server_version.major in ServerHello is not 03." return 1 @@ -8406,13 +8432,81 @@ parse_tls_serverhello() { 0018) tls_extensions+="TLS server extension \"token binding\" (id=24), len=$extension_len\n" ;; 0019) tls_extensions+="TLS server extension \"cached info\" (id=25), len=$extension_len\n" ;; 0023) tls_extensions+="TLS server extension \"session ticket\" (id=35), len=$extension_len\n" ;; - 0028) tls_extensions+="TLS server extension \"key share\" (id=40), len=$extension_len\n" ;; + 0028) tls_extensions+="TLS server extension \"key share\" (id=40), len=$extension_len\n" + if [[ $extension_len -lt 4 ]]; then + debugme tmln_warning "Malformed key share extension." + return 1 + fi + let offset=$extns_offset+12+$i + named_curve=$(hex2dec "${tls_serverhello_ascii:offset:4}") + let offset=$extns_offset+16+$i + msg_len=2*"$(hex2dec "${tls_serverhello_ascii:offset:4}")" + if [[ $msg_len -ne $extension_len-8 ]]; then + debugme tmln_warning "Malformed key share extension." + return 1 + fi + case $named_curve in + 23) dh_bits=256 ; named_curve_str="P-256" ; named_curve_oid="06082a8648ce3d030107" ;; + 24) dh_bits=384 ; named_curve_str="P-384" ; named_curve_oid="06052b81040022" ;; + 25) dh_bits=521 ; named_curve_str="P-521" ; named_curve_oid="06052b81040023" ;; + 29) dh_bits=253 ; named_curve_str="X25519" ;; + 30) dh_bits=448 ; named_curve_str="X448" ;; + 256) dh_bits=2048 ; named_curve_str="ffdhe2048" ;; + 257) dh_bits=3072 ; named_curve_str="ffdhe3072" ;; + 258) dh_bits=4096 ; named_curve_str="ffdhe4096" ;; + 259) dh_bits=6144 ; named_curve_str="ffdhe6144" ;; + 260) dh_bits=8192 ; named_curve_str="ffdhe8192" ;; + *) named_curve_str="" ; named_curve_oid="" ;; + esac + let offset=$extns_offset+20+$i + if [[ $named_curve -eq 29 ]]; then + key_bitstring="302a300506032b656e032100${tls_serverhello_ascii:offset:msg_len}" + elif [[ $named_curve -eq 30 ]]; then + key_bitstring="3042300506032b656f033900${tls_serverhello_ascii:offset:msg_len}" + elif [[ $named_curve -lt 256 ]] && [[ -n "$named_curve_oid" ]]; then + len1="$(printf "%02x" $(($msg_len/2+1)))" + [[ "0x${len1}" -ge "0x80" ]] && len1="81${len1}" + key_bitstring="03${len1}00${tls_serverhello_ascii:offset:msg_len}" + len2="$(printf "%02x" $((${#named_curve_oid}/2+9)))" + len3="$(printf "%02x" $((${#named_curve_oid}/2+${#key_bitstring}/2+11)))" + [[ "0x${len3}" -ge "0x80" ]] && len3="81${len3}" + key_bitstring="30${len3}30${len2}06072a8648ce3d0201${named_curve_oid}${key_bitstring}" + elif [[ "$named_curve_str" =~ "ffdhe" ]] && [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]]; then + dh_param="$($OPENSSL pkey -pubout -outform DER <<< "${TLS13_KEY_SHARES[named_curve]}" | hexdump -v -e '16/1 "%02X"')" + + # First is the length of the public-key SEQUENCE, and it is always encoded in four bytes (3082xxxx) + # Next is the length of the parameters SEQUENCE, and it is also always encoded in four bytes (3082xxxx) + dh_param_len=8+2*"$(hex2dec "${dh_param:12:4}")" + dh_param="${dh_param:8:dh_param_len}" + if [[ "0x${tls_serverhello_ascii:offset:2}" -ge 0x80 ]]; then + key_bitstring="00${tls_serverhello_ascii:offset:msg_len}" + msg_len+=2 + else + key_bitstring="${tls_serverhello_ascii:offset:msg_len}" + fi + len1="$(printf "%04x" $(($msg_len/2)))" + key_bitstring="0282${len1}$key_bitstring" + len1="$(printf "%04x" $((${#key_bitstring}/2+1)))" + key_bitstring="${dh_param}0382${len1}00$key_bitstring" + len1="$(printf "%04x" $((${#key_bitstring}/2)))" + key_bitstring="3082${len1}$key_bitstring" + fi + if [[ -n "$key_bitstring" ]]; then + 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 + fi + ;; 0029) tls_extensions+="TLS server extension \"pre-shared key\" (id=41), len=$extension_len\n" ;; 002A) tls_extensions+="TLS server extension \"early data\" (id=42), len=$extension_len\n" ;; 002B) tls_extensions+="TLS server extension \"supported versions\" (id=43), len=$extension_len\n" ;; 002C) tls_extensions+="TLS server extension \"cookie\" (id=44), len=$extension_len\n" ;; 002D) tls_extensions+="TLS server extension \"psk key exchange modes\" (id=45), len=$extension_len\n" ;; 002E) tls_extensions+="TLS server extension \"ticket early data info\" (id=46), len=$extension_len\n" ;; + 002F) tls_extensions+="TLS server extension \"certificate authorities\" (id=47), len=$extension_len\n" ;; + 0030) tls_extensions+="TLS server extension \"oid filters\" (id=48), len=$extension_len\n" ;; + 0031) tls_extensions+="TLS server extension \"post handshake auth\" (id=49), len=$extension_len\n" ;; 3374) tls_extensions+="TLS server extension \"next protocol\" (id=13172), len=$extension_len\n" local -i protocol_len echo -n "Protocols advertised by server: " >> $TMPFILE @@ -8457,6 +8551,21 @@ parse_tls_serverhello() { rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${tls_cipher_suite:0:2},0x${tls_cipher_suite:2:2} " | awk '{ print $3 }')" fi echo "Cipher : $rfc_cipher_suite" >> $TMPFILE + if [[ $dh_bits -ne 0 ]]; then + if [[ "$named_curve_str" =~ "ffdhe" ]]; then + echo "Server Temp Key: DH, $named_curve_str, $dh_bits bits" >> $TMPFILE + elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then + echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE + else + echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE + fi + fi + if [[ -n "$key_bitstring" ]]; then + echo "$key_bitstring" >> $TMPFILE + [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]] && \ + echo "${TLS13_KEY_SHARES[named_curve]}" >> $TMPFILE + fi + echo "===============================================================================" >> $TMPFILE if [[ "0x${tls_protocol2:2:2}" -le "0x03" ]]; then case $tls_compression_method in 00) echo "Compression: NONE" >> $TMPFILE ;; @@ -8478,7 +8587,21 @@ parse_tls_serverhello() { echo -n " tls_hello_time: 0x$tls_hello_time " parse_date "$TLS_TIME" "+%Y-%m-%d %r" "%s" fi - echo " tls_cipher_suite: 0x$tls_cipher_suite" + echo -n " tls_cipher_suite: 0x$tls_cipher_suite" + if [[ -n "$rfc_cipher_suite" ]]; then + echo " ($rfc_cipher_suite)" + else + echo "" + fi + if [[ $dh_bits -ne 0 ]]; then + if [[ "$named_curve_str" =~ "ffdhe" ]]; then + echo " dh_bits: DH, $named_curve_str, $dh_bits bits" + elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then + echo " dh_bits: $named_curve_str, $dh_bits bits" + else + echo " dh_bits: ECDH, $named_curve_str, $dh_bits bits" + fi + fi if [[ "0x${tls_protocol2:2:2}" -le "0x03" ]]; then echo -n " tls_compression_method: 0x$tls_compression_method " case $tls_compression_method in @@ -8869,13 +8992,71 @@ sslv2_sockets() { return $ret } +# arg1: supported groups extension +# arg2: "all" - process full response (including Certificate and certificate_status handshake messages) +# "ephemeralkey" - extract the server's ephemeral key (if any) +# Given the supported groups extension, create a key_share extension that includes a key share for +# each group listed in the supported groups extension. +generate_key_share_extension() { + local supported_groups + local -i i len supported_groups_len group + local extn_len list_len + local key_share key_shares="" + + supported_groups="${1//\\x/}" + [[ "${supported_groups:0:4}" != "000a" ]] && return 1 + + supported_groups_len=${#supported_groups} + [[ $supported_groups_len -lt 16 ]] && return 1 + + len=2*$(hex2dec "${supported_groups:4:4}") + [[ $len+8 -ne $supported_groups_len ]] && return 1 + + len=2*$(hex2dec "${supported_groups:8:4}") + [[ $len+12 -ne $supported_groups_len ]] && return 1 + + for (( i=12; i=0; i=i-1 )); do + if [[ 0x$i -eq 4 ]]; then + # FIXME: The ClientHello currently indicates support + # for drafts 18, 19, 20, and 21 of TLSv1.3 in addition + # to the final version of TLSv1.3. In the future, the + # draft versions should be removed. + extension_supported_versions+=", 03, 04, 7f, 15, 7f, 14, 7f, 13, 7f, 12" + else + extension_supported_versions+=", 03, $(printf "%02x" $i)" + fi + done + [[ -n "$all_extensions" ]] && all_extensions+="," + # FIXME: Adjust the lengths ("+11" and "+9") when the draft versions of TLSv1.3 are removed. + all_extensions+="00, 2b, 00, $(printf "%02x" $((2*0x$tls_low_byte+11))), $(printf "%02x" $((2*0x$tls_low_byte+10)))$extension_supported_versions" + fi if [[ ! "$extra_extensions_list" =~ " 0023 " ]]; then [[ -n "$all_extensions" ]] && all_extensions+="," @@ -9031,6 +9278,12 @@ socksend_tls_clienthello() { [[ -n "$all_extensions" ]] && all_extensions+="," all_extensions+="$extension_supported_groups" fi + + if [[ -n "$extensions_key_share" ]] && [[ ! "$extra_extensions_list" =~ " 0028 " ]]; then + [[ -n "$all_extensions" ]] && all_extensions+="," + all_extensions+="$extensions_key_share" + fi + if [[ -n "$extension_supported_point_formats" ]] && [[ ! "$extra_extensions_list" =~ " 000b " ]]; then [[ -n "$all_extensions" ]] && all_extensions+="," all_extensions+="$extension_supported_point_formats" @@ -9105,6 +9358,8 @@ socksend_tls_clienthello() { # 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" + [[ 0x$tls_legacy_version -ge 0x04 ]] && tls_legacy_version="03" + if "$offer_compression"; then # See http://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml#comp-meth-ids-2 compression_methods="03,01,40,00" # Offer NULL, DEFLATE, and LZS compression @@ -9119,7 +9374,7 @@ socksend_tls_clienthello() { # Handshake header: ,01 # Type (x01 for ClientHello) ,00, $len_client_hello_word # Length ClientHello - ,03, $tls_low_byte # TLS version ClientHello + ,03, $tls_legacy_version # TLS version ClientHello ,54, 51, 1e, 7a # Unix time since see www.moserware.com/2009/06/first-few-milliseconds-of-https.html ,de, ad, be, ef # Random 28 bytes ,31, 33, 07, 00, 00, 00, 00, 00 @@ -9141,6 +9396,172 @@ socksend_tls_clienthello() { return 0 } +# arg1: The server's response +# arg2: CIPHER_SUITES string (lowercase, and in the format output by code2network()) +# arg3: (optional) additional request extensions +# arg4: "all" - process full response (including Certificate and certificate_status handshake messages) +# "ephemeralkey" - extract the server's ephemeral key (if any) +# Return 0 if the response is not a HelloRetryRequest. +# Return 1 if the response is a malformed HelloRetryRequest or if a new ClientHello cannot be sent. +# Return 2 if the response is a HelloRetryRequest, and sending a new ClientHello succeeded. +# Return 6 if the response is a HelloRetryRequest, and sending a new ClientHello failed. +resend_if_hello_retry_request() { + local tls_hello_ascii="$1" + local cipher_list_2send="$2" + local process_full="$4" + local tls_low_byte server_version cipher_suite rfc_cipher_suite + local -i i j msg_len tls_hello_ascii_len + local -i extns_offset hrr_extns_len extra_extensions_len len_extn + local extra_extensions extn_type part2 new_extra_extns="" new_key_share temp + + tls_hello_ascii_len=${#tls_hello_ascii} + # A HelloRetryRequest is at least 13 bytes long + [[ $tls_hello_ascii_len -lt 26 ]] && return 0 + # A HelloRetryRequest is a handshake message (16) with a major record version of 03. + [[ "${tls_hello_ascii:0:4}" != "1603" ]] && return 0 + # The handshake type for hello_retry_request is 06. + [[ "${tls_hello_ascii:10:2}" != "06" ]] && return 0 + + # This appears to be a HelloRetryRequest messsage. + debugme echo "reading hello retry request... " + if [[ "$DEBUG" -ge 4 ]]; then + hexdump -C $SOCK_REPLY_FILE | head -6 + echo + fi + + # Check the length of the handshake message + msg_len=2*$(hex2dec "${tls_hello_ascii:6:4}") + if [[ $msg_len -ne $tls_hello_ascii_len-10 ]]; then + debugme echo "malformed HelloRetryRequest" + return 1 + fi + + # Check the length of the HelloRetryRequest message. + msg_len=2*$(hex2dec "${tls_hello_ascii:12:6}") + if [[ $msg_len -ne $tls_hello_ascii_len-18 ]]; then + debugme echo "malformed HelloRetryRequest" + return 1 + fi + + server_version="${tls_hello_ascii:18:4}" + if [[ "$server_version" == "0304" ]] || [[ 0x$server_version -ge 0x7f13 ]]; then + # Starting with TLSv1.3 draft 19, a HelloRetryRequest is at least 15 bytes long + [[ $tls_hello_ascii_len -lt 30 ]] && return 0 + cipher_suite="${tls_hello_ascii:22:2},${tls_hello_ascii:24:2}" + extns_offset=26 + else + extns_offset=22 + fi + + # Check the length of the extensions. + hrr_extns_len=2*$(hex2dec "${tls_hello_ascii:extns_offset:4}") + if [[ $hrr_extns_len -ne $tls_hello_ascii_len-$extns_offset-4 ]]; then + debugme echo "malformed HelloRetryRequest" + return 1 + fi + + if [[ "${server_version:0:2}" == "7F" ]]; then + tls_low_byte="04" + else + tls_low_byte="${server_version:2:2}" + fi + if [[ $DEBUG -ge 3 ]]; then + echo "TLS message fragments:" + echo " tls_protocol (reclyr): 0x${tls_hello_ascii:2:4}" + echo " tls_content_type: 0x16 (handshake)" + echo " msg_len: $(hex2dec "${tls_hello_ascii:6:4}")" + echo + echo "TLS handshake message:" + echo " handshake type: 0x06 (hello_retry_request)" + echo " msg_len: $(hex2dec "${tls_hello_ascii:12:6}")" + echo + echo "TLS hello retry request message:" + echo " server version: $server_version" + if [[ "$server_version" == "0304" ]] || [[ 0x$server_version -ge 0x7f13 ]]; then + echo -n " cipher suite: $cipher_suite" + if [[ $TLS_NR_CIPHERS -ne 0 ]]; then + if [[ "${cipher_suite:0:2}" == "00" ]]; then + rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:3:2}")" + else + rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:0:2}${cipher_suite:3:2}")" + fi + else + rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${cipher_suite:0:2},0x${cipher_suite:3:2} " | awk '{ print $3 }')" + fi + if [[ -n "$rfc_cipher_suite" ]]; then + echo " ($rfc_cipher_suite)" + else + echo "" + fi + fi + fi + + # Parse HelloRetryRequest extensions + for (( i=extns_offset+4; i < tls_hello_ascii_len; i=i+8+$len_extn )); do + extn_type="${tls_hello_ascii:i:4}" + j=$i+4 + len_extn=2*$(hex2dec "${tls_hello_ascii:j:4}") + j+=4 + if [[ $len_extn -gt $tls_hello_ascii_len-$j ]]; then + debugme echo "malformed HelloRetryRequest" + return 1 + fi + # If the HRR includes a cookie extension, then it needs to be + # included in the next ClientHello. + if [[ "$extn_type" == "002C" ]]; then + j=8+$len_extn + new_extra_extns+="${tls_hello_ascii:i:j}" + fi + # If the HRR includes a key_share extension, then it specifies the + # group to be used in the next ClientHello. So, create a key_share + # extension that specifies this group. + if [[ "$extn_type" == "0028" ]]; then + if [[ $len_extn -ne 4 ]]; then + debugme echo "malformed key share extension in HelloRetryRequest" + return 1 + fi + [[ $DEBUG -ge 3 ]] && echo " key share: 0x${tls_hello_ascii:j:4}" + new_key_share="$(generate_key_share_extension "000a00040002${tls_hello_ascii:j:4}" "$process_full")" + new_extra_extns+="${new_key_share//,/}" + fi + done + debugme echo "" + if [[ -n "$new_extra_extns" ]]; then + temp="$new_extra_extns" + extra_extensions_len=${#temp} + new_extra_extns="" + for (( i=0 ; i < extra_extensions_len; i=i+2 )); do + new_extra_extns+=",${temp:i:2}" + done + new_extra_extns="${new_extra_extns:1}" + fi + + # Include any extra extensions that were included in the first ClientHello, + # except key_share and cookie. + extra_extensions="$(strip_spaces "$(tolower "$3")")" + extra_extensions_len=${#extra_extensions} + for (( i=0; i < extra_extensions_len; i=i+12+$len_extn )); do + part2=$i+3 + extn_type="${extra_extensions:i:2}${extra_extensions:part2:2}" + j=$i+6 + part2=$j+3 + len_extn=3*$(hex2dec "${extra_extensions:j:2}${extra_extensions:part2:2}") + if [[ "$extn_type" != "0028" ]] && [[ "$extn_type" != "002c" ]]; then + j=12+$len_extn + new_extra_extns+=",${extra_extensions:i:j}" + fi + done + + debugme echo -en "\nsending second client hello... " + socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$process_full" "$new_extra_extns" + if [[ $? -ne 0 ]]; then + debugme echo "stuck on sending: $ret" + return 6 + fi + sockread_serverhello 32768 + return 2 +} + # arg1: TLS version low byte # (00: SSLv3, 01: TLS 1.0, 02: TLS 1.1, 03: TLS 1.2) # arg2: (optional) list of cipher suites @@ -9157,8 +9578,9 @@ tls_sockets() { local tls_low_byte local cipher_list_2send local sock_reply_file2 sock_reply_file3 - local tls_hello_ascii next_packet hello_done=0 - local process_full="$3" offer_compression=false + local tls_hello_ascii next_packet + local process_full="$3" offer_compression=false skip=false + local -i hello_done=0 [[ "$5" == "true" ]] && offer_compression=true tls_low_byte="$1" @@ -9175,7 +9597,7 @@ tls_sockets() { cipher_list_2send="$NW_STR" debugme echo -en "\nsending client hello... " - socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$4" "$offer_compression" + socksend_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$process_full" "$4" "$offer_compression" ret=$? # 6 means opening socket didn't succeed, e.g. timeout # if sending didn't succeed we don't bother @@ -9186,41 +9608,60 @@ tls_sockets() { tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" - # The server's response may span more than one packet. So, - # check if response appears to be complete, and if it isn't - # then try to get another packet from the server. + # Check if the response is a HelloRetryRequest. + resend_if_hello_retry_request "$tls_hello_ascii" "$cipher_list_2send" "$4" "$process_full" + ret=$? + if [[ $ret -eq 2 ]]; then + tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") + tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}" + elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then + close_socket + TMPFILE=$SOCK_REPLY_FILE + tmpfile_handle $FUNCNAME.dd + return $ret + fi + + # The server's response may span more than one packet. If only the + # first part of the response needs to be processed, this isn't an + # issue. However, if the entire response needs to be processed or + # if the ephemeral key is needed (which comes last for TLS 1.2 and + # below), then we need to check if response appears to be complete, + # and if it isn't then try to get another packet from the server. if [[ "$process_full" == "all" ]] || [[ "$process_full" == "ephemeralkey" ]]; then - check_tls_serverhellodone "$tls_hello_ascii" - hello_done=$? - [[ "$hello_done" -eq 3 ]] && process_full="ephemeralkey" + hello_done=1; skip=true fi for (( 1 ; hello_done==1; 1 )); do - sock_reply_file2=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 - mv "$SOCK_REPLY_FILE" "$sock_reply_file2" + if ! "$skip"; then + sock_reply_file2=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 + mv "$SOCK_REPLY_FILE" "$sock_reply_file2" - debugme echo -n "requesting more server hello data... " - socksend "" $USLEEP_SND - sockread_serverhello 32768 + debugme echo -n "requesting more server hello data... " + socksend "" $USLEEP_SND + sockread_serverhello 32768 - next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") - next_packet="${next_packet%%[!0-9A-F]*}" - if [[ ${#next_packet} -eq 0 ]]; then - # This shouldn't be necessary. However, it protects against - # getting into an infinite loop if the server has nothing - # left to send and check_tls_serverhellodone doesn't - # correctly catch it. - mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - hello_done=0 - else - tls_hello_ascii+="$next_packet" + next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE") + next_packet="${next_packet%%[!0-9A-F]*}" - sock_reply_file3=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 - mv "$SOCK_REPLY_FILE" "$sock_reply_file3" - mv "$sock_reply_file2" "$SOCK_REPLY_FILE" - cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE" - rm "$sock_reply_file3" + if [[ ${#next_packet} -eq 0 ]]; then + # This shouldn't be necessary. However, it protects against + # getting into an infinite loop if the server has nothing + # left to send and check_tls_serverhellodone doesn't + # correctly catch it. + mv "$sock_reply_file2" "$SOCK_REPLY_FILE" + hello_done=0 + else + tls_hello_ascii+="$next_packet" - check_tls_serverhellodone "$tls_hello_ascii" + sock_reply_file3=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7 + mv "$SOCK_REPLY_FILE" "$sock_reply_file3" + mv "$sock_reply_file2" "$SOCK_REPLY_FILE" + cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE" + rm "$sock_reply_file3" + fi + fi + skip=false + if [[ $hello_done -eq 1 ]]; then + check_tls_serverhellodone "$tls_hello_ascii" "$process_full" hello_done=$? [[ "$hello_done" -eq 3 ]] && process_full="ephemeralkey" fi