mirror of
https://github.com/drwetter/testssl.sh.git
synced 2025-01-03 23:39:45 +01:00
Decrypt server's TLSv1.3 response
This PR adds code to decrypt the encrypted portion of the server's response for TLSv1.3 and to then process any certificates and encrypted extensions. This code supports all 5 TLSv1.3 cipher suites, and so any response can be decrypted as long as the session key can be derived (which requires OpenSSL to support the ephemeral key that was used - see #938). For the symmetric decryption, the sym-decrypt() function uses OpenSSL when possible and internal Bash functions when needed. For AES-GCM and AES-CCM ciphers sym-decrypt() normally uses internal Bash functions, which rely on using "$OPENSSL enc" in AES-ECB mode to generate the key stream and then Bash functionality to XOR the key stream with the ciphertext. With some version of OpenSSL the AES-GCM ciphers are decrypted using "$OPENSSL enc" in AES-GCM mode directly. On my system, however, both methods seem to work about equally fast. For ChaCha20 ciphers, "$OPENSSL enc -chacha20" is used, if supported (OpenSSL 1.1.x only). and Bash internal functions (without any OpenSSL support) are used otherwise. In this case, if the Bash internal functions need to be used, decryption is very, very, very slow. Fortunately, in a typical run of testssl.sh there won't be many cases in which the connection will be TLSv1.3 with ChaCha20 and the entire response needs to be processed (requiring decryption). In most cases, even if the connection is TLSv1.3 with ChaCha20, will at most need the ephemeral key, which is available in plain text.
This commit is contained in:
parent
33f8a04066
commit
e8be1f441b
497
testssl.sh
497
testssl.sh
@ -286,6 +286,9 @@ HAS_PROXY=false
|
||||
HAS_XMPP=false
|
||||
HAS_POSTGRES=false
|
||||
HAS_MYSQL=false
|
||||
HAS_CHACHA20=false
|
||||
HAS_AES128_GCM=false
|
||||
HAS_AES256_GCM=false
|
||||
PORT=443 # unless otherwise auto-determined, see below
|
||||
NODE=""
|
||||
NODEIP=""
|
||||
@ -8742,27 +8745,375 @@ derive-handshake-traffic-keys() {
|
||||
return 0
|
||||
}
|
||||
|
||||
generate-ccm-gcm-keystream() {
|
||||
local icb="$1" icb_msb icb_lsb1
|
||||
local -i i icb_lsb n="$2"
|
||||
|
||||
icb_msb="${icb:0:24}"
|
||||
icb_lsb=0x${icb:24:8}
|
||||
|
||||
for (( i=0; i < n; i=i+1 )); do
|
||||
icb_lsb1="$(printf "%08X" $icb_lsb)"
|
||||
printf "\x${icb_msb:0:2}\x${icb_msb:2:2}\x${icb_msb:4:2}\x${icb_msb:6:2}\x${icb_msb:8:2}\x${icb_msb:10:2}\x${icb_msb:12:2}\x${icb_msb:14:2}\x${icb_msb:16:2}\x${icb_msb:18:2}\x${icb_msb:20:2}\x${icb_msb:22:2}\x${icb_lsb1:0:2}\x${icb_lsb1:2:2}\x${icb_lsb1:4:2}\x${icb_lsb1:6:2}"
|
||||
icb_lsb+=1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb)
|
||||
# arg2: key
|
||||
# arg3: initial counter value (must be 128 bits)
|
||||
# arg4: ciphertext
|
||||
# See Sections 6.5 and 7.2 of SP 800-38D and Section 6.2 and Appendix A of SP 800-38C
|
||||
ccm-gcm-decrypt() {
|
||||
local cipher="$1"
|
||||
local key="$2"
|
||||
local icb="$3"
|
||||
local ciphertext="$4"
|
||||
local -i i i1 i2 i3 i4
|
||||
local -i ciphertext_len n mod_check
|
||||
local y plaintext=""
|
||||
|
||||
[[ ${#icb} -ne 32 ]] && return 7
|
||||
|
||||
ciphertext_len=${#ciphertext}
|
||||
n=$ciphertext_len/32
|
||||
mod_check=$ciphertext_len%32
|
||||
[[ $mod_check -ne 0 ]] && n+=1
|
||||
y="$(generate-ccm-gcm-keystream "$icb" "$n" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
|
||||
|
||||
# XOR the ciphertext with the keystream ($y). For efficiency, work in blocks of 16 bytes at a time (but with each XOR operation working on
|
||||
# 32 bits.
|
||||
[[ $mod_check -ne 0 ]] && n=$n-1
|
||||
for (( i=0; i < n; i++ )); do
|
||||
i1=32*$i; i2=$i1+8; i3=$i1+16; i4=$i1+24
|
||||
plaintext+="$(printf "%08X%08X%08X%08X" "$((0x${ciphertext:i1:8} ^ 0x${y:i1:8}))" "$((0x${ciphertext:i2:8} ^ 0x${y:i2:8}))" "$((0x${ciphertext:i3:8} ^ 0x${y:i3:8}))" "$((0x${ciphertext:i4:8} ^ 0x${y:i4:8}))")"
|
||||
done
|
||||
# If the length of the ciphertext is not an even multiple of 16 bytes, then handle the final incomplete block.
|
||||
if [[ $mod_check -ne 0 ]]; then
|
||||
i1=32*$n
|
||||
for (( i=0; i < mod_check; i=i+2 )); do
|
||||
plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${y:i1:2}))")"
|
||||
i1+=2
|
||||
done
|
||||
fi
|
||||
tm_out "$plaintext"
|
||||
return 0
|
||||
}
|
||||
|
||||
# See RFC 7539, Section 2.1
|
||||
chacha20_Qround() {
|
||||
local -i a="0x$1"
|
||||
local -i b="0x$2"
|
||||
local -i c="0x$3"
|
||||
local -i d="0x$4"
|
||||
local -i x y
|
||||
|
||||
a=$(((a+b) & 0xffffffff))
|
||||
d=$((d^a))
|
||||
# rotate d left 16 bits
|
||||
x=$((d & 0xffff0000))
|
||||
x=$((x >> 16))
|
||||
y=$((d & 0x0000ffff))
|
||||
y=$((y << 16))
|
||||
d=$((x | y))
|
||||
|
||||
c=$(((c+d) & 0xffffffff))
|
||||
b=$((b^c))
|
||||
# rotate b left 12 bits
|
||||
x=$((b & 0xfff00000))
|
||||
x=$((x >> 20))
|
||||
y=$((b & 0x000fffff))
|
||||
y=$((y << 12))
|
||||
b=$((x | y))
|
||||
|
||||
a=$(((a+b) & 0xffffffff))
|
||||
d=$((d^a))
|
||||
# rotate d left 8 bits
|
||||
x=$((d & 0xff000000))
|
||||
x=$((x >> 24))
|
||||
y=$((d & 0x00ffffff))
|
||||
y=$((y << 8))
|
||||
d=$((x | y))
|
||||
|
||||
c=$(((c+d) & 0xffffffff))
|
||||
b=$((b^c))
|
||||
# rotate b left 7 bits
|
||||
x=$((b & 0xfe000000))
|
||||
x=$((x >> 25))
|
||||
y=$((b & 0x01ffffff))
|
||||
y=$((y << 7))
|
||||
b=$((x | y))
|
||||
|
||||
tm_out "$(printf "%x" $a) $(printf "%x" $b) $(printf "%x" $c) $(printf "%x" $d)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# See RFC 7539, Section 2.3.1
|
||||
chacha20_inner_block() {
|
||||
local s0="$1" s1="$2" s2="$3" s3="$4"
|
||||
local s4="$5" s5="$6" s6="$7" s7="$8"
|
||||
local s8="$9" s9="${10}" s10="${11}" s11="${12}"
|
||||
local s12="${13}" s13="${14}" s14="${15}" s15="${16}"
|
||||
local res
|
||||
|
||||
res="$(chacha20_Qround "$s0" "$s4" "$s8" "$s12")"
|
||||
read s0 s4 s8 s12 <<< "$res"
|
||||
res="$(chacha20_Qround "$s1" "$s5" "$s9" "$s13")"
|
||||
read s1 s5 s9 s13 <<< "$res"
|
||||
res="$(chacha20_Qround "$s2" "$s6" "$s10" "$s14")"
|
||||
read s2 s6 s10 s14 <<< "$res"
|
||||
res="$(chacha20_Qround "$s3" "$s7" "$s11" "$s15")"
|
||||
read s3 s7 s11 s15 <<< "$res"
|
||||
res="$(chacha20_Qround "$s0" "$s5" "$s10" "$s15")"
|
||||
read s0 s5 s10 s15 <<< "$res"
|
||||
res="$(chacha20_Qround "$s1" "$s6" "$s11" "$s12")"
|
||||
read s1 s6 s11 s12 <<< "$res"
|
||||
res="$(chacha20_Qround "$s2" "$s7" "$s8" "$s13")"
|
||||
read s2 s7 s8 s13 <<< "$res"
|
||||
res="$(chacha20_Qround "$s3" "$s4" "$s9" "$s14")"
|
||||
read s3 s4 s9 s14 <<< "$res"
|
||||
|
||||
tm_out "$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15"
|
||||
return 0
|
||||
}
|
||||
|
||||
# See RFC 7539, Sections 2.3 and 2.3.1
|
||||
chacha20_block() {
|
||||
local key="$1"
|
||||
local counter="$2"
|
||||
local nonce="$3"
|
||||
local s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15
|
||||
local ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15
|
||||
local working_state
|
||||
local -i i
|
||||
|
||||
# create the state variable
|
||||
s0="61707865"; s1="3320646e"; s2="79622d32"; s3="6b206574"
|
||||
s4="${key:6:2}${key:4:2}${key:2:2}${key:0:2}"
|
||||
s5="${key:14:2}${key:12:2}${key:10:2}${key:8:2}"
|
||||
s6="${key:22:2}${key:20:2}${key:18:2}${key:16:2}"
|
||||
s7="${key:30:2}${key:28:2}${key:26:2}${key:24:2}"
|
||||
s8="${key:38:2}${key:36:2}${key:34:2}${key:32:2}"
|
||||
s9="${key:46:2}${key:44:2}${key:42:2}${key:40:2}"
|
||||
s10="${key:54:2}${key:52:2}${key:50:2}${key:48:2}"
|
||||
s11="${key:62:2}${key:60:2}${key:58:2}${key:56:2}"
|
||||
s12="$counter"
|
||||
s13="${nonce:6:2}${nonce:4:2}${nonce:2:2}${nonce:0:2}"
|
||||
s14="${nonce:14:2}${nonce:12:2}${nonce:10:2}${nonce:8:2}"
|
||||
s15="${nonce:22:2}${nonce:20:2}${nonce:18:2}${nonce:16:2}"
|
||||
|
||||
# Initialize working_state to state
|
||||
working_state="$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15"
|
||||
|
||||
# compute the 20 rounds (10 calls to inner block function, each of which
|
||||
# performs 8 quarter rounds).
|
||||
for (( i=0 ; i < 10; i++ )); do
|
||||
working_state="$(chacha20_inner_block $working_state)"
|
||||
done
|
||||
read ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15 <<< "$working_state"
|
||||
|
||||
# Add working state to state
|
||||
s0="$(printf "%08X" $(((0x$s0+0x$ws0) & 0xffffffff)))"
|
||||
s1="$(printf "%08X" $(((0x$s1+0x$ws1) & 0xffffffff)))"
|
||||
s2="$(printf "%08X" $(((0x$s2+0x$ws2) & 0xffffffff)))"
|
||||
s3="$(printf "%08X" $(((0x$s3+0x$ws3) & 0xffffffff)))"
|
||||
s4="$(printf "%08X" $(((0x$s4+0x$ws4) & 0xffffffff)))"
|
||||
s5="$(printf "%08X" $(((0x$s5+0x$ws5) & 0xffffffff)))"
|
||||
s6="$(printf "%08X" $(((0x$s6+0x$ws6) & 0xffffffff)))"
|
||||
s7="$(printf "%08X" $(((0x$s7+0x$ws7) & 0xffffffff)))"
|
||||
s8="$(printf "%08X" $(((0x$s8+0x$ws8) & 0xffffffff)))"
|
||||
s9="$(printf "%08X" $(((0x$s9+0x$ws9) & 0xffffffff)))"
|
||||
s10="$(printf "%08X" $(((0x$s10+0x$ws10) & 0xffffffff)))"
|
||||
s11="$(printf "%08X" $(((0x$s11+0x$ws11) & 0xffffffff)))"
|
||||
s12="$(printf "%08X" $(((0x$s12+0x$ws12) & 0xffffffff)))"
|
||||
s13="$(printf "%08X" $(((0x$s13+0x$ws13) & 0xffffffff)))"
|
||||
s14="$(printf "%08X" $(((0x$s14+0x$ws14) & 0xffffffff)))"
|
||||
s15="$(printf "%08X" $(((0x$s15+0x$ws15) & 0xffffffff)))"
|
||||
|
||||
# serialize the state
|
||||
s0="${s0:6:2}${s0:4:2}${s0:2:2}${s0:0:2}"
|
||||
s1="${s1:6:2}${s1:4:2}${s1:2:2}${s1:0:2}"
|
||||
s2="${s2:6:2}${s2:4:2}${s2:2:2}${s2:0:2}"
|
||||
s3="${s3:6:2}${s3:4:2}${s3:2:2}${s3:0:2}"
|
||||
s4="${s4:6:2}${s4:4:2}${s4:2:2}${s4:0:2}"
|
||||
s5="${s5:6:2}${s5:4:2}${s5:2:2}${s5:0:2}"
|
||||
s6="${s6:6:2}${s6:4:2}${s6:2:2}${s6:0:2}"
|
||||
s7="${s7:6:2}${s7:4:2}${s7:2:2}${s7:0:2}"
|
||||
s8="${s8:6:2}${s8:4:2}${s8:2:2}${s8:0:2}"
|
||||
s9="${s9:6:2}${s9:4:2}${s9:2:2}${s9:0:2}"
|
||||
s10="${s10:6:2}${s10:4:2}${s10:2:2}${s10:0:2}"
|
||||
s11="${s11:6:2}${s11:4:2}${s11:2:2}${s11:0:2}"
|
||||
s12="${s12:6:2}${s12:4:2}${s12:2:2}${s12:0:2}"
|
||||
s13="${s13:6:2}${s13:4:2}${s13:2:2}${s13:0:2}"
|
||||
s14="${s14:6:2}${s14:4:2}${s14:2:2}${s14:0:2}"
|
||||
s15="${s15:6:2}${s15:4:2}${s15:2:2}${s15:0:2}"
|
||||
|
||||
tm_out "$s0$s1$s2$s3$s4$s5$s6$s7$s8$s9$s10$s11$s12$s13$s14$s15"
|
||||
return 0
|
||||
}
|
||||
|
||||
# See RFC 7539, Section 2.4
|
||||
chacha20() {
|
||||
local key="$1"
|
||||
local -i counter=1
|
||||
local nonce="$2"
|
||||
local ciphertext="$3"
|
||||
local -i i ciphertext_len num_blocks mod_check
|
||||
local -i i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15 i16
|
||||
local keystream plaintext=""
|
||||
|
||||
ciphertext_len=${#ciphertext}
|
||||
num_blocks=$ciphertext_len/128
|
||||
|
||||
for (( i=0; i < num_blocks; i++)); do
|
||||
i1=128*$i; i2=$i1+8; i3=$i1+16; i4=$i1+24; i5=$i1+32; i6=$i1+40; i7=$i1+48; i8=$i1+56
|
||||
i9=$i1+64; i10=$i1+72; i11=$i1+80; i12=$i1+88; i13=$i1+96; i14=$i1+104; i15=$i1+112; i16=$i1+120
|
||||
keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")"
|
||||
plaintext+="$(printf "%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X" \
|
||||
"$((0x${ciphertext:i1:8} ^ 0x${keystream:0:8}))" \
|
||||
"$((0x${ciphertext:i2:8} ^ 0x${keystream:8:8}))" \
|
||||
"$((0x${ciphertext:i3:8} ^ 0x${keystream:16:8}))" \
|
||||
"$((0x${ciphertext:i4:8} ^ 0x${keystream:24:8}))" \
|
||||
"$((0x${ciphertext:i5:8} ^ 0x${keystream:32:8}))" \
|
||||
"$((0x${ciphertext:i6:8} ^ 0x${keystream:40:8}))" \
|
||||
"$((0x${ciphertext:i7:8} ^ 0x${keystream:48:8}))" \
|
||||
"$((0x${ciphertext:i8:8} ^ 0x${keystream:56:8}))" \
|
||||
"$((0x${ciphertext:i9:8} ^ 0x${keystream:64:8}))" \
|
||||
"$((0x${ciphertext:i10:8} ^ 0x${keystream:72:8}))" \
|
||||
"$((0x${ciphertext:i11:8} ^ 0x${keystream:80:8}))" \
|
||||
"$((0x${ciphertext:i12:8} ^ 0x${keystream:88:8}))" \
|
||||
"$((0x${ciphertext:i13:8} ^ 0x${keystream:96:8}))" \
|
||||
"$((0x${ciphertext:i14:8} ^ 0x${keystream:104:8}))" \
|
||||
"$((0x${ciphertext:i15:8} ^ 0x${keystream:112:8}))" \
|
||||
"$((0x${ciphertext:i16:8} ^ 0x${keystream:120:8}))")"
|
||||
counter+=1
|
||||
done
|
||||
|
||||
mod_check=$ciphertext_len%128
|
||||
if [[ $mod_check -ne 0 ]]; then
|
||||
keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")"
|
||||
i1=128*$num_blocks
|
||||
for (( i=0; i < mod_check; i=i+2 )); do
|
||||
plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${keystream:i:2}))")"
|
||||
i1+=2
|
||||
done
|
||||
fi
|
||||
tm_out "$plaintext"
|
||||
return 0
|
||||
}
|
||||
|
||||
# arg1: TLS cipher
|
||||
# arg2: key
|
||||
# arg3: nonce (must be 96 bits in length)
|
||||
# arg4: ciphertext
|
||||
sym-decrypt() {
|
||||
local cipher="$1"
|
||||
local key="$2" nonce="$3"
|
||||
local ciphertext="$4"
|
||||
local ossl_cipher
|
||||
local plaintext
|
||||
local -i ciphertext_len tag_len
|
||||
|
||||
case "$cipher" in
|
||||
*CCM_8*)
|
||||
tag_len=16 ;;
|
||||
*CCM*|*GCM*|*CHACHA20_POLY1305*)
|
||||
tag_len=32 ;;
|
||||
*)
|
||||
return 7 ;;
|
||||
esac
|
||||
|
||||
# The final $tag_len characters of the ciphertext are the authentication tag
|
||||
ciphertext_len=${#ciphertext}
|
||||
[[ $ciphertext_len -lt $tag_len ]] && return 7
|
||||
ciphertext_len=$ciphertext_len-$tag_len
|
||||
|
||||
if [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then
|
||||
if "$HAS_CHACHA20"; then
|
||||
plaintext="$(asciihex_to_binary_file "${ciphertext:0:ciphertext_len}" "/dev/stdout" | \
|
||||
$OPENSSL enc -chacha20 -K "$key" -iv "01000000$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
|
||||
plaintext="$(strip_spaces "$plaintext")"
|
||||
else
|
||||
plaintext="$(chacha20 "$key" "$nonce" "${ciphertext:0:ciphertext_len}")"
|
||||
fi
|
||||
elif [[ "$cipher" == "TLS_AES_128_GCM_SHA256" ]] && "$HAS_AES128_GCM"; then
|
||||
plaintext="$(asciihex_to_binary_file "${ciphertext:0:ciphertext_len}" "/dev/stdout" | \
|
||||
$OPENSSL enc -aes-128-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
|
||||
plaintext="$(strip_spaces "$plaintext")"
|
||||
elif [[ "$cipher" == "TLS_AES_256_GCM_SHA384" ]] && "$HAS_AES256_GCM"; then
|
||||
plaintext="$(asciihex_to_binary_file "${ciphertext:0:ciphertext_len}" "/dev/stdout" | \
|
||||
$OPENSSL enc -aes-256-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
|
||||
plaintext="$(strip_spaces "$plaintext")"
|
||||
else
|
||||
if [[ "$cipher" =~ AES_128 ]]; then
|
||||
ossl_cipher="-aes-128-ecb"
|
||||
elif [[ "$cipher" =~ AES_256 ]]; then
|
||||
ossl_cipher="-aes-256-ecb"
|
||||
else
|
||||
return 7
|
||||
fi
|
||||
if [[ "$cipher" =~ CCM ]]; then
|
||||
plaintext="$(ccm-gcm-decrypt "$ossl_cipher" "$key" "02${nonce}000001" "${ciphertext:0:ciphertext_len}")"
|
||||
else # GCM
|
||||
plaintext="$(ccm-gcm-decrypt "$ossl_cipher" "$key" "${nonce}00000002" "${ciphertext:0:ciphertext_len}")"
|
||||
fi
|
||||
fi
|
||||
[[ $? -ne 0 ]] && return 7
|
||||
|
||||
tm_out "$plaintext"
|
||||
return 0
|
||||
}
|
||||
|
||||
# arg1: iv
|
||||
# arg2: sequence number
|
||||
get-nonce() {
|
||||
local iv="$1"
|
||||
local -i seq_num="$2"
|
||||
local -i len lsb
|
||||
local msb nonce
|
||||
|
||||
len=${#iv}
|
||||
[[ $len -lt 8 ]] && return 7
|
||||
i=$len-8
|
||||
msb="${iv:0:i}"
|
||||
lsb="0x${iv:i:8}"
|
||||
nonce="${msb}$(printf "%08X" "$(($lsb ^ $seq_num))")"
|
||||
tm_out "$nonce"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 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
|
||||
# (b) arg1 contains the entire ServerHello (and appears to contain the entire response);
|
||||
# (c) the entire response is supposed to be parsed; and
|
||||
# (d) the key and IV have not been provided to decrypt the response.
|
||||
# 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").
|
||||
# arg3: TLS cipher for decrypting TLSv1.3 response
|
||||
# arg4: key and IV for decrypting TLSv1.3 response
|
||||
check_tls_serverhellodone() {
|
||||
local tls_hello_ascii="$1"
|
||||
local process_full="$2"
|
||||
local cipher="$3"
|
||||
local key_and_iv="$4"
|
||||
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 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_err_level
|
||||
local key iv
|
||||
local -i seq_num=0 plaintext_len
|
||||
local plaintext decrypted_response=""
|
||||
|
||||
DETECTED_TLS_VERSION=""
|
||||
|
||||
[[ -n "$key_and_iv" ]] && read key iv <<< "$key_and_iv"
|
||||
|
||||
if [[ -z "$tls_hello_ascii" ]]; then
|
||||
return 0 # no server hello received
|
||||
fi
|
||||
@ -8788,6 +9139,7 @@ check_tls_serverhellodone() {
|
||||
if [[ "$tls_content_type" == "16" ]]; then
|
||||
tls_handshake_ascii+="${tls_hello_ascii:i:msg_len}"
|
||||
tls_handshake_ascii_len=${#tls_handshake_ascii}
|
||||
decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $(($msg_len/2)))${tls_hello_ascii:i:msg_len}"
|
||||
# the ServerHello MUST be the first handshake message
|
||||
[[ $tls_handshake_ascii_len -ge 2 ]] && [[ "${tls_handshake_ascii:0:2}" != "02" ]] && return 2
|
||||
if [[ $tls_handshake_ascii_len -ge 12 ]]; then
|
||||
@ -8825,12 +9177,35 @@ check_tls_serverhellodone() {
|
||||
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
|
||||
tm_out ""
|
||||
return 0 # The entire ServerHello message has been received (and the rest isn't needed)
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
elif [[ "$tls_content_type" == "15" ]]; then # TLS ALERT
|
||||
tls_alert_ascii+="${tls_hello_ascii:i:msg_len}"
|
||||
decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $(($msg_len/2)))${tls_hello_ascii:i:msg_len}"
|
||||
elif [[ "$tls_content_type" == "17" ]] && [[ -n "$key_and_iv" ]]; then # encrypted data
|
||||
nonce="$(get-nonce "$iv" "$seq_num")"
|
||||
[[ $? -ne 0 ]] && return 2
|
||||
plaintext="$(sym-decrypt "$cipher" "$key" "$nonce" "${tls_hello_ascii:i:msg_len}")"
|
||||
[[ $? -ne 0 ]] && return 2
|
||||
seq_num+=1
|
||||
|
||||
# Remove zeros from end of plaintext, if any
|
||||
plaintext_len=${#plaintext}-2
|
||||
while [[ "${plaintext:plaintext_len:2}" == "00" ]]; do
|
||||
plaintext_len=$plaintext_len-2
|
||||
done
|
||||
tls_content_type="${plaintext:plaintext_len:2}"
|
||||
decrypted_response+="${tls_content_type}0301$(printf "%04X" $(($plaintext_len/2)))${plaintext:0:plaintext_len}"
|
||||
if [[ "$tls_content_type" == "16" ]]; then
|
||||
tls_handshake_ascii+="${plaintext:0:plaintext_len}"
|
||||
elif [[ "$tls_content_type" == "15" ]]; then
|
||||
tls_alert_ascii+="${plaintext:0:plaintext_len}"
|
||||
else
|
||||
return 2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
@ -8840,7 +9215,7 @@ check_tls_serverhellodone() {
|
||||
remaining=$tls_alert_ascii_len-$i
|
||||
[[ $remaining -lt 4 ]] && return 1
|
||||
tls_err_level=${tls_alert_ascii:i:2} # 1: warning, 2: fatal
|
||||
[[ $tls_err_level == "02" ]] && DETECTED_TLS_VERSION="" && return 0
|
||||
[[ $tls_err_level == "02" ]] && DETECTED_TLS_VERSION="" && tm_out "" && return 0
|
||||
done
|
||||
|
||||
# If there is a serverHelloDone or Finished, then we are done.
|
||||
@ -8857,14 +9232,15 @@ check_tls_serverhellodone() {
|
||||
|
||||
# For SSLv3 - TLS1.2 look for a ServerHelloDone message.
|
||||
# For TLS 1.3 look for a Finished message.
|
||||
[[ $tls_msg_type == "0E" ]] && return 0
|
||||
[[ $tls_msg_type == "14" ]] && return 0
|
||||
[[ $tls_msg_type == "0E" ]] && tm_out "" && return 0
|
||||
[[ $tls_msg_type == "14" ]] && tm_out "$decrypted_response" && 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 the response is TLSv1.3 and the full response is to be processed, but the
|
||||
# key and IV have not been provided to decrypt the response, 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
|
||||
[[ -z "$key_and_iv" ]] && [[ $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.
|
||||
@ -8931,14 +9307,17 @@ parse_tls_serverhello() {
|
||||
local -i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len msg_len
|
||||
local tls_serverhello_ascii="" tls_certificate_ascii=""
|
||||
local tls_serverkeyexchange_ascii="" tls_certificate_status_ascii=""
|
||||
local tls_encryptedextensions_ascii="" tls_revised_certificate_msg=""
|
||||
local -i tls_serverhello_ascii_len=0 tls_certificate_ascii_len=0
|
||||
local -i tls_serverkeyexchange_ascii_len=0 tls_certificate_status_ascii_len=0
|
||||
local -i tls_encryptedextensions_ascii_len=0
|
||||
local added_encrypted_extensions=false
|
||||
local tls_alert_descrip tls_sid_len_hex issuerDN subjectDN CAissuerDN CAsubjectDN
|
||||
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_no tls_cipher_suite rfc_cipher_suite tls_compression_method
|
||||
local tls_extensions="" extension_type named_curve_str="" named_curve_oid
|
||||
local -i i j extension_len tls_extensions_len ocsp_response_len=0 ocsp_response_list_len ocsp_resp_offset
|
||||
local -i i j extension_len extn_len tls_extensions_len ocsp_response_len=0 ocsp_response_list_len ocsp_resp_offset
|
||||
local -i certificate_list_len certificate_len cipherlist_len
|
||||
local -i curve_type named_curve
|
||||
local -i dh_bits=0 msb mask
|
||||
@ -9149,6 +9528,14 @@ parse_tls_serverhello() {
|
||||
fi
|
||||
tls_serverhello_ascii="${tls_handshake_ascii:i:msg_len}"
|
||||
tls_serverhello_ascii_len=$msg_len
|
||||
elif [[ "$process_full" == "all" ]] && [[ "$tls_msg_type" == "08" ]]; then
|
||||
# Add excrypted extensions (now decrypted) to end of extensions in SeverHello
|
||||
tls_encryptedextensions_ascii="${tls_handshake_ascii:i:msg_len}"
|
||||
tls_encryptedextensions_ascii_len=$msg_len
|
||||
if [[ $msg_len -lt 2 ]]; then
|
||||
debugme tmln_warning "Response contained a malformed encrypted extensions message"
|
||||
return 1
|
||||
fi
|
||||
elif [[ "$process_full" == "all" ]] && [[ "$tls_msg_type" == "0B" ]]; then
|
||||
if [[ -n "$tls_certificate_ascii" ]]; then
|
||||
debugme tmln_warning "Response contained more than one Certificate handshake message."
|
||||
@ -9450,6 +9837,74 @@ parse_tls_serverhello() {
|
||||
FF01) tls_extensions+="TLS server extension \"renegotiation info\" (id=65281), len=$extension_len\n" ;;
|
||||
*) tls_extensions+="TLS server extension \"unrecognized extension\" (id=$(printf "%d\n\n" "0x$extension_type")), len=$extension_len\n" ;;
|
||||
esac
|
||||
# After processing all of the extensions in the ServerHello message,
|
||||
# if it has been determined that the response is TLSv1.3 and the
|
||||
# response was decrypted, then modify $tls_serverhello_ascii by adding
|
||||
# the extensions from the EncryptedExtensions and Certificate messages
|
||||
# and then process them.
|
||||
if ! "$added_encrypted_extensions" && [[ "$DETECTED_TLS_VERSION" == "0304" ]] && \
|
||||
[[ $((i+8+extension_len)) -eq $tls_extensions_len ]]; then
|
||||
# Note that the encrypted extensions have been added so that
|
||||
# the aren't added a second time.
|
||||
added_encrypted_extensions=true
|
||||
if [[ -n "$tls_encryptedextensions_ascii" ]]; then
|
||||
tls_serverhello_ascii_len+=$tls_encryptedextensions_ascii_len-4
|
||||
tls_extensions_len+=$tls_encryptedextensions_ascii_len-4
|
||||
tls_encryptedextensions_ascii_len=$tls_encryptedextensions_ascii_len/2-2
|
||||
let offset=$extns_offset+4
|
||||
tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $((0x${tls_serverhello_ascii:extns_offset:4}+$tls_encryptedextensions_ascii_len)))${tls_serverhello_ascii:offset}${tls_encryptedextensions_ascii:4}"
|
||||
fi
|
||||
if [[ -n "$tls_certificate_ascii" ]]; then
|
||||
# In TLS 1.3, the Certificate message begins with a zero length certificate_request_context.
|
||||
# In addition, certificate_list is now a list of (certificate, extension) pairs rather than
|
||||
# just certificates. So, extract the extensions and add them to $tls_serverhello_ascii and
|
||||
# create a new $tls_certificate_ascii that only contains a list of certificates.
|
||||
if [[ -n "$tls_certificate_ascii" ]]; then
|
||||
if [[ "${tls_certificate_ascii:0:2}" != "00" ]]; then
|
||||
debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
|
||||
tmpfile_handle $FUNCNAME.txt
|
||||
return 1
|
||||
fi
|
||||
if [[ $tls_certificate_ascii_len -lt 8 ]]; then
|
||||
debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
|
||||
tmpfile_handle $FUNCNAME.txt
|
||||
return 1
|
||||
fi
|
||||
certificate_list_len=2*$(hex2dec "${tls_certificate_ascii:2:6}")
|
||||
if [[ $certificate_list_len -ne $tls_certificate_ascii_len-8 ]]; then
|
||||
debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
|
||||
tmpfile_handle $FUNCNAME.txt
|
||||
return 1
|
||||
fi
|
||||
|
||||
for (( j=8; j < tls_certificate_ascii_len; j=j+extn_len )); do
|
||||
if [[ $tls_certificate_ascii_len-$j -lt 6 ]]; then
|
||||
debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
|
||||
tmpfile_handle $FUNCNAME.txt
|
||||
return 1
|
||||
fi
|
||||
certificate_len=2*$(hex2dec "${tls_certificate_ascii:j:6}")
|
||||
if [[ $certificate_len -gt $tls_certificate_ascii_len-$j-6 ]]; then
|
||||
debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
|
||||
tmpfile_handle $FUNCNAME.txt
|
||||
return 1
|
||||
fi
|
||||
len1=$certificate_len+6
|
||||
tls_revised_certificate_msg+="${tls_certificate_ascii:j:len1}"
|
||||
j+=$len1
|
||||
extn_len=2*$(hex2dec "${tls_certificate_ascii:j:4}")
|
||||
j+=4
|
||||
# TODO: Should only the extensions associated with the EE certificate be added to $tls_serverhello_ascii?
|
||||
tls_serverhello_ascii_len+=$extn_len
|
||||
tls_extensions_len+=$extn_len
|
||||
let offset=$extns_offset+4
|
||||
tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $((0x${tls_serverhello_ascii:extns_offset:4}+$extn_len/2)))${tls_serverhello_ascii:offset}${tls_certificate_ascii:j:extn_len}"
|
||||
done
|
||||
tls_certificate_ascii_len=${#tls_revised_certificate_msg}+6
|
||||
tls_certificate_ascii="$(printf "%06X" $(($tls_certificate_ascii_len/2-3)))$tls_revised_certificate_msg"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
@ -10602,7 +11057,7 @@ tls_sockets() {
|
||||
local process_full="$3" offer_compression=false skip=false
|
||||
local close_connection=true
|
||||
local -i hello_done=0
|
||||
local cipher="" key_and_iv=""
|
||||
local cipher="" key_and_iv="" decrypted_response
|
||||
|
||||
[[ "$5" == "true" ]] && offer_compression=true
|
||||
[[ "$6" == "false" ]] && close_connection=false
|
||||
@ -10690,9 +11145,11 @@ tls_sockets() {
|
||||
fi
|
||||
skip=false
|
||||
if [[ $hello_done -eq 1 ]]; then
|
||||
check_tls_serverhellodone "$tls_hello_ascii" "$process_full"
|
||||
decrypted_response="$(check_tls_serverhellodone "$tls_hello_ascii" "$process_full" "$cipher" "$key_and_iv")"
|
||||
hello_done=$?
|
||||
[[ "$hello_done" -eq 0 ]] && [[ -n "$decrypted_response" ]] && tls_hello_ascii="$(toupper "$decrypted_response")"
|
||||
if [[ "$hello_done" -eq 3 ]]; then
|
||||
hello_done=1; skip=true
|
||||
debugme echo "reading server hello..."
|
||||
parse_tls_serverhello "$tls_hello_ascii" "ephemeralkey"
|
||||
ret=$?
|
||||
@ -10707,13 +11164,6 @@ tls_sockets() {
|
||||
else
|
||||
hello_done=2
|
||||
fi
|
||||
# The following three lines are temporary until the code
|
||||
# to decrypt TLSv1.3 responses has been added, at which point
|
||||
# parse_tls_serverhello() will be called with process_full="all"
|
||||
# and parse_tls_serverhello() will populate these files.
|
||||
process_full="ephemeralkey"
|
||||
[[ -e "$HOSTCERT" ]] && rm "$HOSTCERT"
|
||||
[[ -e "$TEMPDIR/intermediatecerts.pem" ]] && rm "$TEMPDIR/intermediatecerts.pem"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@ -13609,6 +14059,15 @@ find_openssl_binary() {
|
||||
grep -q 'mysql' $s_client_starttls_has && \
|
||||
HAS_MYSQL=true
|
||||
|
||||
$OPENSSL enc -chacha20 -K "12345678901234567890123456789012" -iv "01000000123456789012345678901234" > /dev/null 2> /dev/null <<< "test"
|
||||
[[ $? -eq 0 ]] && HAS_CHACHA20=true
|
||||
|
||||
$OPENSSL enc -aes-128-gcm -K 0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567 > /dev/null 2> /dev/null <<< "test"
|
||||
[[ $? -eq 0 ]] && HAS_AES128_GCM=true
|
||||
|
||||
$OPENSSL enc -aes-256-gcm -K 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567 > /dev/null 2> /dev/null <<< "test"
|
||||
[[ $? -eq 0 ]] && HAS_AES256_GCM=true
|
||||
|
||||
if [[ "$OPENSSL_TIMEOUT" != "" ]]; then
|
||||
if type -p timeout 2>&1 >/dev/null ; then
|
||||
if ! "$do_mass_testing"; then
|
||||
|
Loading…
Reference in New Issue
Block a user