From fe6375e0f23757eb65c1ad491ab625afda0ee821 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 12 Dec 2015 13:21:43 +0100 Subject: Add botan encryption cli app --- src/scripts/cli_tests.py | 115 +++++++++++++++++++++++++++++++++++++++++++++++ src/scripts/vecparser.py | 51 +++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100755 src/scripts/cli_tests.py create mode 100644 src/scripts/vecparser.py (limited to 'src/scripts') diff --git a/src/scripts/cli_tests.py b/src/scripts/cli_tests.py new file mode 100755 index 000000000..b86d37b1b --- /dev/null +++ b/src/scripts/cli_tests.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import binascii +import collections +import unittest +import argparse +import re +import subprocess +import vecparser +import sys + +cli_binary = "" +testdata = {} + +class TestSequence(unittest.TestCase): + pass + +def create_test(data): + def do_test_expected(self): + iv = data['Nonce'] + key = data['Key'] + ad = data['AD'] if 'AD' in data else "" + plaintext = data['In'].lower() + ciphertext = data['Out'].lower() + algorithm = data['Algorithm'] + direction = data['Direction'] + + if algorithm == "AES-128/GCM": + mode = "aes-128-gcm" + elif algorithm == "AES-192/GCM": + mode = "aes-192-gcm" + elif algorithm == "AES-256/GCM": + mode = "aes-256-gcm" + else: raise Exception("Unknown algorithm: '" + algorithm + "'") + + cmd = [ + cli_binary, + "encryption", + "--mode=%s" % mode, + "--iv=%s" % iv, + "--ad=%s" % ad, + "--key=%s" % key] + if direction == "decrypt": + cmd += ['--decrypt'] + # out_raw = subprocess.check_output(cmd) + + if direction == "decrypt": + invalue = ciphertext + else: + invalue = plaintext + + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + out_raw = p.communicate(input=binascii.unhexlify(invalue))[0] + out = binascii.hexlify(out_raw).decode("UTF-8").lower() + + # Renamings + if direction == "decrypt": + expected = plaintext + else: + expected = ciphertext + actual = out + self.assertEqual(expected, actual) + return do_test_expected + +def get_testdata(document): + out = collections.OrderedDict() + for algorithm in document: + if algorithm in ['AES-128/GCM', 'AES-192/GCM', 'AES-256/GCM']: + testcase_number = 0 + for testcase in document[algorithm]: + testcase_number += 1 + for direction in ['encrypt', 'decrypt']: + testname = "%s no %d (%s)" % (algorithm.lower(), testcase_number, direction) + testname = re.sub("[^a-z0-9\-]", "_", testname) + testname = re.sub("_+", "_", testname) + testname = testname.strip("_") + out[testname] = {} + for key in testcase: + value = testcase[key] + out[testname][key] = value + out[testname]['Algorithm'] = algorithm + out[testname]['Direction'] = direction + return out + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('cli_binary', + help='path to the botan cli binary') + parser.add_argument('unittest_args', nargs="*") + args = parser.parse_args() + + cli_binary = args.cli_binary + + vecfile = vecparser.VecDocument("src/tests/data/aead/gcm.vec") + #data = vecfile.get_data() + #for algo in data: + # print(algo) + # i = 0 + # for testcase in data[algo]: + # i += 1 + # print(str(i) + ":", testcase) + + testdata = get_testdata(vecfile.get_data()) + #for testname in testdata: + # print(testname) + # for key in testdata[testname]: + # print(" " + key + ": " + testdata[testname][key]) + for testname in testdata: + test_method = create_test (testdata[testname]) + test_method.__name__ = 'test_%s' % testname + setattr(TestSequence, test_method.__name__, test_method) + + # Hand over sys.argv[0] and unittest_args to the testing framework + sys.argv[1:] = args.unittest_args + unittest.main() diff --git a/src/scripts/vecparser.py b/src/scripts/vecparser.py new file mode 100644 index 000000000..2f59a0ea2 --- /dev/null +++ b/src/scripts/vecparser.py @@ -0,0 +1,51 @@ +from collections import OrderedDict +import re + +class VecDocument: + data = OrderedDict() + + def __init__(self, filepath): + last_testcase_number = 1 + current_testcase_number = 1 + current_group_name = "" + last_group_name = "" + current_testcase = {} + + PATTERN_GROUPHEADER = "^\[(.+)\]$" + PATTERN_KEYVALUE = "^\s*([a-zA-Z]+)\s*=(.*)$" + + with open(filepath, 'r') as f: + # Append one empty line to simplify parsing + lines = f.read().splitlines() + ["\n"] + + for line in lines: + line = line.strip() + if line.startswith("#"): + pass # Skip + elif line == "": + current_testcase_number += 1 + elif re.match(PATTERN_GROUPHEADER, line): + match = re.match(PATTERN_GROUPHEADER, line) + current_group_name = match.group(1) + elif re.match(PATTERN_KEYVALUE, line): + match = re.match(PATTERN_KEYVALUE, line) + key = match.group(1) + value = match.group(2).strip() + current_testcase[key] = value + + if current_testcase_number != last_testcase_number: + if not current_group_name in self.data: + self.data[current_group_name] = [] + if len(current_testcase) != 0: + self.data[current_group_name].append(current_testcase) + current_testcase = {} + last_testcase_number = current_testcase_number + + if current_group_name != last_group_name: + last_group_name = current_group_name + # Reset testcase number + last_testcase_number = 1 + current_testcase_number = 1 + + def get_data(self): + return self.data -- cgit v1.2.3 From 6d8104fc2641b6b1cda005d24953bca7009cf27b Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sat, 26 Dec 2015 21:48:47 +0100 Subject: Fix bug in vecparser: member must not be static --- src/scripts/vecparser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/scripts') diff --git a/src/scripts/vecparser.py b/src/scripts/vecparser.py index 2f59a0ea2..35ee626b8 100644 --- a/src/scripts/vecparser.py +++ b/src/scripts/vecparser.py @@ -2,9 +2,8 @@ from collections import OrderedDict import re class VecDocument: - data = OrderedDict() - def __init__(self, filepath): + self.data = OrderedDict() last_testcase_number = 1 current_testcase_number = 1 current_group_name = "" -- cgit v1.2.3 From 43dc17a7c7396e13fe51ba1c4b54e04631e2f7d9 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 28 Dec 2015 15:08:51 +0100 Subject: Fix whitespace errors --- src/scripts/cli_tests.py | 4 ++-- src/scripts/vecparser.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/scripts') diff --git a/src/scripts/cli_tests.py b/src/scripts/cli_tests.py index b86d37b1b..07e5e339a 100755 --- a/src/scripts/cli_tests.py +++ b/src/scripts/cli_tests.py @@ -47,7 +47,7 @@ def create_test(data): if direction == "decrypt": invalue = ciphertext else: - invalue = plaintext + invalue = plaintext p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) out_raw = p.communicate(input=binascii.unhexlify(invalue))[0] @@ -57,7 +57,7 @@ def create_test(data): if direction == "decrypt": expected = plaintext else: - expected = ciphertext + expected = ciphertext actual = out self.assertEqual(expected, actual) return do_test_expected diff --git a/src/scripts/vecparser.py b/src/scripts/vecparser.py index 35ee626b8..707a58920 100644 --- a/src/scripts/vecparser.py +++ b/src/scripts/vecparser.py @@ -30,7 +30,7 @@ class VecDocument: match = re.match(PATTERN_KEYVALUE, line) key = match.group(1) value = match.group(2).strip() - current_testcase[key] = value + current_testcase[key] = value if current_testcase_number != last_testcase_number: if not current_group_name in self.data: -- cgit v1.2.3 From 539f8dd4eb5a1e954474ab6a7f2cf462868521eb Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 8 Mar 2016 00:05:55 +0100 Subject: Add more ciphers --- src/cli/encryption.cpp | 26 +++++++++++++++++----- src/scripts/cli_tests.py | 58 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 13 deletions(-) (limited to 'src/scripts') diff --git a/src/cli/encryption.cpp b/src/cli/encryption.cpp index 34891be5a..d2bdc7cad 100644 --- a/src/cli/encryption.cpp +++ b/src/cli/encryption.cpp @@ -13,6 +13,7 @@ #include #include +#include using namespace Botan; @@ -23,11 +24,23 @@ namespace { auto VALID_MODES = std::map{ // Don't add algorithms here without extending tests // in `src/scripts/cli_tests.py` + { "aes-128-cfb", "AES-128/CFB" }, + { "aes-192-cfb", "AES-192/CFB" }, + { "aes-256-cfb", "AES-256/CFB" }, { "aes-128-gcm", "AES-128/GCM" }, { "aes-192-gcm", "AES-192/GCM" }, { "aes-256-gcm", "AES-256/GCM" }, + { "aes-128-ocb", "AES-128/OCB" }, + { "aes-128-xts", "AES-128/XTS" }, + { "aes-256-xts", "AES-256/XTS" }, }; +bool is_aead(const std::string &cipher) + { + return cipher.find("/GCM") != std::string::npos + || cipher.find("/OCB") != std::string::npos; + } + secure_vector do_crypt(const std::string &cipher, const secure_vector &input, const SymmetricKey &key, @@ -46,7 +59,7 @@ secure_vector do_crypt(const std::string &cipher, processor->set_key(key); // Set associated data - if (cipher.find("/GCM") != std::string::npos) + if (is_aead(cipher)) { auto aead_processor = std::dynamic_pointer_cast(processor); if(!aead_processor) throw std::runtime_error("Cipher algorithm not could not be converted to AEAD"); @@ -91,11 +104,12 @@ class Encryption : public Command std::string mode = get_arg_or("mode", ""); if (!VALID_MODES.count(mode)) { - std::cout << "Invalid mode: '" << mode << "'\n" - << "valid modes are:"; - for (auto valid_mode : VALID_MODES) std::cout << " " << valid_mode.first; - std::cout << std::endl; - return; + std::ostringstream error; + error << "Invalid mode: '" << mode << "'\n" + << "valid modes are:"; + for (auto valid_mode : VALID_MODES) error << " " << valid_mode.first; + + throw CLI_Usage_Error(error.str()); } std::string key_hex = get_arg("key"); diff --git a/src/scripts/cli_tests.py b/src/scripts/cli_tests.py index 07e5e339a..ce96a43f7 100755 --- a/src/scripts/cli_tests.py +++ b/src/scripts/cli_tests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import binascii -import collections +from collections import OrderedDict import unittest import argparse import re @@ -12,6 +12,23 @@ import sys cli_binary = "" testdata = {} +SUPPORTED_ALGORITHMS = [ + 'AES-128/CFB', + 'AES-192/CFB', + 'AES-256/CFB', + 'AES-128/GCM', + 'AES-192/GCM', + 'AES-256/GCM', + 'AES-128/OCB', + 'AES-128/XTS', + 'AES-256/XTS' +] + +def append_ordered(base, additional_elements): + for key in additional_elements: + value = additional_elements[key] + base[key] = value + class TestSequence(unittest.TestCase): pass @@ -25,12 +42,28 @@ def create_test(data): algorithm = data['Algorithm'] direction = data['Direction'] - if algorithm == "AES-128/GCM": + # CFB + if algorithm == "AES-128/CFB": + mode = "aes-128-cfb" + elif algorithm == "AES-192/CFB": + mode = "aes-192-cfb" + elif algorithm == "AES-256/CFB": + mode = "aes-256-cfb" + # GCM + elif algorithm == "AES-128/GCM": mode = "aes-128-gcm" elif algorithm == "AES-192/GCM": mode = "aes-192-gcm" elif algorithm == "AES-256/GCM": mode = "aes-256-gcm" + # OCB + elif algorithm == "AES-128/OCB": + mode = "aes-128-ocb" + # XTS + elif algorithm == "AES-128/XTS": + mode = "aes-128-xts" + elif algorithm == "AES-256/XTS": + mode = "aes-256-xts" else: raise Exception("Unknown algorithm: '" + algorithm + "'") cmd = [ @@ -49,6 +82,8 @@ def create_test(data): else: invalue = plaintext + #print(cmd) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) out_raw = p.communicate(input=binascii.unhexlify(invalue))[0] out = binascii.hexlify(out_raw).decode("UTF-8").lower() @@ -63,14 +98,15 @@ def create_test(data): return do_test_expected def get_testdata(document): - out = collections.OrderedDict() + out = OrderedDict() for algorithm in document: - if algorithm in ['AES-128/GCM', 'AES-192/GCM', 'AES-256/GCM']: + if algorithm in SUPPORTED_ALGORITHMS: testcase_number = 0 for testcase in document[algorithm]: testcase_number += 1 for direction in ['encrypt', 'decrypt']: - testname = "%s no %d (%s)" % (algorithm.lower(), testcase_number, direction) + testname = "{} no {:0>3} ({})".format( + algorithm.lower(), testcase_number, direction) testname = re.sub("[^a-z0-9\-]", "_", testname) testname = re.sub("_+", "_", testname) testname = testname.strip("_") @@ -91,7 +127,10 @@ if __name__ == '__main__': cli_binary = args.cli_binary - vecfile = vecparser.VecDocument("src/tests/data/aead/gcm.vec") + vecfile_cfb = vecparser.VecDocument("src/tests/data/modes/cfb.vec") + vecfile_gcm = vecparser.VecDocument("src/tests/data/aead/gcm.vec") + vecfile_ocb = vecparser.VecDocument("src/tests/data/aead/ocb.vec") + vecfile_xts = vecparser.VecDocument("src/tests/data/modes/xts.vec") #data = vecfile.get_data() #for algo in data: # print(algo) @@ -100,7 +139,12 @@ if __name__ == '__main__': # i += 1 # print(str(i) + ":", testcase) - testdata = get_testdata(vecfile.get_data()) + testdata = OrderedDict(); + append_ordered(testdata, get_testdata(vecfile_cfb.get_data())) + append_ordered(testdata, get_testdata(vecfile_gcm.get_data())) + append_ordered(testdata, get_testdata(vecfile_ocb.get_data())) + append_ordered(testdata, get_testdata(vecfile_xts.get_data())) + #for testname in testdata: # print(testname) # for key in testdata[testname]: -- cgit v1.2.3 From 7fe500991e84c93d082e799c89df212aee9aa5ed Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sun, 16 Apr 2017 19:12:58 +0200 Subject: Some linting cleanups for cli_tests.py --- src/scripts/cli_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/scripts') diff --git a/src/scripts/cli_tests.py b/src/scripts/cli_tests.py index ce96a43f7..757de654d 100755 --- a/src/scripts/cli_tests.py +++ b/src/scripts/cli_tests.py @@ -6,11 +6,11 @@ import unittest import argparse import re import subprocess -import vecparser import sys +import vecparser + cli_binary = "" -testdata = {} SUPPORTED_ALGORITHMS = [ 'AES-128/CFB', @@ -106,8 +106,8 @@ def get_testdata(document): testcase_number += 1 for direction in ['encrypt', 'decrypt']: testname = "{} no {:0>3} ({})".format( - algorithm.lower(), testcase_number, direction) - testname = re.sub("[^a-z0-9\-]", "_", testname) + algorithm.lower(), testcase_number, direction) + testname = re.sub("[^-a-z0-9-]", "_", testname) testname = re.sub("_+", "_", testname) testname = testname.strip("_") out[testname] = {} @@ -139,7 +139,7 @@ if __name__ == '__main__': # i += 1 # print(str(i) + ":", testcase) - testdata = OrderedDict(); + testdata = OrderedDict() append_ordered(testdata, get_testdata(vecfile_cfb.get_data())) append_ordered(testdata, get_testdata(vecfile_gcm.get_data())) append_ordered(testdata, get_testdata(vecfile_ocb.get_data())) @@ -150,7 +150,7 @@ if __name__ == '__main__': # for key in testdata[testname]: # print(" " + key + ": " + testdata[testname][key]) for testname in testdata: - test_method = create_test (testdata[testname]) + test_method = create_test(testdata[testname]) test_method.__name__ = 'test_%s' % testname setattr(TestSequence, test_method.__name__, test_method) -- cgit v1.2.3 From bb13e9125119c4eecff7e4fe03bd620a5f145ce6 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Sun, 16 Apr 2017 21:45:13 +0200 Subject: Run cli tests on Travis --- src/scripts/ci/travis/build.sh | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/scripts') diff --git a/src/scripts/ci/travis/build.sh b/src/scripts/ci/travis/build.sh index 71d33d1bb..4c6730066 100755 --- a/src/scripts/ci/travis/build.sh +++ b/src/scripts/ci/travis/build.sh @@ -6,6 +6,7 @@ MAKE_PREFIX=() TEST_PREFIX=() TEST_EXE=./botan-test TEST_FLAGS=() +CLI_EXE=./botan CFG_FLAGS=(--prefix=/tmp/botan-installation --cc=$CC --os=$TRAVIS_OS_NAME) CC_BIN=$CXX @@ -179,6 +180,12 @@ else time "${TEST_CMD[@]}" fi +if [ "$BUILD_MODE" = "static" ] || [ "$BUILD_MODE" = "mini-static" ] || [ "$BUILD_MODE" = "shared" ] || [ "$BUILD_MODE" = "mini-shared" ] +then + echo "Running cli tests ..." + ./src/scripts/cli_tests.py "$CLI_EXE" +fi + # Run Python tests (need shared libs) if [ "$BUILD_MODE" = "shared" ] || [ "$BUILD_MODE" = "coverage" ]; then -- cgit v1.2.3 From 26475e2075f30c2348f5dfd1982464b90f5bc09a Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 18 Apr 2017 17:37:00 +0200 Subject: Add BOTAN_HAS_AEAD_MODES requirement for encryption cli --- src/cli/encryption.cpp | 2 +- src/scripts/ci/travis/build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/scripts') diff --git a/src/cli/encryption.cpp b/src/cli/encryption.cpp index 86a88cd80..7bf7ae678 100644 --- a/src/cli/encryption.cpp +++ b/src/cli/encryption.cpp @@ -6,7 +6,7 @@ #include "cli.h" -#if defined(BOTAN_HAS_AES) +#if defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_AEAD_MODES) #include #include diff --git a/src/scripts/ci/travis/build.sh b/src/scripts/ci/travis/build.sh index 4c6730066..36517738b 100755 --- a/src/scripts/ci/travis/build.sh +++ b/src/scripts/ci/travis/build.sh @@ -180,7 +180,7 @@ else time "${TEST_CMD[@]}" fi -if [ "$BUILD_MODE" = "static" ] || [ "$BUILD_MODE" = "mini-static" ] || [ "$BUILD_MODE" = "shared" ] || [ "$BUILD_MODE" = "mini-shared" ] +if [ "$BUILD_MODE" = "static" ] || [ "$BUILD_MODE" = "shared" ] then echo "Running cli tests ..." ./src/scripts/cli_tests.py "$CLI_EXE" -- cgit v1.2.3