blob: 04c36f7f9e7ac956fd096719fdb48fc11575b6d7 [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 Wang6b190d42023-11-01 13:44:14 +0800511 dependencies = psa_information.generate_description_dependencies(dependencies,
512 key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200513 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100514 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100515 tc.set_function('key_storage_' + verb)
516 if self.forward:
517 extra_arguments = []
518 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200519 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100520 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200521 flags.append('TEST_FLAG_EXERCISE')
522 if 'READ_ONLY' in key.lifetime.string:
523 flags.append('TEST_FLAG_READ_ONLY')
524 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100525 tc.set_arguments([key.lifetime.string,
526 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100527 key.expected_usage.string,
528 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100529 '"' + key.material.hex() + '"',
530 '"' + key.hex() + '"',
531 *extra_arguments])
532 return tc
533
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200534 def key_for_lifetime(
535 self,
536 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200537 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200538 """Construct a test key for the given lifetime."""
539 short = lifetime
540 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
541 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100542 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200543 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200544 key = StorageTestData(version=self.version,
545 id=1, lifetime=lifetime,
546 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100547 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200548 material=b'L',
549 description=description)
550 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200551
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200552 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200553 """Generate test keys covering lifetimes."""
554 lifetimes = sorted(self.constructors.lifetimes)
555 expressions = self.constructors.generate_expressions(lifetimes)
556 for lifetime in expressions:
557 # Don't attempt to create or load a volatile key in storage
558 if 'VOLATILE' in lifetime:
559 continue
560 # Don't attempt to create a read-only key in storage,
561 # but do attempt to load one.
562 if 'READ_ONLY' in lifetime and self.forward:
563 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200564 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200565
Gilles Peskinef7614272022-02-24 18:58:08 +0100566 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100567 self,
568 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200569 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100570 test_implicit_usage: Optional[bool] = True
571 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100572 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100573 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100574 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200575 key1 = StorageTestData(version=self.version,
576 id=1, lifetime=0x00000001,
577 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100578 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100579 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100580 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200581 material=b'K',
582 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100583 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100584 usage_expr = key1.expected_usage.string
585 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100586 else:
587 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100588 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100589
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200590 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100591 """Generate test keys covering usage flags."""
592 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100593 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200594 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100595 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200596 for flag1, flag2 in zip(known_flags,
597 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100598 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200599
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200600 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200601 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100602 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200603
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200604 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200605 yield from self.generate_keys_for_usage_flags()
606 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100607
Gilles Peskine7de7c102021-04-29 22:28:07 +0200608 def key_for_type_and_alg(
609 self,
610 kt: crypto_knowledge.KeyType,
611 bits: int,
612 alg: Optional[crypto_knowledge.Algorithm] = None,
613 ) -> StorageTestData:
614 """Construct a test key of the given type.
615
616 If alg is not None, this key allows it.
617 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100618 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100619 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200620 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100621 if alg is not None:
622 alg1 = alg.expression
623 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200624 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100625 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200626 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100627 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200628 key = StorageTestData(version=self.version,
629 id=1, lifetime=0x00000001,
630 type=kt.expression, bits=bits,
631 usage=usage_flags, alg=alg1, alg2=alg2,
632 material=key_material,
633 description=description)
634 return key
635
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100636 def keys_for_type(
637 self,
638 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200639 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200640 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200641 """Generate test keys for the given key type."""
642 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100643 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200644 # Test a non-exercisable key, as well as exercisable keys for
645 # each compatible algorithm.
646 # To do: test reading a key from storage with an incompatible
647 # or unsupported algorithm.
648 yield self.key_for_type_and_alg(kt, bits)
649 compatible_algorithms = [alg for alg in all_algorithms
650 if kt.can_do(alg)]
651 for alg in compatible_algorithms:
652 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100653
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200654 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100655 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200656 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200657 all_algorithms = [crypto_knowledge.Algorithm(alg)
658 for alg in self.constructors.generate_expressions(
659 sorted(self.constructors.algorithms)
660 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200661 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200662 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100663
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200664 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200665 """Generate test keys for the encoding of the specified algorithm."""
666 # These test cases only validate the encoding of algorithms, not
667 # whether the key read from storage is suitable for an operation.
668 # `keys_for_types` generate read tests with an algorithm and a
669 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100670 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100671 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200672 key1 = StorageTestData(version=self.version,
673 id=1, lifetime=0x00000001,
674 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
675 usage=usage, alg=alg, alg2=0,
676 material=b'K',
677 description='alg: ' + descr)
678 yield key1
679 key2 = StorageTestData(version=self.version,
680 id=1, lifetime=0x00000001,
681 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
682 usage=usage, alg=0, alg2=alg,
683 material=b'L',
684 description='alg2: ' + descr)
685 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100686
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200687 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100688 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200689 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200690 for alg in self.constructors.generate_expressions(algorithms):
691 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100692
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200693 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200694 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200695 yield from self.all_keys_for_lifetimes()
696 yield from self.all_keys_for_usage_flags()
697 yield from self.all_keys_for_types()
698 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200699
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200700 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100701 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200702 # First build a list of all keys, then construct all the corresponding
703 # test cases. This allows all required information to be obtained in
704 # one go, which is a significant performance gain as the information
705 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200706 all_keys = list(self.generate_all_keys())
707 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200708 if key.location_value() != 0:
709 # Skip keys with a non-default location, because they
710 # require a driver and we currently have no mechanism to
711 # determine whether a driver is available.
712 continue
713 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100714
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200715class StorageFormatForward(StorageFormat):
716 """Storage format stability test cases for forward compatibility."""
717
Gilles Peskinefdb72232023-06-19 20:46:47 +0200718 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200719 super().__init__(info, version, True)
720
721class StorageFormatV0(StorageFormat):
722 """Storage format stability test cases for version 0 compatibility."""
723
Gilles Peskinefdb72232023-06-19 20:46:47 +0200724 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200725 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100726
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200727 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200728 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100729 yield from super().all_keys_for_usage_flags()
730 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200731
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200732 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200733 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200734 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200735 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200736 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200737 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200738 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200739 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200740 algorithm and key type combination.
741 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200742 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200743 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100744 usage_flags = ['PSA_KEY_USAGE_EXPORT']
745 material_usage_flags = usage_flags + [implyer_usage]
746 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200747 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200748 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100749 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
750 alg_expression = crypto_knowledge.short_expression(alg, 1)
751 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200752 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200753 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200754 key = StorageTestData(version=self.version,
755 id=1, lifetime=0x00000001,
756 type=key_type.expression, bits=bits,
757 usage=material_usage_flags,
758 expected_usage=expected_usage_flags,
759 without_implicit_usage=True,
760 alg=alg, alg2=alg2,
761 material=key_material,
762 description=description)
763 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200764
765 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200766 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200767 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800768 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200769 # must be filtered. Pair them with keywords created from its names.
770 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
771 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
772 keyword_translation = {
773 'ECDSA': 'ECC',
774 'ED[0-9]*.*' : 'EDWARDS'
775 }
776 exclusive_keywords = {
777 'EDWARDS': 'ECC'
778 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200779 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
780 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200781 alg_with_keys = {} #type: Dict[str, List[str]]
782 translation_table = str.maketrans('(', '_', ')')
783 for alg in algorithms:
784 # Generate keywords from the name of the algorithm
785 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
786 # Translate keywords for better matching with the key types
787 for keyword in alg_keywords.copy():
788 for pattern, replace in keyword_translation.items():
789 if re.match(pattern, keyword):
790 alg_keywords.remove(keyword)
791 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800792 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200793 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
794 continue
795
796 for key_type in key_types:
797 # Generate keywords from the of the key type
798 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
799
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800800 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200801 for keyword1, keyword2 in exclusive_keywords.items():
802 if keyword1 in key_type_keywords:
803 key_type_keywords.remove(keyword2)
804
805 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
806 not key_type_keywords.isdisjoint(alg_keywords):
807 if alg in alg_with_keys:
808 alg_with_keys[alg].append(key_type)
809 else:
810 alg_with_keys[alg] = [key_type]
811 return alg_with_keys
812
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200813 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200814 """Generate test keys for usage flag extensions."""
815 # Generate a key type and algorithm pair for each extendable usage
816 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800817 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200818 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200819
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200820 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
821 for alg in sorted(alg_with_keys):
822 for key_type in sorted(alg_with_keys[alg]):
823 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200824 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100825 if kt.is_public() and '_SIGN_' in usage:
826 # Can't sign with a public key
827 continue
828 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200829
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200830 def generate_all_keys(self) -> Iterator[StorageTestData]:
831 yield from super().generate_all_keys()
832 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200833
Gilles Peskinefdb72232023-06-19 20:46:47 +0200834
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200835class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100836 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100837 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200838 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100839 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200840 'test_suite_psa_crypto_generate_key.generated':
841 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100842 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100843 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec9187c52023-06-20 15:22:53 +0200844 'test_suite_psa_crypto_low_hash.generated':
845 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200846 'test_suite_psa_crypto_op_fail.generated':
847 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100848 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200849 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100850 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200851 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskinefdb72232023-06-19 20:46:47 +0200852 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100853
Werner Lewisfbb75e32022-08-24 11:30:03 +0100854 def __init__(self, options):
855 super().__init__(options)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200856 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100857
Werner Lewisfbb75e32022-08-24 11:30:03 +0100858 def generate_target(self, name: str, *target_args) -> None:
859 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100860
Gilles Peskinefdb72232023-06-19 20:46:47 +0200861
Gilles Peskine09940492021-01-26 22:16:30 +0100862if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200863 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)