summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2015-11-27 07:31:00 -0800
committerchrome-bot <chrome-bot@chromium.org>2015-12-03 02:21:19 -0800
commite1be8e179cf9f5e5e7e097bd081a0d92590a2a6f (patch)
tree8179285e067021cfa08b5cdd3ffa6ebb3f3142e1 /test
parent0e9cd956647cca2940d67e2ef1b6e0c35cedd145 (diff)
downloadchrome-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.py220
-rwxr-xr-xtest/tpm_test/tpmtest.py239
-rw-r--r--test/tpm_test/utils.py38
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)