Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions scripts/generate_psa_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def test_cases_for_key_type_not_supported(
'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
'PSA_KEY_TYPE_DH_PUBLIC_KEY')
# SPAKE2+ key types are also parametrised by an ECC family.
SPAKE2P_KEY_TYPES = ('PSA_KEY_TYPE_SPAKE2P_KEY_PAIR',
'PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY')

def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
"""Generate test cases that exercise the creation of keys of unsupported types."""
Expand All @@ -114,6 +117,8 @@ def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
continue
if key_type in self.DH_KEY_TYPES:
continue
if key_type in self.SPAKE2P_KEY_TYPES:
continue
kt = crypto_knowledge.KeyType(key_type)
yield from self.test_cases_for_key_type_not_supported(kt)
for curve_family in sorted(self.constructors.ecc_curves):
Expand All @@ -123,6 +128,14 @@ def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
kt, param_descr='type')
yield from self.test_cases_for_key_type_not_supported(
kt, 0, param_descr='curve')
# SPAKE2+ shares the ECC family parametrisation; sizes_to_test()
# yields nothing for families SPAKE2+ does not support.
for constr in self.SPAKE2P_KEY_TYPES:
kt = crypto_knowledge.KeyType(constr, [curve_family])
yield from self.test_cases_for_key_type_not_supported(
kt, param_descr='type')
yield from self.test_cases_for_key_type_not_supported(
kt, 0, param_descr='curve')
for dh_family in sorted(self.constructors.dh_groups):
for constr in self.DH_KEY_TYPES:
kt = crypto_knowledge.KeyType(constr, [dh_family])
Expand Down Expand Up @@ -181,13 +194,21 @@ def test_cases_for_key_type_key_generation(
tc.set_dependencies([])
yield tc

# SPAKE2+ keys are password-derived registration material, not randomly
# generated, so key generation is not supported and no positive generate
# test cases are emitted for them.
SPAKE2P_KEY_TYPES = ('PSA_KEY_TYPE_SPAKE2P_KEY_PAIR',
'PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY')

def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
"""Generate test cases that exercise the generation of keys."""
for key_type in sorted(self.constructors.key_types):
if key_type in self.ECC_KEY_TYPES:
continue
if key_type in self.DH_KEY_TYPES:
continue
if key_type in self.SPAKE2P_KEY_TYPES:
continue
kt = crypto_knowledge.KeyType(key_type)
yield from self.test_cases_for_key_type_key_generation(kt)
for curve_family in sorted(self.constructors.ecc_curves):
Expand All @@ -214,8 +235,18 @@ def __init__(self, info: psa_information.Information) -> None:
key_type_expressions = self.constructors.generate_expressions(
sorted(self.constructors.key_types)
)
self.key_types = [crypto_knowledge.KeyType(kt_expr)
for kt_expr in key_type_expressions]
# Skip key types with no testable sizes (e.g. SPAKE2+ over an ECC
# family it does not support): make_test_case() needs a concrete size.
# Also skip SPAKE2+ key types altogether: they are PAKE-only, so their
# permitted-algorithm policy must be a SPAKE2+ (PAKE) algorithm, and
# importing them with the incompatible non-PAKE policies this generator
# assigns is rejected at import time rather than at the operation. PAKE
# algorithms are themselves excluded from op-fail generation.
self.key_types = [kt for kt in
(crypto_knowledge.KeyType(kt_expr)
for kt_expr in key_type_expressions)
if kt.sizes_to_test()
and not kt.name.startswith('PSA_KEY_TYPE_SPAKE2P')]

def make_test_case(
self,
Expand Down Expand Up @@ -775,7 +806,9 @@ def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
# To create a valid combination 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'])
# SPAKE2+ key types carry the ECC family keyword but are PAKE keys, not
# signature keys, so exclude them from sign-algorithm matching.
incompatible_key_type_keywords = frozenset(['MONTGOMERY', 'SPAKE2P'])
keyword_translation = {
'ECDSA': 'ECC',
'ED[0-9]*.*' : 'EDWARDS'
Expand Down Expand Up @@ -832,6 +865,10 @@ def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
if kt.is_public() and '_SIGN_' in usage:
# Can't sign with a public key
continue
if not kt.sizes_to_test():
# No testable size (e.g. SPAKE2+ over an unsupported
# ECC family).
continue
yield self.keys_for_implicit_usage(usage, alg, kt)

def generate_all_keys(self) -> Iterator[StorageTestData]:
Expand Down
30 changes: 30 additions & 0 deletions scripts/mbedtls_framework/crypto_knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,26 @@ def is_public(self) -> bool:
'PSA_ECC_FAMILY_MONTGOMERY': (255, 448),
'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
} # type: Dict[str, Tuple[int, ...]]
# SPAKE2+ (RFC 9383) is defined over secp_r1 P-256/P-384/P-521. The key
# size is the curve bit size, shared by the key pair (w0||w1) and the
# public key (w0||L).
SPAKE2P_KEY_SIZES = {
'PSA_ECC_FAMILY_SECP_R1': (256, 384, 521),
} # type: Dict[str, Tuple[int, ...]]
# Valid SPAKE2+ registration material (RFC 9383 Appendix C): a key pair is
# w0 || w1 and a public key is w0 || L. Used as the export representation.
SPAKE2P_KEY_DATA = {
'PSA_KEY_TYPE_SPAKE2P_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)': {
256: bytes.fromhex("bb8e1bbcf3c48f62c08db243652ae55d3e5586053fca77102994f23ad95491b37e945f34d78785b8a3ef44d0df5a1a97d6b3b460409a345ca7830387a74b1dba"),
384: bytes.fromhex("097a61cbb1cee72bb654be96d80f46e0e3531151003903b572fc193f233772c23c22228884a0d5447d0ab49a656ce1d218772816140e6c3c3938a693c600b2191118a34c7956e1f1cd5b0d519b56ea5858060966cfaf27679c9182129949e74f"),
521: bytes.fromhex("009c79bcd7656716314fca5a6e2c5cda7ef86131399438e012a043051e863f60b5aeb3c101731e1505e721580f48535a9b0456b231b9266ae6fff49ee90d25f72f5f01632c15f51fcd916cd79e19075f8a69b72b0099922ad62ff8d540b469569f0aa027047aed2b3f242ea0ac4288b4e4db6a4e5946d8ad32b42192c5aa66d9ef8e1b33"),
},
'PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1)': {
256: bytes.fromhex("bb8e1bbcf3c48f62c08db243652ae55d3e5586053fca77102994f23ad95491b304eb7c9db3d9a9eb1f8adab81b5794c1f13ae3e225efbe91ea487425854c7fc00f00bfedcbd09b2400142d40a14f2064ef31dfaa903b91d1faea7093d835966efd"),
384: bytes.fromhex("097a61cbb1cee72bb654be96d80f46e0e3531151003903b572fc193f233772c23c22228884a0d5447d0ab49a656ce1d204f27dd5384d6b9beb4c5022c94b1978d632779e1d3abe458611e734a529d004e25053398e5dc9eeaa4ffa59743ca7ddbc0e7ce69155295cb2b846da83ee6a44490dd8e96bb0b0f6645281bfd978dd5f6836561ea0d8b2c045ff04cef2e5873d2c"),
521: bytes.fromhex("009c79bcd7656716314fca5a6e2c5cda7ef86131399438e012a043051e863f60b5aeb3c101731e1505e721580f48535a9b0456b231b9266ae6fff49ee90d25f72f5f040135072d0fa36f9e80031294cef5c3c35b882a0efa2c66570d64a49f8bec6c66435bf65bb7c7b2a3e7dece491e02b4d567e7087dbc32fe0fae8af417dcb50be6d704012a194588b690e6d3db492656f72ddea01fc1c7fcec0f5d34a5af0102939f6fdeae39c20cff74fcdb7f09855f0fc9520d20b0520b0b096b8d42c7c3d68b4a66f751"),
},
} # type: Dict[str, Dict[int, bytes]]
KEY_TYPE_SIZES = {
'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
'PSA_KEY_TYPE_ARC4': (8, 128, 2048), # extremes + sensible
Expand Down Expand Up @@ -176,6 +196,11 @@ def sizes_to_test(self) -> Tuple[int, ...]:
if self.private_type == 'PSA_KEY_TYPE_DH_KEY_PAIR':
assert self.params is not None
return self.DH_KEY_SIZES[self.params[0]]
if self.private_type == 'PSA_KEY_TYPE_SPAKE2P_KEY_PAIR':
assert self.params is not None
# SPAKE2+ is only defined over a subset of ECC families; other
# families yield no sizes (hence no generated test cases).
return self.SPAKE2P_KEY_SIZES.get(self.params[0], ())
return self.KEY_TYPE_SIZES[self.private_type]

# "48657265006973206b6579a064617461"
Expand All @@ -193,6 +218,11 @@ def key_material(self, bits: int) -> bytes:
psa_export_key(id, `material`, ...);
```
"""
if self.expression in self.SPAKE2P_KEY_DATA:
if bits not in self.SPAKE2P_KEY_DATA[self.expression]:
raise ValueError('No key data for {}-bit {}'
.format(bits, self.expression))
return self.SPAKE2P_KEY_DATA[self.expression][bits]
if self.expression in ASYMMETRIC_KEY_DATA:
if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
raise ValueError('No key data for {}-bit {}'
Expand Down
18 changes: 8 additions & 10 deletions scripts/mbedtls_framework/psa_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,16 @@ def remove_unwanted_macros(
"""Remove constructors that should be exckuded from systematic testing."""
# Mbed TLS does not support finite-field DSA, but 3.6 defines DSA
# identifiers for historical reasons.
# Mbed TLS and TF-PSA-Crypto 1.0 do not support SPAKE2+, although
# TF-PSA-Crypto 1.0 defines SPAKE2+ identifiers to be able to build
# the psa-arch-tests compliance test suite.
#
# Don't attempt to generate any related test case.
# The corresponding test cases would be commented out anyway,
# but for these types, we don't have enough support in the test scripts
# to generate these test cases.
constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
constructors.key_types.discard('PSA_KEY_TYPE_SPAKE2P_KEY_PAIR')
constructors.key_types.discard('PSA_KEY_TYPE_SPAKE2P_PUBLIC_KEY')
# SPAKE2+ (RFC 9383) is implemented in TF-PSA-Crypto, with the curve
# bit size as the key size and PSA_EXPORT_KEY_OUTPUT_SIZE defined for
# its key types; crypto_knowledge.KeyType has the matching size/material
# model. So its key types take part in systematic key-type generation
# (not-supported and storage). Key *generation* of SPAKE2+ keys is not
# supported (the keys are password-derived registration material, not
# random), so PSA_WANT_KEY_TYPE_SPAKE2P_KEY_PAIR_GENERATE is off and the
# KeyGenerate generator skips them accordingly.

def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
"""Return the list of known key types, algorithms, etc."""
Expand Down
12 changes: 11 additions & 1 deletion tests/src/psa_exercise_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,17 @@ int mbedtls_test_psa_exported_key_sanity_check(
PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(type, bits));
TEST_ASSERT(exported_length <=
PSA_EXPORT_PUBLIC_KEY_MAX_SIZE);
} else {
} else
#if defined(PSA_WANT_ALG_SPAKE2P_HMAC) || \
defined(PSA_WANT_ALG_SPAKE2P_CMAC) || \
defined(PSA_WANT_ALG_SPAKE2P_MATTER)
if (PSA_KEY_TYPE_IS_SPAKE2P(type)) {
/* The export representation is the registration material: w0 || w1 for
* a key pair, or w0 || L for a public key. */
TEST_EQUAL(exported_length, PSA_EXPORT_KEY_OUTPUT_SIZE(type, bits));
} else
#endif
{
(void) exported;
TEST_FAIL("Sanity check not implemented for this key type");
}
Expand Down