Refactor handlibg of the key usage flags

Move implicit usage flags handling to the StorageKey class.
Create a subclass for test case data.

Signed-off-by: gabor-mezei-arm <gabor.mezei@arm.com>
This commit is contained in:
gabor-mezei-arm 2021-06-29 15:29:24 +02:00
parent 61739e3fd3
commit e4b7499f74
No known key found for this signature in database
GPG key ID: 106F5A41ECC305BD
2 changed files with 111 additions and 93 deletions

View file

@ -101,12 +101,6 @@ class Key:
LATEST_VERSION = 0 LATEST_VERSION = 0
"""The latest version of the storage format.""" """The latest version of the storage format."""
IMPLICIT_USAGE_FLAGS = {
'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
} #type: Dict[str, str]
"""Mapping of usage flags to the flags that they imply."""
def __init__(self, *, def __init__(self, *,
version: Optional[int] = None, version: Optional[int] = None,
id: Optional[int] = None, #pylint: disable=redefined-builtin id: Optional[int] = None, #pylint: disable=redefined-builtin
@ -114,27 +108,18 @@ class Key:
type: Exprable, #pylint: disable=redefined-builtin type: Exprable, #pylint: disable=redefined-builtin
bits: int, bits: int,
usage: Exprable, alg: Exprable, alg2: Exprable, usage: Exprable, alg: Exprable, alg2: Exprable,
material: bytes, #pylint: disable=used-before-assignment material: bytes #pylint: disable=used-before-assignment
implicit_usage: bool = True
) -> None: ) -> None:
self.version = self.LATEST_VERSION if version is None else version self.version = self.LATEST_VERSION if version is None else version
self.id = id #pylint: disable=invalid-name #type: Optional[int] self.id = id #pylint: disable=invalid-name #type: Optional[int]
self.lifetime = as_expr(lifetime) #type: Expr self.lifetime = as_expr(lifetime) #type: Expr
self.type = as_expr(type) #type: Expr self.type = as_expr(type) #type: Expr
self.bits = bits #type: int self.bits = bits #type: int
self.original_usage = as_expr(usage) #type: Expr self.usage = as_expr(usage) #type: Expr
self.updated_usage = self.original_usage #type: Expr
self.alg = as_expr(alg) #type: Expr self.alg = as_expr(alg) #type: Expr
self.alg2 = as_expr(alg2) #type: Expr self.alg2 = as_expr(alg2) #type: Expr
self.material = material #type: bytes self.material = material #type: bytes
if implicit_usage:
for flag, extension in self.IMPLICIT_USAGE_FLAGS.items():
if self.original_usage.value() & Expr(flag).value() and \
self.original_usage.value() & Expr(extension).value() == 0:
self.updated_usage = Expr(self.updated_usage.string +
' | ' + extension)
MAGIC = b'PSA\000KEY\000' MAGIC = b'PSA\000KEY\000'
@staticmethod @staticmethod
@ -166,7 +151,7 @@ class Key:
if self.version == 0: if self.version == 0:
attributes = self.pack('LHHLLL', attributes = self.pack('LHHLLL',
self.lifetime, self.type, self.bits, self.lifetime, self.type, self.bits,
self.updated_usage, self.alg, self.alg2) self.usage, self.alg, self.alg2)
material = self.pack('L', len(self.material)) + self.material material = self.pack('L', len(self.material)) + self.material
else: else:
raise NotImplementedError raise NotImplementedError

View file

@ -227,24 +227,50 @@ class NotSupported:
class StorageKey(psa_storage.Key): class StorageKey(psa_storage.Key):
"""Representation of a key for storage format testing.""" """Representation of a key for storage format testing."""
IMPLICIT_USAGE_FLAGS = {
'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
} #type: Dict[str, str]
"""Mapping of usage flags to the flags that they imply."""
def __init__(
self,
usage: str,
without_implicit_usage: Optional[bool] = False,
**kwargs
) -> None:
"""Prepare to generate a key.
* `usage` : The usage flags used for the key.
* `without_implicit_usage`: Flag to defide to apply the usage extension
"""
super().__init__(usage=usage,**kwargs)
if not without_implicit_usage:
for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
if self.usage.value() & psa_storage.Expr(flag).value() and \
self.usage.value() & psa_storage.Expr(implicit).value() == 0:
self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
class StorageTestData(StorageKey):
"""Representation of test case data for storage format testing."""
def __init__( def __init__(
self, self,
description: str, description: str,
expected_usage: Optional[str] = None, expected_usage: Optional[str] = None,
**kwargs **kwargs
) -> None: ) -> None:
"""Prepare to generate a key. """Prepare to generate test data
* `description` : used for the the test case names * `description` : used for the the test case names
* `implicit_usage`: the usage flags generated as the expected usage * `expected_usage`: the usage flags generated as the expected usage flags
flags in the test cases. When testing implicit in the test cases. CAn differ from the usage flags
usage flags, they can differ in the generated keys stored in the keys because of the usage flags extension.
and the expected usage flags in the test cases.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self.description = description #type: str self.description = description #type: str
self.usage = psa_storage.as_expr(expected_usage) if expected_usage is not None else\ self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
self.original_usage #type: psa_storage.Expr
class StorageFormat: class StorageFormat:
"""Storage format stability test cases.""" """Storage format stability test cases."""
@ -263,7 +289,7 @@ class StorageFormat:
self.version = version #type: int self.version = version #type: int
self.forward = forward #type: bool self.forward = forward #type: bool
def make_test_case(self, key: StorageKey) -> test_case.TestCase: def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
"""Construct a storage format test case for the given key. """Construct a storage format test case for the given key.
If ``forward`` is true, generate a forward compatibility test case: If ``forward`` is true, generate a forward compatibility test case:
@ -277,7 +303,7 @@ class StorageFormat:
tc.set_description('PSA storage {}: {}'.format(verb, key.description)) tc.set_description('PSA storage {}: {}'.format(verb, key.description))
dependencies = automatic_dependencies( dependencies = automatic_dependencies(
key.lifetime.string, key.type.string, key.lifetime.string, key.type.string,
key.usage.string, key.alg.string, key.alg2.string, key.expected_usage, key.alg.string, key.alg2.string,
) )
dependencies = finish_family_dependencies(dependencies, key.bits) dependencies = finish_family_dependencies(dependencies, key.bits)
tc.set_dependencies(dependencies) tc.set_dependencies(dependencies)
@ -298,7 +324,7 @@ class StorageFormat:
extra_arguments = [' | '.join(flags) if flags else '0'] extra_arguments = [' | '.join(flags) if flags else '0']
tc.set_arguments([key.lifetime.string, tc.set_arguments([key.lifetime.string,
key.type.string, str(key.bits), key.type.string, str(key.bits),
key.usage.string, key.alg.string, key.alg2.string, key.expected_usage, key.alg.string, key.alg2.string,
'"' + key.material.hex() + '"', '"' + key.material.hex() + '"',
'"' + key.hex() + '"', '"' + key.hex() + '"',
*extra_arguments]) *extra_arguments])
@ -307,21 +333,22 @@ class StorageFormat:
def key_for_lifetime( def key_for_lifetime(
self, self,
lifetime: str, lifetime: str,
) -> StorageKey: ) -> StorageTestData:
"""Construct a test key for the given lifetime.""" """Construct a test key for the given lifetime."""
short = lifetime short = lifetime
short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION', short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
r'', short) r'', short)
short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short) short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
description = 'lifetime: ' + short description = 'lifetime: ' + short
return StorageKey(version=self.version, key = StorageTestData(version=self.version,
id=1, lifetime=lifetime, id=1, lifetime=lifetime,
type='PSA_KEY_TYPE_RAW_DATA', bits=8, type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0, usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
material=b'L', material=b'L',
description=description) description=description)
return key
def all_keys_for_lifetimes(self) -> Iterator[StorageKey]: def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
"""Generate test keys covering lifetimes.""" """Generate test keys covering lifetimes."""
lifetimes = sorted(self.constructors.lifetimes) lifetimes = sorted(self.constructors.lifetimes)
expressions = self.constructors.generate_expressions(lifetimes) expressions = self.constructors.generate_expressions(lifetimes)
@ -340,32 +367,34 @@ class StorageFormat:
usage_flags: List[str], usage_flags: List[str],
short: Optional[str] = None, short: Optional[str] = None,
test_implicit_usage: Optional[bool] = False test_implicit_usage: Optional[bool] = False
) -> Iterator[StorageKey]: ) -> Iterator[StorageTestData]:
"""Construct a test key for the given key usage.""" """Construct a test key for the given key usage."""
usage = ' | '.join(usage_flags) if usage_flags else '0' usage = ' | '.join(usage_flags) if usage_flags else '0'
if short is None: if short is None:
short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage) short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
extra_desc = ' with implication' if test_implicit_usage else '' extra_desc = ' with implication' if test_implicit_usage else ''
description = 'usage' + extra_desc + ': ' + short description = 'usage' + extra_desc + ': ' + short
yield StorageKey(version=self.version, key1 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8, type='PSA_KEY_TYPE_RAW_DATA', bits=8,
expected_usage=usage,
usage=usage, alg=0, alg2=0, usage=usage, alg=0, alg2=0,
material=b'K', material=b'K',
description=description, description=description)
implicit_usage=True) yield key1
if test_implicit_usage: if test_implicit_usage:
description = 'usage without implication' + ': ' + short description = 'usage without implication' + ': ' + short
yield StorageKey(version=self.version, key2 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8, type='PSA_KEY_TYPE_RAW_DATA', bits=8,
without_implicit_usage=True,
usage=usage, alg=0, alg2=0, usage=usage, alg=0, alg2=0,
material=b'K', material=b'K',
description=description, description=description)
implicit_usage=False) yield key2
def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageKey]:
"""Generate test keys covering usage flags.""" """Generate test keys covering usage flags."""
known_flags = sorted(self.constructors.key_usage_flags) known_flags = sorted(self.constructors.key_usage_flags)
yield from self.key_for_usage_flags(['0'], **kwargs) yield from self.key_for_usage_flags(['0'], **kwargs)
@ -375,11 +404,11 @@ class StorageFormat:
known_flags[1:] + [known_flags[0]]): known_flags[1:] + [known_flags[0]]):
yield from self.key_for_usage_flags([flag1, flag2], **kwargs) yield from self.key_for_usage_flags([flag1, flag2], **kwargs)
def generate_key_for_all_usage_flags(self) -> Iterator[StorageKey]: def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
known_flags = sorted(self.constructors.key_usage_flags) known_flags = sorted(self.constructors.key_usage_flags)
yield from self.key_for_usage_flags(known_flags, short='all known') yield from self.key_for_usage_flags(known_flags, short='all known')
def all_keys_for_usage_flags(self) -> Iterator[StorageKey]: def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
yield from self.generate_keys_for_usage_flags() yield from self.generate_keys_for_usage_flags()
yield from self.generate_key_for_all_usage_flags() yield from self.generate_key_for_all_usage_flags()
@ -387,7 +416,7 @@ class StorageFormat:
self, self,
key_type: str, key_type: str,
params: Optional[Iterable[str]] = None params: Optional[Iterable[str]] = None
) -> Iterator[StorageKey]: ) -> Iterator[StorageTestData]:
"""Generate test keys for the given key type. """Generate test keys for the given key type.
For key types that depend on a parameter (e.g. elliptic curve family), For key types that depend on a parameter (e.g. elliptic curve family),
@ -404,20 +433,21 @@ class StorageFormat:
r'', r'',
kt.expression) kt.expression)
description = 'type: {} {}-bit'.format(short_expression, bits) description = 'type: {} {}-bit'.format(short_expression, bits)
yield StorageKey(version=self.version, key = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type=kt.expression, bits=bits, type=kt.expression, bits=bits,
usage=usage_flags, alg=alg, alg2=alg2, usage=usage_flags, alg=alg, alg2=alg2,
material=key_material, material=key_material,
description=description) description=description)
yield key
def all_keys_for_types(self) -> Iterator[StorageKey]: def all_keys_for_types(self) -> Iterator[StorageTestData]:
"""Generate test keys covering key types and their representations.""" """Generate test keys covering key types and their representations."""
key_types = sorted(self.constructors.key_types) key_types = sorted(self.constructors.key_types)
for key_type in self.constructors.generate_expressions(key_types): for key_type in self.constructors.generate_expressions(key_types):
yield from self.keys_for_type(key_type) yield from self.keys_for_type(key_type)
def keys_for_algorithm(self, alg: str) -> Iterator[StorageKey]: def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
"""Generate test keys for the specified algorithm.""" """Generate test keys for the specified algorithm."""
# For now, we don't have information on the compatibility of key # For now, we don't have information on the compatibility of key
# types and algorithms. So we just test the encoding of algorithms, # types and algorithms. So we just test the encoding of algorithms,
@ -425,28 +455,30 @@ class StorageFormat:
descr = re.sub(r'PSA_ALG_', r'', alg) descr = re.sub(r'PSA_ALG_', r'', alg)
descr = re.sub(r',', r', ', re.sub(r' +', r'', descr)) descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
usage = 'PSA_KEY_USAGE_EXPORT' usage = 'PSA_KEY_USAGE_EXPORT'
yield StorageKey(version=self.version, key1 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8, type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage=usage, alg=alg, alg2=0, usage=usage, alg=alg, alg2=0,
material=b'K', material=b'K',
description='alg: ' + descr) description='alg: ' + descr)
yield StorageKey(version=self.version, yield key1
key2 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8, type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage=usage, alg=0, alg2=alg, usage=usage, alg=0, alg2=alg,
material=b'L', material=b'L',
description='alg2: ' + descr) description='alg2: ' + descr)
yield key2
def all_keys_for_algorithms(self) -> Iterator[StorageKey]: def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
"""Generate test keys covering algorithm encodings.""" """Generate test keys covering algorithm encodings."""
algorithms = sorted(self.constructors.algorithms) algorithms = sorted(self.constructors.algorithms)
for alg in self.constructors.generate_expressions(algorithms): for alg in self.constructors.generate_expressions(algorithms):
yield from self.keys_for_algorithm(alg) yield from self.keys_for_algorithm(alg)
def generate_all_keys(self) -> List[StorageKey]: def generate_all_keys(self) -> List[StorageTestData]:
"""Generate all keys for the test cases.""" """Generate all keys for the test cases."""
keys = [] #type: List[StorageKey] keys = [] #type: List[StorageTestData]
keys += self.all_keys_for_lifetimes() keys += self.all_keys_for_lifetimes()
keys += self.all_keys_for_usage_flags() keys += self.all_keys_for_usage_flags()
keys += self.all_keys_for_types() keys += self.all_keys_for_types()
@ -479,7 +511,7 @@ class StorageFormatV0(StorageFormat):
def __init__(self, info: Information) -> None: def __init__(self, info: Information) -> None:
super().__init__(info, 0, False) super().__init__(info, 0, False)
def all_keys_for_usage_flags(self) -> Iterator[StorageKey]: def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
"""Generate test keys covering usage flags.""" """Generate test keys covering usage flags."""
yield from self.generate_keys_for_usage_flags(test_implicit_usage=True) yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
yield from self.generate_key_for_all_usage_flags() yield from self.generate_key_for_all_usage_flags()
@ -489,7 +521,7 @@ class StorageFormatV0(StorageFormat):
implyer_usage: str, implyer_usage: str,
alg: str, alg: str,
key_type: crypto_knowledge.KeyType key_type: crypto_knowledge.KeyType
) -> StorageKey: ) -> StorageTestData:
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
"""Generate test keys for the specified implicit usage flag, """Generate test keys for the specified implicit usage flag,
algorithm and key type combination. algorithm and key type combination.
@ -509,15 +541,16 @@ class StorageFormatV0(StorageFormat):
key_type.expression) key_type.expression)
description = 'implied by {}: {} {} {}-bit'.format( description = 'implied by {}: {} {} {}-bit'.format(
usage_expression, alg_expression, key_type_expression, bits) usage_expression, alg_expression, key_type_expression, bits)
return StorageKey(version=self.version, key = StorageTestData(version=self.version,
id=1, lifetime=0x00000001, id=1, lifetime=0x00000001,
type=key_type.expression, bits=bits, type=key_type.expression, bits=bits,
usage=material_usage_flags, usage=material_usage_flags,
expected_usage=expected_usage_flags, expected_usage=expected_usage_flags,
without_implicit_usage=True,
alg=alg, alg2=alg2, alg=alg, alg2=alg2,
material=key_material, material=key_material,
description=description, description=description)
implicit_usage=False) return key
def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]: def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@ -567,7 +600,7 @@ class StorageFormatV0(StorageFormat):
alg_with_keys[alg] = [key_type] alg_with_keys[alg] = [key_type]
return alg_with_keys return alg_with_keys
def all_keys_for_implicit_usage(self) -> Iterator[StorageKey]: def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
"""Generate test keys for usage flag extensions.""" """Generate test keys for usage flag extensions."""
# Generate a key type and algorithm pair for each extendable usage # Generate a key type and algorithm pair for each extendable usage
# flag to generate a valid key for exercising. The key is generated # flag to generate a valid key for exercising. The key is generated
@ -582,7 +615,7 @@ class StorageFormatV0(StorageFormat):
if kt.is_valid_for_signature(usage): if kt.is_valid_for_signature(usage):
yield self.keys_for_implicit_usage(usage, alg, kt) yield self.keys_for_implicit_usage(usage, alg, kt)
def generate_all_keys(self) -> List[StorageKey]: def generate_all_keys(self) -> List[StorageTestData]:
keys = super().generate_all_keys() keys = super().generate_all_keys()
keys += self.all_keys_for_implicit_usage() keys += self.all_keys_for_implicit_usage()
return keys return keys