blob: 5f350d83a98199d207dfb0003c935399f3900699 [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
Gilles Peskine14e428f2021-01-26 22:19:21 +010029from mbedtls_dev import crypto_knowledge
Tomás González734d22c2023-10-30 15:15:45 +000030from mbedtls_dev import macro_collector #pylint: disable=unused-import
31from mbedtls_dev import psa_information
Gilles Peskine897dff92021-03-10 15:03:44 +010032from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020034from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010035
Gilles Peskine14e428f2021-01-26 22:19:21 +010036
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020037def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010038 verb: str, key_type: str, bits: int,
39 dependencies: List[str],
40 *args: str,
41 param_descr: str = ''
42) -> test_case.TestCase:
43 """Return one test case exercising a key creation method
44 for an unsupported key type or size.
45 """
Tomás González734d22c2023-10-30 15:15:45 +000046 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010047 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +010048 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010049 adverb = 'not' if dependencies else 'never'
50 if param_descr:
51 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020052 tc.set_description('PSA {} {} {}-bit {} supported'
53 .format(verb, short_key_type, bits, adverb))
54 tc.set_dependencies(dependencies)
55 tc.set_function(verb + '_not_supported')
56 tc.set_arguments([key_type] + list(args))
57 return tc
58
Gilles Peskine4fa76bd2022-12-15 22:14:28 +010059class KeyTypeNotSupported:
60 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010061
Tomás González734d22c2023-10-30 15:15:45 +000062 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010063 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010064
Gilles Peskine60b29fe2021-02-16 14:06:50 +010065 ALWAYS_SUPPORTED = frozenset([
66 'PSA_KEY_TYPE_DERIVE',
67 'PSA_KEY_TYPE_RAW_DATA',
68 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010069 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010070 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010071 kt: crypto_knowledge.KeyType,
72 param: Optional[int] = None,
73 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010074 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020075 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010076
77 If param is present and not None, emit test cases conditioned on this
78 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +020079 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010080 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010081 if kt.name in self.ALWAYS_SUPPORTED:
82 # Don't generate test cases for key types that are always supported.
83 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010084 return
Gilles Peskineaf172842021-01-27 18:24:48 +010085 import_dependencies = [('!' if param is None else '') +
Tomás González734d22c2023-10-30 15:15:45 +000086 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010087 if kt.params is not None:
88 import_dependencies += [('!' if param == i else '') +
Tomás González734d22c2023-10-30 15:15:45 +000089 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010090 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010091 if kt.name.endswith('_PUBLIC_KEY'):
92 generate_dependencies = []
93 else:
94 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +010095 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +020096 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +010097 'import', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +000098 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +010099 test_case.hex_string(kt.key_material(bits)),
100 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100101 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100102 if not generate_dependencies and param is not None:
103 # If generation is impossible for this key type, rather than
104 # supported or not depending on implementation capabilities,
105 # only generate the test case once.
106 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100107 # For public key we expect that key generation fails with
108 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +0100109 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200110 yield test_case_for_key_type_not_supported(
111 'generate', kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000112 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200113 str(bits),
114 param_descr=param_descr,
115 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100116 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100117
Gilles Peskineb93f8542021-04-19 13:50:25 +0200118 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
119 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
120
Gilles Peskine3d778392021-02-17 15:11:05 +0100121 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100122 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100123 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200124 if key_type in self.ECC_KEY_TYPES:
125 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100126 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100127 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100128 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200129 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100130 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100131 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100132 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100133 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100134 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100135
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200136def test_case_for_key_generation(
137 key_type: str, bits: int,
138 dependencies: List[str],
139 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200140 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200141) -> test_case.TestCase:
142 """Return one test case exercising a key generation.
143 """
Tomás González734d22c2023-10-30 15:15:45 +0000144 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200145 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100146 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200147 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200148 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200149 tc.set_dependencies(dependencies)
150 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100151 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200152
153 return tc
154
155class KeyGenerate:
156 """Generate positive and negative (invalid argument) test cases for key generation."""
157
Tomás González734d22c2023-10-30 15:15:45 +0000158 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200159 self.constructors = info.constructors
160
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200161 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
162 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
163
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100164 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200165 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200166 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200167 ) -> Iterator[test_case.TestCase]:
168 """Return test cases exercising key generation.
169
170 All key types can be generated except for public keys. For public key
171 PSA_ERROR_INVALID_ARGUMENT status is expected.
172 """
173 result = 'PSA_SUCCESS'
174
Tomás González734d22c2023-10-30 15:15:45 +0000175 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200176 if kt.params is not None:
Tomás González734d22c2023-10-30 15:15:45 +0000177 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200178 for i, sym in enumerate(kt.params)]
179 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100180 # The library checks whether the key type is a public key generically,
181 # before it reaches a point where it needs support for the specific key
182 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200183 generate_dependencies = []
184 result = 'PSA_ERROR_INVALID_ARGUMENT'
185 else:
186 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100187 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200188 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200189 for bits in kt.sizes_to_test():
190 yield test_case_for_key_generation(
191 kt.expression, bits,
Tomás González734d22c2023-10-30 15:15:45 +0000192 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200193 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200194 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200195 )
196
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200197 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
198 """Generate test cases that exercise the generation of keys."""
199 for key_type in sorted(self.constructors.key_types):
200 if key_type in self.ECC_KEY_TYPES:
201 continue
202 kt = crypto_knowledge.KeyType(key_type)
203 yield from self.test_cases_for_key_type_key_generation(kt)
204 for curve_family in sorted(self.constructors.ecc_curves):
205 for constr in self.ECC_KEY_TYPES:
206 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200207 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200208
Gilles Peskinec05158b2021-04-27 20:40:10 +0200209class OpFail:
210 """Generate test cases for operations that must fail."""
211 #pylint: disable=too-few-public-methods
212
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100213 class Reason(enum.Enum):
214 NOT_SUPPORTED = 0
215 INVALID = 1
216 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200217 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100218
Tomás González734d22c2023-10-30 15:15:45 +0000219 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec05158b2021-04-27 20:40:10 +0200220 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100221 key_type_expressions = self.constructors.generate_expressions(
222 sorted(self.constructors.key_types)
223 )
224 self.key_types = [crypto_knowledge.KeyType(kt_expr)
225 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200226
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100227 def make_test_case(
228 self,
229 alg: crypto_knowledge.Algorithm,
230 category: crypto_knowledge.AlgorithmCategory,
231 reason: 'Reason',
232 kt: Optional[crypto_knowledge.KeyType] = None,
233 not_deps: FrozenSet[str] = frozenset(),
234 ) -> test_case.TestCase:
235 """Construct a failure test case for a one-key or keyless operation."""
236 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200237 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100238 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200239 if reason == self.Reason.NOT_SUPPORTED:
240 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
241 for dep in not_deps]
242 pretty_reason = '!' + '&'.join(sorted(short_deps))
243 else:
244 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100245 if kt:
246 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100247 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200248 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100249 key_type = ''
250 pretty_type = ''
251 tc.set_description('PSA {} {}: {}{}'
252 .format(category.name.lower(),
253 pretty_alg,
254 pretty_reason,
255 ' with ' + pretty_type if pretty_type else ''))
Tomás González734d22c2023-10-30 15:15:45 +0000256 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100257 for i, dep in enumerate(dependencies):
258 if dep in not_deps:
259 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200260 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100261 tc.set_function(category.name.lower() + '_fail')
David Horstmann4fc7e0e2023-01-24 18:53:15 +0000262 arguments = [] # type: List[str]
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100263 if kt:
264 key_material = kt.key_material(kt.sizes_to_test()[0])
265 arguments += [key_type, test_case.hex_string(key_material)]
266 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200267 if category.is_asymmetric():
268 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100269 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
270 'INVALID_ARGUMENT')
271 arguments.append('PSA_ERROR_' + error)
272 tc.set_arguments(arguments)
273 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200274
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100275 def no_key_test_cases(
276 self,
277 alg: crypto_knowledge.Algorithm,
278 category: crypto_knowledge.AlgorithmCategory,
279 ) -> Iterator[test_case.TestCase]:
280 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200281 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100282 # Compatible operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000283 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100284 yield self.make_test_case(alg, category,
285 self.Reason.NOT_SUPPORTED,
286 not_deps=frozenset([dep]))
287 else:
288 # Incompatible operation, supported algorithm
289 yield self.make_test_case(alg, category, self.Reason.INVALID)
290
291 def one_key_test_cases(
292 self,
293 alg: crypto_knowledge.Algorithm,
294 category: crypto_knowledge.AlgorithmCategory,
295 ) -> Iterator[test_case.TestCase]:
296 """Generate failure test cases for one-key operations with the specified algorithm."""
297 for kt in self.key_types:
298 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200299 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100300 # Compatible key and operation, unsupported algorithm
Tomás González734d22c2023-10-30 15:15:45 +0000301 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100302 yield self.make_test_case(alg, category,
303 self.Reason.NOT_SUPPORTED,
304 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200305 # Public key for a private-key operation
306 if category.is_asymmetric() and kt.is_public():
307 yield self.make_test_case(alg, category,
308 self.Reason.PUBLIC,
309 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100310 elif key_is_compatible:
311 # Compatible key, incompatible operation, supported algorithm
312 yield self.make_test_case(alg, category,
313 self.Reason.INVALID,
314 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200315 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100316 # Incompatible key, compatible operation, supported algorithm
317 yield self.make_test_case(alg, category,
318 self.Reason.INCOMPATIBLE,
319 kt=kt)
320 else:
321 # Incompatible key and operation. Don't test cases where
322 # multiple things are wrong, to keep the number of test
323 # cases reasonable.
324 pass
325
326 def test_cases_for_algorithm(
327 self,
328 alg: crypto_knowledge.Algorithm,
329 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200330 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100331 for category in crypto_knowledge.AlgorithmCategory:
332 if category == crypto_knowledge.AlgorithmCategory.PAKE:
333 # PAKE operations are not implemented yet
334 pass
335 elif category.requires_key():
336 yield from self.one_key_test_cases(alg, category)
337 else:
338 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200339
Gilles Peskinec05158b2021-04-27 20:40:10 +0200340 def all_test_cases(self) -> Iterator[test_case.TestCase]:
341 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200342 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100343 for expr in self.constructors.generate_expressions(algorithms):
344 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200345 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200346
347
Gilles Peskine897dff92021-03-10 15:03:44 +0100348class StorageKey(psa_storage.Key):
349 """Representation of a key for storage format testing."""
350
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200351 IMPLICIT_USAGE_FLAGS = {
352 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
353 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
354 } #type: Dict[str, str]
355 """Mapping of usage flags to the flags that they imply."""
356
357 def __init__(
358 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100359 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200360 without_implicit_usage: Optional[bool] = False,
361 **kwargs
362 ) -> None:
363 """Prepare to generate a key.
364
365 * `usage` : The usage flags used for the key.
Tom Cosgrove49f99bc2022-12-04 16:44:21 +0000366 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200367 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100368 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200369 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100370 for flag in sorted(usage_flags):
371 if flag in self.IMPLICIT_USAGE_FLAGS:
372 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
373 if usage_flags:
374 usage_expression = ' | '.join(sorted(usage_flags))
375 else:
376 usage_expression = '0'
377 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200378
379class StorageTestData(StorageKey):
380 """Representation of test case data for storage format testing."""
381
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200382 def __init__(
383 self,
384 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100385 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200386 **kwargs
387 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200388 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200389
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200390 * `description` : used for the the test case names
391 * `expected_usage`: the usage flags generated as the expected usage flags
392 in the test cases. CAn differ from the usage flags
393 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200394 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100395 super().__init__(**kwargs)
396 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100397 if expected_usage is None:
398 self.expected_usage = self.usage #type: psa_storage.Expr
399 elif expected_usage:
400 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
401 else:
402 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200403
Gilles Peskine897dff92021-03-10 15:03:44 +0100404class StorageFormat:
405 """Storage format stability test cases."""
406
Tomás González734d22c2023-10-30 15:15:45 +0000407 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100408 """Prepare to generate test cases for storage format stability.
409
Tomás González734d22c2023-10-30 15:15:45 +0000410 * `info`: information about the API. See the `psa_information.Information` class.
Gilles Peskine897dff92021-03-10 15:03:44 +0100411 * `version`: the storage format version to generate test cases for.
412 * `forward`: if true, generate forward compatibility test cases which
413 save a key and check that its representation is as intended. Otherwise
414 generate backward compatibility test cases which inject a key
415 representation and check that it can be read and used.
416 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200417 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
418 self.version = version #type: int
419 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100420
Gilles Peskine32611242022-03-19 12:09:13 +0100421 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100422 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100423 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100424 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100425 cls,
426 key_type: psa_storage.Expr, bits: int,
427 alg: psa_storage.Expr
428 ) -> bool:
Gilles Peskine1efe7fd2022-12-15 23:03:19 +0100429 """Whether to exercise the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100430
431 Normally only the type and algorithm matter for compatibility, and
432 this is handled in crypto_knowledge.KeyType.can_do(). This function
433 exists to detect exceptional cases. Exceptional cases detected here
434 are not tested in OpFail and should therefore have manually written
435 test cases.
436 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100437 # Some test keys have the RAW_DATA type and attributes that don't
438 # necessarily make sense. We do this to validate numerical
439 # encodings of the attributes.
440 # Raw data keys have no useful exercise anyway so there is no
441 # loss of test coverage.
442 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
443 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200444 # Mbed TLS only supports 128-bit keys for RC4.
445 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
446 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100447 # OAEP requires room for two hashes plus wrapping
448 m = cls.RSA_OAEP_RE.match(alg.string)
449 if m:
450 hash_alg = m.group(1)
451 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
452 key_length = (bits + 7) // 8
453 # Leave enough room for at least one byte of plaintext
454 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100455 # There's nothing wrong with ECC keys on Brainpool curves,
456 # but operations with them are very slow. So we only exercise them
457 # with a single algorithm, not with all possible hashes. We do
458 # exercise other curves with all algorithms so test coverage is
459 # perfectly adequate like this.
460 m = cls.BRAINPOOL_RE.match(key_type.string)
461 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
462 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100463 return True
464
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200465 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100466 """Construct a storage format test case for the given key.
467
468 If ``forward`` is true, generate a forward compatibility test case:
469 create a key and validate that it has the expected representation.
470 Otherwise generate a backward compatibility test case: inject the
471 key representation into storage and validate that it can be read
472 correctly.
473 """
474 verb = 'save' if self.forward else 'read'
475 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100476 tc.set_description(verb + ' ' + key.description)
Tomás González734d22c2023-10-30 15:15:45 +0000477 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100478 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100479 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100480 )
Tomás González734d22c2023-10-30 15:15:45 +0000481 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100482 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100483 tc.set_function('key_storage_' + verb)
484 if self.forward:
485 extra_arguments = []
486 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200487 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100488 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200489 flags.append('TEST_FLAG_EXERCISE')
490 if 'READ_ONLY' in key.lifetime.string:
491 flags.append('TEST_FLAG_READ_ONLY')
492 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100493 tc.set_arguments([key.lifetime.string,
494 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100495 key.expected_usage.string,
496 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100497 '"' + key.material.hex() + '"',
498 '"' + key.hex() + '"',
499 *extra_arguments])
500 return tc
501
Gilles Peskineefb584d2021-04-21 22:05:34 +0200502 def key_for_lifetime(
503 self,
504 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200505 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200506 """Construct a test key for the given lifetime."""
507 short = lifetime
508 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
509 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100510 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200511 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200512 key = StorageTestData(version=self.version,
513 id=1, lifetime=lifetime,
514 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100515 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200516 material=b'L',
517 description=description)
518 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200519
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200520 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200521 """Generate test keys covering lifetimes."""
522 lifetimes = sorted(self.constructors.lifetimes)
523 expressions = self.constructors.generate_expressions(lifetimes)
524 for lifetime in expressions:
525 # Don't attempt to create or load a volatile key in storage
526 if 'VOLATILE' in lifetime:
527 continue
528 # Don't attempt to create a read-only key in storage,
529 # but do attempt to load one.
530 if 'READ_ONLY' in lifetime and self.forward:
531 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200532 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200533
Gilles Peskinea296e482022-02-24 18:58:08 +0100534 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100535 self,
536 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200537 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100538 test_implicit_usage: Optional[bool] = True
539 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100540 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100541 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100542 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200543 key1 = StorageTestData(version=self.version,
544 id=1, lifetime=0x00000001,
545 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100546 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100547 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100548 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200549 material=b'K',
550 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100551 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100552 usage_expr = key1.expected_usage.string
553 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100554 else:
555 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100556 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100557
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200558 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100559 """Generate test keys covering usage flags."""
560 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100561 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200562 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100563 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200564 for flag1, flag2 in zip(known_flags,
565 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100566 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200567
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200568 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200569 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100570 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200571
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200572 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200573 yield from self.generate_keys_for_usage_flags()
574 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100575
Gilles Peskine6213a002021-04-29 22:28:07 +0200576 def key_for_type_and_alg(
577 self,
578 kt: crypto_knowledge.KeyType,
579 bits: int,
580 alg: Optional[crypto_knowledge.Algorithm] = None,
581 ) -> StorageTestData:
582 """Construct a test key of the given type.
583
584 If alg is not None, this key allows it.
585 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100586 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100587 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200588 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100589 if alg is not None:
590 alg1 = alg.expression
591 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200592 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100593 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200594 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100595 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200596 key = StorageTestData(version=self.version,
597 id=1, lifetime=0x00000001,
598 type=kt.expression, bits=bits,
599 usage=usage_flags, alg=alg1, alg2=alg2,
600 material=key_material,
601 description=description)
602 return key
603
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100604 def keys_for_type(
605 self,
606 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200607 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200608 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200609 """Generate test keys for the given key type."""
610 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100611 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200612 # Test a non-exercisable key, as well as exercisable keys for
613 # each compatible algorithm.
614 # To do: test reading a key from storage with an incompatible
615 # or unsupported algorithm.
616 yield self.key_for_type_and_alg(kt, bits)
617 compatible_algorithms = [alg for alg in all_algorithms
618 if kt.can_do(alg)]
619 for alg in compatible_algorithms:
620 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100621
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200622 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100623 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200624 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200625 all_algorithms = [crypto_knowledge.Algorithm(alg)
626 for alg in self.constructors.generate_expressions(
627 sorted(self.constructors.algorithms)
628 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200629 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200630 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100631
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200632 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200633 """Generate test keys for the encoding of the specified algorithm."""
634 # These test cases only validate the encoding of algorithms, not
635 # whether the key read from storage is suitable for an operation.
636 # `keys_for_types` generate read tests with an algorithm and a
637 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100638 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100639 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200640 key1 = StorageTestData(version=self.version,
641 id=1, lifetime=0x00000001,
642 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
643 usage=usage, alg=alg, alg2=0,
644 material=b'K',
645 description='alg: ' + descr)
646 yield key1
647 key2 = StorageTestData(version=self.version,
648 id=1, lifetime=0x00000001,
649 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
650 usage=usage, alg=0, alg2=alg,
651 material=b'L',
652 description='alg2: ' + descr)
653 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100654
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200655 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100656 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200657 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200658 for alg in self.constructors.generate_expressions(algorithms):
659 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100660
gabor-mezei-armea840de2021-06-29 15:42:57 +0200661 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200662 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200663 yield from self.all_keys_for_lifetimes()
664 yield from self.all_keys_for_usage_flags()
665 yield from self.all_keys_for_types()
666 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200667
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200668 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100669 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200670 # First build a list of all keys, then construct all the corresponding
671 # test cases. This allows all required information to be obtained in
672 # one go, which is a significant performance gain as the information
673 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200674 all_keys = list(self.generate_all_keys())
675 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200676 if key.location_value() != 0:
677 # Skip keys with a non-default location, because they
678 # require a driver and we currently have no mechanism to
679 # determine whether a driver is available.
680 continue
681 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100682
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200683class StorageFormatForward(StorageFormat):
684 """Storage format stability test cases for forward compatibility."""
685
Tomás González734d22c2023-10-30 15:15:45 +0000686 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200687 super().__init__(info, version, True)
688
689class StorageFormatV0(StorageFormat):
690 """Storage format stability test cases for version 0 compatibility."""
691
Tomás González734d22c2023-10-30 15:15:45 +0000692 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200693 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100694
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200695 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200696 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100697 yield from super().all_keys_for_usage_flags()
698 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200699
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200700 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200701 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200702 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200703 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200704 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200705 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200706 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200707 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200708 algorithm and key type combination.
709 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200710 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200711 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100712 usage_flags = ['PSA_KEY_USAGE_EXPORT']
713 material_usage_flags = usage_flags + [implyer_usage]
714 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200715 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200716 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100717 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
718 alg_expression = crypto_knowledge.short_expression(alg, 1)
719 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200720 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200721 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200722 key = StorageTestData(version=self.version,
723 id=1, lifetime=0x00000001,
724 type=key_type.expression, bits=bits,
725 usage=material_usage_flags,
726 expected_usage=expected_usage_flags,
727 without_implicit_usage=True,
728 alg=alg, alg2=alg2,
729 material=key_material,
730 description=description)
731 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200732
733 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200734 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200735 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800736 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200737 # must be filtered. Pair them with keywords created from its names.
738 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
739 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
740 keyword_translation = {
741 'ECDSA': 'ECC',
742 'ED[0-9]*.*' : 'EDWARDS'
743 }
744 exclusive_keywords = {
745 'EDWARDS': 'ECC'
746 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200747 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
748 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200749 alg_with_keys = {} #type: Dict[str, List[str]]
750 translation_table = str.maketrans('(', '_', ')')
751 for alg in algorithms:
752 # Generate keywords from the name of the algorithm
753 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
754 # Translate keywords for better matching with the key types
755 for keyword in alg_keywords.copy():
756 for pattern, replace in keyword_translation.items():
757 if re.match(pattern, keyword):
758 alg_keywords.remove(keyword)
759 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800760 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200761 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
762 continue
763
764 for key_type in key_types:
765 # Generate keywords from the of the key type
766 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
767
Shaun Case0e7791f2021-12-20 21:14:10 -0800768 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200769 for keyword1, keyword2 in exclusive_keywords.items():
770 if keyword1 in key_type_keywords:
771 key_type_keywords.remove(keyword2)
772
773 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
774 not key_type_keywords.isdisjoint(alg_keywords):
775 if alg in alg_with_keys:
776 alg_with_keys[alg].append(key_type)
777 else:
778 alg_with_keys[alg] = [key_type]
779 return alg_with_keys
780
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200781 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200782 """Generate test keys for usage flag extensions."""
783 # Generate a key type and algorithm pair for each extendable usage
784 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800785 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200786 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200787
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200788 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
789 for alg in sorted(alg_with_keys):
790 for key_type in sorted(alg_with_keys[alg]):
791 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200792 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100793 if kt.is_public() and '_SIGN_' in usage:
794 # Can't sign with a public key
795 continue
796 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200797
gabor-mezei-armea840de2021-06-29 15:42:57 +0200798 def generate_all_keys(self) -> Iterator[StorageTestData]:
799 yield from super().generate_all_keys()
800 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200801
Tomás González734d22c2023-10-30 15:15:45 +0000802
Gilles Peskine69feebd2022-09-16 21:41:47 +0200803class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisdcad1e92022-08-24 11:30:03 +0100804 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100805 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200806 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100807 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200808 'test_suite_psa_crypto_generate_key.generated':
809 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100810 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100811 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200812 'test_suite_psa_crypto_op_fail.generated':
813 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100814 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200815 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100816 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200817 lambda info: StorageFormatV0(info).all_test_cases(),
Tomás González734d22c2023-10-30 15:15:45 +0000818 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100819
Werner Lewisdcad1e92022-08-24 11:30:03 +0100820 def __init__(self, options):
821 super().__init__(options)
Tomás González734d22c2023-10-30 15:15:45 +0000822 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100823
Werner Lewisdcad1e92022-08-24 11:30:03 +0100824 def generate_target(self, name: str, *target_args) -> None:
825 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100826
Tomás González734d22c2023-10-30 15:15:45 +0000827
Gilles Peskine09940492021-01-26 22:16:30 +0100828if __name__ == '__main__':
Gilles Peskine69feebd2022-09-16 21:41:47 +0200829 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)