Add test case generation for usage extensions when loading keys
Add test cases validating that if a stored key only had the hash policy, then after loading it psa_get_key_attributes reports that it also has the message policy, and the key can be used with message functions. Signed-off-by: gabor-mezei-arm <gabor.mezei@arm.com>
This commit is contained in:
parent
7748b6f24b
commit
672e376ba5
3 changed files with 148 additions and 5 deletions
|
@ -101,6 +101,7 @@ class PSAMacroEnumerator:
|
||||||
self.kdf_algorithms = set() #type: Set[str]
|
self.kdf_algorithms = set() #type: Set[str]
|
||||||
self.pake_algorithms = set() #type: Set[str]
|
self.pake_algorithms = set() #type: Set[str]
|
||||||
self.aead_algorithms = set() #type: Set[str]
|
self.aead_algorithms = set() #type: Set[str]
|
||||||
|
self.sign_algorithms = set() #type: Set[str]
|
||||||
# macro name -> list of argument names
|
# macro name -> list of argument names
|
||||||
self.argspecs = {} #type: Dict[str, List[str]]
|
self.argspecs = {} #type: Dict[str, List[str]]
|
||||||
# argument name -> list of values
|
# argument name -> list of values
|
||||||
|
@ -135,6 +136,7 @@ class PSAMacroEnumerator:
|
||||||
self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
|
self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
|
||||||
self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
|
self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
|
||||||
self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
|
self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
|
||||||
|
self.arguments_for['sign_alg'] = sorted(self.sign_algorithms)
|
||||||
self.arguments_for['curve'] = sorted(self.ecc_curves)
|
self.arguments_for['curve'] = sorted(self.ecc_curves)
|
||||||
self.arguments_for['group'] = sorted(self.dh_groups)
|
self.arguments_for['group'] = sorted(self.dh_groups)
|
||||||
self.arguments_for['persistence'] = sorted(self.persistence_levels)
|
self.arguments_for['persistence'] = sorted(self.persistence_levels)
|
||||||
|
@ -368,11 +370,11 @@ enumerate
|
||||||
'hash_algorithm': [self.hash_algorithms],
|
'hash_algorithm': [self.hash_algorithms],
|
||||||
'mac_algorithm': [self.mac_algorithms],
|
'mac_algorithm': [self.mac_algorithms],
|
||||||
'cipher_algorithm': [],
|
'cipher_algorithm': [],
|
||||||
'hmac_algorithm': [self.mac_algorithms],
|
'hmac_algorithm': [self.mac_algorithms, self.sign_algorithms],
|
||||||
'aead_algorithm': [self.aead_algorithms],
|
'aead_algorithm': [self.aead_algorithms],
|
||||||
'key_derivation_algorithm': [self.kdf_algorithms],
|
'key_derivation_algorithm': [self.kdf_algorithms],
|
||||||
'key_agreement_algorithm': [self.ka_algorithms],
|
'key_agreement_algorithm': [self.ka_algorithms],
|
||||||
'asymmetric_signature_algorithm': [],
|
'asymmetric_signature_algorithm': [self.sign_algorithms],
|
||||||
'asymmetric_signature_wildcard': [self.algorithms],
|
'asymmetric_signature_wildcard': [self.algorithms],
|
||||||
'asymmetric_encryption_algorithm': [],
|
'asymmetric_encryption_algorithm': [],
|
||||||
'pake_algorithm': [self.pake_algorithms],
|
'pake_algorithm': [self.pake_algorithms],
|
||||||
|
|
|
@ -107,6 +107,14 @@ class Key:
|
||||||
} #type: Dict[Expr, Expr]
|
} #type: Dict[Expr, Expr]
|
||||||
"""The extendable usage flags with the corresponding extension flags."""
|
"""The extendable usage flags with the corresponding extension flags."""
|
||||||
|
|
||||||
|
EXTENDABLE_USAGE_FLAGS_KEY_RESTRICTION = {
|
||||||
|
'PSA_KEY_USAGE_SIGN_HASH': '.*KEY_PAIR',
|
||||||
|
'PSA_KEY_USAGE_VERIFY_HASH': '.*KEY.*'
|
||||||
|
} #type: Dict[str, str]
|
||||||
|
"""The key type filter for the extendable usage flags.
|
||||||
|
The filter is a regexp.
|
||||||
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -233,10 +233,25 @@ 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."""
|
||||||
|
|
||||||
def __init__(self, *, description: str, **kwargs) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
description: str,
|
||||||
|
expected_usage: Optional[str] = None,
|
||||||
|
**kwargs
|
||||||
|
) -> None:
|
||||||
|
"""Prepare to generate a key.
|
||||||
|
|
||||||
|
* `description`: used for the the test case names
|
||||||
|
* `expected_usage`: the usage flags generated as the expected usage
|
||||||
|
flags in the test cases. When testing usage
|
||||||
|
extension the usage flags can differ in the
|
||||||
|
generated key 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 = self.original_usage #type: psa_storage.Expr
|
self.usage = psa_storage.as_expr(expected_usage) if expected_usage is not None else\
|
||||||
|
self.original_usage #type: psa_storage.Expr
|
||||||
|
|
||||||
class StorageKeyBuilder:
|
class StorageKeyBuilder:
|
||||||
def __init__(self, usage_extension: bool) -> None:
|
def __init__(self, usage_extension: bool) -> None:
|
||||||
|
@ -475,7 +490,10 @@ 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) -> List[StorageKey]:
|
def all_keys_for_usage_flags(
|
||||||
|
self,
|
||||||
|
extra_desc: Optional[str] = None
|
||||||
|
) -> List[StorageKey]:
|
||||||
"""Generate test keys covering usage flags."""
|
"""Generate test keys covering usage flags."""
|
||||||
# First generate keys without usage policy extension for
|
# First generate keys without usage policy extension for
|
||||||
# compatibility testing, then generate the keys with extension
|
# compatibility testing, then generate the keys with extension
|
||||||
|
@ -492,6 +510,121 @@ class StorageFormatV0(StorageFormat):
|
||||||
self.key_builder = prev_builder
|
self.key_builder = prev_builder
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
def keys_for_usage_extension(
|
||||||
|
self,
|
||||||
|
extendable: psa_storage.Expr,
|
||||||
|
alg: str,
|
||||||
|
key_type: str,
|
||||||
|
params: Optional[Iterable[str]] = None
|
||||||
|
) -> List[StorageKey]:
|
||||||
|
"""Generate test keys for the specified extendable usage flag,
|
||||||
|
algorithm and key type combination.
|
||||||
|
"""
|
||||||
|
keys = [] #type: List[StorageKey]
|
||||||
|
kt = crypto_knowledge.KeyType(key_type, params)
|
||||||
|
for bits in kt.sizes_to_test():
|
||||||
|
extension = StorageKey.EXTENDABLE_USAGE_FLAGS[extendable]
|
||||||
|
usage_flags = 'PSA_KEY_USAGE_EXPORT'
|
||||||
|
material_usage_flags = usage_flags + ' | ' + extendable.string
|
||||||
|
expected_usage_flags = material_usage_flags + ' | ' + extension.string
|
||||||
|
alg2 = 0
|
||||||
|
key_material = kt.key_material(bits)
|
||||||
|
usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', extendable.string)
|
||||||
|
alg_expression = re.sub(r'PSA_ALG_', r'', alg)
|
||||||
|
alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
|
||||||
|
key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
|
||||||
|
r'',
|
||||||
|
kt.expression)
|
||||||
|
description = 'extend {}: {} {} {}-bit'.format(
|
||||||
|
usage_expression, alg_expression, key_type_expression, bits)
|
||||||
|
keys.append(self.key_builder.build(
|
||||||
|
version=self.version,
|
||||||
|
id=1, lifetime=0x00000001,
|
||||||
|
type=kt.expression, bits=bits,
|
||||||
|
usage=material_usage_flags,
|
||||||
|
expected_usage=expected_usage_flags,
|
||||||
|
alg=alg, alg2=alg2,
|
||||||
|
material=key_material,
|
||||||
|
description=description))
|
||||||
|
return keys
|
||||||
|
|
||||||
|
def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
|
||||||
|
"""Match possible key types for sign algorithms."""
|
||||||
|
# To create a valid combinaton both the algorithms and key types
|
||||||
|
# must be filtered. Pair them with keywords created from its names.
|
||||||
|
incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
|
||||||
|
incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
|
||||||
|
keyword_translation = {
|
||||||
|
'ECDSA': 'ECC',
|
||||||
|
'ED[0-9]*.*' : 'EDWARDS'
|
||||||
|
}
|
||||||
|
exclusive_keywords = {
|
||||||
|
'EDWARDS': 'ECC'
|
||||||
|
}
|
||||||
|
key_types = set(self.constructors.generate_expressions(
|
||||||
|
self.constructors.key_types))
|
||||||
|
algorithms = set(self.constructors.generate_expressions(
|
||||||
|
self.constructors.sign_algorithms))
|
||||||
|
alg_with_keys = {} #type: Dict[str, List[str]]
|
||||||
|
translation_table = str.maketrans('(', '_', ')')
|
||||||
|
for alg in algorithms:
|
||||||
|
# Generate keywords from the name of the algorithm
|
||||||
|
alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
|
||||||
|
# Translate keywords for better matching with the key types
|
||||||
|
for keyword in alg_keywords.copy():
|
||||||
|
for pattern, replace in keyword_translation.items():
|
||||||
|
if re.match(pattern, keyword):
|
||||||
|
alg_keywords.remove(keyword)
|
||||||
|
alg_keywords.add(replace)
|
||||||
|
# Filter out incompatible algortihms
|
||||||
|
if not alg_keywords.isdisjoint(incompatible_alg_keyword):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for key_type in key_types:
|
||||||
|
# Generate keywords from the of the key type
|
||||||
|
key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
|
||||||
|
|
||||||
|
# Remove ambigious keywords
|
||||||
|
for keyword1, keyword2 in exclusive_keywords.items():
|
||||||
|
if keyword1 in key_type_keywords:
|
||||||
|
key_type_keywords.remove(keyword2)
|
||||||
|
|
||||||
|
if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
|
||||||
|
not key_type_keywords.isdisjoint(alg_keywords):
|
||||||
|
if alg in alg_with_keys:
|
||||||
|
alg_with_keys[alg].append(key_type)
|
||||||
|
else:
|
||||||
|
alg_with_keys[alg] = [key_type]
|
||||||
|
return alg_with_keys
|
||||||
|
|
||||||
|
def all_keys_for_usage_extension(self) -> List[StorageKey]:
|
||||||
|
"""Generate test keys for usage flag extensions."""
|
||||||
|
# Generate a key type and algorithm pair for each extendable usage
|
||||||
|
# flag to generate a valid key for exercising. The key is generated
|
||||||
|
# without usage extension to check the extension compatiblity.
|
||||||
|
keys = [] #type: List[StorageKey]
|
||||||
|
prev_builder = self.key_builder
|
||||||
|
|
||||||
|
# Generate the key without usage extension
|
||||||
|
self.key_builder = StorageKeyBuilder(usage_extension = False)
|
||||||
|
alg_with_keys = self.gather_key_types_for_sign_alg()
|
||||||
|
key_restrictions = StorageKey.EXTENDABLE_USAGE_FLAGS_KEY_RESTRICTION
|
||||||
|
# Walk through all combintion. The key types must be filtered to fit
|
||||||
|
# the specific usage flag.
|
||||||
|
keys += [key for usage in StorageKey.EXTENDABLE_USAGE_FLAGS.keys()
|
||||||
|
for alg in sorted(alg_with_keys.keys())
|
||||||
|
for key_type in sorted(filter(
|
||||||
|
lambda kt: re.match(key_restrictions[usage.string], kt),
|
||||||
|
alg_with_keys[alg]))
|
||||||
|
for key in self.keys_for_usage_extension(usage, alg, key_type)]
|
||||||
|
|
||||||
|
self.key_builder = prev_builder
|
||||||
|
return keys
|
||||||
|
|
||||||
|
def generate_all_keys(self) -> List[StorageKey]:
|
||||||
|
keys = super().generate_all_keys()
|
||||||
|
keys += self.all_keys_for_usage_extension()
|
||||||
|
return keys
|
||||||
|
|
||||||
class TestGenerator:
|
class TestGenerator:
|
||||||
"""Generate test data."""
|
"""Generate test data."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue