blob: faebe510c034b73293b11a1c77adac44217da566 [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,
28 dependencies: List[str],
29 *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 """
Tomás González734d22c2023-10-30 15:15:45 +000035 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010036 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +010037 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010038 adverb = 'not' if dependencies else 'never'
39 if param_descr:
40 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020041 tc.set_description('PSA {} {} {}-bit {} supported'
42 .format(verb, short_key_type, bits, adverb))
43 tc.set_dependencies(dependencies)
44 tc.set_function(verb + '_not_supported')
45 tc.set_arguments([key_type] + list(args))
46 return tc
47
Gilles Peskine4fa76bd2022-12-15 22:14:28 +010048class KeyTypeNotSupported:
49 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010050
Tomás González734d22c2023-10-30 15:15:45 +000051 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010052 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010053
Gilles Peskine60b29fe2021-02-16 14:06:50 +010054 ALWAYS_SUPPORTED = frozenset([
55 'PSA_KEY_TYPE_DERIVE',
56 'PSA_KEY_TYPE_RAW_DATA',
57 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010058 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010059 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010060 kt: crypto_knowledge.KeyType,
61 param: Optional[int] = None,
62 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010063 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020064 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010065
66 If param is present and not None, emit test cases conditioned on this
67 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020068 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010069 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010070 if kt.name in self.ALWAYS_SUPPORTED:
71 # Don't generate test cases for key types that are always supported.
72 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010073 return
Gilles Peskineaf172842021-01-27 18:24:48 +010074 import_dependencies = [('!' if param is None else '') +
Tomás González734d22c2023-10-30 15:15:45 +000075 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010076 if kt.params is not None:
77 import_dependencies += [('!' if param == i else '') +
Tomás González734d22c2023-10-30 15:15:45 +000078 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010079 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010080 if kt.name.endswith('_PUBLIC_KEY'):
81 generate_dependencies = []
82 else:
83 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +010084 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020085 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +010086 'import', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +000087 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +010088 test_case.hex_string(kt.key_material(bits)),
89 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +010090 )
Gilles Peskineaf172842021-01-27 18:24:48 +010091 if not generate_dependencies and param is not None:
92 # If generation is impossible for this key type, rather than
93 # supported or not depending on implementation capabilities,
94 # only generate the test case once.
95 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +010096 # For public key we expect that key generation fails with
97 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +010098 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020099 yield test_case_for_key_type_not_supported(
100 'generate', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000101 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200102 str(bits),
103 param_descr=param_descr,
104 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100105 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100106
Gilles Peskineb93f8542021-04-19 13:50:25 +0200107 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
108 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
109
Gilles Peskine3d778392021-02-17 15:11:05 +0100110 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100111 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100112 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200113 if key_type in self.ECC_KEY_TYPES:
114 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100115 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100116 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100117 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200118 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100119 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100120 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100121 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100122 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100123 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100124
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200125def test_case_for_key_generation(
126 key_type: str, bits: int,
127 dependencies: List[str],
128 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200129 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200130) -> test_case.TestCase:
131 """Return one test case exercising a key generation.
132 """
Tomás González734d22c2023-10-30 15:15:45 +0000133 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200134 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100135 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200136 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200137 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200138 tc.set_dependencies(dependencies)
139 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100140 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200141
142 return tc
143
144class KeyGenerate:
145 """Generate positive and negative (invalid argument) test cases for key generation."""
146
Tomás González734d22c2023-10-30 15:15:45 +0000147 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200148 self.constructors = info.constructors
149
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200150 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
151 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
152
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100153 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200154 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200155 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200156 ) -> Iterator[test_case.TestCase]:
157 """Return test cases exercising key generation.
158
159 All key types can be generated except for public keys. For public key
160 PSA_ERROR_INVALID_ARGUMENT status is expected.
161 """
162 result = 'PSA_SUCCESS'
163
Tomás González734d22c2023-10-30 15:15:45 +0000164 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200165 if kt.params is not None:
Tomás González734d22c2023-10-30 15:15:45 +0000166 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200167 for i, sym in enumerate(kt.params)]
168 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100169 # The library checks whether the key type is a public key generically,
170 # before it reaches a point where it needs support for the specific key
171 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200172 generate_dependencies = []
173 result = 'PSA_ERROR_INVALID_ARGUMENT'
174 else:
175 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100176 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200177 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200178 for bits in kt.sizes_to_test():
179 yield test_case_for_key_generation(
180 kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000181 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200182 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200183 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200184 )
185
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200186 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
187 """Generate test cases that exercise the generation of keys."""
188 for key_type in sorted(self.constructors.key_types):
189 if key_type in self.ECC_KEY_TYPES:
190 continue
191 kt = crypto_knowledge.KeyType(key_type)
192 yield from self.test_cases_for_key_type_key_generation(kt)
193 for curve_family in sorted(self.constructors.ecc_curves):
194 for constr in self.ECC_KEY_TYPES:
195 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200196 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200197
Gilles Peskinec05158b2021-04-27 20:40:10 +0200198class OpFail:
199 """Generate test cases for operations that must fail."""
200 #pylint: disable=too-few-public-methods
201
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100202 class Reason(enum.Enum):
203 NOT_SUPPORTED = 0
204 INVALID = 1
205 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200206 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100207
Tomás González734d22c2023-10-30 15:15:45 +0000208 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec05158b2021-04-27 20:40:10 +0200209 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100210 key_type_expressions = self.constructors.generate_expressions(
211 sorted(self.constructors.key_types)
212 )
213 self.key_types = [crypto_knowledge.KeyType(kt_expr)
214 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200215
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100216 def make_test_case(
217 self,
218 alg: crypto_knowledge.Algorithm,
219 category: crypto_knowledge.AlgorithmCategory,
220 reason: 'Reason',
221 kt: Optional[crypto_knowledge.KeyType] = None,
222 not_deps: FrozenSet[str] = frozenset(),
223 ) -> test_case.TestCase:
224 """Construct a failure test case for a one-key or keyless operation."""
225 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200226 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100227 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200228 if reason == self.Reason.NOT_SUPPORTED:
229 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
230 for dep in not_deps]
231 pretty_reason = '!' + '&'.join(sorted(short_deps))
232 else:
233 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100234 if kt:
235 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100236 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200237 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100238 key_type = ''
239 pretty_type = ''
240 tc.set_description('PSA {} {}: {}{}'
241 .format(category.name.lower(),
242 pretty_alg,
243 pretty_reason,
244 ' with ' + pretty_type if pretty_type else ''))
Tomás González734d22c2023-10-30 15:15:45 +0000245 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100246 for i, dep in enumerate(dependencies):
247 if dep in not_deps:
248 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200249 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100250 tc.set_function(category.name.lower() + '_fail')
David Horstmann4fc7e0e2023-01-24 18:53:15 +0000251 arguments = [] # type: List[str]
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100252 if kt:
253 key_material = kt.key_material(kt.sizes_to_test()[0])
254 arguments += [key_type, test_case.hex_string(key_material)]
255 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200256 if category.is_asymmetric():
257 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100258 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
259 'INVALID_ARGUMENT')
260 arguments.append('PSA_ERROR_' + error)
261 tc.set_arguments(arguments)
262 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200263
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100264 def no_key_test_cases(
265 self,
266 alg: crypto_knowledge.Algorithm,
267 category: crypto_knowledge.AlgorithmCategory,
268 ) -> Iterator[test_case.TestCase]:
269 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200270 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100271 # Compatible operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000272 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100273 yield self.make_test_case(alg, category,
274 self.Reason.NOT_SUPPORTED,
275 not_deps=frozenset([dep]))
276 else:
277 # Incompatible operation, supported algorithm
278 yield self.make_test_case(alg, category, self.Reason.INVALID)
279
280 def one_key_test_cases(
281 self,
282 alg: crypto_knowledge.Algorithm,
283 category: crypto_knowledge.AlgorithmCategory,
284 ) -> Iterator[test_case.TestCase]:
285 """Generate failure test cases for one-key operations with the specified algorithm."""
286 for kt in self.key_types:
287 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200288 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100289 # Compatible key and operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000290 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100291 yield self.make_test_case(alg, category,
292 self.Reason.NOT_SUPPORTED,
293 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200294 # Public key for a private-key operation
295 if category.is_asymmetric() and kt.is_public():
296 yield self.make_test_case(alg, category,
297 self.Reason.PUBLIC,
298 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100299 elif key_is_compatible:
300 # Compatible key, incompatible operation, supported algorithm
301 yield self.make_test_case(alg, category,
302 self.Reason.INVALID,
303 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200304 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100305 # Incompatible key, compatible operation, supported algorithm
306 yield self.make_test_case(alg, category,
307 self.Reason.INCOMPATIBLE,
308 kt=kt)
309 else:
310 # Incompatible key and operation. Don't test cases where
311 # multiple things are wrong, to keep the number of test
312 # cases reasonable.
313 pass
314
315 def test_cases_for_algorithm(
316 self,
317 alg: crypto_knowledge.Algorithm,
318 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200319 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100320 for category in crypto_knowledge.AlgorithmCategory:
321 if category == crypto_knowledge.AlgorithmCategory.PAKE:
322 # PAKE operations are not implemented yet
323 pass
324 elif category.requires_key():
325 yield from self.one_key_test_cases(alg, category)
326 else:
327 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200328
Gilles Peskinec05158b2021-04-27 20:40:10 +0200329 def all_test_cases(self) -> Iterator[test_case.TestCase]:
330 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200331 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100332 for expr in self.constructors.generate_expressions(algorithms):
333 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200334 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200335
336
Gilles Peskine897dff92021-03-10 15:03:44 +0100337class StorageKey(psa_storage.Key):
338 """Representation of a key for storage format testing."""
339
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200340 IMPLICIT_USAGE_FLAGS = {
341 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
342 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
343 } #type: Dict[str, str]
344 """Mapping of usage flags to the flags that they imply."""
345
346 def __init__(
347 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100348 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200349 without_implicit_usage: Optional[bool] = False,
350 **kwargs
351 ) -> None:
352 """Prepare to generate a key.
353
354 * `usage` : The usage flags used for the key.
Tom Cosgrove49f99bc2022-12-04 16:44:21 +0000355 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200356 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100357 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200358 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100359 for flag in sorted(usage_flags):
360 if flag in self.IMPLICIT_USAGE_FLAGS:
361 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
362 if usage_flags:
363 usage_expression = ' | '.join(sorted(usage_flags))
364 else:
365 usage_expression = '0'
366 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200367
368class StorageTestData(StorageKey):
369 """Representation of test case data for storage format testing."""
370
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200371 def __init__(
372 self,
373 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100374 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200375 **kwargs
376 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200377 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200378
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200379 * `description` : used for the the test case names
380 * `expected_usage`: the usage flags generated as the expected usage flags
381 in the test cases. CAn differ from the usage flags
382 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200383 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100384 super().__init__(**kwargs)
385 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100386 if expected_usage is None:
387 self.expected_usage = self.usage #type: psa_storage.Expr
388 elif expected_usage:
389 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
390 else:
391 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200392
Gilles Peskine897dff92021-03-10 15:03:44 +0100393class StorageFormat:
394 """Storage format stability test cases."""
395
Tomás González734d22c2023-10-30 15:15:45 +0000396 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100397 """Prepare to generate test cases for storage format stability.
398
Tomás González734d22c2023-10-30 15:15:45 +0000399 * `info`: information about the API. See the `psa_information.Information` class.
Gilles Peskine897dff92021-03-10 15:03:44 +0100400 * `version`: the storage format version to generate test cases for.
401 * `forward`: if true, generate forward compatibility test cases which
402 save a key and check that its representation is as intended. Otherwise
403 generate backward compatibility test cases which inject a key
404 representation and check that it can be read and used.
405 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200406 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
407 self.version = version #type: int
408 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100409
Gilles Peskine32611242022-03-19 12:09:13 +0100410 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100411 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100412 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100413 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100414 cls,
415 key_type: psa_storage.Expr, bits: int,
416 alg: psa_storage.Expr
417 ) -> bool:
Gilles Peskine1efe7fd2022-12-15 23:03:19 +0100418 """Whether to exercise the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100419
420 Normally only the type and algorithm matter for compatibility, and
421 this is handled in crypto_knowledge.KeyType.can_do(). This function
422 exists to detect exceptional cases. Exceptional cases detected here
423 are not tested in OpFail and should therefore have manually written
424 test cases.
425 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100426 # Some test keys have the RAW_DATA type and attributes that don't
427 # necessarily make sense. We do this to validate numerical
428 # encodings of the attributes.
429 # Raw data keys have no useful exercise anyway so there is no
430 # loss of test coverage.
431 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
432 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200433 # Mbed TLS only supports 128-bit keys for RC4.
434 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
435 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100436 # OAEP requires room for two hashes plus wrapping
437 m = cls.RSA_OAEP_RE.match(alg.string)
438 if m:
439 hash_alg = m.group(1)
440 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
441 key_length = (bits + 7) // 8
442 # Leave enough room for at least one byte of plaintext
443 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100444 # There's nothing wrong with ECC keys on Brainpool curves,
445 # but operations with them are very slow. So we only exercise them
446 # with a single algorithm, not with all possible hashes. We do
447 # exercise other curves with all algorithms so test coverage is
448 # perfectly adequate like this.
449 m = cls.BRAINPOOL_RE.match(key_type.string)
450 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
451 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100452 return True
453
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200454 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100455 """Construct a storage format test case for the given key.
456
457 If ``forward`` is true, generate a forward compatibility test case:
458 create a key and validate that it has the expected representation.
459 Otherwise generate a backward compatibility test case: inject the
460 key representation into storage and validate that it can be read
461 correctly.
462 """
463 verb = 'save' if self.forward else 'read'
464 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100465 tc.set_description(verb + ' ' + key.description)
Tomás González734d22c2023-10-30 15:15:45 +0000466 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100467 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100468 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100469 )
Tomás González734d22c2023-10-30 15:15:45 +0000470 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100471 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100472 tc.set_function('key_storage_' + verb)
473 if self.forward:
474 extra_arguments = []
475 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200476 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100477 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200478 flags.append('TEST_FLAG_EXERCISE')
479 if 'READ_ONLY' in key.lifetime.string:
480 flags.append('TEST_FLAG_READ_ONLY')
481 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100482 tc.set_arguments([key.lifetime.string,
483 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100484 key.expected_usage.string,
485 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100486 '"' + key.material.hex() + '"',
487 '"' + key.hex() + '"',
488 *extra_arguments])
489 return tc
490
Gilles Peskineefb584d2021-04-21 22:05:34 +0200491 def key_for_lifetime(
492 self,
493 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200494 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200495 """Construct a test key for the given lifetime."""
496 short = lifetime
497 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
498 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100499 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200500 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200501 key = StorageTestData(version=self.version,
502 id=1, lifetime=lifetime,
503 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100504 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200505 material=b'L',
506 description=description)
507 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200508
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200509 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200510 """Generate test keys covering lifetimes."""
511 lifetimes = sorted(self.constructors.lifetimes)
512 expressions = self.constructors.generate_expressions(lifetimes)
513 for lifetime in expressions:
514 # Don't attempt to create or load a volatile key in storage
515 if 'VOLATILE' in lifetime:
516 continue
517 # Don't attempt to create a read-only key in storage,
518 # but do attempt to load one.
519 if 'READ_ONLY' in lifetime and self.forward:
520 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200521 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200522
Gilles Peskinea296e482022-02-24 18:58:08 +0100523 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100524 self,
525 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200526 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100527 test_implicit_usage: Optional[bool] = True
528 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100529 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100530 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100531 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200532 key1 = StorageTestData(version=self.version,
533 id=1, lifetime=0x00000001,
534 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100535 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100536 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100537 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200538 material=b'K',
539 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100540 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100541 usage_expr = key1.expected_usage.string
542 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100543 else:
544 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100545 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100546
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200547 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100548 """Generate test keys covering usage flags."""
549 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100550 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200551 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100552 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200553 for flag1, flag2 in zip(known_flags,
554 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100555 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200556
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200557 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200558 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100559 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200560
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200561 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200562 yield from self.generate_keys_for_usage_flags()
563 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100564
Gilles Peskine6213a002021-04-29 22:28:07 +0200565 def key_for_type_and_alg(
566 self,
567 kt: crypto_knowledge.KeyType,
568 bits: int,
569 alg: Optional[crypto_knowledge.Algorithm] = None,
570 ) -> StorageTestData:
571 """Construct a test key of the given type.
572
573 If alg is not None, this key allows it.
574 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100575 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100576 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200577 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100578 if alg is not None:
579 alg1 = alg.expression
580 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200581 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100582 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200583 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100584 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200585 key = StorageTestData(version=self.version,
586 id=1, lifetime=0x00000001,
587 type=kt.expression, bits=bits,
588 usage=usage_flags, alg=alg1, alg2=alg2,
589 material=key_material,
590 description=description)
591 return key
592
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100593 def keys_for_type(
594 self,
595 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200596 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200597 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200598 """Generate test keys for the given key type."""
599 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100600 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200601 # Test a non-exercisable key, as well as exercisable keys for
602 # each compatible algorithm.
603 # To do: test reading a key from storage with an incompatible
604 # or unsupported algorithm.
605 yield self.key_for_type_and_alg(kt, bits)
606 compatible_algorithms = [alg for alg in all_algorithms
607 if kt.can_do(alg)]
608 for alg in compatible_algorithms:
609 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100610
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200611 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100612 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200613 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200614 all_algorithms = [crypto_knowledge.Algorithm(alg)
615 for alg in self.constructors.generate_expressions(
616 sorted(self.constructors.algorithms)
617 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200618 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200619 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100620
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200621 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200622 """Generate test keys for the encoding of the specified algorithm."""
623 # These test cases only validate the encoding of algorithms, not
624 # whether the key read from storage is suitable for an operation.
625 # `keys_for_types` generate read tests with an algorithm and a
626 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100627 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100628 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200629 key1 = StorageTestData(version=self.version,
630 id=1, lifetime=0x00000001,
631 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
632 usage=usage, alg=alg, alg2=0,
633 material=b'K',
634 description='alg: ' + descr)
635 yield key1
636 key2 = StorageTestData(version=self.version,
637 id=1, lifetime=0x00000001,
638 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
639 usage=usage, alg=0, alg2=alg,
640 material=b'L',
641 description='alg2: ' + descr)
642 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100643
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200644 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100645 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200646 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200647 for alg in self.constructors.generate_expressions(algorithms):
648 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100649
gabor-mezei-armea840de2021-06-29 15:42:57 +0200650 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200651 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200652 yield from self.all_keys_for_lifetimes()
653 yield from self.all_keys_for_usage_flags()
654 yield from self.all_keys_for_types()
655 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200656
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200657 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100658 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200659 # First build a list of all keys, then construct all the corresponding
660 # test cases. This allows all required information to be obtained in
661 # one go, which is a significant performance gain as the information
662 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200663 all_keys = list(self.generate_all_keys())
664 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200665 if key.location_value() != 0:
666 # Skip keys with a non-default location, because they
667 # require a driver and we currently have no mechanism to
668 # determine whether a driver is available.
669 continue
670 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100671
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200672class StorageFormatForward(StorageFormat):
673 """Storage format stability test cases for forward compatibility."""
674
Tomás González734d22c2023-10-30 15:15:45 +0000675 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200676 super().__init__(info, version, True)
677
678class StorageFormatV0(StorageFormat):
679 """Storage format stability test cases for version 0 compatibility."""
680
Tomás González734d22c2023-10-30 15:15:45 +0000681 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200682 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100683
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200684 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200685 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100686 yield from super().all_keys_for_usage_flags()
687 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200688
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200689 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200690 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200691 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200692 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200693 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200694 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200695 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200696 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200697 algorithm and key type combination.
698 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200699 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200700 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100701 usage_flags = ['PSA_KEY_USAGE_EXPORT']
702 material_usage_flags = usage_flags + [implyer_usage]
703 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200704 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200705 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100706 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
707 alg_expression = crypto_knowledge.short_expression(alg, 1)
708 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200709 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200710 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200711 key = StorageTestData(version=self.version,
712 id=1, lifetime=0x00000001,
713 type=key_type.expression, bits=bits,
714 usage=material_usage_flags,
715 expected_usage=expected_usage_flags,
716 without_implicit_usage=True,
717 alg=alg, alg2=alg2,
718 material=key_material,
719 description=description)
720 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200721
722 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200723 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200724 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800725 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200726 # must be filtered. Pair them with keywords created from its names.
727 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
728 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
729 keyword_translation = {
730 'ECDSA': 'ECC',
731 'ED[0-9]*.*' : 'EDWARDS'
732 }
733 exclusive_keywords = {
734 'EDWARDS': 'ECC'
735 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200736 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
737 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200738 alg_with_keys = {} #type: Dict[str, List[str]]
739 translation_table = str.maketrans('(', '_', ')')
740 for alg in algorithms:
741 # Generate keywords from the name of the algorithm
742 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
743 # Translate keywords for better matching with the key types
744 for keyword in alg_keywords.copy():
745 for pattern, replace in keyword_translation.items():
746 if re.match(pattern, keyword):
747 alg_keywords.remove(keyword)
748 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800749 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200750 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
751 continue
752
753 for key_type in key_types:
754 # Generate keywords from the of the key type
755 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
756
Shaun Case0e7791f2021-12-20 21:14:10 -0800757 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200758 for keyword1, keyword2 in exclusive_keywords.items():
759 if keyword1 in key_type_keywords:
760 key_type_keywords.remove(keyword2)
761
762 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
763 not key_type_keywords.isdisjoint(alg_keywords):
764 if alg in alg_with_keys:
765 alg_with_keys[alg].append(key_type)
766 else:
767 alg_with_keys[alg] = [key_type]
768 return alg_with_keys
769
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200770 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200771 """Generate test keys for usage flag extensions."""
772 # Generate a key type and algorithm pair for each extendable usage
773 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800774 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200775 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200776
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200777 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
778 for alg in sorted(alg_with_keys):
779 for key_type in sorted(alg_with_keys[alg]):
780 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200781 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100782 if kt.is_public() and '_SIGN_' in usage:
783 # Can't sign with a public key
784 continue
785 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200786
gabor-mezei-armea840de2021-06-29 15:42:57 +0200787 def generate_all_keys(self) -> Iterator[StorageTestData]:
788 yield from super().generate_all_keys()
789 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200790
Tomás González734d22c2023-10-30 15:15:45 +0000791
Gilles Peskine69feebd2022-09-16 21:41:47 +0200792class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisdcad1e92022-08-24 11:30:03 +0100793 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100794 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200795 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100796 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200797 'test_suite_psa_crypto_generate_key.generated':
798 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100799 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100800 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Tomás González2bff1bf2023-10-30 15:29:23 +0000801 'test_suite_psa_crypto_low_hash.generated':
802 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200803 'test_suite_psa_crypto_op_fail.generated':
804 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100805 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200806 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100807 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200808 lambda info: StorageFormatV0(info).all_test_cases(),
Tomás González734d22c2023-10-30 15:15:45 +0000809 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100810
Werner Lewisdcad1e92022-08-24 11:30:03 +0100811 def __init__(self, options):
812 super().__init__(options)
Tomás González734d22c2023-10-30 15:15:45 +0000813 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100814
Werner Lewisdcad1e92022-08-24 11:30:03 +0100815 def generate_target(self, name: str, *target_args) -> None:
816 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100817
Tomás González734d22c2023-10-30 15:15:45 +0000818
Gilles Peskine09940492021-01-26 22:16:30 +0100819if __name__ == '__main__':
Gilles Peskine69feebd2022-09-16 21:41:47 +0200820 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)