diff --git a/testssl.sh b/testssl.sh index 3b6c55c..94c1c44 100755 --- a/testssl.sh +++ b/testssl.sh @@ -11097,63 +11097,7 @@ 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 +# See RFC 8439, Section 2.1 chacha20_Qround() { local -i a="0x$1" local -i b="0x$2" @@ -11197,11 +11141,11 @@ chacha20_Qround() { y=$((y << 7)) b=$((x | y)) - tm_out "$(printf "%x" $a) $(printf "%x" $b) $(printf "%x" $c) $(printf "%x" $d)" + tm_out "$(printf "%X" $a) $(printf "%X" $b) $(printf "%X" $c) $(printf "%X" $d)" return 0 } -# See RFC 7539, Section 2.3.1 +# See RFC 8439, 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" @@ -11230,7 +11174,7 @@ chacha20_inner_block() { return 0 } -# See RFC 7539, Sections 2.3 and 2.3.1 +# See RFC 8439, Sections 2.3 and 2.3.1 chacha20_block() { local key="$1" local counter="$2" @@ -11305,7 +11249,7 @@ chacha20_block() { return 0 } -# See RFC 7539, Section 2.4 +# See RFC 8439, Section 2.4 chacha20() { local key="$1" local -i counter=1 @@ -11315,12 +11259,19 @@ chacha20() { local -i i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15 i16 local keystream plaintext="" + if "$HAS_CHACHA20"; then + plaintext="$(asciihex_to_binary "$ciphertext" | \ + $OPENSSL enc -chacha20 -K "$key" -iv "01000000$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + tm_out "$(strip_spaces "$plaintext")" + return 0 + fi + 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 + 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}))" \ @@ -11345,7 +11296,7 @@ chacha20() { mod_check=$ciphertext_len%128 if [[ $mod_check -ne 0 ]]; then keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")" - i1=128*$num_blocks + 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 @@ -11355,17 +11306,737 @@ chacha20() { return 0 } +# Implement U8to32 from https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna-32.h +# Used to decode value encoded as 32-bit little-endian integer +u8to32() { + local p="$1" + + tm_out "0x${p:6:2}${p:4:2}${p:2:2}${p:0:2}" + return 0 +} + +# Implement U32to8 from https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna-32.h +# Used to encode value as 32-bit little-endian integer +u32to8() { + local -i v="$1" + local p + + v=$((v & 0xffffffff)) + p="$(printf "%08X" $v)" + tm_out "${p:6:2}${p:4:2}${p:2:2}${p:0:2}" + return 0 +} + +# Used to encode value as 64-bit little-endian integer +u64to8() { + local -i v="$1" + local p + + 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 +} + + +# arg1: 32-byte key +# arg2: message to be authenticated +# See RFC 8439, Section 2.5 +# Implementation based on https://github.com/floodyberry/poly1305-donna +poly1305_mac() { + local key="$1" nonce="$2" ciphertext="$3" aad="$4" + local mac_key msg + local -i ciphertext_len aad_len + local -i bytes + local -i r0 r1 r2 r3 r4 + local -i h0=0 h1=0 h2=0 h3=0 h4=0 + local -i pad0 pad1 pad2 pad3 + local -i s1 s2 s3 s4 + local -i d0 d1 d2 d3 d4 + local -i g0 g1 g2 g3 g4 + local -i i c f blocksize hibit + + # poly1305_key_gen - RFC 8439, Section 2.6 + # The MAC key is actually just the first 64 characters (32 bytes) of the + # output of the chacha20_block function. However, there is no need to + # truncate the key, since the code below will ignore all but the first + # 64 characters. + mac_key="$(chacha20_block "$key" "00000000" "$nonce")" + + # Construct message to be authenticated. RFC 8439, Section 2.8 + msg="$aad" + aad_len=$((${#aad}/2)) + bytes=$(( aad_len % 16 )) + if [[ $bytes -ne 0 ]]; then + for (( i=bytes; i < 16; i++ )); do + msg+="00" + done + fi + msg+="$ciphertext" + ciphertext_len=$((${#ciphertext}/2)) + bytes=$(( ciphertext_len % 16 )) + if [[ $bytes -ne 0 ]]; then + for (( i=bytes; i < 16; i++ )); do + msg+="00" + done + fi + msg+="$(u64to8 $aad_len)$(u64to8 $ciphertext_len)" + bytes="${#msg}" + + # poly1305_init + r0=$(( $(u8to32 "${mac_key:0:8}") & 0x3ffffff )) + r1=$(( ($(u8to32 "${mac_key:6:8}") >> 2) & 0x3ffff03 )) + r2=$(( ($(u8to32 "${mac_key:12:8}") >> 4) & 0x3ffc0ff )) + r3=$(( ($(u8to32 "${mac_key:18:8}") >> 6) & 0x3f03fff )) + r4=$(( ($(u8to32 "${mac_key:24:8}") >> 8) & 0x00fffff )) + + s1=$((r1*5)) + s2=$((r2*5)) + s3=$((r3*5)) + s4=$((r4*5)) + + pad0=$(u8to32 "${mac_key:32:8}") + pad1=$(u8to32 "${mac_key:40:8}") + pad2=$(u8to32 "${mac_key:48:8}") + pad3=$(u8to32 "${mac_key:56:8}") + + # poly1305_update + for (( 1 ; bytes > 0; bytes=bytes-blocksize )); do + if [[ $bytes -ge 32 ]]; then + blocksize=32 + hibit=0x1000000 + else + blocksize=$bytes + hibit=0 + msg+="01" + for (( i=bytes+2; i < 32; i+=2 )); do + msg+="00" + done + fi + h0+=$(( $(u8to32 "${msg:0:8}") & 0x3ffffff )) + h1+=$(( ($(u8to32 "${msg:6:8}") >> 2) & 0x3ffffff )) + h2+=$(( ($(u8to32 "${msg:12:8}") >> 4) & 0x3ffffff )) + h3+=$(( ($(u8to32 "${msg:18:8}") >> 6) & 0x3ffffff )) + h4+=$(( (($(u8to32 "${msg:24:8}") >> 8) & 0xffffff) | hibit )) + + d0=$(( h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1 )) + d1=$(( h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2 )) + d2=$(( h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3 )) + d3=$(( h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4 )) + d4=$(( h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0 )) + + c=$(( (d0 >> 26) & 0x3fffffffff )); h0=$(( d0 & 0x3ffffff )) + d1+=$c; c=$(( (d1 >> 26) & 0x3fffffffff )); h1=$(( d1 & 0x3ffffff )) + d2+=$c; c=$(( (d2 >> 26) & 0x3fffffffff )); h2=$(( d2 & 0x3ffffff )) + d3+=$c; c=$(( (d3 >> 26) & 0x3fffffffff )); h3=$(( d3 & 0x3ffffff )) + d4+=$c; c=$(( (d4 >> 26) & 0x3fffffffff )); h4=$(( d4 & 0x3ffffff )) + h0+=$((c*5)); c=$(( (h0 >> 26) & 0x3fffffffff )); h0=$(( h0 & 0x3ffffff )) + h1+=$c + + msg="${msg:32}" + done + + # poly1305_finish + c=$(( (h0 >> 26) & 0x3f )); h1=$(( h1 & 0x3ffffff )) + h2+=$c; c=$(( (h2 >> 26) & 0x3f )); h2=$(( h2 & 0x3ffffff )) + h3+=$c; c=$(( (h3 >> 26) & 0x3f )); h3=$(( h3 & 0x3ffffff )) + h4+=$c; c=$(( (h4 >> 26) & 0x3f )); h4=$(( h4 & 0x3ffffff )) + h0+=$((c*5)); c=$(( (h0 >> 26) & 0x3f )); h0=$(( h0 & 0x3ffffff )) + h1+=$c + + g0=$((h0+5)); c=$(( (g0 >> 26) & 0x3f )); g0=$(( g0 & 0x3ffffff )) + g1=$((h1+c)); c=$(( (g1 >> 26) & 0x3f )); g1=$(( g1 & 0x3ffffff )) + g2=$((h2+c)); c=$(( (g2 >> 26) & 0x3f )); g2=$(( g2 & 0x3ffffff )) + g3=$((h3+c)); c=$(( (g3 >> 26) & 0x3f )); g3=$(( g3 & 0x3ffffff )) + g4=$((h4+c-0x4000000)) + + if [[ $((g4 & 0x8000000000000000)) -eq 0 ]]; then + h0=$g0; h1=$g1; h2=$g2; h3=$g3; h4=$g4 + fi + h0=$(( ( h0 | (h1 << 26)) & 0xffffffff)) + h1=$(( ((h1 >> 6) | (h2 << 20)) & 0xffffffff)) + h2=$(( ((h2 >> 12) | (h3 << 14)) & 0xffffffff)) + h3=$(( ((h3 >> 18) | (h4 << 8)) & 0xffffffff)) + + f=$(( h0+pad0 )); h0=$f + f=$(( h1+pad1+(f>>32) )); h1=$f + f=$(( h2+pad2+(f>>32) )); h2=$f + f=$(( h3+pad3+(f>>32) )); h3=$f + + tm_out "$(u32to8 $h0)$(u32to8 $h1)$(u32to8 $h2)$(u32to8 $h3)" + return 0 +} + +# arg1: key +# arg2: nonce (must be 96 bits in length) +# arg3: ciphertext +# arg4: additional authenticated data +# arg5: expected tag +# arg6: true if authentication tag should be checked. false otherwise. +chacha20_aead_decrypt() { + local key="$1" nonce="$2" ciphertext="$3" aad="$4" expected_tag="$(toupper "$5")" + local compute_tag="$6" + local plaintext computed_tag + + plaintext="$(chacha20 "$key" "$nonce" "$ciphertext")" + [[ $? -ne 0 ]] && return 7 + + if "$compute_tag"; then + computed_tag="$(poly1305_mac "$key" "$nonce" "$ciphertext" "$aad")" + [[ $? -ne 0 ]] && return 7 + [[ "$computed_tag" == $expected_tag ]] || return 7 + fi + + tm_out "$plaintext" + return 0 +} + +# arg1: key +# arg2: nonce (must be 96 bits in length) +# arg3: plaintext +# arg4: additional authenticated data +chacha20_aead_encrypt() { + local key="$1" nonce="$2" plaintext="$3" aad="$4" + local ciphertext computed_tag + + ciphertext="$(chacha20 "$key" "$nonce" "$plaintext")" + [[ $? -ne 0 ]] && return 7 + + computed_tag="$(poly1305_mac "$key" "$nonce" "$ciphertext" "$aad")" + [[ $? -ne 0 ]] && return 7 + + tm_out "$ciphertext $computed_tag" + return 0 +} + +# arg1: nonce (must be 96 bits) +# arg2: number of blocks needed for plaintext/ciphertext +# Generate the sequence of counter blocks, which are to be encrypted and then +# XORed with either the plaintext or the ciphertext. +# See Section 6.1, Section 6.2, and Appendix A.3 of NIST SP 800-38C and +# Section 5.3 of RFC 5116. +generate-ccm-counter-blocks() { + local ctr="02${1}000000" ctr_msb ctr_lsb1 + local -i i ctr_lsb n="$2" + + ctr_msb="${ctr:0:24}" + ctr_lsb=0x${ctr:24:8} + + for (( i=0; i <= n; i=i+1 )); do + ctr_lsb1="$(printf "%08X" "$ctr_lsb")" + printf "\x${ctr_msb:0:2}\x${ctr_msb:2:2}\x${ctr_msb:4:2}\x${ctr_msb:6:2}\x${ctr_msb:8:2}\x${ctr_msb:10:2}\x${ctr_msb:12:2}\x${ctr_msb:14:2}\x${ctr_msb:16:2}\x${ctr_msb:18:2}\x${ctr_msb:20:2}\x${ctr_msb:22:2}\x${ctr_lsb1:0:2}\x${ctr_lsb1:2:2}\x${ctr_lsb1:4:2}\x${ctr_lsb1:6:2}" + ctr_lsb+=1 + done + return 0 +} + +# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb) +# arg2: key +# arg3: iv (must be 96 bits in length) +# arg4: additional authenticated data +# arg5: plaintext +# arg6: tag length (must be 16 or 32) +# Compute the CCM authentication tag +ccm-compute-tag() { + local cipher="$1" key="$2" iv="$3" aad="$4" plaintext="$5" + local -i tag_len="$6" + local b tag + local -i i aad_len plaintext_len final_block_len nr_blocks + + aad_len=$((${#aad}/2)) + plaintext_len=$((${#plaintext}/2)) + + # Apply the formatting function to create b=B0B1B2... as in + # Appendix A.2 of NIST SP 800-38C. + + # The first block consists of the flags, nonce, and length of plaintext + # See Section 5.3 of RFC 5116 for value of q. + if [[ $aad_len -ne 0 ]]; then + if [[ $tag_len -eq 16 ]]; then + b="5A${iv}$(printf "%06X" $plaintext_len)" + else + b="7A${iv}$(printf "%06X" $plaintext_len)" + fi + elif [[ $tag_len -eq 16 ]]; then + b="1A${iv}$(printf "%06X" $plaintext_len)" + else + b="3A${iv}$(printf "%06X" $plaintext_len)" + fi + + # Next comes any additional authenticated data + if [[ $aad_len -ne 0 ]]; then + if [[ $aad_len -lt 0xFF00 ]]; then + b+="$(printf "%04X" $aad_len)$aad" + final_block_len=$(( (aad_len+2) % 16 )) + elif [[ $aad_len -lt 0x100000000 ]]; then + b+="FFFE$(printf "%08X" $aad_len)$aad" + final_block_len=$(( (aad_len+6) % 16 )) + else + # AES-CCM supports lengths up to 2^64, but there doesn't + # seem to be any reason to try to support such lengths. + return 7 + fi + # Add padding to complete block + if [[ $final_block_len -ne 0 ]]; then + for (( i=final_block_len; i < 16; i++ )); do + b+="00" + done + fi + fi + + # Finally add the plaintext and any padding needed to complete block + b+="$plaintext" + final_block_len=$((plaintext_len % 16)) + if [[ $final_block_len -ne 0 ]]; then + for (( i=final_block_len; i < 16; i++ )); do + b+="00" + done + fi + + # Compute the authentication tag as described in + # Sections 6.1 and 6.2 of NIST SP 800-38C. + nr_blocks=$((${#b}/32)) + tag="${b:0:32}" + for (( i=0; i < nr_blocks; i++ )); do + # XOR current block with previous block and then encrypt + [[ $i -ne 0 ]] && + tag="$(printf "%08X%08X%08X%08X" "$((0x${b:0:8} ^ 0x${tag:0:8}))" "$((0x${b:8:8} ^ 0x${tag:8:8}))" "$((0x${b:16:8} ^ 0x${tag:16:8}))" "$((0x${b:24:8} ^ 0x${tag:24:8}))")" + + tag="$(printf "\x${tag:0:2}\x${tag:2:2}\x${tag:4:2}\x${tag:6:2}\x${tag:8:2}\x${tag:10:2}\x${tag:12:2}\x${tag:14:2}\x${tag:16:2}\x${tag:18:2}\x${tag:20:2}\x${tag:22:2}\x${tag:24:2}\x${tag:26:2}\x${tag:28:2}\x${tag:30:2}" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + + b="${b:32}" + done + + tm_out "${tag:0:tag_len}" + return 0 +} + +# arg1: AES-CCM TLS cipher +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: ciphertext +# arg5: additional authenticated data +# arg6: expected tag (must be 16 or 32 characters) +# arg7: true if authentication tag should be checked. false otherwise. +# See Section 6.2 of NIST SP 800-38C +ccm-decrypt() { + local cipher="$1" key="$2" nonce="$3" ciphertext="$4" aad="$5" enciphered_expected_tag="$6" + local compute_tag="$7" + local plaintext="" expected_tag computed_tag + local -i i i1 i2 i3 i4 tag_len + local -i ciphertext_len n mod_check + local s s0 + + [[ ${#nonce} -ne 24 ]] && return 7 + + case "$cipher" in + *AES_128*) cipher="-aes-128-ecb" ;; + *AES_256*) cipher="-aes-256-ecb" ;; + *) return 7 + esac + + ciphertext_len=${#ciphertext} + n=$((ciphertext_len/32)) + mod_check=$((ciphertext_len%32)) + [[ $mod_check -ne 0 ]] && n+=1 + + # generate keystream + s="$(generate-ccm-counter-blocks "$nonce" "$n" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + + # The first 16-bytes of the keystream ($s) are used to decrypt the + # authentication tag and the remaining bytes are used to decrypt the + # ciphertext. + s0="${s:0:32}" + s="${s:32}" + + # XOR the ciphertext with the keystream ($s). 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${s:i1:8}))" "$((0x${ciphertext:i2:8} ^ 0x${s:i2:8}))" "$((0x${ciphertext:i3:8} ^ 0x${s:i3:8}))" "$((0x${ciphertext:i4:8} ^ 0x${s: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${s:i1:2}))")" + i1+=2 + done + fi + + if "$compute_tag"; then + tag_len=${#enciphered_expected_tag} + + # Decrypt the authentication tag that was provided with the message + if [[ $tag_len -eq 16 ]]; then + expected_tag="$(printf "%08X%08X" "$((0x${enciphered_expected_tag:0:8} ^ 0x${s0:0:8}))" "$((0x${enciphered_expected_tag:8:8} ^ 0x${s0:8:8}))")" + elif [[ $tag_len -eq 32 ]]; then + expected_tag="$(printf "%08X%08X%08X%08X" "$((0x${enciphered_expected_tag:0:8} ^ 0x${s0:0:8}))" "$((0x${enciphered_expected_tag:8:8} ^ 0x${s0:8:8}))" "$((0x${enciphered_expected_tag:16:8} ^ 0x${s0:16:8}))" "$((0x${enciphered_expected_tag:24:8} ^ 0x${s0:24:8}))")" + else + return 7 + fi + + # obtain the actual authentication tag for the decrypted message + computed_tag="$(ccm-compute-tag "$cipher" "$key" "$nonce" "$aad" "$plaintext" "$tag_len")" + [[ $? -ne 0 ]] && return 7 + fi + + if ! "$compute_tag" || [[ "$computed_tag" == $expected_tag ]]; then + tm_out "$plaintext" + return 0 + else + return 7 + fi +} + +# arg1: AES-CCM TLS cipher +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: plaintext +# arg5: additional authenticated data +# See Section 6.1 of NIST SP 800-38C +ccm-encrypt() { + local cipher="$1" key="$2" nonce="$3" plaintext="$4" aad="$5" + local -i tag_len + local ossl_cipher="-aes-128-ecb" + local ciphertext="" tag encrypted_tag + local -i i i1 i2 i3 i4 + local -i plaintext_len n mod_check + local s s0 + + [[ ${#nonce} -ne 24 ]] && return 7 + + case "$cipher" in + TLS_AES_128_CCM_SHA256) tag_len=32 ;; + TLS_AES_128_CCM_8_SHA256) tag_len=16 ;; + *) return 7 + esac + + # compute the authentication tag + tag="$(ccm-compute-tag "$ossl_cipher" "$key" "$nonce" "$aad" "$plaintext" "$tag_len")" + [[ $? -ne 0 ]] && return 7 + + plaintext_len=${#plaintext} + n=$((plaintext_len/32)) + mod_check=$((plaintext_len%32)) + [[ $mod_check -ne 0 ]] && n+=1 + + # generate keystream + s="$(generate-ccm-counter-blocks "$nonce" "$n" | $OPENSSL enc "$ossl_cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + + # encrypt the authentication tag using the first 16 bytes of the keystrem ($s) + if [[ $tag_len -eq 16 ]]; then + encrypted_tag="$(printf "%08X%08X" "$((0x${tag:0:8} ^ 0x${s:0:8}))" "$((0x${tag:8:8} ^ 0x${s:8:8}))")" + elif [[ $tag_len -eq 32 ]]; then + encrypted_tag="$(printf "%08X%08X%08X%08X" "$((0x${tag:0:8} ^ 0x${s:0:8}))" "$((0x${tag:8:8} ^ 0x${s:8:8}))" "$((0x${tag:16:8} ^ 0x${s:16:8}))" "$((0x${tag:24:8} ^ 0x${s:24:8}))")" + else + return 7 + fi + + # XOR the plaintext with the keystream ($s). For efficiency, work in blocks + # of 16 bytes at a time (but with each XOR operation working on 32 bits. + s="${s:32}" + [[ $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)) + ciphertext+="$(printf "%08X%08X%08X%08X" "$((0x${plaintext:i1:8} ^ 0x${s:i1:8}))" "$((0x${plaintext:i2:8} ^ 0x${s:i2:8}))" "$((0x${plaintext:i3:8} ^ 0x${s:i3:8}))" "$((0x${plaintext:i4:8} ^ 0x${s:i4:8}))")" + done + # If the length of the plaintext 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 + ciphertext+="$(printf "%02X" "$((0x${plaintext:i1:2} ^ 0x${s:i1:2}))")" + i1+=2 + done + fi + tm_out "$ciphertext$encrypted_tag" + return 0 +} + +# This function is based on gcm_mult in https://github.com/mko-x/SharedAES-GCM +# args 1-16: HL from gcm_ctx +# args 17-32: HH from gcm_ctx +# args 33-48: x - the input vector +gcm_mult() { + local -a gcm_ctx_hl=( "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" "${12}" "${13}" "${14}" "${15}" "${16}" ) + local -a gcm_ctx_hh=( "${17}" "${18}" "${19}" "${20}" "${21}" "${22}" "${23}" "${24}" "${25}" "${26}" "${27}" "${28}" "${29}" "${30}" "${31}" "${32}" ) + local -a x=( "${33}" "${34}" "${35}" "${36}" "${37}" "${38}" "${39}" "${40}" "${41}" "${42}" "${43}" "${44}" "${45}" "${46}" "${47}" "${48}" ) + local output + local -i i lo hi rem zh zl + local -r -a -i last4=(0x0000 0x1c20 0x3840 0x2460 0x7080 0x6ca0 0x48c0 0x54e0 0xe100 0xfd20 0xd940 0xc560 0x9180 0x8da0 0xa9c0 0xb5e0) + + lo=$((0x${x[15]} & 0x0F)) + hi=$((0x${x[15]} >> 4)) + zh=0x${gcm_ctx_hh[$lo]} + zl=0x${gcm_ctx_hl[$lo]} + + for (( i=15; i >=0; i=i-1 )); do + lo=$((0x${x[i]} & 0x0F)) + hi=$((0x${x[i]} >> 4)) + if [[ $i -ne 15 ]]; then + rem=$((zl & 0x0F)) + zl=$(((zl >> 4) & 0x0fffffffffffffff)) + zl=$(((zh << 60) | zl)) + zh=$(((zh >> 4) & 0x0fffffffffffffff)) + zh=$((zh^(last4[rem] << 48))) + zh=$((zh^0x${gcm_ctx_hh[$lo]})) + zl=$((zl^0x${gcm_ctx_hl[$lo]})) + fi + rem=$((zl & 0x0F)) + zl=$(((zl >> 4) & 0x0fffffffffffffff)) + zl=$(((zh << 60) | zl)) + zh=$(((zh >> 4) & 0x0fffffffffffffff)) + zh=$((zh^(last4[rem] << 48))) + zh=$((zh^0x${gcm_ctx_hh[$hi]})) + zl=$((zl^0x${gcm_ctx_hl[$hi]})) + done + output="$(printf "%016X" $zh)$(printf "%016X" $zl)" + tm_out "${output:0:2} ${output:2:2} ${output:4:2} ${output:6:2} ${output:8:2} ${output:10:2} ${output:12:2} ${output:14:2} ${output:16:2} ${output:18:2} ${output:20:2} ${output:22:2} ${output:24:2} ${output:26:2} ${output:28:2} ${output:30:2}" + return 0 +} + +# arg1: a hexadecimal string that is at least 8 bytes in length +# See Section 6.2 of NIST SP 800-38D +inc32() { + local -i i len + local x="$1" + local msb + local -i lsb + + len=${#x} + [[ $len -lt 8 ]] && return 7 + i=$len-8 + msb="${x:0:i}" + lsb="0x${x:i:8}" + if [[ "$lsb" -eq "0xffffffff" ]]; then + lsb=0 + else + lsb+=1 + fi + tm_out "${msb}$(printf "%08X" "$lsb")" + return 0 +} + +# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb) +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: ciphertext +# arg5: aad +# arg6: mode +# arg7: true if authentication tag should be computed. false otherwise. +# This function is based on gcm_setkey, gcm_start, gcm_update, and gcm_finish +# in https://github.com/mko-x/SharedAES-GCM +gcm() { + local cipher="$1" aes_key="$2" y="${3}00000001" input="$4" aad="$5" mode="$6" + local compute_tag="$7" + local -a -i gcm_ctx_hl=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) + local -a -i gcm_ctx_hh=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) + local -a -i tag + local -a gcm_ctx_buf=("00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" ) + local -i i j hi lo vl vh t length + local h hl="" hh="" buf ectr base_ectr tmp + local -i input_len="$((${#input}/2))" aad_len="$((${#aad}/2))" use_len + + if "$compute_tag"; then + # gcm_setkey - populate HL and HH from gcm_ctx + h+=$(printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"| \ + $OPENSSL enc "$cipher" -K "$aes_key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"') + hi=0x${h:0:8} + lo=0x${h:8:8} + vh=$(((hi << 32) | lo)) + + hi=0x${h:16:8} + lo=0x${h:24:8} + vl=$(((hi << 32) | lo)) + + gcm_ctx_hl[8]=$vl + gcm_ctx_hh[8]=$vh + gcm_ctx_hh[0]=0 + gcm_ctx_hl[0]=0 + + for (( i=4; i > 0; i=i>>1 )); do + t=$(((vl & 1) * 0xe1000000)) + vl=$(((vl >> 1) & 0x7fffffffffffffff)) + vl=$(((vh << 63) | vl)) + vh=$(((vh >> 1) & 0x7fffffffffffffff)) + vh=$((vh ^ (t << 32))) + gcm_ctx_hl[i]=$vl + gcm_ctx_hh[i]=$vh + done + + for (( i=2; i < 16; i=i<<1 )); do + vh=${gcm_ctx_hh[i]} + vl=${gcm_ctx_hl[i]} + for (( j=1; j < i; j++ )); do + gcm_ctx_hh[$((i+j))]=$((vh ^ gcm_ctx_hh[j])) + gcm_ctx_hl[$((i+j))]=$((vl ^ gcm_ctx_hl[j])) + done + done + + # place HL and HH in strings so that can be passed to gcm_mult + for (( i=0; i < 16; i++ )); do + hl+="$(printf "%016X" ${gcm_ctx_hl[i]}) " + hh+="$(printf "%016X" ${gcm_ctx_hh[i]}) " + done + + # gcm_start + # compute the encrypted counter for later use in computing the authentication tag + base_ectr="$(printf "\x${y:0:2}\x${y:2:2}\x${y:4:2}\x${y:6:2}\x${y:8:2}\x${y:10:2}\x${y:12:2}\x${y:14:2}\x${y:16:2}\x${y:18:2}\x${y:20:2}\x${y:22:2}\x${y:24:2}\x${y:26:2}\x${y:28:2}\x${y:30:2}" | $OPENSSL enc "$cipher" -K "$aes_key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + + # Feed any additional authenticated data into the computation for the authentication tag. + for (( i=0; i < aad_len; i+=use_len )); do + [[ $((aad_len-i)) -lt 16 ]] && use_len=$((aad_len-i)) || use_len=16 + for (( j=0; j < use_len; j++ )); do + gcm_ctx_buf[j]="$(printf "%02X" $((0x${gcm_ctx_buf[j]} ^ 0x${aad:$((2*i+2*j)):2})))" + done + + buf="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})" + read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$buf" + done + fi + + # gcm_update + # Encrypt or decrypt the input and feed the ciphertext into the computation for the authentication tag. + for (( length=input_len; length > 0; length=length-use_len )); do + [[ $length -lt 16 ]] && use_len=$length || use_len=16 + + y="$(inc32 "$y")" + ectr="$(printf "\x${y:0:2}\x${y:2:2}\x${y:4:2}\x${y:6:2}\x${y:8:2}\x${y:10:2}\x${y:12:2}\x${y:14:2}\x${y:16:2}\x${y:18:2}\x${y:20:2}\x${y:22:2}\x${y:24:2}\x${y:26:2}\x${y:28:2}\x${y:30:2}" | $OPENSSL enc "$cipher" -K "$aes_key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + + for (( i=0; i < use_len; i++ )); do + tmp="$(printf "%02X" $((0x${ectr:$((2*i)):2} ^ 0x${input:$((2*i)):2})))" + output+="$tmp" + if "$compute_tag"; then + if [[ $mode == encrypt ]]; then + gcm_ctx_buf[i]="$(printf "%02X" $((0x${gcm_ctx_buf[i]} ^ 0x$tmp)))" + else + gcm_ctx_buf[i]="$(printf "%02X" $((0x${gcm_ctx_buf[i]} ^ 0x${input:$((2*i)):2})))" + fi + fi + done + + if "$compute_tag"; then + tmp="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})" + read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$tmp" + fi + + input="${input:$((2*use_len))}" + done + + if "$compute_tag"; then + # gcm_finish - feed the lengths of the ciphertext and additional authenticated data + # into the computation for the authentication tag. + input_len=$((8*input_len)) + aad_len=$((8*aad_len)) + output+=" " + for (( i=0; i < 16; i++ )); do + tag[i]=0x${base_ectr:$((2*i)):2} + done + + if ( [[ $input_len -ne 0 ]] || [[ $aad_len -ne 0 ]] ); then + buf="$(printf "%016X" $aad_len)$(printf "%016X" $input_len)" + for (( i=0; i < 16; i++ )); do + gcm_ctx_buf[i]="$(printf "%02X" $((0x${gcm_ctx_buf[i]} ^ 0x${buf:$((2*i)):2})))" + done + + buf="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})" + read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$buf" + for (( i=0; i < 16; i++ )); do + tag[i]=$((tag[i] ^ 0x${gcm_ctx_buf[i]})) + done + fi + for (( i=0; i < 16; i++ )); do + output+="$(printf "%02X" ${tag[i]})" + done + fi + tm_out "$output" + return 0 +} + +# arg1: AES-GCM TLS cipher +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: ciphertext +# arg5: aad +# arg6: expected tag +# arg7: true if authentication tag should be checked. false otherwise. +gcm-decrypt() { + local cipher="$1" key="$2" nonce="$3" ciphertext="$4" aad="$5" expected_tag="$(toupper "$6")" + local compute_tag="$7" + local plaintext computed_tag tmp + + [[ ${#nonce} -ne 24 ]] && return 7 + + if [[ "$cipher" == TLS_AES_128_GCM_SHA256 ]] && "$HAS_AES128_GCM" && ! "$compute_tag"; then + plaintext="$(asciihex_to_binary "$ciphertext" | \ + $OPENSSL enc -aes-128-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + tm_out "$(strip_spaces "$plaintext")" + return 0 + elif [[ "$cipher" == TLS_AES_256_GCM_SHA384 ]] && "$HAS_AES256_GCM" && ! "$compute_tag"; then + plaintext="$(asciihex_to_binary "$ciphertext" | \ + $OPENSSL enc -aes-256-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')" + tm_out "$(strip_spaces "$plaintext")" + return 0 + fi + + case "$cipher" in + *AES_128*) cipher="-aes-128-ecb" ;; + *AES_256*) cipher="-aes-256-ecb" ;; + *) return 7 + esac + + tmp="$(gcm "$cipher" "$key" "$nonce" "$ciphertext" "$aad" "decrypt" "$compute_tag")" + [[ $? -ne 0 ]] && return 7 + computed_tag="${tmp##* }" + plaintext="${tmp% $computed_tag}" + + if ! "$compute_tag" || [[ "$computed_tag" == $expected_tag ]]; then + tm_out "$plaintext" + return 0 + else + return 7 + fi +} + +# arg1: AES-GCM TLS cipher +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: plaintext +# arg5: aad +# See Section 7.2 of SP 800-38D +gcm-encrypt() { + local cipher + + case "$1" in + *AES_128*) cipher="-aes-128-ecb" ;; + *AES_256*) cipher="-aes-256-ecb" ;; + *) return 7 + esac + [[ ${#3} -ne 24 ]] && return 7 + + tm_out "$(gcm "$cipher" "$2" "$3" "$4" "$5" "encrypt" true)" + return $? +} + # arg1: TLS cipher # arg2: key # arg3: nonce (must be 96 bits in length) # arg4: ciphertext +# arg5: additional authenticated data sym-decrypt() { local cipher="$1" local key="$2" nonce="$3" local ciphertext="$4" - local ossl_cipher + local additional_data="$5" local plaintext 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*) @@ -11379,37 +12050,14 @@ sym-decrypt() { # 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 + ciphertext_len=$((ciphertext_len-tag_len)) if [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then - if "$HAS_CHACHA20"; then - plaintext="$(asciihex_to_binary "${ciphertext:0:ciphertext_len}" | \ - $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 "${ciphertext:0:ciphertext_len}" | \ - $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 "${ciphertext:0:ciphertext_len}" | \ - $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 + plaintext="$(chacha20_aead_decrypt "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag")" + elif [[ "$cipher" =~ CCM ]]; then + plaintext=$(ccm-decrypt "$cipher" "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag") + else # GCM + plaintext=$(gcm-decrypt "$cipher" "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag") fi [[ $? -ne 0 ]] && return 7 @@ -11417,6 +12065,31 @@ sym-decrypt() { return 0 } +# arg1: TLS cipher +# arg2: key +# arg3: nonce (must be 96 bits in length) +# arg4: plaintext +# arg5: additional authenticated data +sym-encrypt() { + local cipher="$1" key="$2" nonce="$3" plaintext="$4" additional_data="$5" + local ciphertext="" + + + if [[ "$cipher" =~ CCM ]]; then + ciphertext=$(ccm-encrypt "$cipher" "$key" "$nonce" "$plaintext" "$additional_data") + elif [[ "$cipher" =~ GCM ]]; then + ciphertext=$(gcm-encrypt "$cipher" "$key" "$nonce" "$plaintext" "$additional_data") + elif [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then + ciphertext="$(chacha20_aead_encrypt "$key" "$nonce" "$plaintext" "$additional_data")" + else + return 7 + fi + [[ $? -ne 0 ]] && return 7 + + tm_out "$(strip_spaces "$ciphertext")" + return 0 +} + # arg1: iv # arg2: sequence number get-nonce() { @@ -11460,7 +12133,8 @@ check_tls_serverhellodone() { local tls_err_level local key iv local -i seq_num=0 plaintext_len - local plaintext decrypted_response="" + local plaintext decrypted_response="" additional_data + local include_headers=true DETECTED_TLS_VERSION="" @@ -11483,6 +12157,7 @@ check_tls_serverhellodone() { [[ -z "$DETECTED_TLS_VERSION" ]] && DETECTED_TLS_VERSION="$tls_protocol" [[ "${tls_protocol:0:2}" != 03 ]] && return 2 i=$i+4 + additional_data="$tls_content_type$tls_protocol${tls_hello_ascii:i:4}" msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}") i=$i+4 remaining=$tls_hello_ascii_len-$i @@ -11525,7 +12200,10 @@ check_tls_serverhellodone() { fi fi # 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 [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]]; then + [[ 0x${DETECTED_TLS_VERSION:2:2} -lt 25 ]] && include_headers=false + DETECTED_TLS_VERSION=0304 + fi 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 @@ -11538,9 +12216,11 @@ check_tls_serverhellodone() { 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 + # The header information was added to additional data in TLSv1.3 draft 25. + "$include_headers" || additional_data="" nonce="$(get-nonce "$iv" "$seq_num")" [[ $? -ne 0 ]] && return 2 - plaintext="$(sym-decrypt "$cipher" "$key" "$nonce" "${tls_hello_ascii:i:msg_len}")" + plaintext="$(sym-decrypt "$cipher" "$key" "$nonce" "${tls_hello_ascii:i:msg_len}" "$additional_data")" [[ $? -ne 0 ]] && return 2 seq_num+=1