From 351bb7a4e8560f49400a5a018c893ea0b2343686 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Fri, 24 Jan 2020 15:26:13 -0500 Subject: [PATCH] Full AEAD cipher implementations RFC 8446 specifies cipher suites that use three symmetric encryption algorithms, all of which are Authenticated Encryption with Associated Data (AEAD) algorithms. In each of these algorithms when data is encryption an authentication tag is created, which allows the recipient to verify that the data has not been modified. The authentication may also cover some additional data that was not encrypted. The current implementations of these algorithms in testssl.sh decrypt the ciphertext, but do not check that the authentication tag is correct (which involves the recipient computing the correct tag for the received data and then comparing it to the provided tag). While testssl.sh can get away with not checking authentication tags when receiving data, the ability to compute authentication tags is needed in order to send encrypted data as TLS servers would reject any encrypted data that did not have a correct authentication tag. Being able to send encrypted data is necessary to be able to complete the TLS 1.3 handshake. This PR replaces the current implementations of the symmetric encryption algorithms with full implementations of each of the algorithms. These full implementations include the ability to encrypt data for sending, and can also verify the authentication tag when decrypting data. Since the Bash implementations of these algorithms is very slow, the decryption code is designed to only compute and check authentication tags in debug mode. While the implementation of the code to compute authentication tags for AES-CCM was based on NIST Special Publication 800-38C, I was not able to implement the code for AES-GCM or Poly1305 from their specifications (NIST Special Publication 800-38D and RFC 8439, respectively). So, I would very much like to thank the implementers of https://github.com/mko-x/SharedAES-GCM and https://github.com/floodyberry/poly1305-donna. The implementations of AES-GCM and Poly1305 in the PR were developed by translating the C code in https://github.com/mko-x/SharedAES-GCM and https://github.com/floodyberry/poly1305-donna into Bash. I don't understand what that code is doing, but it seems to work. :-) I have only tested this code on a computer with a 64-bit operating system. While I have not tested it, I believe that the decryption code will work with 32-bit integers if not in debug mode (i.e., if not trying to compute the authentication tags). I also believe that the AES-CCM code for computing authentication tags will work with 32-bit integers. However, AES-GCM and Poly1305 code for computing authentication tags will definitely only work on systems that have 64-bit integers. So, on systems that do not have 64-bit integers, encryption will not work for AES-GCM or ChaCha20-Poly1305, and decryption will not work for these algorithms if in debug mode. --- testssl.sh | 874 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 777 insertions(+), 97 deletions(-) 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