1
0
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:
Dave Rodgman
2021-06-30 14:51:03 +01:00
committed by GitHub
13 changed files with 891 additions and 196 deletions

View File

@ -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: