blob: 75d02b9e2326ded1f79f3712eaab491904328606 [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
Dave Rodgman16799db2023-11-02 19:47:20 +00009# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine09940492021-01-26 22:16:30 +010010
Gilles Peskinecba28a72022-03-15 17:26:33 +010011import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010012import re
Gilles Peskine09940492021-01-26 22:16:30 +010013import sys
Werner Lewisfbb75e32022-08-24 11:30:03 +010014from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010015
16import scripts_path # pylint: disable=unused-import
David Horstmanncd84bb22024-05-03 14:36:12 +010017from mbedtls_framework import crypto_data_tests
18from mbedtls_framework import crypto_knowledge
19from mbedtls_framework import macro_collector #pylint: disable=unused-import
20from mbedtls_framework import psa_information
21from mbedtls_framework import psa_storage
22from mbedtls_framework import test_case
23from mbedtls_framework import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010024
Gilles Peskine14e428f2021-01-26 22:19:21 +010025
Gilles Peskine14e428f2021-01-26 22:19:21 +010026
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020027def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010028 verb: str, key_type: str, bits: int,
29 dependencies: List[str],
30 *args: str,
31 param_descr: str = ''
32) -> test_case.TestCase:
33 """Return one test case exercising a key creation method
34 for an unsupported key type or size.
35 """
Gilles Peskinefdb72232023-06-19 20:46:47 +020036 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010037 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +010038 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010039 adverb = 'not' if dependencies else 'never'
40 if param_descr:
41 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020042 tc.set_description('PSA {} {} {}-bit {} supported'
43 .format(verb, short_key_type, bits, adverb))
44 tc.set_dependencies(dependencies)
45 tc.set_function(verb + '_not_supported')
46 tc.set_arguments([key_type] + list(args))
47 return tc
48
Gilles Peskine0e9e4422022-12-15 22:14:28 +010049class KeyTypeNotSupported:
50 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010051
Gilles Peskinefdb72232023-06-19 20:46:47 +020052 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010053 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010054
Gilles Peskine60b29fe2021-02-16 14:06:50 +010055 ALWAYS_SUPPORTED = frozenset([
56 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +010057 'PSA_KEY_TYPE_PASSWORD',
58 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +010059 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +020060 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +010061 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010062 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010063 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010064 kt: crypto_knowledge.KeyType,
65 param: Optional[int] = None,
66 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010067 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020068 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010069
70 If param is present and not None, emit test cases conditioned on this
71 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020072 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010073 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010074 if kt.name in self.ALWAYS_SUPPORTED:
75 # Don't generate test cases for key types that are always supported.
76 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010077 return
Gilles Peskineaf172842021-01-27 18:24:48 +010078 import_dependencies = [('!' if param is None else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020079 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010080 if kt.params is not None:
81 import_dependencies += [('!' if param == i else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020082 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010083 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010084 if kt.name.endswith('_PUBLIC_KEY'):
85 generate_dependencies = []
86 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +020087 generate_dependencies = \
88 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
89 import_dependencies = \
90 psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +010091 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020092 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +010093 'import', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +020094 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +010095 test_case.hex_string(kt.key_material(bits)),
96 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +010097 )
Gilles Peskineaf172842021-01-27 18:24:48 +010098 if not generate_dependencies and param is not None:
99 # If generation is impossible for this key type, rather than
100 # supported or not depending on implementation capabilities,
101 # only generate the test case once.
102 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100103 # For public key we expect that key generation fails with
104 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100105 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200106 yield test_case_for_key_type_not_supported(
107 'generate', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200108 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200109 str(bits),
110 param_descr=param_descr,
111 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100112 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100113
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200114 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
115 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200116 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
117 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200118
Gilles Peskine3d778392021-02-17 15:11:05 +0100119 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100120 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100121 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200122 if key_type in self.ECC_KEY_TYPES:
123 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200124 if key_type in self.DH_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 Peskine537d5fa2021-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')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200135 for dh_family in sorted(self.constructors.dh_groups):
136 for constr in self.DH_KEY_TYPES:
137 kt = crypto_knowledge.KeyType(constr, [dh_family])
138 yield from self.test_cases_for_key_type_not_supported(
139 kt, param_descr='type')
140 yield from self.test_cases_for_key_type_not_supported(
141 kt, 0, param_descr='group')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100142
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200143def test_case_for_key_generation(
144 key_type: str, bits: int,
145 dependencies: List[str],
146 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200147 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200148) -> test_case.TestCase:
149 """Return one test case exercising a key generation.
150 """
Gilles Peskinefdb72232023-06-19 20:46:47 +0200151 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200152 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100153 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200154 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200155 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200156 tc.set_dependencies(dependencies)
157 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100158 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200159
160 return tc
161
162class KeyGenerate:
163 """Generate positive and negative (invalid argument) test cases for key generation."""
164
Gilles Peskinefdb72232023-06-19 20:46:47 +0200165 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200166 self.constructors = info.constructors
167
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200168 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
169 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200170 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
171 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200172
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100173 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200174 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200175 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200176 ) -> Iterator[test_case.TestCase]:
177 """Return test cases exercising key generation.
178
179 All key types can be generated except for public keys. For public key
180 PSA_ERROR_INVALID_ARGUMENT status is expected.
181 """
182 result = 'PSA_SUCCESS'
183
Gilles Peskinefdb72232023-06-19 20:46:47 +0200184 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200185 if kt.params is not None:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200186 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200187 for i, sym in enumerate(kt.params)]
188 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100189 # The library checks whether the key type is a public key generically,
190 # before it reaches a point where it needs support for the specific key
191 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200192 generate_dependencies = []
193 result = 'PSA_ERROR_INVALID_ARGUMENT'
194 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200195 generate_dependencies = \
196 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200197 for bits in kt.sizes_to_test():
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000198 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Waleed Elmelegyd7bdbbe2023-07-20 16:26:58 +0000199 size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits)
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000200 test_dependencies = generate_dependencies + [size_dependency]
201 else:
202 test_dependencies = generate_dependencies
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200203 yield test_case_for_key_generation(
204 kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200205 psa_information.finish_family_dependencies(test_dependencies, bits),
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200206 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200207 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200208 )
209
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200210 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
211 """Generate test cases that exercise the generation of keys."""
212 for key_type in sorted(self.constructors.key_types):
213 if key_type in self.ECC_KEY_TYPES:
214 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200215 if key_type in self.DH_KEY_TYPES:
216 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200217 kt = crypto_knowledge.KeyType(key_type)
218 yield from self.test_cases_for_key_type_key_generation(kt)
219 for curve_family in sorted(self.constructors.ecc_curves):
220 for constr in self.ECC_KEY_TYPES:
221 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200222 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200223 for dh_family in sorted(self.constructors.dh_groups):
224 for constr in self.DH_KEY_TYPES:
225 kt = crypto_knowledge.KeyType(constr, [dh_family])
226 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200227
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200228class OpFail:
229 """Generate test cases for operations that must fail."""
230 #pylint: disable=too-few-public-methods
231
Gilles Peskinecba28a72022-03-15 17:26:33 +0100232 class Reason(enum.Enum):
233 NOT_SUPPORTED = 0
234 INVALID = 1
235 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200236 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100237
Gilles Peskinefdb72232023-06-19 20:46:47 +0200238 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200239 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100240 key_type_expressions = self.constructors.generate_expressions(
241 sorted(self.constructors.key_types)
242 )
243 self.key_types = [crypto_knowledge.KeyType(kt_expr)
244 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200245
Gilles Peskinecba28a72022-03-15 17:26:33 +0100246 def make_test_case(
247 self,
248 alg: crypto_knowledge.Algorithm,
249 category: crypto_knowledge.AlgorithmCategory,
250 reason: 'Reason',
251 kt: Optional[crypto_knowledge.KeyType] = None,
252 not_deps: FrozenSet[str] = frozenset(),
253 ) -> test_case.TestCase:
254 """Construct a failure test case for a one-key or keyless operation."""
255 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200256 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100257 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200258 if reason == self.Reason.NOT_SUPPORTED:
259 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
260 for dep in not_deps]
261 pretty_reason = '!' + '&'.join(sorted(short_deps))
262 else:
263 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100264 if kt:
265 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100266 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200267 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100268 key_type = ''
269 pretty_type = ''
270 tc.set_description('PSA {} {}: {}{}'
271 .format(category.name.lower(),
272 pretty_alg,
273 pretty_reason,
274 ' with ' + pretty_type if pretty_type else ''))
Gilles Peskinefdb72232023-06-19 20:46:47 +0200275 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
276 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100277 for i, dep in enumerate(dependencies):
278 if dep in not_deps:
279 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200280 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100281 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000282 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100283 if kt:
284 key_material = kt.key_material(kt.sizes_to_test()[0])
285 arguments += [key_type, test_case.hex_string(key_material)]
286 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200287 if category.is_asymmetric():
288 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100289 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
290 'INVALID_ARGUMENT')
291 arguments.append('PSA_ERROR_' + error)
292 tc.set_arguments(arguments)
293 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200294
Gilles Peskinecba28a72022-03-15 17:26:33 +0100295 def no_key_test_cases(
296 self,
297 alg: crypto_knowledge.Algorithm,
298 category: crypto_knowledge.AlgorithmCategory,
299 ) -> Iterator[test_case.TestCase]:
300 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200301 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100302 # Compatible operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200303 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100304 yield self.make_test_case(alg, category,
305 self.Reason.NOT_SUPPORTED,
306 not_deps=frozenset([dep]))
307 else:
308 # Incompatible operation, supported algorithm
309 yield self.make_test_case(alg, category, self.Reason.INVALID)
310
311 def one_key_test_cases(
312 self,
313 alg: crypto_knowledge.Algorithm,
314 category: crypto_knowledge.AlgorithmCategory,
315 ) -> Iterator[test_case.TestCase]:
316 """Generate failure test cases for one-key operations with the specified algorithm."""
317 for kt in self.key_types:
318 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200319 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100320 # Compatible key and operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200321 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100322 yield self.make_test_case(alg, category,
323 self.Reason.NOT_SUPPORTED,
324 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200325 # Public key for a private-key operation
326 if category.is_asymmetric() and kt.is_public():
327 yield self.make_test_case(alg, category,
328 self.Reason.PUBLIC,
329 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100330 elif key_is_compatible:
331 # Compatible key, incompatible operation, supported algorithm
332 yield self.make_test_case(alg, category,
333 self.Reason.INVALID,
334 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200335 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100336 # Incompatible key, compatible operation, supported algorithm
337 yield self.make_test_case(alg, category,
338 self.Reason.INCOMPATIBLE,
339 kt=kt)
340 else:
341 # Incompatible key and operation. Don't test cases where
342 # multiple things are wrong, to keep the number of test
343 # cases reasonable.
344 pass
345
346 def test_cases_for_algorithm(
347 self,
348 alg: crypto_knowledge.Algorithm,
349 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200350 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100351 for category in crypto_knowledge.AlgorithmCategory:
352 if category == crypto_knowledge.AlgorithmCategory.PAKE:
353 # PAKE operations are not implemented yet
354 pass
355 elif category.requires_key():
356 yield from self.one_key_test_cases(alg, category)
357 else:
358 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200359
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200360 def all_test_cases(self) -> Iterator[test_case.TestCase]:
361 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200362 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100363 for expr in self.constructors.generate_expressions(algorithms):
364 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200365 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200366
367
Gilles Peskine897dff92021-03-10 15:03:44 +0100368class StorageKey(psa_storage.Key):
369 """Representation of a key for storage format testing."""
370
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200371 IMPLICIT_USAGE_FLAGS = {
372 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
373 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
374 } #type: Dict[str, str]
375 """Mapping of usage flags to the flags that they imply."""
376
377 def __init__(
378 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100379 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200380 without_implicit_usage: Optional[bool] = False,
381 **kwargs
382 ) -> None:
383 """Prepare to generate a key.
384
385 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000386 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200387 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100388 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200389 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100390 for flag in sorted(usage_flags):
391 if flag in self.IMPLICIT_USAGE_FLAGS:
392 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
393 if usage_flags:
394 usage_expression = ' | '.join(sorted(usage_flags))
395 else:
396 usage_expression = '0'
397 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200398
399class StorageTestData(StorageKey):
400 """Representation of test case data for storage format testing."""
401
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200402 def __init__(
403 self,
404 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100405 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200406 **kwargs
407 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200408 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200409
Tom Cosgrove1797b052022-12-04 17:19:59 +0000410 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200411 * `expected_usage`: the usage flags generated as the expected usage flags
412 in the test cases. CAn differ from the usage flags
413 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200414 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100415 super().__init__(**kwargs)
416 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100417 if expected_usage is None:
418 self.expected_usage = self.usage #type: psa_storage.Expr
419 elif expected_usage:
420 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
421 else:
422 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200423
Gilles Peskine897dff92021-03-10 15:03:44 +0100424class StorageFormat:
425 """Storage format stability test cases."""
426
Gilles Peskinefdb72232023-06-19 20:46:47 +0200427 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100428 """Prepare to generate test cases for storage format stability.
429
430 * `info`: information about the API. See the `Information` class.
431 * `version`: the storage format version to generate test cases for.
432 * `forward`: if true, generate forward compatibility test cases which
433 save a key and check that its representation is as intended. Otherwise
434 generate backward compatibility test cases which inject a key
435 representation and check that it can be read and used.
436 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200437 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
438 self.version = version #type: int
439 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100440
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100441 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100442 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100443 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100444 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100445 cls,
446 key_type: psa_storage.Expr, bits: int,
447 alg: psa_storage.Expr
448 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100449 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100450
451 Normally only the type and algorithm matter for compatibility, and
452 this is handled in crypto_knowledge.KeyType.can_do(). This function
453 exists to detect exceptional cases. Exceptional cases detected here
454 are not tested in OpFail and should therefore have manually written
455 test cases.
456 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100457 # Some test keys have the RAW_DATA type and attributes that don't
458 # necessarily make sense. We do this to validate numerical
459 # encodings of the attributes.
460 # Raw data keys have no useful exercise anyway so there is no
461 # loss of test coverage.
462 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
463 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100464 # OAEP requires room for two hashes plus wrapping
465 m = cls.RSA_OAEP_RE.match(alg.string)
466 if m:
467 hash_alg = m.group(1)
468 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
469 key_length = (bits + 7) // 8
470 # Leave enough room for at least one byte of plaintext
471 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100472 # There's nothing wrong with ECC keys on Brainpool curves,
473 # but operations with them are very slow. So we only exercise them
474 # with a single algorithm, not with all possible hashes. We do
475 # exercise other curves with all algorithms so test coverage is
476 # perfectly adequate like this.
477 m = cls.BRAINPOOL_RE.match(key_type.string)
478 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
479 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100480 return True
481
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200482 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100483 """Construct a storage format test case for the given key.
484
485 If ``forward`` is true, generate a forward compatibility test case:
486 create a key and validate that it has the expected representation.
487 Otherwise generate a backward compatibility test case: inject the
488 key representation into storage and validate that it can be read
489 correctly.
490 """
491 verb = 'save' if self.forward else 'read'
492 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100493 tc.set_description(verb + ' ' + key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200494 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100495 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100496 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100497 )
Gilles Peskinefdb72232023-06-19 20:46:47 +0200498 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
Yanray Wang19583e42023-11-13 17:39:32 +0800499 dependencies += psa_information.generate_deps_from_description(key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200500 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100501 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100502 tc.set_function('key_storage_' + verb)
503 if self.forward:
504 extra_arguments = []
505 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200506 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100507 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200508 flags.append('TEST_FLAG_EXERCISE')
509 if 'READ_ONLY' in key.lifetime.string:
510 flags.append('TEST_FLAG_READ_ONLY')
511 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100512 tc.set_arguments([key.lifetime.string,
513 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100514 key.expected_usage.string,
515 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100516 '"' + key.material.hex() + '"',
517 '"' + key.hex() + '"',
518 *extra_arguments])
519 return tc
520
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200521 def key_for_lifetime(
522 self,
523 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200524 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200525 """Construct a test key for the given lifetime."""
526 short = lifetime
527 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
528 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100529 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200530 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200531 key = StorageTestData(version=self.version,
532 id=1, lifetime=lifetime,
533 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100534 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200535 material=b'L',
536 description=description)
537 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200538
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200539 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200540 """Generate test keys covering lifetimes."""
541 lifetimes = sorted(self.constructors.lifetimes)
542 expressions = self.constructors.generate_expressions(lifetimes)
543 for lifetime in expressions:
544 # Don't attempt to create or load a volatile key in storage
545 if 'VOLATILE' in lifetime:
546 continue
547 # Don't attempt to create a read-only key in storage,
548 # but do attempt to load one.
549 if 'READ_ONLY' in lifetime and self.forward:
550 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200551 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200552
Gilles Peskinef7614272022-02-24 18:58:08 +0100553 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100554 self,
555 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200556 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100557 test_implicit_usage: Optional[bool] = True
558 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100559 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100560 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100561 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200562 key1 = StorageTestData(version=self.version,
563 id=1, lifetime=0x00000001,
564 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100565 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100566 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100567 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200568 material=b'K',
569 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100570 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100571 usage_expr = key1.expected_usage.string
572 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100573 else:
574 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100575 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100576
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200577 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100578 """Generate test keys covering usage flags."""
579 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100580 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200581 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100582 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200583 for flag1, flag2 in zip(known_flags,
584 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100585 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200586
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200587 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200588 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100589 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200590
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200591 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200592 yield from self.generate_keys_for_usage_flags()
593 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100594
Gilles Peskine7de7c102021-04-29 22:28:07 +0200595 def key_for_type_and_alg(
596 self,
597 kt: crypto_knowledge.KeyType,
598 bits: int,
599 alg: Optional[crypto_knowledge.Algorithm] = None,
600 ) -> StorageTestData:
601 """Construct a test key of the given type.
602
603 If alg is not None, this key allows it.
604 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100605 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100606 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200607 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100608 if alg is not None:
609 alg1 = alg.expression
610 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200611 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100612 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200613 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100614 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200615 key = StorageTestData(version=self.version,
616 id=1, lifetime=0x00000001,
617 type=kt.expression, bits=bits,
618 usage=usage_flags, alg=alg1, alg2=alg2,
619 material=key_material,
620 description=description)
621 return key
622
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100623 def keys_for_type(
624 self,
625 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200626 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200627 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200628 """Generate test keys for the given key type."""
629 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100630 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200631 # Test a non-exercisable key, as well as exercisable keys for
632 # each compatible algorithm.
633 # To do: test reading a key from storage with an incompatible
634 # or unsupported algorithm.
635 yield self.key_for_type_and_alg(kt, bits)
636 compatible_algorithms = [alg for alg in all_algorithms
637 if kt.can_do(alg)]
638 for alg in compatible_algorithms:
639 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100640
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200641 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100642 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200643 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200644 all_algorithms = [crypto_knowledge.Algorithm(alg)
645 for alg in self.constructors.generate_expressions(
646 sorted(self.constructors.algorithms)
647 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200648 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200649 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100650
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200652 """Generate test keys for the encoding of the specified algorithm."""
653 # These test cases only validate the encoding of algorithms, not
654 # whether the key read from storage is suitable for an operation.
655 # `keys_for_types` generate read tests with an algorithm and a
656 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100657 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100658 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200659 key1 = StorageTestData(version=self.version,
660 id=1, lifetime=0x00000001,
661 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
662 usage=usage, alg=alg, alg2=0,
663 material=b'K',
664 description='alg: ' + descr)
665 yield key1
666 key2 = StorageTestData(version=self.version,
667 id=1, lifetime=0x00000001,
668 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
669 usage=usage, alg=0, alg2=alg,
670 material=b'L',
671 description='alg2: ' + descr)
672 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100673
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200674 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100675 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200676 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200677 for alg in self.constructors.generate_expressions(algorithms):
678 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100679
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200680 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200681 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200682 yield from self.all_keys_for_lifetimes()
683 yield from self.all_keys_for_usage_flags()
684 yield from self.all_keys_for_types()
685 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200686
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200687 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100688 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200689 # First build a list of all keys, then construct all the corresponding
690 # test cases. This allows all required information to be obtained in
691 # one go, which is a significant performance gain as the information
692 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200693 all_keys = list(self.generate_all_keys())
694 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200695 if key.location_value() != 0:
696 # Skip keys with a non-default location, because they
697 # require a driver and we currently have no mechanism to
698 # determine whether a driver is available.
699 continue
700 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100701
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200702class StorageFormatForward(StorageFormat):
703 """Storage format stability test cases for forward compatibility."""
704
Gilles Peskinefdb72232023-06-19 20:46:47 +0200705 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200706 super().__init__(info, version, True)
707
708class StorageFormatV0(StorageFormat):
709 """Storage format stability test cases for version 0 compatibility."""
710
Gilles Peskinefdb72232023-06-19 20:46:47 +0200711 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200712 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100713
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200714 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200715 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100716 yield from super().all_keys_for_usage_flags()
717 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200718
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200719 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200720 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200721 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200722 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200723 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200724 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200725 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200726 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200727 algorithm and key type combination.
728 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200729 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200730 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100731 usage_flags = ['PSA_KEY_USAGE_EXPORT']
732 material_usage_flags = usage_flags + [implyer_usage]
733 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200734 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200735 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100736 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
737 alg_expression = crypto_knowledge.short_expression(alg, 1)
738 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200739 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200740 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200741 key = StorageTestData(version=self.version,
742 id=1, lifetime=0x00000001,
743 type=key_type.expression, bits=bits,
744 usage=material_usage_flags,
745 expected_usage=expected_usage_flags,
746 without_implicit_usage=True,
747 alg=alg, alg2=alg2,
748 material=key_material,
749 description=description)
750 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200751
752 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200753 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200754 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800755 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200756 # must be filtered. Pair them with keywords created from its names.
757 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
758 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
759 keyword_translation = {
760 'ECDSA': 'ECC',
761 'ED[0-9]*.*' : 'EDWARDS'
762 }
763 exclusive_keywords = {
764 'EDWARDS': 'ECC'
765 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200766 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
767 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200768 alg_with_keys = {} #type: Dict[str, List[str]]
769 translation_table = str.maketrans('(', '_', ')')
770 for alg in algorithms:
771 # Generate keywords from the name of the algorithm
772 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
773 # Translate keywords for better matching with the key types
774 for keyword in alg_keywords.copy():
775 for pattern, replace in keyword_translation.items():
776 if re.match(pattern, keyword):
777 alg_keywords.remove(keyword)
778 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800779 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200780 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
781 continue
782
783 for key_type in key_types:
784 # Generate keywords from the of the key type
785 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
786
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800787 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200788 for keyword1, keyword2 in exclusive_keywords.items():
789 if keyword1 in key_type_keywords:
790 key_type_keywords.remove(keyword2)
791
792 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
793 not key_type_keywords.isdisjoint(alg_keywords):
794 if alg in alg_with_keys:
795 alg_with_keys[alg].append(key_type)
796 else:
797 alg_with_keys[alg] = [key_type]
798 return alg_with_keys
799
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200800 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200801 """Generate test keys for usage flag extensions."""
802 # Generate a key type and algorithm pair for each extendable usage
803 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800804 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200805 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200806
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200807 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
808 for alg in sorted(alg_with_keys):
809 for key_type in sorted(alg_with_keys[alg]):
810 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200811 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100812 if kt.is_public() and '_SIGN_' in usage:
813 # Can't sign with a public key
814 continue
815 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200816
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200817 def generate_all_keys(self) -> Iterator[StorageTestData]:
818 yield from super().generate_all_keys()
819 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200820
Gilles Peskinefdb72232023-06-19 20:46:47 +0200821
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200822class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100823 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100824 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200825 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100826 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200827 'test_suite_psa_crypto_generate_key.generated':
828 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100829 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100830 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec9187c52023-06-20 15:22:53 +0200831 'test_suite_psa_crypto_low_hash.generated':
832 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200833 'test_suite_psa_crypto_op_fail.generated':
834 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100835 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200836 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100837 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200838 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskinefdb72232023-06-19 20:46:47 +0200839 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100840
Werner Lewisfbb75e32022-08-24 11:30:03 +0100841 def __init__(self, options):
842 super().__init__(options)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200843 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100844
Werner Lewisfbb75e32022-08-24 11:30:03 +0100845 def generate_target(self, name: str, *target_args) -> None:
846 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100847
Gilles Peskinefdb72232023-06-19 20:46:47 +0200848
Gilles Peskine09940492021-01-26 22:16:30 +0100849if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200850 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)