Merge pull request #1484 from dcooper16/tls13_post_handshake
TLS 1.3 post-handshake messages
This commit is contained in:
commit
2f54613d6d
288
testssl.sh
288
testssl.sh
|
@ -258,6 +258,7 @@ TLS12_CIPHER_OFFERED="" # This contains the hexcode of a cipher
|
|||
CURVES_OFFERED="" # This keeps which curves have been detected. Just for error handling
|
||||
KNOWN_OSSL_PROB=false # We need OpenSSL a few times. This variable is an indicator if we can't connect. Eases handling
|
||||
DETECTED_TLS_VERSION="" # .. as hex string, e.g. 0300 or 0303
|
||||
APP_TRAF_KEY_INFO="" # Information about the application traffic keys for a TLS 1.3 connection.
|
||||
TLS13_ONLY=false # Does the server support TLS 1.3 ONLY?
|
||||
OSSL_SHORTCUT=${OSSL_SHORTCUT:-false} # Hack: if during the scan turns out the OpenSSL binary suports TLS 1.3 would be a better choice, this enables it.
|
||||
TLS_EXTENSIONS=""
|
||||
|
@ -2050,10 +2051,31 @@ service_detection() {
|
|||
local -i was_killed
|
||||
|
||||
if ! "$CLIENT_AUTH"; then
|
||||
if ! "$HAS_TLS13" && "$TLS13_ONLY"; then
|
||||
# Using sockets is a lot slower than using OpenSSL, and it is
|
||||
# not as reliable, but if OpenSSL can't connect to the server,
|
||||
# trying with sockets is better than not even trying.
|
||||
tls_sockets "04" "$TLS13_CIPHER" "all+" "" "" false
|
||||
if [[ $? -eq 0 ]]; then
|
||||
plaintext="$(printf "$GET_REQ11" | hexdump -v -e '16/1 "%02X"')"
|
||||
plaintext="${plaintext%%[!0-9A-F]*}"
|
||||
send_app_data "$plaintext"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
receive_app_data true
|
||||
[[ $? -eq 0 ]] || > "$TMPFILE"
|
||||
else
|
||||
> "$TMPFILE"
|
||||
fi
|
||||
send_close_notify "$DETECTED_TLS_VERSION"
|
||||
else
|
||||
> "$TMPFILE"
|
||||
fi
|
||||
else
|
||||
# SNI is not standardized for !HTTPS but fortunately for other protocols s_client doesn't seem to care
|
||||
printf "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$1 -quiet $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE &
|
||||
wait_kill $! $HEADER_MAXSLEEP
|
||||
was_killed=$?
|
||||
fi
|
||||
head $TMPFILE | grep -aq '^HTTP\/' && SERVICE=HTTP
|
||||
[[ -z "$SERVICE" ]] && head $TMPFILE | grep -waq "SMTP|ESMTP|Exim|IdeaSmtpServer|Kerio Connect|Postfix" && SERVICE=SMTP # I know some overlap here
|
||||
[[ -z "$SERVICE" ]] && head $TMPFILE | grep -Ewaq "POP|Gpop|MailEnable POP3 Server|OK Dovecot|Cyrus POP3" && SERVICE=POP # I know some overlap here
|
||||
|
@ -11159,6 +11181,92 @@ derive-handshake-traffic-keys() {
|
|||
tm_out "$key $iv $finished"
|
||||
}
|
||||
|
||||
#arg1: TLS cipher
|
||||
#arg2: handshake secret
|
||||
derive-master-secret() {
|
||||
local cipher="$1"
|
||||
local handshake_secret="$2"
|
||||
local -i retcode
|
||||
local hash_fn
|
||||
local derived_secret zeros master_secret
|
||||
|
||||
if [[ "$cipher" == *SHA256 ]]; then
|
||||
hash_fn="-sha256"
|
||||
zeros="0000000000000000000000000000000000000000000000000000000000000000"
|
||||
elif [[ "$cipher" == *SHA384 ]]; then
|
||||
hash_fn="-sha384"
|
||||
zeros="000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${TLS_SERVER_HELLO:8:4}" == 7F12 ]]; then
|
||||
derived_secret="$handshake_secret"
|
||||
elif [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
|
||||
derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "6465726976656420736563726574" "")"
|
||||
else
|
||||
derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "64657269766564" "")"
|
||||
fi
|
||||
master_secret="$(hmac "$hash_fn" "$derived_secret" "$zeros")"
|
||||
[[ $? -ne 0 ]] && return 7
|
||||
|
||||
tm_out "$master_secret"
|
||||
return 0
|
||||
}
|
||||
|
||||
# arg1: TLS cipher
|
||||
# arg2: master secret
|
||||
# arg3: transcipt
|
||||
# arg4: "client" or "server"
|
||||
derive-application-traffic-keys() {
|
||||
local cipher="$1" master_secret="$2" transcript="$3"
|
||||
local sender="$4"
|
||||
local hash_fn
|
||||
local -i key_len
|
||||
local application_traffic_secret_0 label key iv
|
||||
|
||||
if [[ "$cipher" == *SHA256 ]]; then
|
||||
hash_fn="-sha256"
|
||||
elif [[ "$cipher" == *SHA384 ]]; then
|
||||
hash_fn="-sha384"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
if [[ "$cipher" == *AES_128* ]]; then
|
||||
key_len=16
|
||||
elif ( [[ "$cipher" == *AES_256* ]] || [[ "$cipher" == *CHACHA20_POLY1305* ]] ); then
|
||||
key_len=32
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
|
||||
if [[ "$sender" == server ]]; then
|
||||
# "736572766572206170706c69636174696f6e207472616666696320736563726574" = "server application traffic secret"
|
||||
label="736572766572206170706c69636174696f6e207472616666696320736563726574"
|
||||
else
|
||||
# "636c69656e74206170706c69636174696f6e207472616666696320736563726574" = "client application traffic secret"
|
||||
label="636c69656e74206170706c69636174696f6e207472616666696320736563726574"
|
||||
fi
|
||||
elif [[ "$sender" == server ]]; then
|
||||
# "732061702074726166666963" = "s hs traffic"
|
||||
label="732061702074726166666963"
|
||||
else
|
||||
# "632061702074726166666963" = "c hs traffic"
|
||||
label="632061702074726166666963"
|
||||
fi
|
||||
application_traffic_secret_0="$(derive-secret "$hash_fn" "$master_secret" "$label" "$transcript")"
|
||||
[[ $? -ne 0 ]] && return 7
|
||||
|
||||
# "6b6579" = "key"
|
||||
key="$(derive-traffic-key "$hash_fn" "$application_traffic_secret_0" "6b6579" "$key_len")"
|
||||
[[ $? -ne 0 ]] && return 1
|
||||
# "6976" = "iv"
|
||||
iv="$(derive-traffic-key "$hash_fn" "$application_traffic_secret_0" "6976" "12")"
|
||||
[[ $? -ne 0 ]] && return 1
|
||||
tm_out "$key $iv"
|
||||
}
|
||||
|
||||
# See RFC 8439, Section 2.1
|
||||
chacha20_Qround() {
|
||||
local -i a="0x$1"
|
||||
|
@ -12095,11 +12203,6 @@ sym-decrypt() {
|
|||
local -i ciphertext_len tag_len
|
||||
local compute_tag=false
|
||||
|
||||
# In general there is no need to verify that the authentication tag is correct
|
||||
# when decrypting, and performing the check is time consuming when the
|
||||
# computations are performed in Bash.
|
||||
[[ $DEBUG -ge 1 ]] && compute_tag=true
|
||||
|
||||
case "$cipher" in
|
||||
*CCM_8*)
|
||||
tag_len=16 ;;
|
||||
|
@ -12114,6 +12217,13 @@ sym-decrypt() {
|
|||
[[ $ciphertext_len -lt $tag_len ]] && return 7
|
||||
ciphertext_len=$((ciphertext_len-tag_len))
|
||||
|
||||
# In general there is no need to verify that the authentication tag is correct
|
||||
# when decrypting, and performing the check is time consuming when the
|
||||
# computations are performed in Bash. If the ciphertext is very long (e.g.,
|
||||
# some application data), then trying to compute the authentication tag is
|
||||
# too time consuming even for debug mode.
|
||||
[[ $DEBUG -ge 1 ]] && [[ $ciphertext_len -le 1024 ]] && compute_tag=true
|
||||
|
||||
if [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then
|
||||
plaintext="$(chacha20_aead_decrypt "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag")"
|
||||
elif [[ "$cipher" =~ CCM ]]; then
|
||||
|
@ -13527,6 +13637,56 @@ parse_tls_serverhello() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# ASCII-HEX encoded session ticket
|
||||
parse_tls13_new_session_ticket() {
|
||||
local tls_version="$1"
|
||||
local new_session_ticket="$2"
|
||||
local -i len ticket_lifetime ticket_age_add min_len remainder
|
||||
local ticket_nonce ticket extensions
|
||||
local has_nonce=true
|
||||
|
||||
[[ "${new_session_ticket:0:2}" == 04 ]] || return 7
|
||||
# Prior to draft 21 the NewSessionTicket did not include a ticket_nonce.
|
||||
[[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -le 20 ]] && has_nonce=false
|
||||
|
||||
# Set min_len to the minimum length that a session ticket can be.
|
||||
min_len=28
|
||||
"$has_nonce" || min_len=$((min_len-2))
|
||||
|
||||
remainder=$((2*0x${new_session_ticket:2:6}))
|
||||
[[ $remainder -ge $min_len ]] || return 7
|
||||
[[ ${#new_session_ticket} -ge $((remainder + 8)) ]] || return 7
|
||||
|
||||
ticket_lifetime=0x${new_session_ticket:8:8}
|
||||
ticket_age_add=0x${new_session_ticket:16:8}
|
||||
new_session_ticket="${new_session_ticket:24}"
|
||||
remainder=$((remainder-16))
|
||||
|
||||
if "$has_nonce"; then
|
||||
len=$((2*0x${new_session_ticket:0:2}))
|
||||
new_session_ticket="${new_session_ticket:2}"
|
||||
[[ $remainder -ge $((len + 12)) ]] || return 7
|
||||
ticket_nonce="${new_session_ticket:0:len}"
|
||||
new_session_ticket="${new_session_ticket:len}"
|
||||
remainder=$((remainder-len-2))
|
||||
fi
|
||||
|
||||
len=$((2*0x${new_session_ticket:0:4}))
|
||||
new_session_ticket="${new_session_ticket:4}"
|
||||
[[ $remainder -ge $((len + 8)) ]] || return 7
|
||||
ticket="${new_session_ticket:0:len}"
|
||||
new_session_ticket="${new_session_ticket:len}"
|
||||
remainder=$((remainder-len-4))
|
||||
|
||||
len=$((2*0x${new_session_ticket:0:4}))
|
||||
new_session_ticket="${new_session_ticket:4}"
|
||||
[[ $remainder -eq $((len + 4)) ]] || return 7
|
||||
extensions="${new_session_ticket:0:len}"
|
||||
|
||||
echo " TLS session ticket lifetime hint: $ticket_lifetime (seconds)" > $TMPFILE
|
||||
tmpfile_handle ${FUNCNAME[0]}.txt $TMPFILE
|
||||
return 0
|
||||
}
|
||||
|
||||
#arg1 (optional): list of ciphers suites or empty
|
||||
#arg2 (optional): "true" if full server response should be parsed.
|
||||
|
@ -14338,10 +14498,11 @@ tls_sockets() {
|
|||
local clienthello1 original_clienthello hrr=""
|
||||
local process_full="$3" offer_compression=false skip=false
|
||||
local close_connection=true include_headers=true
|
||||
local -i i len tag_len hello_done=0 plaintext_len
|
||||
local cipher="" tls_version handshake_secret="" res plaintext
|
||||
local -i i len tag_len hello_done=0
|
||||
local cipher="" tls_version handshake_secret="" res
|
||||
local initial_msg_transcript msg_transcript finished_msg aad="" data=""
|
||||
local handshake_traffic_keys key iv finished_key
|
||||
local master_secret master_traffic_keys
|
||||
|
||||
[[ "$5" == true ]] && offer_compression=true
|
||||
[[ "$6" == false ]] && close_connection=false
|
||||
|
@ -14509,6 +14670,23 @@ tls_sockets() {
|
|||
debugme echo -e "\nsending finished..."
|
||||
socksend_clienthello "${data}"
|
||||
sleep $USLEEP_SND
|
||||
|
||||
# Compute application traffic keys and IVs.
|
||||
master_secret="$(derive-master-secret "$cipher" "$handshake_secret")"
|
||||
master_traffic_keys="$(derive-application-traffic-keys "$cipher" "$master_secret" "$msg_transcript" server)"
|
||||
APP_TRAF_KEY_INFO="$tls_version $cipher $master_traffic_keys 0 "
|
||||
master_traffic_keys="$(derive-application-traffic-keys "$cipher" "$master_secret" "$msg_transcript" client)"
|
||||
APP_TRAF_KEY_INFO+="$master_traffic_keys 0"
|
||||
|
||||
# Some servers send new session tickets as soon as the handshake is complete.
|
||||
[[ -s "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt" ]] && \
|
||||
rm "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt"
|
||||
receive_app_data
|
||||
if [[ $? -eq 0 ]] && [[ $DEBUG -ge 2 ]]; then
|
||||
[[ -s "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt" ]] && \
|
||||
echo -n "Ticket: " && cat "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt"
|
||||
[[ -s $TMPFILE ]] && echo -n "Unexpected response: " && cat "$TMPFILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# determine the return value for higher level, so that they can tell what the result is
|
||||
|
@ -14541,6 +14719,102 @@ tls_sockets() {
|
|||
return $ret
|
||||
}
|
||||
|
||||
# Send application data over a TLS 1.3 channel that has already been created.
|
||||
send_app_data() {
|
||||
local plaintext="$1"
|
||||
local tls_version cipher client_key client_iv server_key server_iv
|
||||
local aad res data
|
||||
local -i i client_seq server_seq tag_len len
|
||||
local include_headers=true
|
||||
|
||||
read -r tls_version cipher server_key server_iv server_seq client_key client_iv client_seq <<< "$APP_TRAF_KEY_INFO"
|
||||
[[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false
|
||||
[[ "$cipher" =~ CCM_8 ]] && tag_len=8 || tag_len=16
|
||||
|
||||
aad="170303$(printf "%04X" "$(( ${#plaintext}/2 + tag_len + 1 ))")"
|
||||
if "$include_headers"; then
|
||||
res="$(sym-encrypt "$cipher" "$client_key" "$(get-nonce "$client_iv" $client_seq)" "${plaintext}17" "$aad")"
|
||||
else
|
||||
res="$(sym-encrypt "$cipher" "$client_key" "$(get-nonce "$client_iv" $client_seq)" "${plaintext}17" "")"
|
||||
fi
|
||||
[[ $? -eq 0 ]] || return 1
|
||||
client_seq+=1
|
||||
APP_TRAF_KEY_INFO="$tls_version $cipher $server_key $server_iv $server_seq $client_key $client_iv $client_seq"
|
||||
|
||||
res="$aad$res"
|
||||
len=${#res}
|
||||
data=""
|
||||
for (( i=0; i < len; i=i+2 )); do
|
||||
data+=",x${res:i:2}"
|
||||
done
|
||||
socksend "$data" $USLEEP_SND
|
||||
}
|
||||
|
||||
# Receive application data from a TLS 1.3 channel that has already been created.
|
||||
# arg1: true if only the first block of application data should be decrypted.
|
||||
# This can save a lot of time if the server sends a lot a data (e.g., a
|
||||
# big home page), but only the first part of the data is needed. However,
|
||||
# no further data may be received over this connection as the message
|
||||
# sequence number will not be correct.
|
||||
receive_app_data() {
|
||||
local plaintext=""
|
||||
local tls_version cipher client_key client_iv server_key server_iv
|
||||
local aad ciphertext="" res="" data
|
||||
local -i client_seq server_seq len msg_len
|
||||
local include_headers=true
|
||||
local first_block_only=false
|
||||
|
||||
[[ "$1" == true ]] && first_block_only=true
|
||||
|
||||
read -r tls_version cipher server_key server_iv server_seq client_key client_iv client_seq <<< "$APP_TRAF_KEY_INFO"
|
||||
[[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false
|
||||
|
||||
sleep $USLEEP_REC
|
||||
while true; do
|
||||
len=${#ciphertext}
|
||||
if [[ $len -ge 10 ]]; then
|
||||
[[ "${ciphertext:0:5}" == 17030 ]] || break
|
||||
msg_len=$((2*0x${ciphertext:6:4}))
|
||||
fi
|
||||
if [[ $len -lt 10 ]] || [[ $len -lt $((msg_len+10)) ]]; then
|
||||
if "$FAST_SOCKET"; then
|
||||
res="$(sockread_fast 32768)"
|
||||
else
|
||||
sockread_serverhello 32768
|
||||
res="$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")"
|
||||
fi
|
||||
res="${res%%[!0-9A-F]*}"
|
||||
[[ -z "$res" ]] && break
|
||||
ciphertext+="$res"
|
||||
continue
|
||||
fi
|
||||
"$include_headers" && aad="${ciphertext:0:10}" || aad=""
|
||||
data="$(sym-decrypt "$cipher" "$server_key" "$(get-nonce "$server_iv" "$server_seq")" "${ciphertext:10:msg_len}" "$aad")"
|
||||
[[ $? -eq 0 ]] || return 1
|
||||
|
||||
len=${#data}-2
|
||||
while [[ "${data:len:2}" == 00 ]]; do
|
||||
len=$((len-2))
|
||||
done
|
||||
content_type="${data:len:2}"
|
||||
|
||||
if [[ "$content_type" == 16 ]] && [[ "${data:0:2}" == 04 ]]; then
|
||||
# This is a new_session_ticket
|
||||
parse_tls13_new_session_ticket "$tls_version" "${data:0:len}"
|
||||
elif [[ "$content_type" == 17 ]]; then
|
||||
# This really is application data.
|
||||
plaintext+="${data:0:len}"
|
||||
"$first_block_only" && break
|
||||
fi
|
||||
ciphertext=${ciphertext:$((msg_len+10))}
|
||||
server_seq+=1
|
||||
[[ -z "$ciphertext" ]] && break
|
||||
done
|
||||
APP_TRAF_KEY_INFO="$tls_version $cipher $server_key $server_iv $server_seq $client_key $client_iv $client_seq"
|
||||
asciihex_to_binary "$plaintext" > "$TMPFILE"
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
####### Vulnerabilities follow #######
|
||||
# General overview which browser "supports" which vulnerability:
|
||||
|
|
Loading…
Reference in New Issue