blob: 872d21472582578ffa486831bb3240c8ae2a06ef [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
9# SPDX-License-Identifier: Apache-2.0
10#
11# Licensed under the Apache License, Version 2.0 (the "License"); you may
12# not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22
Gilles Peskinef8b6b502022-03-15 17:26:33 +010023import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import re
Gilles Peskine09940492021-01-26 22:16:30 +010025import sys
Werner Lewisdcad1e92022-08-24 11:30:03 +010026from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010027
28import scripts_path # pylint: disable=unused-import
Tomás González2bff1bf2023-10-30 15:29:23 +000029from mbedtls_dev import crypto_data_tests
Gilles Peskine14e428f2021-01-26 22:19:21 +010030from mbedtls_dev import crypto_knowledge
Tomás González734d22c2023-10-30 15:15:45 +000031from mbedtls_dev import macro_collector #pylint: disable=unused-import
32from mbedtls_dev import psa_information
Gilles Peskine897dff92021-03-10 15:03:44 +010033from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010034from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020035from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010036
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020038def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010039 verb: str, key_type: str, bits: int,
40 dependencies: List[str],
41 *args: str,
42 param_descr: str = ''
43) -> test_case.TestCase:
44 """Return one test case exercising a key creation method
45 for an unsupported key type or size.
46 """
Tomás González734d22c2023-10-30 15:15:45 +000047 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010048 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +010049 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010050 adverb = 'not' if dependencies else 'never'
51 if param_descr:
52 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020053 tc.set_description('PSA {} {} {}-bit {} supported'
54 .format(verb, short_key_type, bits, adverb))
55 tc.set_dependencies(dependencies)
56 tc.set_function(verb + '_not_supported')
57 tc.set_arguments([key_type] + list(args))
58 return tc
59
Gilles Peskine4fa76bd2022-12-15 22:14:28 +010060class KeyTypeNotSupported:
61 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010062
Tomás González734d22c2023-10-30 15:15:45 +000063 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010064 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010065
Gilles Peskine60b29fe2021-02-16 14:06:50 +010066 ALWAYS_SUPPORTED = frozenset([
67 'PSA_KEY_TYPE_DERIVE',
68 'PSA_KEY_TYPE_RAW_DATA',
69 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010070 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010071 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010072 kt: crypto_knowledge.KeyType,
73 param: Optional[int] = None,
74 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010075 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020076 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010077
78 If param is present and not None, emit test cases conditioned on this
79 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020080 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010081 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010082 if kt.name in self.ALWAYS_SUPPORTED:
83 # Don't generate test cases for key types that are always supported.
84 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010085 return
Gilles Peskineaf172842021-01-27 18:24:48 +010086 import_dependencies = [('!' if param is None else '') +
Tomás González734d22c2023-10-30 15:15:45 +000087 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010088 if kt.params is not None:
89 import_dependencies += [('!' if param == i else '') +
Tomás González734d22c2023-10-30 15:15:45 +000090 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010091 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010092 if kt.name.endswith('_PUBLIC_KEY'):
93 generate_dependencies = []
94 else:
95 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +010096 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020097 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +010098 'import', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +000099 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100100 test_case.hex_string(kt.key_material(bits)),
101 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100102 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100103 if not generate_dependencies and param is not None:
104 # If generation is impossible for this key type, rather than
105 # supported or not depending on implementation capabilities,
106 # only generate the test case once.
107 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100108 # For public key we expect that key generation fails with
109 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +0100110 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200111 yield test_case_for_key_type_not_supported(
112 'generate', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000113 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200114 str(bits),
115 param_descr=param_descr,
116 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100117 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100118
Gilles Peskineb93f8542021-04-19 13:50:25 +0200119 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
120 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
121
Gilles Peskine3d778392021-02-17 15:11:05 +0100122 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100123 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100124 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200125 if key_type in self.ECC_KEY_TYPES:
126 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100127 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100128 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100129 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200130 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100131 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100132 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100133 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100134 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100135 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200137def test_case_for_key_generation(
138 key_type: str, bits: int,
139 dependencies: List[str],
140 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200141 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200142) -> test_case.TestCase:
143 """Return one test case exercising a key generation.
144 """
Tomás González734d22c2023-10-30 15:15:45 +0000145 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200146 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100147 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200148 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200149 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200150 tc.set_dependencies(dependencies)
151 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100152 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200153
154 return tc
155
156class KeyGenerate:
157 """Generate positive and negative (invalid argument) test cases for key generation."""
158
Tomás González734d22c2023-10-30 15:15:45 +0000159 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200160 self.constructors = info.constructors
161
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200162 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
163 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
164
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100165 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200166 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200167 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200168 ) -> Iterator[test_case.TestCase]:
169 """Return test cases exercising key generation.
170
171 All key types can be generated except for public keys. For public key
172 PSA_ERROR_INVALID_ARGUMENT status is expected.
173 """
174 result = 'PSA_SUCCESS'
175
Tomás González734d22c2023-10-30 15:15:45 +0000176 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200177 if kt.params is not None:
Tomás González734d22c2023-10-30 15:15:45 +0000178 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200179 for i, sym in enumerate(kt.params)]
180 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100181 # The library checks whether the key type is a public key generically,
182 # before it reaches a point where it needs support for the specific key
183 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200184 generate_dependencies = []
185 result = 'PSA_ERROR_INVALID_ARGUMENT'
186 else:
187 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100188 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200189 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200190 for bits in kt.sizes_to_test():
191 yield test_case_for_key_generation(
192 kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000193 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200194 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200195 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200196 )
197
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200198 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
199 """Generate test cases that exercise the generation of keys."""
200 for key_type in sorted(self.constructors.key_types):
201 if key_type in self.ECC_KEY_TYPES:
202 continue
203 kt = crypto_knowledge.KeyType(key_type)
204 yield from self.test_cases_for_key_type_key_generation(kt)
205 for curve_family in sorted(self.constructors.ecc_curves):
206 for constr in self.ECC_KEY_TYPES:
207 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200208 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200209
Gilles Peskinec05158b2021-04-27 20:40:10 +0200210class OpFail:
211 """Generate test cases for operations that must fail."""
212 #pylint: disable=too-few-public-methods
213
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100214 class Reason(enum.Enum):
215 NOT_SUPPORTED = 0
216 INVALID = 1
217 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200218 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100219
Tomás González734d22c2023-10-30 15:15:45 +0000220 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec05158b2021-04-27 20:40:10 +0200221 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100222 key_type_expressions = self.constructors.generate_expressions(
223 sorted(self.constructors.key_types)
224 )
225 self.key_types = [crypto_knowledge.KeyType(kt_expr)
226 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200227
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100228 def make_test_case(
229 self,
230 alg: crypto_knowledge.Algorithm,
231 category: crypto_knowledge.AlgorithmCategory,
232 reason: 'Reason',
233 kt: Optional[crypto_knowledge.KeyType] = None,
234 not_deps: FrozenSet[str] = frozenset(),
235 ) -> test_case.TestCase:
236 """Construct a failure test case for a one-key or keyless operation."""
237 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200238 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100239 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200240 if reason == self.Reason.NOT_SUPPORTED:
241 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
242 for dep in not_deps]
243 pretty_reason = '!' + '&'.join(sorted(short_deps))
244 else:
245 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100246 if kt:
247 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100248 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200249 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100250 key_type = ''
251 pretty_type = ''
252 tc.set_description('PSA {} {}: {}{}'
253 .format(category.name.lower(),
254 pretty_alg,
255 pretty_reason,
256 ' with ' + pretty_type if pretty_type else ''))
Tomás González734d22c2023-10-30 15:15:45 +0000257 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100258 for i, dep in enumerate(dependencies):
259 if dep in not_deps:
260 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200261 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100262 tc.set_function(category.name.lower() + '_fail')
David Horstmann4fc7e0e2023-01-24 18:53:15 +0000263 arguments = [] # type: List[str]
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100264 if kt:
265 key_material = kt.key_material(kt.sizes_to_test()[0])
266 arguments += [key_type, test_case.hex_string(key_material)]
267 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200268 if category.is_asymmetric():
269 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100270 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
271 'INVALID_ARGUMENT')
272 arguments.append('PSA_ERROR_' + error)
273 tc.set_arguments(arguments)
274 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200275
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100276 def no_key_test_cases(
277 self,
278 alg: crypto_knowledge.Algorithm,
279 category: crypto_knowledge.AlgorithmCategory,
280 ) -> Iterator[test_case.TestCase]:
281 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200282 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100283 # Compatible operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000284 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100285 yield self.make_test_case(alg, category,
286 self.Reason.NOT_SUPPORTED,
287 not_deps=frozenset([dep]))
288 else:
289 # Incompatible operation, supported algorithm
290 yield self.make_test_case(alg, category, self.Reason.INVALID)
291
292 def one_key_test_cases(
293 self,
294 alg: crypto_knowledge.Algorithm,
295 category: crypto_knowledge.AlgorithmCategory,
296 ) -> Iterator[test_case.TestCase]:
297 """Generate failure test cases for one-key operations with the specified algorithm."""
298 for kt in self.key_types:
299 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200300 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100301 # Compatible key and operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000302 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100303 yield self.make_test_case(alg, category,
304 self.Reason.NOT_SUPPORTED,
305 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200306 # Public key for a private-key operation
307 if category.is_asymmetric() and kt.is_public():
308 yield self.make_test_case(alg, category,
309 self.Reason.PUBLIC,
310 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100311 elif key_is_compatible:
312 # Compatible key, incompatible operation, supported algorithm
313 yield self.make_test_case(alg, category,
314 self.Reason.INVALID,
315 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200316 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100317 # Incompatible key, compatible operation, supported algorithm
318 yield self.make_test_case(alg, category,
319 self.Reason.INCOMPATIBLE,
320 kt=kt)
321 else:
322 # Incompatible key and operation. Don't test cases where
323 # multiple things are wrong, to keep the number of test
324 # cases reasonable.
325 pass
326
327 def test_cases_for_algorithm(
328 self,
329 alg: crypto_knowledge.Algorithm,
330 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200331 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100332 for category in crypto_knowledge.AlgorithmCategory:
333 if category == crypto_knowledge.AlgorithmCategory.PAKE:
334 # PAKE operations are not implemented yet
335 pass
336 elif category.requires_key():
337 yield from self.one_key_test_cases(alg, category)
338 else:
339 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200340
Gilles Peskinec05158b2021-04-27 20:40:10 +0200341 def all_test_cases(self) -> Iterator[test_case.TestCase]:
342 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200343 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100344 for expr in self.constructors.generate_expressions(algorithms):
345 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200346 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200347
348
Gilles Peskine897dff92021-03-10 15:03:44 +0100349class StorageKey(psa_storage.Key):
350 """Representation of a key for storage format testing."""
351
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200352 IMPLICIT_USAGE_FLAGS = {
353 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
354 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
355 } #type: Dict[str, str]
356 """Mapping of usage flags to the flags that they imply."""
357
358 def __init__(
359 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100360 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200361 without_implicit_usage: Optional[bool] = False,
362 **kwargs
363 ) -> None:
364 """Prepare to generate a key.
365
366 * `usage` : The usage flags used for the key.
Tom Cosgrove49f99bc2022-12-04 16:44:21 +0000367 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200368 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100369 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200370 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100371 for flag in sorted(usage_flags):
372 if flag in self.IMPLICIT_USAGE_FLAGS:
373 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
374 if usage_flags:
375 usage_expression = ' | '.join(sorted(usage_flags))
376 else:
377 usage_expression = '0'
378 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200379
380class StorageTestData(StorageKey):
381 """Representation of test case data for storage format testing."""
382
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200383 def __init__(
384 self,
385 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100386 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200387 **kwargs
388 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200389 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200390
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200391 * `description` : used for the the test case names
392 * `expected_usage`: the usage flags generated as the expected usage flags
393 in the test cases. CAn differ from the usage flags
394 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200395 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100396 super().__init__(**kwargs)
397 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100398 if expected_usage is None:
399 self.expected_usage = self.usage #type: psa_storage.Expr
400 elif expected_usage:
401 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
402 else:
403 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200404
Gilles Peskine897dff92021-03-10 15:03:44 +0100405class StorageFormat:
406 """Storage format stability test cases."""
407
Tomás González734d22c2023-10-30 15:15:45 +0000408 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100409 """Prepare to generate test cases for storage format stability.
410
Tomás González734d22c2023-10-30 15:15:45 +0000411 * `info`: information about the API. See the `psa_information.Information` class.
Gilles Peskine897dff92021-03-10 15:03:44 +0100412 * `version`: the storage format version to generate test cases for.
413 * `forward`: if true, generate forward compatibility test cases which
414 save a key and check that its representation is as intended. Otherwise
415 generate backward compatibility test cases which inject a key
416 representation and check that it can be read and used.
417 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200418 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
419 self.version = version #type: int
420 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100421
Gilles Peskine32611242022-03-19 12:09:13 +0100422 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100423 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100424 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100425 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100426 cls,
427 key_type: psa_storage.Expr, bits: int,
428 alg: psa_storage.Expr
429 ) -> bool:
Gilles Peskine1efe7fd2022-12-15 23:03:19 +0100430 """Whether to exercise the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100431
432 Normally only the type and algorithm matter for compatibility, and
433 this is handled in crypto_knowledge.KeyType.can_do(). This function
434 exists to detect exceptional cases. Exceptional cases detected here
435 are not tested in OpFail and should therefore have manually written
436 test cases.
437 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100438 # Some test keys have the RAW_DATA type and attributes that don't
439 # necessarily make sense. We do this to validate numerical
440 # encodings of the attributes.
441 # Raw data keys have no useful exercise anyway so there is no
442 # loss of test coverage.
443 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
444 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200445 # Mbed TLS only supports 128-bit keys for RC4.
446 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
447 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100448 # OAEP requires room for two hashes plus wrapping
449 m = cls.RSA_OAEP_RE.match(alg.string)
450 if m:
451 hash_alg = m.group(1)
452 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
453 key_length = (bits + 7) // 8
454 # Leave enough room for at least one byte of plaintext
455 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100456 # There's nothing wrong with ECC keys on Brainpool curves,
457 # but operations with them are very slow. So we only exercise them
458 # with a single algorithm, not with all possible hashes. We do
459 # exercise other curves with all algorithms so test coverage is
460 # perfectly adequate like this.
461 m = cls.BRAINPOOL_RE.match(key_type.string)
462 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
463 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100464 return True
465
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200466 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100467 """Construct a storage format test case for the given key.
468
469 If ``forward`` is true, generate a forward compatibility test case:
470 create a key and validate that it has the expected representation.
471 Otherwise generate a backward compatibility test case: inject the
472 key representation into storage and validate that it can be read
473 correctly.
474 """
475 verb = 'save' if self.forward else 'read'
476 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100477 tc.set_description(verb + ' ' + key.description)
Tomás González734d22c2023-10-30 15:15:45 +0000478 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100479 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100480 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100481 )
Tomás González734d22c2023-10-30 15:15:45 +0000482 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100483 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100484 tc.set_function('key_storage_' + verb)
485 if self.forward:
486 extra_arguments = []
487 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200488 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100489 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200490 flags.append('TEST_FLAG_EXERCISE')
491 if 'READ_ONLY' in key.lifetime.string:
492 flags.append('TEST_FLAG_READ_ONLY')
493 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100494 tc.set_arguments([key.lifetime.string,
495 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100496 key.expected_usage.string,
497 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100498 '"' + key.material.hex() + '"',
499 '"' + key.hex() + '"',
500 *extra_arguments])
501 return tc
502
Gilles Peskineefb584d2021-04-21 22:05:34 +0200503 def key_for_lifetime(
504 self,
505 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200506 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200507 """Construct a test key for the given lifetime."""
508 short = lifetime
509 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
510 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100511 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200512 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200513 key = StorageTestData(version=self.version,
514 id=1, lifetime=lifetime,
515 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100516 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200517 material=b'L',
518 description=description)
519 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200520
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200521 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200522 """Generate test keys covering lifetimes."""
523 lifetimes = sorted(self.constructors.lifetimes)
524 expressions = self.constructors.generate_expressions(lifetimes)
525 for lifetime in expressions:
526 # Don't attempt to create or load a volatile key in storage
527 if 'VOLATILE' in lifetime:
528 continue
529 # Don't attempt to create a read-only key in storage,
530 # but do attempt to load one.
531 if 'READ_ONLY' in lifetime and self.forward:
532 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200533 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200534
Gilles Peskinea296e482022-02-24 18:58:08 +0100535 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100536 self,
537 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200538 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100539 test_implicit_usage: Optional[bool] = True
540 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100541 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100542 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100543 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200544 key1 = StorageTestData(version=self.version,
545 id=1, lifetime=0x00000001,
546 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100547 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100548 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100549 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200550 material=b'K',
551 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100552 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100553 usage_expr = key1.expected_usage.string
554 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100555 else:
556 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100557 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100558
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200559 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100560 """Generate test keys covering usage flags."""
561 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100562 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200563 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100564 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200565 for flag1, flag2 in zip(known_flags,
566 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100567 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200568
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200569 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200570 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100571 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200572
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200573 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200574 yield from self.generate_keys_for_usage_flags()
575 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100576
Gilles Peskine6213a002021-04-29 22:28:07 +0200577 def key_for_type_and_alg(
578 self,
579 kt: crypto_knowledge.KeyType,
580 bits: int,
581 alg: Optional[crypto_knowledge.Algorithm] = None,
582 ) -> StorageTestData:
583 """Construct a test key of the given type.
584
585 If alg is not None, this key allows it.
586 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100587 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100588 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200589 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100590 if alg is not None:
591 alg1 = alg.expression
592 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200593 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100594 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200595 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100596 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200597 key = StorageTestData(version=self.version,
598 id=1, lifetime=0x00000001,
599 type=kt.expression, bits=bits,
600 usage=usage_flags, alg=alg1, alg2=alg2,
601 material=key_material,
602 description=description)
603 return key
604
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100605 def keys_for_type(
606 self,
607 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200608 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200609 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200610 """Generate test keys for the given key type."""
611 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100612 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200613 # Test a non-exercisable key, as well as exercisable keys for
614 # each compatible algorithm.
615 # To do: test reading a key from storage with an incompatible
616 # or unsupported algorithm.
617 yield self.key_for_type_and_alg(kt, bits)
618 compatible_algorithms = [alg for alg in all_algorithms
619 if kt.can_do(alg)]
620 for alg in compatible_algorithms:
621 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100622
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200623 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100624 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200625 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200626 all_algorithms = [crypto_knowledge.Algorithm(alg)
627 for alg in self.constructors.generate_expressions(
628 sorted(self.constructors.algorithms)
629 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200630 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200631 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100632
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200633 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200634 """Generate test keys for the encoding of the specified algorithm."""
635 # These test cases only validate the encoding of algorithms, not
636 # whether the key read from storage is suitable for an operation.
637 # `keys_for_types` generate read tests with an algorithm and a
638 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100639 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100640 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200641 key1 = StorageTestData(version=self.version,
642 id=1, lifetime=0x00000001,
643 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
644 usage=usage, alg=alg, alg2=0,
645 material=b'K',
646 description='alg: ' + descr)
647 yield key1
648 key2 = StorageTestData(version=self.version,
649 id=1, lifetime=0x00000001,
650 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
651 usage=usage, alg=0, alg2=alg,
652 material=b'L',
653 description='alg2: ' + descr)
654 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100655
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200656 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100657 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200658 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200659 for alg in self.constructors.generate_expressions(algorithms):
660 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100661
gabor-mezei-armea840de2021-06-29 15:42:57 +0200662 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200663 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200664 yield from self.all_keys_for_lifetimes()
665 yield from self.all_keys_for_usage_flags()
666 yield from self.all_keys_for_types()
667 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200668
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200669 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100670 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200671 # First build a list of all keys, then construct all the corresponding
672 # test cases. This allows all required information to be obtained in
673 # one go, which is a significant performance gain as the information
674 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200675 all_keys = list(self.generate_all_keys())
676 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200677 if key.location_value() != 0:
678 # Skip keys with a non-default location, because they
679 # require a driver and we currently have no mechanism to
680 # determine whether a driver is available.
681 continue
682 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100683
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200684class StorageFormatForward(StorageFormat):
685 """Storage format stability test cases for forward compatibility."""
686
Tomás González734d22c2023-10-30 15:15:45 +0000687 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200688 super().__init__(info, version, True)
689
690class StorageFormatV0(StorageFormat):
691 """Storage format stability test cases for version 0 compatibility."""
692
Tomás González734d22c2023-10-30 15:15:45 +0000693 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200694 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100695
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200696 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200697 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100698 yield from super().all_keys_for_usage_flags()
699 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200700
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200701 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200702 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200703 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200704 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200705 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200706 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200707 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200708 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200709 algorithm and key type combination.
710 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200711 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200712 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100713 usage_flags = ['PSA_KEY_USAGE_EXPORT']
714 material_usage_flags = usage_flags + [implyer_usage]
715 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200716 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200717 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100718 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
719 alg_expression = crypto_knowledge.short_expression(alg, 1)
720 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200721 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200722 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200723 key = StorageTestData(version=self.version,
724 id=1, lifetime=0x00000001,
725 type=key_type.expression, bits=bits,
726 usage=material_usage_flags,
727 expected_usage=expected_usage_flags,
728 without_implicit_usage=True,
729 alg=alg, alg2=alg2,
730 material=key_material,
731 description=description)
732 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200733
734 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200735 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200736 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800737 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200738 # must be filtered. Pair them with keywords created from its names.
739 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
740 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
741 keyword_translation = {
742 'ECDSA': 'ECC',
743 'ED[0-9]*.*' : 'EDWARDS'
744 }
745 exclusive_keywords = {
746 'EDWARDS': 'ECC'
747 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200748 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
749 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200750 alg_with_keys = {} #type: Dict[str, List[str]]
751 translation_table = str.maketrans('(', '_', ')')
752 for alg in algorithms:
753 # Generate keywords from the name of the algorithm
754 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
755 # Translate keywords for better matching with the key types
756 for keyword in alg_keywords.copy():
757 for pattern, replace in keyword_translation.items():
758 if re.match(pattern, keyword):
759 alg_keywords.remove(keyword)
760 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800761 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200762 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
763 continue
764
765 for key_type in key_types:
766 # Generate keywords from the of the key type
767 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
768
Shaun Case0e7791f2021-12-20 21:14:10 -0800769 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200770 for keyword1, keyword2 in exclusive_keywords.items():
771 if keyword1 in key_type_keywords:
772 key_type_keywords.remove(keyword2)
773
774 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
775 not key_type_keywords.isdisjoint(alg_keywords):
776 if alg in alg_with_keys:
777 alg_with_keys[alg].append(key_type)
778 else:
779 alg_with_keys[alg] = [key_type]
780 return alg_with_keys
781
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200782 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200783 """Generate test keys for usage flag extensions."""
784 # Generate a key type and algorithm pair for each extendable usage
785 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800786 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200787 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200788
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200789 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
790 for alg in sorted(alg_with_keys):
791 for key_type in sorted(alg_with_keys[alg]):
792 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200793 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100794 if kt.is_public() and '_SIGN_' in usage:
795 # Can't sign with a public key
796 continue
797 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200798
gabor-mezei-armea840de2021-06-29 15:42:57 +0200799 def generate_all_keys(self) -> Iterator[StorageTestData]:
800 yield from super().generate_all_keys()
801 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200802
Tomás González734d22c2023-10-30 15:15:45 +0000803
Gilles Peskine69feebd2022-09-16 21:41:47 +0200804class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisdcad1e92022-08-24 11:30:03 +0100805 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100806 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200807 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100808 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200809 'test_suite_psa_crypto_generate_key.generated':
810 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100811 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100812 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Tomás González2bff1bf2023-10-30 15:29:23 +0000813 'test_suite_psa_crypto_low_hash.generated':
814 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200815 'test_suite_psa_crypto_op_fail.generated':
816 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100817 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200818 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100819 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200820 lambda info: StorageFormatV0(info).all_test_cases(),
Tomás González734d22c2023-10-30 15:15:45 +0000821 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100822
Werner Lewisdcad1e92022-08-24 11:30:03 +0100823 def __init__(self, options):
824 super().__init__(options)
Tomás González734d22c2023-10-30 15:15:45 +0000825 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100826
Werner Lewisdcad1e92022-08-24 11:30:03 +0100827 def generate_target(self, name: str, *target_args) -> None:
828 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100829
Tomás González734d22c2023-10-30 15:15:45 +0000830
Gilles Peskine09940492021-01-26 22:16:30 +0100831if __name__ == '__main__':
Gilles Peskine69feebd2022-09-16 21:41:47 +0200832 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)