Merge pull request #1473 from dcooper16/aead
Full AEAD cipher implementations
This commit is contained in:
commit
51ca429b48
874
testssl.sh
874
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue