TLS 1.3 Finished messages

This PR adds processing of the Finished messages in TLS 1.3 handshakes. It also addresses some shellcheck issues.

If in debug mode, the HMAC of the transcript hash of the handshake context ($msg_transcript) is computed and compared against the Finished message sent by the server.

If the full server response is parsed and the connection with the server is not to be closed when tls_sockets() completes, then the TLS 1.3 handshake is completed by creating the client Finished message and sending it to the server.
This commit is contained in:
David Cooper 2020-01-28 11:15:03 -05:00 committed by GitHub
parent 3da67437f3
commit 7516c01315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 101 additions and 20 deletions

View File

@ -10859,6 +10859,31 @@ hmac() {
return $ret
}
# arg1: hash function
# arg2: key
# arg3: transcript
# Compute the HMAC of the hash of the transcript
hmac-transcript() {
local hash_fn="$1"
local key="$2" transcript="$3" output
local -i ret
if [[ ! "$OSSL_NAME" =~ LibreSSL ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then
output="$(asciihex_to_binary "$transcript" | \
$OPENSSL dgst "$hash_fn" -binary 2>/dev/null | \
$OPENSSL mac -macopt digest:"${hash_fn/-/}" -macopt hexkey:"$key" HMAC 2>/dev/null)"
ret=$?
tm_out "$(toupper "$(strip_lf "$output")")"
else
output="$(asciihex_to_binary "$transcript" | \
$OPENSSL dgst "$hash_fn" -binary 2>/dev/null | \
$OPENSSL dgst "$hash_fn" -mac HMAC -macopt hexkey:"$key" 2>/dev/null)"
ret=$?
tm_out "$(toupper "$(awk '/=/ { print $2 }' <<< "$output")")"
fi
return $ret
}
# arg1: hash function
# arg2: pseudorandom key (PRK)
# arg2: info
@ -10890,7 +10915,7 @@ hkdf-expand() {
output+="$ti"
tim1="$ti"
done
out_len=2*$out_len
out_len=$((2*out_len))
tm_out "${output:0:out_len}"
return 0
}
@ -10910,8 +10935,8 @@ hkdf-expand-label() {
local hkdflabel_length
local -i len
hkdflabel_length="$(printf "%04X\n" $length)"
if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
hkdflabel_length="$(printf "%04X\n" "$length")"
if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
# "544c5320312e332c20" = "TLS 1.3, "
hkdflabel_label="544c5320312e332c20$label"
else
@ -10919,9 +10944,9 @@ hkdf-expand-label() {
hkdflabel_label="746c73313320$label"
fi
len=${#hkdflabel_label}/2
hkdflabel_label="$(printf "%02X\n" $len)$hkdflabel_label"
hkdflabel_label="$(printf "%02X\n" "$len")$hkdflabel_label"
len=${#context}/2
hkdflabel_context="$(printf "%02X\n" $len)$context"
hkdflabel_context="$(printf "%02X\n" "$len")$context"
hkdflabel="$hkdflabel_length$hkdflabel_label$hkdflabel_context"
hkdf-expand "$hash_fn" "$secret" "$hkdflabel" "$length"
@ -10977,7 +11002,7 @@ create-initial-transcript() {
local clienthello1="$2" hrr="$3" clienthello2="$4" serverhello="$5"
local hash_clienthello1 msg_transcript
if [[ -n "$hrr" ]] && [[ "${serverhello:8:4}" == "7F12" ]]; then
if [[ -n "$hrr" ]] && [[ "${serverhello:8:4}" == 7F12 ]]; then
msg_transcript="$clienthello1$hrr$clienthello2$serverhello"
elif [[ -n "$hrr" ]]; then
if [[ "$cipher" == *SHA256 ]]; then
@ -11036,7 +11061,7 @@ derive-handshake-secret() {
# early_secret="$(hmac "$hash_fn" "000...000" "000...000")"
case "$hash_fn" in
"-sha256") early_secret="33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a"
if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
# "6465726976656420736563726574" = "derived secret"
# derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")"
derived_secret="c1c0c36bf8fb1d1afa949fbd360e71af69a6244a4c2eaef5bbbb6442a7277d2c"
@ -11047,7 +11072,7 @@ derive-handshake-secret() {
fi
;;
"-sha384") early_secret="7ee8206f5570023e6dc7519eb1073bc4e791ad37b5c382aa10ba18e2357e716971f9362f2c2fe2a76bfd78dfec4ea9b5"
if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
# "6465726976656420736563726574" = "derived secret"
# derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")"
derived_secret="54c80fa05ee9e0532ce3db8ddeca37a0365683bcd3b27bdc88d2b9fdc115ca4ebc8edc1f0b72a6a0861e803fc34761ef"
@ -11060,7 +11085,7 @@ derive-handshake-secret() {
esac
shared_secret="$($OPENSSL pkeyutl -derive -inkey "$priv_file" -peerkey "$pub_file" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
rm $pub_file $priv_file
rm "$pub_file" "$priv_file"
# For draft 18 use $early_secret rather than $derived_secret.
if [[ "${TLS_SERVER_HELLO:8:4}" == "7F12" ]]; then
@ -11083,7 +11108,7 @@ derive-handshake-traffic-keys() {
local sender="$4"
local hash_fn
local -i hash_len key_len
local handshake_traffic_secret label key iv
local handshake_traffic_secret label key iv finished="0000"
if [[ "$cipher" == *SHA256 ]]; then
hash_fn="-sha256"
@ -11102,7 +11127,7 @@ derive-handshake-traffic-keys() {
return 1
fi
if [[ "${TLS_SERVER_HELLO:8:2}" == "7F" ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
if [[ "$sender" == server ]]; then
# "7365727665722068616e647368616b65207472616666696320736563726574" = "server handshake traffic secret"
label="7365727665722068616e647368616b65207472616666696320736563726574"
@ -11126,7 +11151,12 @@ derive-handshake-traffic-keys() {
# "6976" = "iv"
iv="$(derive-traffic-key "$hash_fn" "$handshake_traffic_secret" "6976" "12")"
[[ $? -ne 0 ]] && return 1
tm_out "$key $iv"
if [[ $DEBUG -ge 1 ]] || [[ "$sender" == client ]]; then
# "66696e6973686564" = "finished"
finished="$(derive-traffic-key "$hash_fn" "$handshake_traffic_secret" "66696e6973686564" "$hash_len")"
[[ $? -ne 0 ]] && return 1
fi
tm_out "$key $iv $finished"
}
# See RFC 8439, Section 2.1
@ -11364,7 +11394,7 @@ u64to8() {
local -i v="$1"
local p
p="$(printf "%016X" $v)"
p="$(printf "%016X" "$v")"
tm_out "${p:14:2}${p:12:2}${p:10:2}${p:8:2}${p:6:2}${p:4:2}${p:2:2}${p:0:2}"
return 0
}
@ -12163,9 +12193,9 @@ check_tls_serverhellodone() {
local -i i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len
local -i msg_len remaining tls_serverhello_ascii_len sid_len
local -i j offset tls_extensions_len extension_len
local tls_content_type tls_protocol tls_handshake_type tls_msg_type extension_type
local tls_content_type tls_protocol tls_msg_type extension_type
local tls_err_level
local hash_fn handshake_traffic_keys key="" iv=""
local hash_fn handshake_traffic_keys key="" iv="" finished_key=""
local -i seq_num=0 plaintext_len
local plaintext decrypted_response="" additional_data
local include_headers=true
@ -12174,7 +12204,7 @@ check_tls_serverhellodone() {
if [[ -n "$handshake_secret" ]]; then
handshake_traffic_keys="$(derive-handshake-traffic-keys "$cipher" "$handshake_secret" "$msg_transcript" "server")"
read -r key iv <<< "$handshake_traffic_keys"
read -r key iv finished_key <<< "$handshake_traffic_keys"
fi
if [[ -z "$tls_hello_ascii" ]]; then
@ -12302,6 +12332,20 @@ check_tls_serverhellodone() {
# The ServerHello has already been added to $msg_transcript,
# but all other handshake messages need to be added.
if [[ -n "$key" ]] && [[ "$tls_msg_type" != 02 ]]; then
if [[ $DEBUG -ge 1 ]] && [[ "$tls_msg_type" == 14 ]]; then
# Check the Finished message
if [[ "$cipher" == *SHA256 ]]; then
hash_fn="-sha256"
[[ $msg_len -eq 64 ]] || return 2
elif [[ "$cipher" == *SHA384 ]]; then
hash_fn="-sha384"
[[ $msg_len -eq 96 ]] || return 2
else
return 2
fi
[[ "${tls_handshake_ascii:i:msg_len}" != $(hmac-transcript "$hash_fn" "$finished_key" "$msg_transcript") ]] && \
return 2
fi
msg_transcript+="$tls_msg_type${tls_handshake_ascii:$((i-6)):6}${tls_handshake_ascii:i:msg_len}"
fi
# For SSLv3 - TLS1.2 look for a ServerHelloDone message.
@ -14294,10 +14338,11 @@ tls_sockets() {
local tls_hello_ascii next_packet
local clienthello1 original_clienthello hrr=""
local process_full="$3" offer_compression=false skip=false
local close_connection=true
local -i hello_done=0
local cipher="" handshake_secret="" res
local initial_msg_transcript msg_transcript
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 initial_msg_transcript msg_transcript finished_msg aad="" data=""
local handshake_traffic_keys key iv finished_key
[[ "$5" == true ]] && offer_compression=true
[[ "$6" == false ]] && close_connection=false
@ -14431,6 +14476,42 @@ tls_sockets() {
tm_out " ($lines lines returned) "
fi
if ! "$close_connection" && [[ $save == 0 ]] && \
[[ -n "$handshake_secret" ]] && [[ "$process_full" == all+ ]]; then
tls_version="$DETECTED_TLS_VERSION"
if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then
tls_version="${TLS_SERVER_HELLO:8:4}"
elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[0-9A-F] ]]; then
tls_version="${BASH_REMATCH:8:4}"
fi
[[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false
handshake_traffic_keys="$(derive-handshake-traffic-keys "$cipher" "$handshake_secret" "$initial_msg_transcript" "client")"
read -r key iv finished_key <<< "$handshake_traffic_keys"
if [[ "$cipher" == *SHA256 ]]; then
finished_msg="14000020$(hmac-transcript "-sha256" "$finished_key" "$msg_transcript")"
else
finished_msg="14000030$(hmac-transcript "-sha384" "$finished_key" "$msg_transcript")"
fi
[[ "$cipher" =~ CCM_8 ]] && tag_len=8 || tag_len=16
aad="170303$(printf "%04X" "$(( ${#finished_msg}/2 + tag_len + 1 ))")"
if "$include_headers"; then
# The header information was added to additional data in TLSv1.3 draft 25.
finished_msg="$(sym-encrypt "$cipher" "$key" "$(get-nonce "$iv" 0)" "${finished_msg}16" "$aad")"
else
finished_msg="$(sym-encrypt "$cipher" "$key" "$(get-nonce "$iv" 0)" "${finished_msg}16" "")"
fi
finished_msg="$aad$finished_msg"
len=${#finished_msg}
for (( i=0; i < len; i=i+2 )); do
data+=", ${finished_msg:i:2}"
done
debugme echo -e "\nsending finished..."
socksend_clienthello "${data}"
sleep $USLEEP_SND
fi
# determine the return value for higher level, so that they can tell what the result is
if [[ $save -eq 1 ]] || [[ $lines -eq 1 ]]; then
ret=1 # NOT available