blob: a6683f51dab64cca8d38b63910b7156500880283 [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 Peskinecba28a72022-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 Lewisfbb75e32022-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 Peskinec9187c52023-06-20 15:22:53 +020029from mbedtls_dev import crypto_data_tests
Gilles Peskine14e428f2021-01-26 22:19:21 +010030from mbedtls_dev import crypto_knowledge
Gilles Peskinefdb72232023-06-19 20:46:47 +020031from mbedtls_dev import macro_collector #pylint: disable=unused-import
32from mbedtls_dev import psa_information
Gilles Peskine897dff92021-03-10 15:03:44 +010033from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010034from mbedtls_dev import test_case
Gilles Peskine64f2efd2022-09-16 21:41:47 +020035from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010036
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Gilles Peskine14e428f2021-01-26 22:19:21 +010038
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020039def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010040 verb: str, key_type: str, bits: int,
41 dependencies: List[str],
42 *args: str,
43 param_descr: str = ''
44) -> test_case.TestCase:
45 """Return one test case exercising a key creation method
46 for an unsupported key type or size.
47 """
Gilles Peskinefdb72232023-06-19 20:46:47 +020048 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010049 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +010050 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010051 adverb = 'not' if dependencies else 'never'
52 if param_descr:
53 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020054 tc.set_description('PSA {} {} {}-bit {} supported'
55 .format(verb, short_key_type, bits, adverb))
56 tc.set_dependencies(dependencies)
57 tc.set_function(verb + '_not_supported')
58 tc.set_arguments([key_type] + list(args))
59 return tc
60
Gilles Peskine0e9e4422022-12-15 22:14:28 +010061class KeyTypeNotSupported:
62 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010063
Gilles Peskinefdb72232023-06-19 20:46:47 +020064 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010065 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010066
Gilles Peskine60b29fe2021-02-16 14:06:50 +010067 ALWAYS_SUPPORTED = frozenset([
68 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +010069 'PSA_KEY_TYPE_PASSWORD',
70 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +010071 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +020072 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +010073 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010074 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010075 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010076 kt: crypto_knowledge.KeyType,
77 param: Optional[int] = None,
78 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010079 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020080 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010081
82 If param is present and not None, emit test cases conditioned on this
83 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020084 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010085 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010086 if kt.name in self.ALWAYS_SUPPORTED:
87 # Don't generate test cases for key types that are always supported.
88 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010089 return
Gilles Peskineaf172842021-01-27 18:24:48 +010090 import_dependencies = [('!' if param is None else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020091 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010092 if kt.params is not None:
93 import_dependencies += [('!' if param == i else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020094 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010095 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010096 if kt.name.endswith('_PUBLIC_KEY'):
97 generate_dependencies = []
98 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +020099 generate_dependencies = \
100 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
101 import_dependencies = \
102 psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100103 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200104 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100105 'import', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200106 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100107 test_case.hex_string(kt.key_material(bits)),
108 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100109 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100110 if not generate_dependencies and param is not None:
111 # If generation is impossible for this key type, rather than
112 # supported or not depending on implementation capabilities,
113 # only generate the test case once.
114 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100115 # For public key we expect that key generation fails with
116 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100117 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200118 yield test_case_for_key_type_not_supported(
119 'generate', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200120 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200121 str(bits),
122 param_descr=param_descr,
123 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100124 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100125
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200126 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
127 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200128 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
129 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200130
Gilles Peskine3d778392021-02-17 15:11:05 +0100131 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100132 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100133 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200134 if key_type in self.ECC_KEY_TYPES:
135 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200136 if key_type in self.DH_KEY_TYPES:
137 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100138 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100139 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100140 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200141 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100142 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100143 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100144 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100145 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100146 kt, 0, param_descr='curve')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200147 for dh_family in sorted(self.constructors.dh_groups):
148 for constr in self.DH_KEY_TYPES:
149 kt = crypto_knowledge.KeyType(constr, [dh_family])
150 yield from self.test_cases_for_key_type_not_supported(
151 kt, param_descr='type')
152 yield from self.test_cases_for_key_type_not_supported(
153 kt, 0, param_descr='group')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200155def test_case_for_key_generation(
156 key_type: str, bits: int,
157 dependencies: List[str],
158 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200159 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200160) -> test_case.TestCase:
161 """Return one test case exercising a key generation.
162 """
Gilles Peskinefdb72232023-06-19 20:46:47 +0200163 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200164 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100165 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200166 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200167 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200168 tc.set_dependencies(dependencies)
169 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100170 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200171
172 return tc
173
174class KeyGenerate:
175 """Generate positive and negative (invalid argument) test cases for key generation."""
176
Gilles Peskinefdb72232023-06-19 20:46:47 +0200177 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200178 self.constructors = info.constructors
179
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200180 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
181 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200182 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
183 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200184
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100185 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200186 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200187 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200188 ) -> Iterator[test_case.TestCase]:
189 """Return test cases exercising key generation.
190
191 All key types can be generated except for public keys. For public key
192 PSA_ERROR_INVALID_ARGUMENT status is expected.
193 """
194 result = 'PSA_SUCCESS'
195
Gilles Peskinefdb72232023-06-19 20:46:47 +0200196 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200197 if kt.params is not None:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200198 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200199 for i, sym in enumerate(kt.params)]
200 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100201 # The library checks whether the key type is a public key generically,
202 # before it reaches a point where it needs support for the specific key
203 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200204 generate_dependencies = []
205 result = 'PSA_ERROR_INVALID_ARGUMENT'
206 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200207 generate_dependencies = \
208 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200209 for bits in kt.sizes_to_test():
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000210 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Waleed Elmelegyd7bdbbe2023-07-20 16:26:58 +0000211 size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits)
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000212 test_dependencies = generate_dependencies + [size_dependency]
213 else:
214 test_dependencies = generate_dependencies
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200215 yield test_case_for_key_generation(
216 kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200217 psa_information.finish_family_dependencies(test_dependencies, bits),
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200218 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200219 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200220 )
221
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200222 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
223 """Generate test cases that exercise the generation of keys."""
224 for key_type in sorted(self.constructors.key_types):
225 if key_type in self.ECC_KEY_TYPES:
226 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200227 if key_type in self.DH_KEY_TYPES:
228 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200229 kt = crypto_knowledge.KeyType(key_type)
230 yield from self.test_cases_for_key_type_key_generation(kt)
231 for curve_family in sorted(self.constructors.ecc_curves):
232 for constr in self.ECC_KEY_TYPES:
233 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200234 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200235 for dh_family in sorted(self.constructors.dh_groups):
236 for constr in self.DH_KEY_TYPES:
237 kt = crypto_knowledge.KeyType(constr, [dh_family])
238 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200239
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200240class OpFail:
241 """Generate test cases for operations that must fail."""
242 #pylint: disable=too-few-public-methods
243
Gilles Peskinecba28a72022-03-15 17:26:33 +0100244 class Reason(enum.Enum):
245 NOT_SUPPORTED = 0
246 INVALID = 1
247 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200248 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100249
Gilles Peskinefdb72232023-06-19 20:46:47 +0200250 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200251 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100252 key_type_expressions = self.constructors.generate_expressions(
253 sorted(self.constructors.key_types)
254 )
255 self.key_types = [crypto_knowledge.KeyType(kt_expr)
256 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200257
Gilles Peskinecba28a72022-03-15 17:26:33 +0100258 def make_test_case(
259 self,
260 alg: crypto_knowledge.Algorithm,
261 category: crypto_knowledge.AlgorithmCategory,
262 reason: 'Reason',
263 kt: Optional[crypto_knowledge.KeyType] = None,
264 not_deps: FrozenSet[str] = frozenset(),
265 ) -> test_case.TestCase:
266 """Construct a failure test case for a one-key or keyless operation."""
267 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200268 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100269 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200270 if reason == self.Reason.NOT_SUPPORTED:
271 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
272 for dep in not_deps]
273 pretty_reason = '!' + '&'.join(sorted(short_deps))
274 else:
275 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100276 if kt:
277 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100278 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200279 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100280 key_type = ''
281 pretty_type = ''
282 tc.set_description('PSA {} {}: {}{}'
283 .format(category.name.lower(),
284 pretty_alg,
285 pretty_reason,
286 ' with ' + pretty_type if pretty_type else ''))
Gilles Peskinefdb72232023-06-19 20:46:47 +0200287 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
288 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100289 for i, dep in enumerate(dependencies):
290 if dep in not_deps:
291 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200292 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100293 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000294 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100295 if kt:
296 key_material = kt.key_material(kt.sizes_to_test()[0])
297 arguments += [key_type, test_case.hex_string(key_material)]
298 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200299 if category.is_asymmetric():
300 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100301 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
302 'INVALID_ARGUMENT')
303 arguments.append('PSA_ERROR_' + error)
304 tc.set_arguments(arguments)
305 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200306
Gilles Peskinecba28a72022-03-15 17:26:33 +0100307 def no_key_test_cases(
308 self,
309 alg: crypto_knowledge.Algorithm,
310 category: crypto_knowledge.AlgorithmCategory,
311 ) -> Iterator[test_case.TestCase]:
312 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200313 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100314 # Compatible operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200315 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100316 yield self.make_test_case(alg, category,
317 self.Reason.NOT_SUPPORTED,
318 not_deps=frozenset([dep]))
319 else:
320 # Incompatible operation, supported algorithm
321 yield self.make_test_case(alg, category, self.Reason.INVALID)
322
323 def one_key_test_cases(
324 self,
325 alg: crypto_knowledge.Algorithm,
326 category: crypto_knowledge.AlgorithmCategory,
327 ) -> Iterator[test_case.TestCase]:
328 """Generate failure test cases for one-key operations with the specified algorithm."""
329 for kt in self.key_types:
330 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200331 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100332 # Compatible key and operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200333 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100334 yield self.make_test_case(alg, category,
335 self.Reason.NOT_SUPPORTED,
336 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200337 # Public key for a private-key operation
338 if category.is_asymmetric() and kt.is_public():
339 yield self.make_test_case(alg, category,
340 self.Reason.PUBLIC,
341 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100342 elif key_is_compatible:
343 # Compatible key, incompatible operation, supported algorithm
344 yield self.make_test_case(alg, category,
345 self.Reason.INVALID,
346 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200347 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100348 # Incompatible key, compatible operation, supported algorithm
349 yield self.make_test_case(alg, category,
350 self.Reason.INCOMPATIBLE,
351 kt=kt)
352 else:
353 # Incompatible key and operation. Don't test cases where
354 # multiple things are wrong, to keep the number of test
355 # cases reasonable.
356 pass
357
358 def test_cases_for_algorithm(
359 self,
360 alg: crypto_knowledge.Algorithm,
361 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200362 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100363 for category in crypto_knowledge.AlgorithmCategory:
364 if category == crypto_knowledge.AlgorithmCategory.PAKE:
365 # PAKE operations are not implemented yet
366 pass
367 elif category.requires_key():
368 yield from self.one_key_test_cases(alg, category)
369 else:
370 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200371
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200372 def all_test_cases(self) -> Iterator[test_case.TestCase]:
373 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200374 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100375 for expr in self.constructors.generate_expressions(algorithms):
376 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200377 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200378
379
Gilles Peskine897dff92021-03-10 15:03:44 +0100380class StorageKey(psa_storage.Key):
381 """Representation of a key for storage format testing."""
382
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200383 IMPLICIT_USAGE_FLAGS = {
384 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
385 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
386 } #type: Dict[str, str]
387 """Mapping of usage flags to the flags that they imply."""
388
389 def __init__(
390 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100391 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200392 without_implicit_usage: Optional[bool] = False,
393 **kwargs
394 ) -> None:
395 """Prepare to generate a key.
396
397 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000398 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200399 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100400 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200401 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100402 for flag in sorted(usage_flags):
403 if flag in self.IMPLICIT_USAGE_FLAGS:
404 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
405 if usage_flags:
406 usage_expression = ' | '.join(sorted(usage_flags))
407 else:
408 usage_expression = '0'
409 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200410
411class StorageTestData(StorageKey):
412 """Representation of test case data for storage format testing."""
413
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200414 def __init__(
415 self,
416 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100417 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200418 **kwargs
419 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200420 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200421
Tom Cosgrove1797b052022-12-04 17:19:59 +0000422 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200423 * `expected_usage`: the usage flags generated as the expected usage flags
424 in the test cases. CAn differ from the usage flags
425 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200426 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100427 super().__init__(**kwargs)
428 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100429 if expected_usage is None:
430 self.expected_usage = self.usage #type: psa_storage.Expr
431 elif expected_usage:
432 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
433 else:
434 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200435
Gilles Peskine897dff92021-03-10 15:03:44 +0100436class StorageFormat:
437 """Storage format stability test cases."""
438
Gilles Peskinefdb72232023-06-19 20:46:47 +0200439 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100440 """Prepare to generate test cases for storage format stability.
441
442 * `info`: information about the API. See the `Information` class.
443 * `version`: the storage format version to generate test cases for.
444 * `forward`: if true, generate forward compatibility test cases which
445 save a key and check that its representation is as intended. Otherwise
446 generate backward compatibility test cases which inject a key
447 representation and check that it can be read and used.
448 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200449 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
450 self.version = version #type: int
451 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100452
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100453 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100454 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100455 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100456 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100457 cls,
458 key_type: psa_storage.Expr, bits: int,
459 alg: psa_storage.Expr
460 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100461 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100462
463 Normally only the type and algorithm matter for compatibility, and
464 this is handled in crypto_knowledge.KeyType.can_do(). This function
465 exists to detect exceptional cases. Exceptional cases detected here
466 are not tested in OpFail and should therefore have manually written
467 test cases.
468 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100469 # Some test keys have the RAW_DATA type and attributes that don't
470 # necessarily make sense. We do this to validate numerical
471 # encodings of the attributes.
472 # Raw data keys have no useful exercise anyway so there is no
473 # loss of test coverage.
474 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
475 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100476 # OAEP requires room for two hashes plus wrapping
477 m = cls.RSA_OAEP_RE.match(alg.string)
478 if m:
479 hash_alg = m.group(1)
480 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
481 key_length = (bits + 7) // 8
482 # Leave enough room for at least one byte of plaintext
483 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100484 # There's nothing wrong with ECC keys on Brainpool curves,
485 # but operations with them are very slow. So we only exercise them
486 # with a single algorithm, not with all possible hashes. We do
487 # exercise other curves with all algorithms so test coverage is
488 # perfectly adequate like this.
489 m = cls.BRAINPOOL_RE.match(key_type.string)
490 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
491 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100492 return True
493
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100495 """Construct a storage format test case for the given key.
496
497 If ``forward`` is true, generate a forward compatibility test case:
498 create a key and validate that it has the expected representation.
499 Otherwise generate a backward compatibility test case: inject the
500 key representation into storage and validate that it can be read
501 correctly.
502 """
503 verb = 'save' if self.forward else 'read'
504 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100505 tc.set_description(verb + ' ' + key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200506 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100507 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100508 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100509 )
Gilles Peskinefdb72232023-06-19 20:46:47 +0200510 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
Yanray Wang19583e42023-11-13 17:39:32 +0800511 dependencies += psa_information.generate_deps_from_description(key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200512 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100513 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100514 tc.set_function('key_storage_' + verb)
515 if self.forward:
516 extra_arguments = []
517 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200518 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100519 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200520 flags.append('TEST_FLAG_EXERCISE')
521 if 'READ_ONLY' in key.lifetime.string:
522 flags.append('TEST_FLAG_READ_ONLY')
523 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100524 tc.set_arguments([key.lifetime.string,
525 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100526 key.expected_usage.string,
527 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100528 '"' + key.material.hex() + '"',
529 '"' + key.hex() + '"',
530 *extra_arguments])
531 return tc
532
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200533 def key_for_lifetime(
534 self,
535 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200536 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200537 """Construct a test key for the given lifetime."""
538 short = lifetime
539 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
540 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100541 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200542 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200543 key = StorageTestData(version=self.version,
544 id=1, lifetime=lifetime,
545 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100546 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200547 material=b'L',
548 description=description)
549 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200550
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200551 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200552 """Generate test keys covering lifetimes."""
553 lifetimes = sorted(self.constructors.lifetimes)
554 expressions = self.constructors.generate_expressions(lifetimes)
555 for lifetime in expressions:
556 # Don't attempt to create or load a volatile key in storage
557 if 'VOLATILE' in lifetime:
558 continue
559 # Don't attempt to create a read-only key in storage,
560 # but do attempt to load one.
561 if 'READ_ONLY' in lifetime and self.forward:
562 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200563 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200564
Gilles Peskinef7614272022-02-24 18:58:08 +0100565 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100566 self,
567 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200568 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100569 test_implicit_usage: Optional[bool] = True
570 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100571 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100572 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100573 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200574 key1 = StorageTestData(version=self.version,
575 id=1, lifetime=0x00000001,
576 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100577 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100578 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100579 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200580 material=b'K',
581 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100582 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100583 usage_expr = key1.expected_usage.string
584 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100585 else:
586 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100587 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100588
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200589 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100590 """Generate test keys covering usage flags."""
591 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100592 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200593 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100594 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200595 for flag1, flag2 in zip(known_flags,
596 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100597 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200598
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200599 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200600 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100601 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200602
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200603 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200604 yield from self.generate_keys_for_usage_flags()
605 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100606
Gilles Peskine7de7c102021-04-29 22:28:07 +0200607 def key_for_type_and_alg(
608 self,
609 kt: crypto_knowledge.KeyType,
610 bits: int,
611 alg: Optional[crypto_knowledge.Algorithm] = None,
612 ) -> StorageTestData:
613 """Construct a test key of the given type.
614
615 If alg is not None, this key allows it.
616 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100617 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100618 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200619 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100620 if alg is not None:
621 alg1 = alg.expression
622 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200623 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100624 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200625 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100626 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200627 key = StorageTestData(version=self.version,
628 id=1, lifetime=0x00000001,
629 type=kt.expression, bits=bits,
630 usage=usage_flags, alg=alg1, alg2=alg2,
631 material=key_material,
632 description=description)
633 return key
634
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100635 def keys_for_type(
636 self,
637 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200638 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200639 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200640 """Generate test keys for the given key type."""
641 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100642 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200643 # Test a non-exercisable key, as well as exercisable keys for
644 # each compatible algorithm.
645 # To do: test reading a key from storage with an incompatible
646 # or unsupported algorithm.
647 yield self.key_for_type_and_alg(kt, bits)
648 compatible_algorithms = [alg for alg in all_algorithms
649 if kt.can_do(alg)]
650 for alg in compatible_algorithms:
651 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100652
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200653 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100654 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200655 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200656 all_algorithms = [crypto_knowledge.Algorithm(alg)
657 for alg in self.constructors.generate_expressions(
658 sorted(self.constructors.algorithms)
659 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200660 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200661 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100662
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200663 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200664 """Generate test keys for the encoding of the specified algorithm."""
665 # These test cases only validate the encoding of algorithms, not
666 # whether the key read from storage is suitable for an operation.
667 # `keys_for_types` generate read tests with an algorithm and a
668 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100669 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100670 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200671 key1 = StorageTestData(version=self.version,
672 id=1, lifetime=0x00000001,
673 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
674 usage=usage, alg=alg, alg2=0,
675 material=b'K',
676 description='alg: ' + descr)
677 yield key1
678 key2 = StorageTestData(version=self.version,
679 id=1, lifetime=0x00000001,
680 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
681 usage=usage, alg=0, alg2=alg,
682 material=b'L',
683 description='alg2: ' + descr)
684 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100685
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200686 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100687 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200688 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200689 for alg in self.constructors.generate_expressions(algorithms):
690 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100691
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200692 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200693 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200694 yield from self.all_keys_for_lifetimes()
695 yield from self.all_keys_for_usage_flags()
696 yield from self.all_keys_for_types()
697 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200698
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200699 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100700 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200701 # First build a list of all keys, then construct all the corresponding
702 # test cases. This allows all required information to be obtained in
703 # one go, which is a significant performance gain as the information
704 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200705 all_keys = list(self.generate_all_keys())
706 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200707 if key.location_value() != 0:
708 # Skip keys with a non-default location, because they
709 # require a driver and we currently have no mechanism to
710 # determine whether a driver is available.
711 continue
712 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100713
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200714class StorageFormatForward(StorageFormat):
715 """Storage format stability test cases for forward compatibility."""
716
Gilles Peskinefdb72232023-06-19 20:46:47 +0200717 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200718 super().__init__(info, version, True)
719
720class StorageFormatV0(StorageFormat):
721 """Storage format stability test cases for version 0 compatibility."""
722
Gilles Peskinefdb72232023-06-19 20:46:47 +0200723 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200724 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100725
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200726 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200727 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100728 yield from super().all_keys_for_usage_flags()
729 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200730
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200731 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200732 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200733 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200734 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200735 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200736 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200737 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200738 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200739 algorithm and key type combination.
740 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200741 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200742 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100743 usage_flags = ['PSA_KEY_USAGE_EXPORT']
744 material_usage_flags = usage_flags + [implyer_usage]
745 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200746 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200747 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100748 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
749 alg_expression = crypto_knowledge.short_expression(alg, 1)
750 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200751 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200752 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200753 key = StorageTestData(version=self.version,
754 id=1, lifetime=0x00000001,
755 type=key_type.expression, bits=bits,
756 usage=material_usage_flags,
757 expected_usage=expected_usage_flags,
758 without_implicit_usage=True,
759 alg=alg, alg2=alg2,
760 material=key_material,
761 description=description)
762 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200763
764 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200765 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200766 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800767 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200768 # must be filtered. Pair them with keywords created from its names.
769 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
770 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
771 keyword_translation = {
772 'ECDSA': 'ECC',
773 'ED[0-9]*.*' : 'EDWARDS'
774 }
775 exclusive_keywords = {
776 'EDWARDS': 'ECC'
777 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200778 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
779 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200780 alg_with_keys = {} #type: Dict[str, List[str]]
781 translation_table = str.maketrans('(', '_', ')')
782 for alg in algorithms:
783 # Generate keywords from the name of the algorithm
784 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
785 # Translate keywords for better matching with the key types
786 for keyword in alg_keywords.copy():
787 for pattern, replace in keyword_translation.items():
788 if re.match(pattern, keyword):
789 alg_keywords.remove(keyword)
790 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800791 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200792 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
793 continue
794
795 for key_type in key_types:
796 # Generate keywords from the of the key type
797 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
798
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800799 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200800 for keyword1, keyword2 in exclusive_keywords.items():
801 if keyword1 in key_type_keywords:
802 key_type_keywords.remove(keyword2)
803
804 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
805 not key_type_keywords.isdisjoint(alg_keywords):
806 if alg in alg_with_keys:
807 alg_with_keys[alg].append(key_type)
808 else:
809 alg_with_keys[alg] = [key_type]
810 return alg_with_keys
811
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200812 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200813 """Generate test keys for usage flag extensions."""
814 # Generate a key type and algorithm pair for each extendable usage
815 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800816 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200817 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200818
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200819 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
820 for alg in sorted(alg_with_keys):
821 for key_type in sorted(alg_with_keys[alg]):
822 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200823 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100824 if kt.is_public() and '_SIGN_' in usage:
825 # Can't sign with a public key
826 continue
827 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200828
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200829 def generate_all_keys(self) -> Iterator[StorageTestData]:
830 yield from super().generate_all_keys()
831 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200832
Gilles Peskinefdb72232023-06-19 20:46:47 +0200833
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200834class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100835 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100836 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200837 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100838 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200839 'test_suite_psa_crypto_generate_key.generated':
840 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100841 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100842 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec9187c52023-06-20 15:22:53 +0200843 'test_suite_psa_crypto_low_hash.generated':
844 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200845 'test_suite_psa_crypto_op_fail.generated':
846 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100847 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200848 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100849 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200850 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskinefdb72232023-06-19 20:46:47 +0200851 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100852
Werner Lewisfbb75e32022-08-24 11:30:03 +0100853 def __init__(self, options):
854 super().__init__(options)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200855 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100856
Werner Lewisfbb75e32022-08-24 11:30:03 +0100857 def generate_target(self, name: str, *target_args) -> None:
858 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100859
Gilles Peskinefdb72232023-06-19 20:46:47 +0200860
Gilles Peskine09940492021-01-26 22:16:30 +0100861if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200862 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)