diff options
author | Vadim Bendebury <vbendeb@chromium.org> | 2015-11-27 07:31:00 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2015-12-03 02:21:19 -0800 |
commit | e1be8e179cf9f5e5e7e097bd081a0d92590a2a6f (patch) | |
tree | 8179285e067021cfa08b5cdd3ffa6ebb3f3142e1 /test | |
parent | 0e9cd956647cca2940d67e2ef1b6e0c35cedd145 (diff) | |
download | chrome-ec-e1be8e179cf9f5e5e7e097bd081a0d92590a2a6f.tar.gz |
cr50: test: move crypto test into its own module
This is a no-op change moving some common code out of tpmtest.py,
preparing it to support different testing modes.
BRANCH=none
BUG=chrome-os-partner:43025
TEST=the AES test still succeeds:
$ test/tpm_test/tpmtest.py
Starting MPSSE at 800 kHz
Connected to device vid:did:rid of 1ae0:0028:00
SUCCESS: AES:ECB common
SUCCESS: AES:ECB128 1
SUCCESS: AES:ECB192 1
SUCCESS: AES:ECB256 1
SUCCESS: AES:ECB256 2
SUCCESS: AES:CTR128I 1
SUCCESS: AES:CTR256I 1
Change-Id: Ia6e0e3e89f99875297da0a4f6137de5901c8ca08
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/314691
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Diffstat (limited to 'test')
-rw-r--r-- | test/tpm_test/crypto_test.py | 220 | ||||
-rwxr-xr-x | test/tpm_test/tpmtest.py | 239 | ||||
-rw-r--r-- | test/tpm_test/utils.py | 38 |
3 files changed, 267 insertions, 230 deletions
diff --git a/test/tpm_test/crypto_test.py b/test/tpm_test/crypto_test.py new file mode 100644 index 0000000000..147b5f301b --- /dev/null +++ b/test/tpm_test/crypto_test.py @@ -0,0 +1,220 @@ +#!/usr/bin/python +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module for testing cryptography functions using extended commands.""" + +from __future__ import print_function + +import struct +import xml.etree.ElementTree as ET + +import utils + +# Extension 'cryptography' subcommand codes: +AES = 0 + +# Basic crypto operations +DECRYPT = 0 +ENCRYPT = 1 + +class CryptoError(Exception): + pass + +def get_attribute(tdesc, attr_name, required=True): + """Retrieve an attribute value from an XML node. + + Args: + tdesc: an Element of the ElementTree, a test descriptor containing + necessary information to run a single encryption/description + session. + attr_name: a string, the name of the attribute to retrieve. + required: a Boolean, if True - the attribute must be present in the + descriptor, otherwise it is considered optional + Returns: + The attribute value as a string (ascii or binary) + Raises: + CryptoError: on various format errors, or in case a required attribute is + not found, the error message describes the problem. + + """ + # Fields stored in hex format by default. + default_hex = ('cipher_text', 'iv', 'key') + + data = tdesc.find(attr_name) + if data is None: + if required: + raise CryptoError('node "%s" does not have attribute "%s"' % + (tdesc.get('name'), attr_name)) + return '' + + # Attribute is present, does it have to be decoded from hex? + cell_format = data.get('format') + if not cell_format: + if attr_name in default_hex: + cell_format = 'hex' + else: + cell_format = 'ascii' + elif cell_format not in ('hex', 'ascii'): + raise CryptoError('%s:%s, unrecognizable format "%s"' % + (tdesc.get('name'), attr_name, cell_format)) + + text = ' '.join(x.strip() for x in data.text.splitlines() if x) + if cell_format == 'ascii': + return text + + # Drop spaces from hex representation. + text = text.replace(' ', '') + if len(text) & 3: + raise CryptoError('%s:%s %swrong hex number size' % + (tdesc.get('name'), attr_name, utils.hex_dump(text))) + # Convert text to binary + value = '' + for x in range(len(text)/8): + try: + value += struct.pack('<I', int('0x%s' % text[8*x:8*(x+1)], 16)) + except ValueError: + raise CryptoError('%s:%s %swrong hex value' % + (tdesc.get('name'), attr_name, utils.hex_dump(text))) + return value + + +class CryptoD(object): + """A helper object to contain an encryption scheme description. + + Attributes: + subcmd: a 16 bit max integer, the extension subcommand to be used with + this encryption scheme. + sumbodes: an optional dictionary, the keys are strings, names of the + encryption scheme submodes, the values are integers to be included in + the appropriate subcommand fields to communicat the submode to the + device. + """ + + def __init__(self, subcommand, submodes=None): + self.subcmd = subcommand + if not submodes: + submodes = {} + self.submodes = submodes + +SUPPORTED_MODES = { + 'AES': CryptoD(AES, { + 'ECB': 0, + 'CTR': 1, + 'CBC': 2, + 'GCM': 3 + }), +} + +def crypto_run(node_name, op_type, key, iv, in_text, out_text, tpm): + """Perform a basic operation(encrypt or decrypt). + + This function creates an extended command with the requested parameters, + sends it to the device, and then compares the response to the expected + value. + + Args: + node_name: a string, the name of the XML node this data comes from. The + format of the name is "<enc type>:<submode> ....", where <enc type> is + the major encryption mode (say AED or DES) and submode - a variant of + the major scheme, if exists. + + op_type: an int, encodes the operation to perform (encrypt/decrypt), passed + directly to the device as a field in the extended command + key: a binary string + iv: a binary string, might be empty + in_text: a binary string, the input of the encrypt/decrypt operation + out_text: a binary string, might be empty, the expected output of the + operation. Note that it could be shorter than actual output (padded to + integer number of blocks), in which case only its length of bytes is + compared debug_mode: a Boolean, if True - enables tracing on the console + tpm: a TPM object to send extended commands to an initialized TPM + + Returns: + The actual binary string, result of the operation, if the + comparison with the expected value was successful. + + Raises: + CryptoError: in case there were problems parsing the node name, or verifying + the operation results. + """ + mode_name, submode_name = node_name.split(':') + submode_name = submode_name[:3].upper() + + mode = SUPPORTED_MODES.get(mode_name.upper()) + if not mode: + raise CryptoError('unrecognizable mode in node "%s"' % node_name) + + submode = mode.submodes.get(submode_name, 0) + cmd = '%c' % op_type # Encrypt or decrypt + cmd += '%c' % submode # A particular type of a generic algorithm. + cmd += '%c' % len(key) + cmd += key + cmd += '%c' % len(iv) + if iv: + cmd += iv + cmd += struct.pack('>H', len(in_text)) + cmd += in_text + if tpm.debug_enabled(): + print('%d:%d cmd size' % (op_type, mode.subcmd), + len(cmd), utils.hex_dump(cmd)) + wrapped_response = tpm.command(tpm.wrap_ext_command(mode.subcmd, cmd)) + real_out_text = tpm.unwrap_ext_response(mode.subcmd, wrapped_response) + if out_text: + if len(real_out_text) > len(out_text): + real_out_text = real_out_text[:len(out_text)] # Ignore padding + if real_out_text != out_text: + if tpm.debug_enabled(): + print('Out text mismatch in node %s:\n' % node_name) + else: + raise CryptoError('Out text mismatch in node %s, operation %d:\n' + 'In text:%sExpected out text:%sReal out text:%s' % ( + node_name, op_type, + utils.hex_dump(in_text), + utils.hex_dump(out_text), + utils.hex_dump(real_out_text))) + return real_out_text + + +def crypto_test(tdesc, tpm): + """Perform a single test described in the xml file. + + The xml node contains all pertinent information about the test inputs and + outputs. + + Args: + tdesc: an Element of the ElementTree, a test descriptor containing + necessary information to run a single encryption/description + session. + tpm: a TPM object to send extended commands to an initialized TPM + Raises: + CryptoError: on various execution errors, the details are included in the + error message. + """ + node_name = tdesc.get('name') + key = get_attribute(tdesc, 'key') + if len(key) not in (16, 24, 32): + raise CryptoError('wrong key size "%s:%s"' % ( + node_name, + ''.join('%2.2x' % ord(x) for x in key))) + iv = get_attribute(tdesc, 'iv', required=False) + if iv and len(iv) != 16: + raise CryptoError('wrong iv size "%s:%s"' % ( + node_name, + ''.join('%2.2x' % ord(x) for x in iv))) + clear_text = get_attribute(tdesc, 'clear_text') + if tpm.debug_enabled(): + print('clear text size', len(clear_text)) + cipher_text = get_attribute(tdesc, 'cipher_text', required=False) + real_cipher_text = crypto_run(node_name, ENCRYPT, key, iv, + clear_text, cipher_text, tpm) + crypto_run(node_name, DECRYPT, key, iv, real_cipher_text, + clear_text, tpm) + print(utils.cursor_back() + 'SUCCESS: %s' % node_name) + +def crypto_tests(tpm, xml_file): + tree = ET.parse(xml_file) + root = tree.getroot() + for child in root: + crypto_test(child, tpm) diff --git a/test/tpm_test/tpmtest.py b/test/tpm_test/tpmtest.py index 5117f2b1ad..fe866f3665 100755 --- a/test/tpm_test/tpmtest.py +++ b/test/tpm_test/tpmtest.py @@ -3,7 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Module for testing TPM, using both conventional and extended commands.""" +"""Module for initializing and driving a SPI TPM.""" from __future__ import print_function @@ -11,31 +11,19 @@ import os import struct import sys import traceback -import xml.etree.ElementTree as ET # Suppressing pylint warning about an import not at the top of the file. The # path needs to be set *before* the last import. # pylint: disable=C6204 root_dir = os.path.dirname(os.path.abspath(sys.argv[0])) sys.path.append(os.path.join(root_dir, '..', '..', 'build', 'tpm_test')) -import ftdi_spi_tpm -# Basic crypto operations -DECRYPT = 0 -ENCRYPT = 1 +import crypto_test +import ftdi_spi_tpm # Extension command for dcypto testing EXT_CMD = 0xbaccd00a -# Extension subcommands for encryption types -AES = 0 - -if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): - cursor_back = '\x1b[1D' # Move one space to the left. -else: - cursor_back = '' - - class TpmError(Exception): pass @@ -59,6 +47,7 @@ class TPM(object): '80 01 00 00 00 0a 00 00 01 00') def __init__(self, freq=800*1000, debug_mode=False): + self._debug_enabled = debug_mode self._handle = ftdi_spi_tpm if not self._handle.FtdiSpiInit(freq, debug_mode): raise TpmError() @@ -132,229 +121,19 @@ class TPM(object): size, len(response))) return response[header_size:] - -def hex_dump(binstr): - """Convert string into its hex representation.""" - dump_lines = ['',] - i = 0 - while i < len(binstr): - strsize = min(16, len(binstr) - i) - hexstr = ' '.join('%2.2x' % ord(x) for x in binstr[i:i+strsize]) - dump_lines.append(hexstr) - i += strsize - dump_lines.append('') - return '\n'.join(dump_lines) - - -def get_attribute(tdesc, attr_name, required=True): - """Retrieve an attribute value from an XML node. - - Args: - - tdesc: an Element of the ElementTree, a test descriptor containing - necessary information to run a single encryption/description - session. - attr_name: a string, the name of the attribute to retrieve. - required: a Boolean, if True - the attribute must be present in the - descriptor, otherwise it is considered optional - Returns: - The attribute value as a string (ascii or binary) - Raises: - TpmError: on various format errors, or in case a required attribute is not - found, the error message describes the problem. - - """ - # Fields stored in hex format by default. - default_hex = ('cipher_text', 'iv', 'key') - - data = tdesc.find(attr_name) - if data is None: - if required: - raise TpmError('node "%s" does not have attribute "%s"' % - (tdesc.get('name'), attr_name)) - return '' - - # Attribute is present, does it have to be decoded from hex? - cell_format = data.get('format') - if not cell_format: - if attr_name in default_hex: - cell_format = 'hex' - else: - cell_format = 'ascii' - elif cell_format not in ('hex', 'ascii'): - raise TpmError('%s:%s, unrecognizable format "%s"' % - (tdesc.get('name'), attr_name, cell_format)) - - text = ' '.join(x.strip() for x in data.text.splitlines() if x) - if cell_format == 'ascii': - return text - - # Drop spaces from hex representation. - text = text.replace(' ', '') - if len(text) & 3: - raise TpmError('%s:%s %swrong hex number size' % - (tdesc.get('name'), attr_name, hex_dump(text))) - # Convert text to binary - value = '' - for x in range(len(text)/8): - try: - value += struct.pack('<I', int('0x%s' % text[8*x:8*(x+1)], 16)) - except ValueError: - raise TpmError('%s:%s %swrong hex value' % - (tdesc.get('name'), attr_name, hex_dump(text))) - return value - - -class CryptoD(object): - """A helper object to contain an encryption scheme description. - - Attributes: - subcmd: a 16 bit max integer, the extension subcommand to be used with - this encryption scheme. - sumbodes: an optional dictionary, the keys are strings, names of the - encryption scheme submodes, the values are integers to be included in - the appropriate subcommand fields to communicat the submode to the - device. - """ - - def __init__(self, subcommand, submodes=None): - self.subcmd = subcommand - if not submodes: - submodes = {} - self.submodes = submodes - -SUPPORTED_MODES = { - 'AES': CryptoD(AES, { - 'ECB': 0, - 'CTR': 1, - 'CBC': 2, - 'GCM': 3 - }), -} - - -def crypto_run(node_name, op_type, key, iv, in_text, out_text, tpm, debug_mode): - """Perform a basic operation(encrypt or decrypt). - - This function creates an extended command with the requested parameters, - sends it to the device, and then compares the response to the expected - value. - - Args: - node_name: a string, the name of the XML node this data comes from. The - format of the name is "<enc type>:<submode> ....", where <enc type> is - the major encryption mode (say AED or DES) and submode - a variant of - the major scheme, if exists. - - op_type: an int, encodes the operation to perform (encrypt/decrypt), passed - directly to the device as a field in the extended command - key: a binary string - iv: a binary string, might be empty - in_text: a binary string, the input of the encrypt/decrypt operation - out_text: a binary string, might be empty, the expected output of the - operation. Note that it could be shorter than actual output (padded to - integer number of blocks), in which case only its length of bytes is - compared debug_mode: a Boolean, if True - enables tracing on the console - tpm: a TPM object to send extended commands to an initialized TPM - debug_mode: a Boolean, if True - this function and the FTDI driver - generate debug messated on the console. - - Returns: - The actual binary string, result of the operation, if the - comparison with the expected value was successful. - - Raises: - TpmError: in case there were problems parsing the node name, or verifying - the operation results. - """ - mode_name, submode_name = node_name.split(':') - submode_name = submode_name[:3].upper() - - mode = SUPPORTED_MODES.get(mode_name.upper()) - if not mode: - raise TpmError('unrecognizable mode in node "%s"' % node_name) - - submode = mode.submodes.get(submode_name, 0) - cmd = '%c' % op_type # Encrypt or decrypt - cmd += '%c' % submode # A particular type of a generic algorithm. - cmd += '%c' % len(key) - cmd += key - cmd += '%c' % len(iv) - if iv: - cmd += iv - cmd += struct.pack('>H', len(in_text)) - cmd += in_text - if debug_mode: - print('%d:%d cmd size' % (op_type, mode.subcmd), len(cmd), hex_dump(cmd)) - wrapped_response = tpm.command(tpm.wrap_ext_command(mode.subcmd, cmd)) - real_out_text = tpm.unwrap_ext_response(mode.subcmd, wrapped_response) - if out_text: - if len(real_out_text) > len(out_text): - real_out_text = real_out_text[:len(out_text)] # Ignore padding - if real_out_text != out_text: - if debug_mode: - print('Out text mismatch in node %s:\n' % node_name) - else: - raise TpmError('Out text mismatch in node %s, operation %d:\n' - 'In text:%sExpected out text:%sReal out text:%s' % ( - node_name, op_type, - hex_dump(in_text), - hex_dump(out_text), - hex_dump(real_out_text))) - return real_out_text - - -def crypto_test(tdesc, tpm, debug_mode): - """Perform a single test described in the xml file. - - The xml node contains all pertinent information about the test inputs and - outputs. - - Args: - tdesc: an Element of the ElementTree, a test descriptor containing - necessary information to run a single encryption/description - session. - tpm: a TPM object to send extended commands to an initialized TPM - debug_mode: a Boolean, if True - this function and the FTDI driver - generate debug messated on the console. - Raises: - TpmError: on various execution errors, the details are included in the - error message. - """ - node_name = tdesc.get('name') - key = get_attribute(tdesc, 'key') - if len(key) not in (16, 24, 32): - raise TpmError('wrong key size "%s:%s"' % ( - node_name, - ''.join('%2.2x' % ord(x) for x in key))) - iv = get_attribute(tdesc, 'iv', required=False) - if iv and len(iv) != 16: - raise TpmError('wrong iv size "%s:%s"' % ( - node_name, - ''.join('%2.2x' % ord(x) for x in iv))) - clear_text = get_attribute(tdesc, 'clear_text') - if debug_mode: - print('clear text size', len(clear_text)) - cipher_text = get_attribute(tdesc, 'cipher_text', required=False) - real_cipher_text = crypto_run(node_name, ENCRYPT, key, iv, - clear_text, cipher_text, tpm, debug_mode) - crypto_run(node_name, DECRYPT, key, iv, real_cipher_text, - clear_text, tpm, debug_mode) - print(cursor_back + 'SUCCESS: %s' % node_name) + def debug_enabled(self): + return self._debug_enabled if __name__ == '__main__': - tree = ET.parse(os.path.join(root_dir, 'crypto_test.xml')) - root = tree.getroot() try: debug_needed = len(sys.argv) == 2 and sys.argv[1] == '-d' t = TPM(debug_mode=debug_needed) - for child in root: - crypto_test(child, t, debug_needed) - except TpmError as e: + crypto_test.crypto_tests(t, os.path.join(root_dir, 'crypto_test.xml')) + except (TpmError, crypto_test.CryptoError) as e: print() - print(e) + print('Error:', e) if debug_needed: traceback.print_exc() sys.exit(1) diff --git a/test/tpm_test/utils.py b/test/tpm_test/utils.py new file mode 100644 index 0000000000..233a97eeff --- /dev/null +++ b/test/tpm_test/utils.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Support functions for extended command based testing.""" + +import sys + +if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): + cursor_back_cmd = '\x1b[1D' # Move one space to the left. +else: + cursor_back_cmd = '' + + +def cursor_back(): + """Return a string which would move cursor one space left, if available. + + This is used to remove the remaining 'spinner' character after the test + completes and its result is printed on the same line where the 'spinner' was + spinning. + + """ + return cursor_back_cmd + + +def hex_dump(binstr): + """Convert binary string into its multiline hex representation.""" + + dump_lines = ['',] + i = 0 + while i < len(binstr): + strsize = min(16, len(binstr) - i) + hexstr = ' '.join('%2.2x' % ord(x) for x in binstr[i:i+strsize]) + dump_lines.append(hexstr) + i += strsize + dump_lines.append('') + return '\n'.join(dump_lines) |