Add autopkgtests
Add autopkgtests for aeskeyfind. Those include a smoke test and
the extraction of 128-bit and 256-bit AES keys from process memory
dumps, wich are generated during the tests runtime.
Jan Gru authored 2 years ago
Samuel Henrique committed 2 years ago
0 | Tests: smoke, recover-aes-128, recover-aes-256 | |
1 | Depends: aeskeyfind, gdb, procps, python3-pycryptodome | |
2 | Restrictions: allow-stderr |
0 | #!/bin/sh | |
1 | ||
2 | # $1 = key specification as multiline string | |
3 | # $2 = dump file to store process memory | |
4 | ||
5 | GEN_SCRIPT="debian/tests/helpers/gen_aes_key.py" | |
6 | ||
7 | # Spawns subshell running the key generation script | |
8 | (echo "$1" | python3 "${GEN_SCRIPT}") & | |
9 | ||
10 | # Sleep to retrieve the PID of the child process reliably | |
11 | sleep .3 | |
12 | ||
13 | PID=$(pgrep -f "python3 ${GEN_SCRIPT}") | |
14 | ||
15 | #gcore -o"$3" ${PID} > /dev/null 2>&1 | |
16 | # Dumps memory | |
17 | grep rw-p /proc/${PID}/maps | sed -n 's/^\([0-9a-f]*\)-\([0-9a-f]*\) .*$/\1\t\2/p' \ | |
18 | | while read start stop; | |
19 | do gdb --batch --pid ${PID} -ex "append memory $2 0x$start 0x$stop" > /dev/null 2>&1; | |
20 | done | |
21 | ||
22 | # Terminates key generation script | |
23 | kill -9 ${PID} |
0 | import argparse | |
1 | import time | |
2 | import sys | |
3 | ||
4 | # Use pycryptodome's AES library | |
5 | # See https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html | |
6 | from Cryptodome.Cipher import AES | |
7 | ||
8 | ||
9 | KEY_ELEMENTS = ["KEY", "IV"] | |
10 | ||
11 | ||
12 | def main(infile=None): | |
13 | lines = [] | |
14 | ||
15 | # Checks, if interactive session or reading from a file | |
16 | if sys.stdin.isatty(): | |
17 | if infile.name != "<stdin>": | |
18 | # Reads specification file | |
19 | with open(infile.name, "r") as f: | |
20 | lines = f.readlines() | |
21 | else: | |
22 | exit("Supply data via STDIN or file!") | |
23 | else: | |
24 | lines = sys.stdin.readlines() | |
25 | ||
26 | construct_key(lines) | |
27 | ||
28 | ||
29 | def construct_key(lines): | |
30 | keymaterial = {} | |
31 | ||
32 | # Populates keymaterial | |
33 | for line in lines: | |
34 | # Strips of \n | |
35 | line = line.strip() | |
36 | ||
37 | # Tokenizes line | |
38 | parts = line.split("=", 1) | |
39 | name = parts[0] | |
40 | val = parts[-1] | |
41 | ||
42 | # Stores line in dict | |
43 | if name in KEY_ELEMENTS: | |
44 | keymaterial[name] = bytes(val, "utf-8") | |
45 | ||
46 | # Performs checks regarding well-formed key material | |
47 | if "KEY" not in keymaterial: | |
48 | exit("Key specification is incomplete.") | |
49 | elif ( | |
50 | len(keymaterial["KEY"]) != 16 | |
51 | and len(keymaterial["KEY"]) != 24 | |
52 | and len(keymaterial["KEY"]) != 32 | |
53 | ): | |
54 | exit("Unsupported key length: " + str(len(keymaterial["KEY"]))) | |
55 | ||
56 | # Constructs AES key by given parameters | |
57 | # Assign it to a variable to keep it in memory | |
58 | if "IV" in keymaterial.keys(): | |
59 | aes_key = AES.new( | |
60 | keymaterial["KEY"], AES.MODE_CBC, iv=keymaterial["IV"] | |
61 | ) # noqa: F841 | |
62 | else: | |
63 | aes_key = AES.new(keymaterial["KEY"], AES.MODE_CBC) # noqa: F841 | |
64 | ||
65 | while True: | |
66 | time.sleep(1) | |
67 | ||
68 | ||
69 | def parse_args(): | |
70 | parser = argparse.ArgumentParser( | |
71 | description="Construct an AES key according to specified key material and sleep forever." # noqa: E501 | |
72 | ) | |
73 | parser.add_argument( | |
74 | "keyspec", nargs="?", type=argparse.FileType("r"), default=sys.stdin | |
75 | ) | |
76 | args = parser.parse_args() | |
77 | return args | |
78 | ||
79 | ||
80 | if __name__ == "__main__": | |
81 | args = parse_args() | |
82 | construct_key(args.keyspec) |
0 | #!/bin/sh | |
1 | ||
2 | ################################################################ | |
3 | # Usage: # | |
4 | # test_aes_extraction <KEY> <IV> # | |
5 | # # | |
6 | # Example: # | |
7 | # test_aes_extraction "DebianDebianDebi" "aaaabbbbccccdddd"# | |
8 | # # | |
9 | # # | |
10 | # Note: Use either 16, 24 or 32 byte long keys and IVs. # | |
11 | ################################################################ | |
12 | ||
13 | # Exit on first failure | |
14 | set -e | |
15 | ||
16 | # Converts a ASCII string to its hex representation | |
17 | str2hex() { | |
18 | echo "$1" | sed 's/\(.\)/\1\n/g' | \ | |
19 | xargs -I {} printf "%x" "'{}" | |
20 | } | |
21 | ||
22 | # Path of the script, which is used to create AES keys | |
23 | # and dump the memory of the process performing AES construction | |
24 | GEN_DUMP="debian/tests/helpers/gen_aes_dump.sh" | |
25 | ||
26 | ||
27 | # Defines the AES key-material, used to generate the dump | |
28 | # and to serve as target value for the extraction | |
29 | if [ -z "$1" ] | |
30 | then | |
31 | echo "No key provided!" | |
32 | exit 1 | |
33 | fi | |
34 | ||
35 | KEY="$1" | |
36 | ||
37 | # Combination of the parameters to feed into the script | |
38 | if [ -z "$2" ] | |
39 | then | |
40 | KEYSPEC="KEY=$1" | |
41 | else | |
42 | KEYSPEC="KEY=$1\nIV=$2" | |
43 | fi | |
44 | ||
45 | # Memory dump to process | |
46 | DMP="${AUTOPKGTEST_TMP}/aes.dmp" | |
47 | ||
48 | # Dump memory of process, which generates key material | |
49 | sh ${GEN_DUMP} "$KEYSPEC" "$DMP" | |
50 | ||
51 | # Performs key extraction | |
52 | # This fails on kernel 5.10.0.6 and glibc 2.31-11 | |
53 | # Change threshold to 50 via `-t 50`, then it succeeds | |
54 | # https://bugs.debian.org/989179 | |
55 | ACTUAL=$(aeskeyfind -t 50 "${DMP}") # FAILS! | |
56 | ||
57 | # Checks, compare actual result with target values | |
58 | echo "${ACTUAL}" | grep -q $(str2hex "${KEY}") | |
59 | ||
60 | # Clean up | |
61 | rm ${DMP} | |
62 | ||
63 | exit 0 |
0 | #!/bin/sh | |
1 | ||
2 | set -e | |
3 | ||
4 | # Helper script to use for dumping and key extraction | |
5 | TEST_SCRIPT="debian/tests/helpers/test_aes_extraction.sh" | |
6 | ||
7 | # Defines the AES key-material | |
8 | KEY="DebianDebianDebi" | |
9 | ||
10 | # Dump memory of process, which generates key material | |
11 | sh ${TEST_SCRIPT} "$KEY" | |
12 |
0 | #!/bin/sh | |
1 | ||
2 | set -e | |
3 | ||
4 | # Helper script to use for dumping and key extraction | |
5 | TEST_SCRIPT="debian/tests/helpers/test_aes_extraction.sh" | |
6 | ||
7 | # Defines the AES key-material | |
8 | KEY="DebianDebianDebianDebianDebianDe" | |
9 | ||
10 | # Dump memory of process, which generates key material | |
11 | sh ${TEST_SCRIPT} "$KEY" | |
12 |
0 | #!/bin/sh | |
1 | ||
2 | # Exit on failure immediately | |
3 | set -e | |
4 | ||
5 | # Specifies testfile containing 1K of zeros | |
6 | DMP="${AUTOPKGTEST_TMP}/zeros.dmp" | |
7 | truncate -s 1K ${DMP} | |
8 | ||
9 | # Run aeskeyfind with zero-filled dump | |
10 | # This is needed, since aeskeyfind returns 1 upon calling without parameters | |
11 | aeskeyfind ${DMP} | |
12 | ||
13 | # Clean up | |
14 | rm ${DMP} | |
15 | ||
16 | exit 0 |