aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2015-06-09 19:05:24 +0300
committerEgor Tensin <Egor.Tensin@gmail.com>2015-06-09 19:05:24 +0300
commit9c74535b3829bea94e6a358d66e83477b9445f67 (patch)
treecdc0bce2a3779678c9b14bdc6d99d04e1abe0fe4
parenttest/800-38a.py: get rid of unittest (diff)
downloadaes-tools-9c74535b3829bea94e6a358d66e83477b9445f67.tar.gz
aes-tools-9c74535b3829bea94e6a358d66e83477b9445f67.zip
add KAT vectors from the CAVP
-rw-r--r--test/KAT_AES.zipbin0 -> 726299 bytes
-rw-r--r--test/README.md19
-rw-r--r--test/cavp.py187
3 files changed, 206 insertions, 0 deletions
diff --git a/test/KAT_AES.zip b/test/KAT_AES.zip
new file mode 100644
index 0000000..128a74c
--- /dev/null
+++ b/test/KAT_AES.zip
Binary files differ
diff --git a/test/README.md b/test/README.md
index fd0f37c..69058be 100644
--- a/test/README.md
+++ b/test/README.md
@@ -56,3 +56,22 @@ using
python 800-32a.py -r C:\build\test\Debug -e
The script writes a log file, with a short summary at the end.
+
+### From Cryptographic Algorithm Validation Program
+
+You can test the implementation against the vectors from
+[CAVP](http://csrc.nist.gov/groups/STM/cavp/) using `cavp.py`.
+The AES Known Answer Test (KAT) Vectors are used and included in `KAT_AES.zip`.
+
+The script is written in Python 3, so you need to be able to run Python 3 scripts prior to testing.
+Then you can run the script, passing the path to the directory with the required `*_encrypt.exe` and `*_decrypt.exe` files like this:
+
+ python cavp.py -r C:\build\test\Debug
+
+On older CPUs, you can make the script run the executables
+[using Intel SDE](https://github.com/egor-tensin/aesni#running-on-older-cpus)
+using
+
+ python cavp.py -r C:\build\test\Debug -e
+
+The script writes a log file, with a short summary at the end.
diff --git a/test/cavp.py b/test/cavp.py
new file mode 100644
index 0000000..3599648
--- /dev/null
+++ b/test/cavp.py
@@ -0,0 +1,187 @@
+# Copyright 2015 Egor Tensin <Egor.Tensin@gmail.com>
+# This file is licensed under the terms of the MIT License.
+# See LICENSE.txt for details.
+
+from collections import OrderedDict
+import configparser
+from datetime import datetime
+import logging
+import os.path
+import toolkit
+import zipfile
+
+class _MultiOrderedDict(OrderedDict):
+ def __setitem__(self, key, value):
+ if isinstance(value, list) and key in self:
+ self[key].extend(value)
+ else:
+ super(OrderedDict, self).__setitem__(key, value)
+
+def _gen_inputs(cls, keys, plaintexts, init_vectors):
+ if init_vectors is None:
+ init_vectors = [None for key in keys]
+ for key, plaintext, iv in zip(keys, plaintexts, init_vectors):
+ yield cls(key, [plaintext], iv)
+
+def _gen_encryption_inputs(keys, plaintexts, init_vectors):
+ return _gen_inputs(toolkit.EncryptionInput, keys, plaintexts, init_vectors)
+
+def _gen_decryption_inputs(keys, ciphertexts, init_vectors):
+ return _gen_inputs(toolkit.DecryptionInput, keys, ciphertexts, init_vectors)
+
+def _split_into_chunks(expected_output, inputs, max_len=100):
+ for i in range(0, len(inputs), max_len):
+ yield expected_output[i:i+max_len], inputs[i:i+max_len]
+
+def _assert_output(actual, expected):
+ if len(actual) != len(expected):
+ logging.error('Unexpected output length {0} (expected {1})'.format(len(actual), len(expected)))
+ return False
+ if actual != expected:
+ logging.error('Expected output:\n' + '\n'.join(expected))
+ return False
+ return True
+
+class _TestExitCode:
+ SUCCESS, FAILURE, ERROR, SKIPPED = range(4)
+
+class _TestVectorsFile:
+ def __init__(self, path, archive):
+ self._archive = archive
+ self._path = path
+ self._fn = os.path.split(path)[1]
+ self._valid = False
+ self._parse()
+
+ def valid(self):
+ return self._valid
+
+ def algorithm(self):
+ return self._algo
+
+ def mode(self):
+ return self._mode
+
+ def parse(self):
+ self._parser = configparser.ConfigParser(dict_type=_MultiOrderedDict,
+ strict=False,
+ interpolation=None,
+ empty_lines_in_values=False)
+ self._parser.read_string(self._archive.read(self._path).decode('utf-8'))
+
+ def _extract_test_data(self, section):
+ keys = self._parser.get(section, 'key')
+ plaintexts = self._parser.get(section, 'plaintext')
+ ciphertexts = self._parser.get(section, 'ciphertext')
+ init_vectors = None
+ if toolkit.mode_requires_init_vector(self.mode()):
+ init_vectors = self._parser.get(section, 'iv')
+ return keys, plaintexts, ciphertexts, init_vectors
+
+ def _run_tests(self, tool, inputs, expected_output):
+ try:
+ for expected_output_chunk, input_chunk in _split_into_chunks(expected_output, list(inputs)):
+ actual_output = tool(self.algorithm(), self.mode(), input_chunk)
+ if not _assert_output(actual_output, expected_output_chunk):
+ return _TestExitCode.FAILURE
+ return _TestExitCode.SUCCESS
+ except toolkit.ToolkitError as e:
+ logging.error('Encountered an exception, skipping...')
+ logging.exception(e)
+ return _TestExitCode.ERROR
+
+ def run_encryption_tests(self, tools):
+ logging.info('Running encryption tests...')
+ keys, plaintexts, ciphertexts, init_vectors = self._extract_test_data('ENCRYPT')
+ inputs = _gen_encryption_inputs(keys, plaintexts, init_vectors)
+ return self._run_tests(tools.run_encrypt_tool, inputs, ciphertexts)
+
+ def run_decryption_tests(self, tools):
+ logging.info('Running decryption tests...')
+ keys, plaintexts, ciphertexts, init_vectors = self._extract_test_data('DECRYPT')
+ inputs = _gen_decryption_inputs(keys, ciphertexts, init_vectors)
+ return self._run_tests(tools.run_decrypt_tool, inputs, plaintexts)
+
+ def _parse(self):
+ logging.info('Trying to parse test vectors file name \'{0}\'...'.format(self._fn))
+ stub = self._strip_extension(self._fn)
+ if not stub: return
+ stub = self._strip_algorithm(stub)
+ if not stub: return
+ stub = self._strip_method(stub)
+ if not stub: return
+ stub = self._strip_mode(stub)
+ if not stub: return
+ self._valid = True
+
+ def _strip_extension(self, stub):
+ stub, ext = os.path.splitext(stub)
+ if ext != '.rsp':
+ logging.warn('Unknown test vectors file extension \'{0}\'!'.format(self._fn))
+ return None
+ return stub
+
+ def _strip_algorithm(self, stub):
+ algo_size = stub[-3:]
+ maybe_algo = 'aes{0}'.format(algo_size)
+ self._algo = toolkit.to_supported_algorithm(maybe_algo)
+ if self._algo:
+ logging.info('\tAlgorithm: {0}'.format(self._algo))
+ return stub[0:-3]
+ else:
+ logging.warn('Unknown or unsupported algorithm \'{0}\''.format(self._fn))
+ return None
+
+ def _strip_method(self, stub):
+ for method in ('GFSbox', 'KeySbox', 'VarKey', 'VarTxt'):
+ if stub.endswith(method):
+ logging.info('\tMethod: {0}'.format(method))
+ return stub[0:len(stub) - len(method)]
+ logging.warn('Unknown or unsupported method \'{0}\''.format(self._fn))
+
+ def _strip_mode(self, stub):
+ self._mode = toolkit.to_supported_mode(stub)
+ if self._mode:
+ logging.info('\tMode: {0}'.format(self._mode))
+ return self._mode
+ else:
+ logging.warn('Unknown or unsupported mode \'{0}\''.format(self._fn))
+ return None
+
+def _parse_test_vectors_archive(tools, archive_path='KAT_AES.zip'):
+ archive = zipfile.ZipFile(archive_path)
+ exit_codes = []
+ for fn in archive.namelist():
+ member = _TestVectorsFile(fn, archive)
+ if member.valid():
+ member.parse()
+ exit_codes.append(member.run_encryption_tests(tools))
+ exit_codes.append(member.run_decryption_tests(tools))
+ else:
+ exit_codes.append(_TestExitCode.SKIPPED)
+ logging.info('Test exit codes:')
+ logging.info('\tSkipped: {0}'.format(exit_codes.count(_TestExitCode.SKIPPED)))
+ logging.info('\tError(s): {0}'.format(exit_codes.count(_TestExitCode.ERROR)))
+ logging.info('\tSucceeded: {0}'.format(exit_codes.count(_TestExitCode.SUCCESS)))
+ logging.info('\tFailed: {0}'.format(exit_codes.count(_TestExitCode.FAILURE)))
+
+if __name__ == '__main__':
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--root', '-r', required=True,
+ help='set path to *.exe files')
+ parser.add_argument('--sde', '-e', action='store_true',
+ help='use Intel SDE to run *.exe files')
+ parser.add_argument('--log', '-l', help='set log file path')
+ args = parser.parse_args()
+
+ logging_options = {'format': '%(asctime)s | %(module)s | %(levelname)s | %(message)s',
+ 'level': logging.DEBUG}
+ if args.log is None:
+ logging_options['filename'] = datetime.now().strftime('cavp_%Y-%m-%d_%H-%M-%S.log')
+ else:
+ logging_options['filename'] = args.log
+ logging.basicConfig(**logging_options)
+
+ tools = toolkit.Tools(args.root, use_sde=args.sde)
+ _parse_test_vectors_archive(tools)