blob: 5cceb96948283cf93c3c4046e30fa46b323414b0 [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
Gilles Peskine0298bda2021-03-10 02:34:37 +01003
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Gilles Peskine09940492021-01-26 22:16:30 +01006"""
7
8# Copyright The Mbed TLS Contributors
Tomás González5fae5602023-11-13 11:45:12 +00009# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine09940492021-01-26 22:16:30 +010010
Gilles Peskinef8b6b502022-03-15 17:26:33 +010011import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010012import re
Gilles Peskine09940492021-01-26 22:16:30 +010013import sys
Werner Lewisdcad1e92022-08-24 11:30:03 +010014from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010015
16import scripts_path # pylint: disable=unused-import
Tomás González2bff1bf2023-10-30 15:29:23 +000017from mbedtls_dev import crypto_data_tests
Gilles Peskine14e428f2021-01-26 22:19:21 +010018from mbedtls_dev import crypto_knowledge
Tomás González734d22c2023-10-30 15:15:45 +000019from mbedtls_dev import macro_collector #pylint: disable=unused-import
20from mbedtls_dev import psa_information
Gilles Peskine897dff92021-03-10 15:03:44 +010021from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010022from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020023from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010024
Gilles Peskine14e428f2021-01-26 22:19:21 +010025
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020026def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010027 verb: str, key_type: str, bits: int,
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020028 not_supported_mechanism: str,
Gilles Peskineb94ea512021-03-10 02:12:08 +010029 *args: str,
30 param_descr: str = ''
31) -> test_case.TestCase:
32 """Return one test case exercising a key creation method
33 for an unsupported key type or size.
34 """
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020035 tc = psa_information.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +010036 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020037 tc.set_description('PSA {} {} {}-bit{} not supported'
38 .format(verb, short_key_type, bits,
39 ' ' + param_descr if param_descr else ''))
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020040 tc.set_function(verb + '_not_supported')
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020041 tc.set_key_bits(bits)
42 tc.assumes_not_supported(not_supported_mechanism)
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020043 tc.set_arguments([key_type] + list(args))
44 return tc
45
Gilles Peskine4fa76bd2022-12-15 22:14:28 +010046class KeyTypeNotSupported:
47 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010048
Tomás González734d22c2023-10-30 15:15:45 +000049 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010050 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010051
Gilles Peskine60b29fe2021-02-16 14:06:50 +010052 ALWAYS_SUPPORTED = frozenset([
53 'PSA_KEY_TYPE_DERIVE',
54 'PSA_KEY_TYPE_RAW_DATA',
55 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010056 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010057 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010058 kt: crypto_knowledge.KeyType,
59 param: Optional[int] = None,
60 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010061 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020062 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010063
64 If param is present and not None, emit test cases conditioned on this
65 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020066 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010067 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010068 if kt.name in self.ALWAYS_SUPPORTED:
69 # Don't generate test cases for key types that are always supported.
70 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010071 return
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020072 if param is None:
73 not_supported_mechanism = kt.name
Gilles Peskine14e428f2021-01-26 22:19:21 +010074 else:
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020075 assert kt.params is not None
76 not_supported_mechanism = kt.params[param]
Gilles Peskine14e428f2021-01-26 22:19:21 +010077 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020078 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +010079 'import', kt.expression, bits,
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020080 not_supported_mechanism,
Gilles Peskineaf172842021-01-27 18:24:48 +010081 test_case.hex_string(kt.key_material(bits)),
82 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +010083 )
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020084 # Don't generate not-supported test cases for key generation of
85 # public keys. Our implementation always returns
86 # PSA_ERROR_INVALID_ARGUMENT when attempting to generate a
87 # public key, so we cover this together with the positive cases
88 # in the KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +010089 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020090 yield test_case_for_key_type_not_supported(
91 'generate', kt.expression, bits,
Gilles Peskine1ae57ec2024-04-10 17:16:16 +020092 not_supported_mechanism,
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020093 str(bits),
94 param_descr=param_descr,
95 )
Gilles Peskine14e428f2021-01-26 22:19:21 +010096 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +010097
Gilles Peskineb93f8542021-04-19 13:50:25 +020098 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
99 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
100
Gilles Peskine3d778392021-02-17 15:11:05 +0100101 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100102 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100103 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200104 if key_type in self.ECC_KEY_TYPES:
105 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100106 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100107 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100108 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200109 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100110 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100111 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100112 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100113 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100114 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100115
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200116def test_case_for_key_generation(
117 key_type: str, bits: int,
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200118 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200119 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200120) -> test_case.TestCase:
121 """Return one test case exercising a key generation.
122 """
Gilles Peskine6281cf42024-04-10 16:07:29 +0200123 tc = psa_information.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100124 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200125 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200126 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200127 tc.set_function('generate_key')
Gilles Peskine6281cf42024-04-10 16:07:29 +0200128 tc.set_key_bits(bits)
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100129 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200130 return tc
131
132class KeyGenerate:
133 """Generate positive and negative (invalid argument) test cases for key generation."""
134
Tomás González734d22c2023-10-30 15:15:45 +0000135 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200136 self.constructors = info.constructors
137
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200138 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
139 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
140
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100141 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200142 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200143 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200144 ) -> Iterator[test_case.TestCase]:
145 """Return test cases exercising key generation.
146
147 All key types can be generated except for public keys. For public key
148 PSA_ERROR_INVALID_ARGUMENT status is expected.
149 """
150 result = 'PSA_SUCCESS'
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200151 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200152 result = 'PSA_ERROR_INVALID_ARGUMENT'
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200153 for bits in kt.sizes_to_test():
Gilles Peskine6281cf42024-04-10 16:07:29 +0200154 tc = test_case_for_key_generation(
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200155 kt.expression, bits,
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200156 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200157 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200158 )
Gilles Peskine6281cf42024-04-10 16:07:29 +0200159 if result == 'PSA_ERROR_INVALID_ARGUMENT':
160 # The library checks whether the key type is a public key generically,
161 # before it reaches a point where it needs support for the specific key
162 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
163 tc.set_dependencies([])
164 elif kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
165 # A necessary deviation because PSA_WANT symbols don't
166 # distinguish between key generation and usage, but for
167 # RSA key generation has an extra requirement.
168 tc.dependencies.insert(0, 'MBEDTLS_GENPRIME')
169 yield tc
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200170
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200171 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
172 """Generate test cases that exercise the generation of keys."""
173 for key_type in sorted(self.constructors.key_types):
174 if key_type in self.ECC_KEY_TYPES:
175 continue
176 kt = crypto_knowledge.KeyType(key_type)
177 yield from self.test_cases_for_key_type_key_generation(kt)
178 for curve_family in sorted(self.constructors.ecc_curves):
179 for constr in self.ECC_KEY_TYPES:
180 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200181 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200182
Gilles Peskinec05158b2021-04-27 20:40:10 +0200183class OpFail:
184 """Generate test cases for operations that must fail."""
185 #pylint: disable=too-few-public-methods
186
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100187 class Reason(enum.Enum):
188 NOT_SUPPORTED = 0
189 INVALID = 1
190 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200191 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100192
Tomás González734d22c2023-10-30 15:15:45 +0000193 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec05158b2021-04-27 20:40:10 +0200194 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100195 key_type_expressions = self.constructors.generate_expressions(
196 sorted(self.constructors.key_types)
197 )
198 self.key_types = [crypto_knowledge.KeyType(kt_expr)
199 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200200
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100201 def make_test_case(
202 self,
203 alg: crypto_knowledge.Algorithm,
204 category: crypto_knowledge.AlgorithmCategory,
205 reason: 'Reason',
206 kt: Optional[crypto_knowledge.KeyType] = None,
207 not_deps: FrozenSet[str] = frozenset(),
208 ) -> test_case.TestCase:
209 """Construct a failure test case for a one-key or keyless operation."""
210 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine764c2d32024-04-10 18:12:02 +0200211 tc = psa_information.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100212 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200213 if reason == self.Reason.NOT_SUPPORTED:
214 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
215 for dep in not_deps]
216 pretty_reason = '!' + '&'.join(sorted(short_deps))
217 else:
218 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100219 if kt:
220 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100221 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200222 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100223 key_type = ''
224 pretty_type = ''
225 tc.set_description('PSA {} {}: {}{}'
226 .format(category.name.lower(),
227 pretty_alg,
228 pretty_reason,
229 ' with ' + pretty_type if pretty_type else ''))
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100230 tc.set_function(category.name.lower() + '_fail')
David Horstmann4fc7e0e2023-01-24 18:53:15 +0000231 arguments = [] # type: List[str]
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100232 if kt:
Gilles Peskine764c2d32024-04-10 18:12:02 +0200233 bits = kt.sizes_to_test()[0]
234 key_material = kt.key_material(bits)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100235 arguments += [key_type, test_case.hex_string(key_material)]
Gilles Peskine764c2d32024-04-10 18:12:02 +0200236 tc.set_key_bits(bits)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100237 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200238 if category.is_asymmetric():
Gilles Peskine9ffffab2024-04-19 19:08:34 +0200239 private_only = (reason == self.Reason.PUBLIC)
240 arguments.append('1' if private_only else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100241 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
242 'INVALID_ARGUMENT')
243 arguments.append('PSA_ERROR_' + error)
Gilles Peskine764c2d32024-04-10 18:12:02 +0200244 for dep in not_deps:
245 tc.assumes_not_supported(dep)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100246 tc.set_arguments(arguments)
247 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200248
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100249 def no_key_test_cases(
250 self,
251 alg: crypto_knowledge.Algorithm,
252 category: crypto_knowledge.AlgorithmCategory,
253 ) -> Iterator[test_case.TestCase]:
254 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200255 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100256 # Compatible operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000257 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100258 yield self.make_test_case(alg, category,
259 self.Reason.NOT_SUPPORTED,
260 not_deps=frozenset([dep]))
261 else:
262 # Incompatible operation, supported algorithm
263 yield self.make_test_case(alg, category, self.Reason.INVALID)
264
265 def one_key_test_cases(
266 self,
267 alg: crypto_knowledge.Algorithm,
268 category: crypto_knowledge.AlgorithmCategory,
269 ) -> Iterator[test_case.TestCase]:
270 """Generate failure test cases for one-key operations with the specified algorithm."""
271 for kt in self.key_types:
272 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200273 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100274 # Compatible key and operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000275 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskine9ffffab2024-04-19 19:08:34 +0200276 deps = [dep]
277 # Special case: if one of deterministic/randomized
278 # ECDSA is supported but not the other, then the one
279 # that is not supported in the signature direction is
280 # still supported in the verification direction,
281 # because the two verification algorithms are
282 # identical. This property is how Mbed TLS chooses to
283 # behave, the specification would also allow it to
284 # reject the algorithm. In the generated test cases,
285 # we avoid this difficulty by not running the
286 # not-supported test case when exactly one of the
287 # two variants is supported.
288 if dep == 'PSA_WANT_ALG_DETERMINISTIC_ECDSA':
289 deps.append('PSA_WANT_ALG_ECDSA')
290 elif dep == 'PSA_WANT_ALG_ECDSA':
291 deps.append('PSA_WANT_ALG_DETERMINISTIC_ECDSA')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100292 yield self.make_test_case(alg, category,
293 self.Reason.NOT_SUPPORTED,
Gilles Peskine9ffffab2024-04-19 19:08:34 +0200294 kt=kt, not_deps=frozenset(deps))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200295 # Public key for a private-key operation
296 if category.is_asymmetric() and kt.is_public():
297 yield self.make_test_case(alg, category,
298 self.Reason.PUBLIC,
299 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100300 elif key_is_compatible:
301 # Compatible key, incompatible operation, supported algorithm
302 yield self.make_test_case(alg, category,
303 self.Reason.INVALID,
304 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200305 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100306 # Incompatible key, compatible operation, supported algorithm
307 yield self.make_test_case(alg, category,
308 self.Reason.INCOMPATIBLE,
309 kt=kt)
310 else:
311 # Incompatible key and operation. Don't test cases where
312 # multiple things are wrong, to keep the number of test
313 # cases reasonable.
314 pass
315
316 def test_cases_for_algorithm(
317 self,
318 alg: crypto_knowledge.Algorithm,
319 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200320 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100321 for category in crypto_knowledge.AlgorithmCategory:
322 if category == crypto_knowledge.AlgorithmCategory.PAKE:
323 # PAKE operations are not implemented yet
324 pass
325 elif category.requires_key():
326 yield from self.one_key_test_cases(alg, category)
327 else:
328 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200329
Gilles Peskinec05158b2021-04-27 20:40:10 +0200330 def all_test_cases(self) -> Iterator[test_case.TestCase]:
331 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200332 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100333 for expr in self.constructors.generate_expressions(algorithms):
334 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200335 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200336
337
Gilles Peskine897dff92021-03-10 15:03:44 +0100338class StorageKey(psa_storage.Key):
339 """Representation of a key for storage format testing."""
340
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200341 IMPLICIT_USAGE_FLAGS = {
342 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
343 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
344 } #type: Dict[str, str]
345 """Mapping of usage flags to the flags that they imply."""
346
347 def __init__(
348 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100349 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200350 without_implicit_usage: Optional[bool] = False,
351 **kwargs
352 ) -> None:
353 """Prepare to generate a key.
354
355 * `usage` : The usage flags used for the key.
Tom Cosgrove49f99bc2022-12-04 16:44:21 +0000356 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200357 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100358 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200359 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100360 for flag in sorted(usage_flags):
361 if flag in self.IMPLICIT_USAGE_FLAGS:
362 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
363 if usage_flags:
364 usage_expression = ' | '.join(sorted(usage_flags))
365 else:
366 usage_expression = '0'
367 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200368
369class StorageTestData(StorageKey):
370 """Representation of test case data for storage format testing."""
371
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200372 def __init__(
373 self,
374 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100375 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200376 **kwargs
377 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200378 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200379
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200380 * `description` : used for the the test case names
381 * `expected_usage`: the usage flags generated as the expected usage flags
382 in the test cases. CAn differ from the usage flags
383 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200384 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100385 super().__init__(**kwargs)
386 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100387 if expected_usage is None:
388 self.expected_usage = self.usage #type: psa_storage.Expr
389 elif expected_usage:
390 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
391 else:
392 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200393
Gilles Peskine897dff92021-03-10 15:03:44 +0100394class StorageFormat:
395 """Storage format stability test cases."""
396
Tomás González734d22c2023-10-30 15:15:45 +0000397 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100398 """Prepare to generate test cases for storage format stability.
399
Tomás González734d22c2023-10-30 15:15:45 +0000400 * `info`: information about the API. See the `psa_information.Information` class.
Gilles Peskine897dff92021-03-10 15:03:44 +0100401 * `version`: the storage format version to generate test cases for.
402 * `forward`: if true, generate forward compatibility test cases which
403 save a key and check that its representation is as intended. Otherwise
404 generate backward compatibility test cases which inject a key
405 representation and check that it can be read and used.
406 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200407 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
408 self.version = version #type: int
409 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100410
Gilles Peskine32611242022-03-19 12:09:13 +0100411 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100412 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100413 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100414 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100415 cls,
416 key_type: psa_storage.Expr, bits: int,
417 alg: psa_storage.Expr
418 ) -> bool:
Gilles Peskine1efe7fd2022-12-15 23:03:19 +0100419 """Whether to exercise the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100420
421 Normally only the type and algorithm matter for compatibility, and
422 this is handled in crypto_knowledge.KeyType.can_do(). This function
423 exists to detect exceptional cases. Exceptional cases detected here
424 are not tested in OpFail and should therefore have manually written
425 test cases.
426 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100427 # Some test keys have the RAW_DATA type and attributes that don't
428 # necessarily make sense. We do this to validate numerical
429 # encodings of the attributes.
430 # Raw data keys have no useful exercise anyway so there is no
431 # loss of test coverage.
432 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
433 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200434 # Mbed TLS only supports 128-bit keys for RC4.
435 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
436 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100437 # OAEP requires room for two hashes plus wrapping
438 m = cls.RSA_OAEP_RE.match(alg.string)
439 if m:
440 hash_alg = m.group(1)
441 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
442 key_length = (bits + 7) // 8
443 # Leave enough room for at least one byte of plaintext
444 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100445 # There's nothing wrong with ECC keys on Brainpool curves,
446 # but operations with them are very slow. So we only exercise them
447 # with a single algorithm, not with all possible hashes. We do
448 # exercise other curves with all algorithms so test coverage is
449 # perfectly adequate like this.
450 m = cls.BRAINPOOL_RE.match(key_type.string)
451 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
452 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100453 return True
454
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200455 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100456 """Construct a storage format test case for the given key.
457
458 If ``forward`` is true, generate a forward compatibility test case:
459 create a key and validate that it has the expected representation.
460 Otherwise generate a backward compatibility test case: inject the
461 key representation into storage and validate that it can be read
462 correctly.
463 """
464 verb = 'save' if self.forward else 'read'
Gilles Peskinec7b58d52024-04-10 15:55:39 +0200465 tc = psa_information.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100466 tc.set_description(verb + ' ' + key.description)
Gilles Peskinec7b58d52024-04-10 15:55:39 +0200467 tc.set_key_bits(key.bits)
Gilles Peskine897dff92021-03-10 15:03:44 +0100468 tc.set_function('key_storage_' + verb)
469 if self.forward:
470 extra_arguments = []
471 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200472 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100473 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200474 flags.append('TEST_FLAG_EXERCISE')
475 if 'READ_ONLY' in key.lifetime.string:
476 flags.append('TEST_FLAG_READ_ONLY')
477 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100478 tc.set_arguments([key.lifetime.string,
479 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100480 key.expected_usage.string,
481 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100482 '"' + key.material.hex() + '"',
483 '"' + key.hex() + '"',
484 *extra_arguments])
485 return tc
486
Gilles Peskineefb584d2021-04-21 22:05:34 +0200487 def key_for_lifetime(
488 self,
489 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200490 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200491 """Construct a test key for the given lifetime."""
492 short = lifetime
493 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
494 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100495 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200496 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200497 key = StorageTestData(version=self.version,
498 id=1, lifetime=lifetime,
499 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100500 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200501 material=b'L',
502 description=description)
503 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200504
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200505 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200506 """Generate test keys covering lifetimes."""
507 lifetimes = sorted(self.constructors.lifetimes)
508 expressions = self.constructors.generate_expressions(lifetimes)
509 for lifetime in expressions:
510 # Don't attempt to create or load a volatile key in storage
511 if 'VOLATILE' in lifetime:
512 continue
513 # Don't attempt to create a read-only key in storage,
514 # but do attempt to load one.
515 if 'READ_ONLY' in lifetime and self.forward:
516 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200517 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200518
Gilles Peskinea296e482022-02-24 18:58:08 +0100519 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100520 self,
521 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200522 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100523 test_implicit_usage: Optional[bool] = True
524 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100525 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100526 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100527 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200528 key1 = StorageTestData(version=self.version,
529 id=1, lifetime=0x00000001,
530 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100531 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100532 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100533 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200534 material=b'K',
535 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100536 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100537 usage_expr = key1.expected_usage.string
538 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100539 else:
540 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100541 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100542
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200543 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100544 """Generate test keys covering usage flags."""
545 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100546 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200547 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100548 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200549 for flag1, flag2 in zip(known_flags,
550 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100551 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200552
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200553 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200554 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100555 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200556
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200557 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200558 yield from self.generate_keys_for_usage_flags()
559 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100560
Gilles Peskine6213a002021-04-29 22:28:07 +0200561 def key_for_type_and_alg(
562 self,
563 kt: crypto_knowledge.KeyType,
564 bits: int,
565 alg: Optional[crypto_knowledge.Algorithm] = None,
566 ) -> StorageTestData:
567 """Construct a test key of the given type.
568
569 If alg is not None, this key allows it.
570 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100571 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100572 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200573 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100574 if alg is not None:
575 alg1 = alg.expression
576 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200577 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100578 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200579 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100580 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200581 key = StorageTestData(version=self.version,
582 id=1, lifetime=0x00000001,
583 type=kt.expression, bits=bits,
584 usage=usage_flags, alg=alg1, alg2=alg2,
585 material=key_material,
586 description=description)
587 return key
588
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100589 def keys_for_type(
590 self,
591 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200592 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200593 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200594 """Generate test keys for the given key type."""
595 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100596 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200597 # Test a non-exercisable key, as well as exercisable keys for
598 # each compatible algorithm.
599 # To do: test reading a key from storage with an incompatible
600 # or unsupported algorithm.
601 yield self.key_for_type_and_alg(kt, bits)
602 compatible_algorithms = [alg for alg in all_algorithms
603 if kt.can_do(alg)]
604 for alg in compatible_algorithms:
605 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100606
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200607 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100608 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200609 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200610 all_algorithms = [crypto_knowledge.Algorithm(alg)
611 for alg in self.constructors.generate_expressions(
612 sorted(self.constructors.algorithms)
613 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200614 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200615 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100616
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200617 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200618 """Generate test keys for the encoding of the specified algorithm."""
619 # These test cases only validate the encoding of algorithms, not
620 # whether the key read from storage is suitable for an operation.
621 # `keys_for_types` generate read tests with an algorithm and a
622 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100623 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100624 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200625 key1 = StorageTestData(version=self.version,
626 id=1, lifetime=0x00000001,
627 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
628 usage=usage, alg=alg, alg2=0,
629 material=b'K',
630 description='alg: ' + descr)
631 yield key1
632 key2 = StorageTestData(version=self.version,
633 id=1, lifetime=0x00000001,
634 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
635 usage=usage, alg=0, alg2=alg,
636 material=b'L',
637 description='alg2: ' + descr)
638 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100639
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200640 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100641 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200642 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200643 for alg in self.constructors.generate_expressions(algorithms):
644 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100645
gabor-mezei-armea840de2021-06-29 15:42:57 +0200646 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200647 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200648 yield from self.all_keys_for_lifetimes()
649 yield from self.all_keys_for_usage_flags()
650 yield from self.all_keys_for_types()
651 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200652
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200653 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100654 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200655 # First build a list of all keys, then construct all the corresponding
656 # test cases. This allows all required information to be obtained in
657 # one go, which is a significant performance gain as the information
658 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200659 all_keys = list(self.generate_all_keys())
660 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200661 if key.location_value() != 0:
662 # Skip keys with a non-default location, because they
663 # require a driver and we currently have no mechanism to
664 # determine whether a driver is available.
665 continue
666 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100667
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200668class StorageFormatForward(StorageFormat):
669 """Storage format stability test cases for forward compatibility."""
670
Tomás González734d22c2023-10-30 15:15:45 +0000671 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200672 super().__init__(info, version, True)
673
674class StorageFormatV0(StorageFormat):
675 """Storage format stability test cases for version 0 compatibility."""
676
Tomás González734d22c2023-10-30 15:15:45 +0000677 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200678 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100679
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200680 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200681 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100682 yield from super().all_keys_for_usage_flags()
683 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200684
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200685 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200686 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200687 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200688 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200689 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200690 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200691 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200692 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200693 algorithm and key type combination.
694 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200695 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200696 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100697 usage_flags = ['PSA_KEY_USAGE_EXPORT']
698 material_usage_flags = usage_flags + [implyer_usage]
699 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200700 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200701 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100702 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
703 alg_expression = crypto_knowledge.short_expression(alg, 1)
704 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200705 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200706 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200707 key = StorageTestData(version=self.version,
708 id=1, lifetime=0x00000001,
709 type=key_type.expression, bits=bits,
710 usage=material_usage_flags,
711 expected_usage=expected_usage_flags,
712 without_implicit_usage=True,
713 alg=alg, alg2=alg2,
714 material=key_material,
715 description=description)
716 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200717
718 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200719 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200720 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800721 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200722 # must be filtered. Pair them with keywords created from its names.
723 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
724 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
725 keyword_translation = {
726 'ECDSA': 'ECC',
727 'ED[0-9]*.*' : 'EDWARDS'
728 }
729 exclusive_keywords = {
730 'EDWARDS': 'ECC'
731 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200732 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
733 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200734 alg_with_keys = {} #type: Dict[str, List[str]]
735 translation_table = str.maketrans('(', '_', ')')
736 for alg in algorithms:
737 # Generate keywords from the name of the algorithm
738 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
739 # Translate keywords for better matching with the key types
740 for keyword in alg_keywords.copy():
741 for pattern, replace in keyword_translation.items():
742 if re.match(pattern, keyword):
743 alg_keywords.remove(keyword)
744 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800745 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200746 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
747 continue
748
749 for key_type in key_types:
750 # Generate keywords from the of the key type
751 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
752
Shaun Case0e7791f2021-12-20 21:14:10 -0800753 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200754 for keyword1, keyword2 in exclusive_keywords.items():
755 if keyword1 in key_type_keywords:
756 key_type_keywords.remove(keyword2)
757
758 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
759 not key_type_keywords.isdisjoint(alg_keywords):
760 if alg in alg_with_keys:
761 alg_with_keys[alg].append(key_type)
762 else:
763 alg_with_keys[alg] = [key_type]
764 return alg_with_keys
765
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200766 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200767 """Generate test keys for usage flag extensions."""
768 # Generate a key type and algorithm pair for each extendable usage
769 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800770 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200771 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200772
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200773 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
774 for alg in sorted(alg_with_keys):
775 for key_type in sorted(alg_with_keys[alg]):
776 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200777 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100778 if kt.is_public() and '_SIGN_' in usage:
779 # Can't sign with a public key
780 continue
781 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200782
gabor-mezei-armea840de2021-06-29 15:42:57 +0200783 def generate_all_keys(self) -> Iterator[StorageTestData]:
784 yield from super().generate_all_keys()
785 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200786
Tomás González734d22c2023-10-30 15:15:45 +0000787
Gilles Peskine69feebd2022-09-16 21:41:47 +0200788class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisdcad1e92022-08-24 11:30:03 +0100789 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100790 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200791 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100792 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200793 'test_suite_psa_crypto_generate_key.generated':
794 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100795 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100796 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Tomás González2bff1bf2023-10-30 15:29:23 +0000797 'test_suite_psa_crypto_low_hash.generated':
798 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200799 'test_suite_psa_crypto_op_fail.generated':
800 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100801 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200802 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100803 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200804 lambda info: StorageFormatV0(info).all_test_cases(),
Tomás González734d22c2023-10-30 15:15:45 +0000805 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100806
Werner Lewisdcad1e92022-08-24 11:30:03 +0100807 def __init__(self, options):
808 super().__init__(options)
Tomás González734d22c2023-10-30 15:15:45 +0000809 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100810
Werner Lewisdcad1e92022-08-24 11:30:03 +0100811 def generate_target(self, name: str, *target_args) -> None:
812 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100813
Tomás González734d22c2023-10-30 15:15:45 +0000814
Gilles Peskine09940492021-01-26 22:16:30 +0100815if __name__ == '__main__':
Gilles Peskine69feebd2022-09-16 21:41:47 +0200816 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)