PSA test case generation: dependency inference class: key not supported

In `psa_test_case.TestCase`, add a method `assumes_not_supported` which
allows using the automatic dependency calculation framework when the test
case intends to run in configurations where one mechanism is not supported.

Use `psa_test_case.TestCase` for not-supported test cases for key import and
generation.

No change to the generated output.

Signed-off-by: Gilles Peskine <[email protected]>
This commit is contained in:
Gilles Peskine
2025-01-09 18:24:59 +01:00
parent 5dcf16ad7b
commit 91a3626ab7
2 changed files with 59 additions and 31 deletions
+18 -31
View File
@@ -26,7 +26,7 @@ from mbedtls_framework import test_data_generation
def test_case_for_key_type_not_supported(
verb: str, key_type: str, bits: int,
dependencies: List[str],
not_supported_mechanism: str,
*args: str,
param_descr: str = ''
) -> test_case.TestCase:
@@ -35,17 +35,16 @@ def test_case_for_key_type_not_supported(
"""
tc = psa_test_case.TestCase()
short_key_type = crypto_knowledge.short_expression(key_type)
adverb = 'not' if dependencies else 'never'
if param_descr:
adverb = param_descr + ' ' + adverb
tc.set_description('PSA {} {} {}-bit {} supported'
.format(verb, short_key_type, bits, adverb))
tc.set_description('PSA {} {} {}-bit{} not supported'
.format(verb, short_key_type, bits,
' ' + param_descr if param_descr else ''))
# if tc.description == 'PSA import RSA_KEY_PAIR 1024-bit not supported':
# import pdb; pdb.set_trace()
tc.set_function(verb + '_not_supported')
tc.set_key_bits(bits)
tc.set_key_pair_usage([verb.upper()])
tc.assumes_not_supported(not_supported_mechanism)
tc.set_arguments([key_type] + list(args))
tc.set_dependencies(dependencies)
tc.skip_if_any_not_implemented(dependencies)
return tc
class KeyTypeNotSupported:
@@ -77,39 +76,27 @@ class KeyTypeNotSupported:
# Don't generate test cases for key types that are always supported.
# They would be skipped in all configurations, which is noise.
return
import_dependencies = [('!' if param is None else '') +
psa_information.psa_want_symbol(kt.name)]
if kt.params is not None:
import_dependencies += [('!' if param == i else '') +
psa_information.psa_want_symbol(sym)
for i, sym in enumerate(kt.params)]
if kt.name.endswith('_PUBLIC_KEY'):
generate_dependencies = []
if param is None:
not_supported_mechanism = kt.name
else:
generate_dependencies = \
psa_information.fix_key_pair_dependencies(import_dependencies,
['GENERATE'])
import_dependencies = \
psa_information.fix_key_pair_dependencies(import_dependencies,
['IMPORT'])
assert kt.params is not None
not_supported_mechanism = kt.params[param]
for bits in kt.sizes_to_test():
yield test_case_for_key_type_not_supported(
'import', kt.expression, bits,
psa_information.finish_family_dependencies(import_dependencies, bits),
not_supported_mechanism,
test_case.hex_string(kt.key_material(bits)),
param_descr=param_descr,
)
if not generate_dependencies and param is not None:
# If generation is impossible for this key type, rather than
# supported or not depending on implementation capabilities,
# only generate the test case once.
continue
# For public key we expect that key generation fails with
# INVALID_ARGUMENT. It is handled by KeyGenerate class.
# Don't generate not-supported test cases for key generation of
# public keys. Our implementation always returns
# PSA_ERROR_INVALID_ARGUMENT when attempting to generate a
# public key, so we cover this together with the positive cases
# in the KeyGenerate class.
if not kt.is_public():
yield test_case_for_key_type_not_supported(
'generate', kt.expression, bits,
psa_information.finish_family_dependencies(generate_dependencies, bits),
not_supported_mechanism,
str(bits),
param_descr=param_descr,
)
@@ -70,6 +70,7 @@ class TestCase(test_case.TestCase):
self.manual_dependencies = [] #type: List[str]
self.automatic_dependencies = set() #type: Set[str]
self.dependency_prefix = dependency_prefix #type: Optional[str]
self.negated_dependencies = set() #type: Set[str]
self.key_bits = None #type: Optional[int]
self.key_pair_usage = None #type: Optional[List[str]]
@@ -104,16 +105,56 @@ class TestCase(test_case.TestCase):
dependencies = psa_information.fix_key_pair_dependencies(dependencies,
self.key_pair_usage)
if 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' in dependencies and \
'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE' not in self.negated_dependencies and \
self.key_bits is not None:
size_dependency = ('PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= ' +
str(self.key_bits))
dependencies.append(size_dependency)
return dependencies
def assumes_not_supported(self, name: str) -> None:
"""Negate the given mechanism for automatic dependency generation.
Call this function before set_arguments() for a test case that should
run if the given mechanism is not supported.
Call modifiers such as set_key_bits() and set_key_pair_usage() before
calling this method, if applicable.
A mechanism is a PSA_XXX symbol, e.g. PSA_KEY_TYPE_AES, PSA_ALG_HMAC,
etc. For mechanisms like ECC curves where the support status includes
the key bit-size, this class assumes that only one bit-size is
involved in a given test case.
"""
if name == 'PSA_KEY_TYPE_RSA_KEY_PAIR' and \
self.key_bits is not None and \
self.key_pair_usage == ['GENERATE']:
# When RSA key pair generation is not supported, it could be
# due to the specific key size is out of range, or because
# RSA key pair generation itself is not supported. Assume the
# latter.
dep = psa_information.psa_want_symbol(name, prefix=self.dependency_prefix)
self.negated_dependencies.add(dep + '_GENERATE')
return
dependencies = self.infer_dependencies([name])
# * If we have more than one dependency to negate, the result would
# say that all of the dependencies are disabled, which is not
# a desirable outcome: the negation of (A and B) is (!A or !B),
# not (!A and !B).
# * If we have no dependency to negate, the result wouldn't be a
# not-supported case.
# Assert that we don't reach either such case.
assert len(dependencies) == 1
self.negated_dependencies.add(dependencies[0])
def set_arguments(self, arguments: List[str]) -> None:
"""Set test case arguments and automatically infer dependencies."""
super().set_arguments(arguments)
dependencies = self.infer_dependencies(arguments)
for i in range(len(dependencies)): #pylint: disable=consider-using-enumerate
if dependencies[i] in self.negated_dependencies:
dependencies[i] = '!' + dependencies[i]
self.skip_if_any_not_implemented(dependencies)
self.automatic_dependencies.update(dependencies)