mirror of
https://github.com/Mbed-TLS/mbedtls.git
synced 2025-07-30 22:43:08 +03:00
Merge pull request #4605 from gabor-mezei-arm/3267_sign_verify_key_policies
[Backport 2.x] Key policy extension for PSA_KEY_USAGE_SIGN/VERIFY_HASH
This commit is contained in:
@ -227,9 +227,50 @@ class NotSupported:
|
||||
class StorageKey(psa_storage.Key):
|
||||
"""Representation of a key for storage format testing."""
|
||||
|
||||
def __init__(self, *, description: str, **kwargs) -> None:
|
||||
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__(
|
||||
self,
|
||||
description: str,
|
||||
expected_usage: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""Prepare to generate test data
|
||||
|
||||
* `description` : used for the the test case names
|
||||
* `expected_usage`: the usage flags generated as the expected usage flags
|
||||
in the test cases. CAn differ from the usage flags
|
||||
stored in the keys because of the usage flags extension.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.description = description #type: str
|
||||
self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
|
||||
|
||||
class StorageFormat:
|
||||
"""Storage format stability test cases."""
|
||||
@ -244,11 +285,11 @@ class StorageFormat:
|
||||
generate backward compatibility test cases which inject a key
|
||||
representation and check that it can be read and used.
|
||||
"""
|
||||
self.constructors = info.constructors
|
||||
self.version = version
|
||||
self.forward = forward
|
||||
self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
|
||||
self.version = version #type: int
|
||||
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.
|
||||
|
||||
If ``forward`` is true, generate a forward compatibility test case:
|
||||
@ -262,7 +303,7 @@ class StorageFormat:
|
||||
tc.set_description('PSA storage {}: {}'.format(verb, key.description))
|
||||
dependencies = automatic_dependencies(
|
||||
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)
|
||||
tc.set_dependencies(dependencies)
|
||||
@ -283,7 +324,7 @@ class StorageFormat:
|
||||
extra_arguments = [' | '.join(flags) if flags else '0']
|
||||
tc.set_arguments([key.lifetime.string,
|
||||
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.hex() + '"',
|
||||
*extra_arguments])
|
||||
@ -292,22 +333,22 @@ class StorageFormat:
|
||||
def key_for_lifetime(
|
||||
self,
|
||||
lifetime: str,
|
||||
) -> StorageKey:
|
||||
) -> StorageTestData:
|
||||
"""Construct a test key for the given lifetime."""
|
||||
short = lifetime
|
||||
short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
|
||||
r'', short)
|
||||
short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
|
||||
description = 'lifetime: ' + short
|
||||
key = StorageKey(version=self.version,
|
||||
id=1, lifetime=lifetime,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
|
||||
material=b'L',
|
||||
description=description)
|
||||
key = StorageTestData(version=self.version,
|
||||
id=1, lifetime=lifetime,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
|
||||
material=b'L',
|
||||
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."""
|
||||
lifetimes = sorted(self.constructors.lifetimes)
|
||||
expressions = self.constructors.generate_expressions(lifetimes)
|
||||
@ -321,40 +362,61 @@ class StorageFormat:
|
||||
continue
|
||||
yield self.key_for_lifetime(lifetime)
|
||||
|
||||
def key_for_usage_flags(
|
||||
def keys_for_usage_flags(
|
||||
self,
|
||||
usage_flags: List[str],
|
||||
short: Optional[str] = None
|
||||
) -> StorageKey:
|
||||
short: Optional[str] = None,
|
||||
test_implicit_usage: Optional[bool] = False
|
||||
) -> Iterator[StorageTestData]:
|
||||
"""Construct a test key for the given key usage."""
|
||||
usage = ' | '.join(usage_flags) if usage_flags else '0'
|
||||
if short is None:
|
||||
short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
|
||||
description = 'usage: ' + short
|
||||
key = StorageKey(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage=usage, alg=0, alg2=0,
|
||||
material=b'K',
|
||||
description=description)
|
||||
return key
|
||||
extra_desc = ' with implication' if test_implicit_usage else ''
|
||||
description = 'usage' + extra_desc + ': ' + short
|
||||
key1 = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
expected_usage=usage,
|
||||
usage=usage, alg=0, alg2=0,
|
||||
material=b'K',
|
||||
description=description)
|
||||
yield key1
|
||||
|
||||
def all_keys_for_usage_flags(self) -> Iterator[StorageKey]:
|
||||
if test_implicit_usage:
|
||||
description = 'usage without implication' + ': ' + short
|
||||
key2 = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
without_implicit_usage=True,
|
||||
usage=usage, alg=0, alg2=0,
|
||||
material=b'K',
|
||||
description=description)
|
||||
yield key2
|
||||
|
||||
def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
|
||||
"""Generate test keys covering usage flags."""
|
||||
known_flags = sorted(self.constructors.key_usage_flags)
|
||||
yield self.key_for_usage_flags(['0'])
|
||||
yield from self.keys_for_usage_flags(['0'], **kwargs)
|
||||
for usage_flag in known_flags:
|
||||
yield self.key_for_usage_flags([usage_flag])
|
||||
yield from self.keys_for_usage_flags([usage_flag], **kwargs)
|
||||
for flag1, flag2 in zip(known_flags,
|
||||
known_flags[1:] + [known_flags[0]]):
|
||||
yield self.key_for_usage_flags([flag1, flag2])
|
||||
yield self.key_for_usage_flags(known_flags, short='all known')
|
||||
yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
|
||||
|
||||
def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
|
||||
known_flags = sorted(self.constructors.key_usage_flags)
|
||||
yield from self.keys_for_usage_flags(known_flags, short='all known')
|
||||
|
||||
def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
|
||||
yield from self.generate_keys_for_usage_flags()
|
||||
yield from self.generate_key_for_all_usage_flags()
|
||||
|
||||
def keys_for_type(
|
||||
self,
|
||||
key_type: str,
|
||||
params: Optional[Iterable[str]] = None
|
||||
) -> Iterator[StorageKey]:
|
||||
) -> Iterator[StorageTestData]:
|
||||
"""Generate test keys for the given key type.
|
||||
|
||||
For key types that depend on a parameter (e.g. elliptic curve family),
|
||||
@ -371,21 +433,21 @@ class StorageFormat:
|
||||
r'',
|
||||
kt.expression)
|
||||
description = 'type: {} {}-bit'.format(short_expression, bits)
|
||||
key = StorageKey(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type=kt.expression, bits=bits,
|
||||
usage=usage_flags, alg=alg, alg2=alg2,
|
||||
material=key_material,
|
||||
description=description)
|
||||
key = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type=kt.expression, bits=bits,
|
||||
usage=usage_flags, alg=alg, alg2=alg2,
|
||||
material=key_material,
|
||||
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."""
|
||||
key_types = sorted(self.constructors.key_types)
|
||||
for key_type in self.constructors.generate_expressions(key_types):
|
||||
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."""
|
||||
# For now, we don't have information on the compatibility of key
|
||||
# types and algorithms. So we just test the encoding of algorithms,
|
||||
@ -393,39 +455,41 @@ class StorageFormat:
|
||||
descr = re.sub(r'PSA_ALG_', r'', alg)
|
||||
descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
|
||||
usage = 'PSA_KEY_USAGE_EXPORT'
|
||||
key1 = StorageKey(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage=usage, alg=alg, alg2=0,
|
||||
material=b'K',
|
||||
description='alg: ' + descr)
|
||||
key1 = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage=usage, alg=alg, alg2=0,
|
||||
material=b'K',
|
||||
description='alg: ' + descr)
|
||||
yield key1
|
||||
key2 = StorageKey(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage=usage, alg=0, alg2=alg,
|
||||
material=b'L',
|
||||
description='alg2: ' + descr)
|
||||
key2 = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
|
||||
usage=usage, alg=0, alg2=alg,
|
||||
material=b'L',
|
||||
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."""
|
||||
algorithms = sorted(self.constructors.algorithms)
|
||||
for alg in self.constructors.generate_expressions(algorithms):
|
||||
yield from self.keys_for_algorithm(alg)
|
||||
|
||||
def generate_all_keys(self) -> Iterator[StorageTestData]:
|
||||
"""Generate all keys for the test cases."""
|
||||
yield from self.all_keys_for_lifetimes()
|
||||
yield from self.all_keys_for_usage_flags()
|
||||
yield from self.all_keys_for_types()
|
||||
yield from self.all_keys_for_algorithms()
|
||||
|
||||
def all_test_cases(self) -> Iterator[test_case.TestCase]:
|
||||
"""Generate all storage format test cases."""
|
||||
# First build a list of all keys, then construct all the corresponding
|
||||
# test cases. This allows all required information to be obtained in
|
||||
# one go, which is a significant performance gain as the information
|
||||
# includes numerical values obtained by compiling a C program.
|
||||
keys = [] #type: List[StorageKey]
|
||||
keys += self.all_keys_for_lifetimes()
|
||||
keys += self.all_keys_for_usage_flags()
|
||||
keys += self.all_keys_for_types()
|
||||
keys += self.all_keys_for_algorithms()
|
||||
for key in keys:
|
||||
for key in self.generate_all_keys():
|
||||
if key.location_value() != 0:
|
||||
# Skip keys with a non-default location, because they
|
||||
# require a driver and we currently have no mechanism to
|
||||
@ -433,6 +497,125 @@ class StorageFormat:
|
||||
continue
|
||||
yield self.make_test_case(key)
|
||||
|
||||
class StorageFormatForward(StorageFormat):
|
||||
"""Storage format stability test cases for forward compatibility."""
|
||||
|
||||
def __init__(self, info: Information, version: int) -> None:
|
||||
super().__init__(info, version, True)
|
||||
|
||||
class StorageFormatV0(StorageFormat):
|
||||
"""Storage format stability test cases for version 0 compatibility."""
|
||||
|
||||
def __init__(self, info: Information) -> None:
|
||||
super().__init__(info, 0, False)
|
||||
|
||||
def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
|
||||
"""Generate test keys covering usage flags."""
|
||||
yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
|
||||
yield from self.generate_key_for_all_usage_flags()
|
||||
|
||||
def keys_for_implicit_usage(
|
||||
self,
|
||||
implyer_usage: str,
|
||||
alg: str,
|
||||
key_type: crypto_knowledge.KeyType
|
||||
) -> StorageTestData:
|
||||
# pylint: disable=too-many-locals
|
||||
"""Generate test keys for the specified implicit usage flag,
|
||||
algorithm and key type combination.
|
||||
"""
|
||||
bits = key_type.sizes_to_test()[0]
|
||||
implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
|
||||
usage_flags = 'PSA_KEY_USAGE_EXPORT'
|
||||
material_usage_flags = usage_flags + ' | ' + implyer_usage
|
||||
expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
|
||||
alg2 = 0
|
||||
key_material = key_type.key_material(bits)
|
||||
usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
|
||||
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'',
|
||||
key_type.expression)
|
||||
description = 'implied by {}: {} {} {}-bit'.format(
|
||||
usage_expression, alg_expression, key_type_expression, bits)
|
||||
key = StorageTestData(version=self.version,
|
||||
id=1, lifetime=0x00000001,
|
||||
type=key_type.expression, bits=bits,
|
||||
usage=material_usage_flags,
|
||||
expected_usage=expected_usage_flags,
|
||||
without_implicit_usage=True,
|
||||
alg=alg, alg2=alg2,
|
||||
material=key_material,
|
||||
description=description)
|
||||
return key
|
||||
|
||||
def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
|
||||
# pylint: disable=too-many-locals
|
||||
"""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_implicit_usage(self) -> Iterator[StorageTestData]:
|
||||
"""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.
|
||||
alg_with_keys = self.gather_key_types_for_sign_alg()
|
||||
|
||||
for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
|
||||
for alg in sorted(alg_with_keys):
|
||||
for key_type in sorted(alg_with_keys[alg]):
|
||||
# The key types must be filtered to fit the specific usage flag.
|
||||
kt = crypto_knowledge.KeyType(key_type)
|
||||
if kt.is_valid_for_signature(usage):
|
||||
yield self.keys_for_implicit_usage(usage, alg, kt)
|
||||
|
||||
def generate_all_keys(self) -> Iterator[StorageTestData]:
|
||||
yield from super().generate_all_keys()
|
||||
yield from self.all_keys_for_implicit_usage()
|
||||
|
||||
class TestGenerator:
|
||||
"""Generate test data."""
|
||||
@ -464,9 +647,9 @@ class TestGenerator:
|
||||
'test_suite_psa_crypto_not_supported.generated':
|
||||
lambda info: NotSupported(info).test_cases_for_not_supported(),
|
||||
'test_suite_psa_crypto_storage_format.current':
|
||||
lambda info: StorageFormat(info, 0, True).all_test_cases(),
|
||||
lambda info: StorageFormatForward(info, 0).all_test_cases(),
|
||||
'test_suite_psa_crypto_storage_format.v0':
|
||||
lambda info: StorageFormat(info, 0, False).all_test_cases(),
|
||||
lambda info: StorageFormatV0(info).all_test_cases(),
|
||||
} #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
|
||||
|
||||
def generate_target(self, name: str) -> None:
|
||||
|
Reference in New Issue
Block a user