blob: 5cdeb3bae49f64deed3af309e5c0aebeb77c464e [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 Peskine14e428f2021-01-26 22:19:21 +010029from mbedtls_dev import crypto_knowledge
Gilles Peskinefdb72232023-06-19 20:46:47 +020030from mbedtls_dev import macro_collector #pylint: disable=unused-import
31from mbedtls_dev import psa_information
Gilles Peskine897dff92021-03-10 15:03:44 +010032from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import test_case
Gilles Peskine64f2efd2022-09-16 21:41:47 +020034from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010035
Gilles Peskine14e428f2021-01-26 22:19:21 +010036
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020038def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +010039 verb: str, key_type: str, bits: int,
40 dependencies: List[str],
41 *args: str,
42 param_descr: str = ''
43) -> test_case.TestCase:
44 """Return one test case exercising a key creation method
45 for an unsupported key type or size.
46 """
Gilles Peskinefdb72232023-06-19 20:46:47 +020047 psa_information.hack_dependencies_not_implemented(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +010048 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +010049 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +010050 adverb = 'not' if dependencies else 'never'
51 if param_descr:
52 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +020053 tc.set_description('PSA {} {} {}-bit {} supported'
54 .format(verb, short_key_type, bits, adverb))
55 tc.set_dependencies(dependencies)
56 tc.set_function(verb + '_not_supported')
57 tc.set_arguments([key_type] + list(args))
58 return tc
59
Gilles Peskine0e9e4422022-12-15 22:14:28 +010060class KeyTypeNotSupported:
61 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +010062
Gilles Peskinefdb72232023-06-19 20:46:47 +020063 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskineb94ea512021-03-10 02:12:08 +010064 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +010065
Gilles Peskine60b29fe2021-02-16 14:06:50 +010066 ALWAYS_SUPPORTED = frozenset([
67 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +010068 'PSA_KEY_TYPE_PASSWORD',
69 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +010070 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +020071 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +010072 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +010073 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +010074 self,
Gilles Peskineaf172842021-01-27 18:24:48 +010075 kt: crypto_knowledge.KeyType,
76 param: Optional[int] = None,
77 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +010078 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020079 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +010080
81 If param is present and not None, emit test cases conditioned on this
82 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +020083 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +010084 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +010085 if kt.name in self.ALWAYS_SUPPORTED:
86 # Don't generate test cases for key types that are always supported.
87 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +010088 return
Gilles Peskineaf172842021-01-27 18:24:48 +010089 import_dependencies = [('!' if param is None else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020090 psa_information.psa_want_symbol(kt.name)]
Gilles Peskineaf172842021-01-27 18:24:48 +010091 if kt.params is not None:
92 import_dependencies += [('!' if param == i else '') +
Gilles Peskinefdb72232023-06-19 20:46:47 +020093 psa_information.psa_want_symbol(sym)
Gilles Peskineaf172842021-01-27 18:24:48 +010094 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +010095 if kt.name.endswith('_PUBLIC_KEY'):
96 generate_dependencies = []
97 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +020098 generate_dependencies = \
99 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
100 import_dependencies = \
101 psa_information.fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100102 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200103 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100104 'import', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200105 psa_information.finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100106 test_case.hex_string(kt.key_material(bits)),
107 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100108 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100109 if not generate_dependencies and param is not None:
110 # If generation is impossible for this key type, rather than
111 # supported or not depending on implementation capabilities,
112 # only generate the test case once.
113 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100114 # For public key we expect that key generation fails with
115 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100116 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200117 yield test_case_for_key_type_not_supported(
118 'generate', kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200119 psa_information.finish_family_dependencies(generate_dependencies, bits),
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200120 str(bits),
121 param_descr=param_descr,
122 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100123 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100124
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200125 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
126 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200127 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
128 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200129
Gilles Peskine3d778392021-02-17 15:11:05 +0100130 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100131 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100132 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200133 if key_type in self.ECC_KEY_TYPES:
134 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200135 if key_type in self.DH_KEY_TYPES:
136 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100137 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100138 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100139 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200140 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100141 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100142 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100143 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100144 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100145 kt, 0, param_descr='curve')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200146 for dh_family in sorted(self.constructors.dh_groups):
147 for constr in self.DH_KEY_TYPES:
148 kt = crypto_knowledge.KeyType(constr, [dh_family])
149 yield from self.test_cases_for_key_type_not_supported(
150 kt, param_descr='type')
151 yield from self.test_cases_for_key_type_not_supported(
152 kt, 0, param_descr='group')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100153
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200154def test_case_for_key_generation(
155 key_type: str, bits: int,
156 dependencies: List[str],
157 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200158 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200159) -> test_case.TestCase:
160 """Return one test case exercising a key generation.
161 """
Gilles Peskinefdb72232023-06-19 20:46:47 +0200162 psa_information.hack_dependencies_not_implemented(dependencies)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200163 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100164 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200165 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200166 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200167 tc.set_dependencies(dependencies)
168 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100169 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200170
171 return tc
172
173class KeyGenerate:
174 """Generate positive and negative (invalid argument) test cases for key generation."""
175
Gilles Peskinefdb72232023-06-19 20:46:47 +0200176 def __init__(self, info: psa_information.Information) -> None:
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200177 self.constructors = info.constructors
178
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200179 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
180 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200181 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
182 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200183
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100184 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200185 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200186 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200187 ) -> Iterator[test_case.TestCase]:
188 """Return test cases exercising key generation.
189
190 All key types can be generated except for public keys. For public key
191 PSA_ERROR_INVALID_ARGUMENT status is expected.
192 """
193 result = 'PSA_SUCCESS'
194
Gilles Peskinefdb72232023-06-19 20:46:47 +0200195 import_dependencies = [psa_information.psa_want_symbol(kt.name)]
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200196 if kt.params is not None:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200197 import_dependencies += [psa_information.psa_want_symbol(sym)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200198 for i, sym in enumerate(kt.params)]
199 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100200 # The library checks whether the key type is a public key generically,
201 # before it reaches a point where it needs support for the specific key
202 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200203 generate_dependencies = []
204 result = 'PSA_ERROR_INVALID_ARGUMENT'
205 else:
Gilles Peskinefdb72232023-06-19 20:46:47 +0200206 generate_dependencies = \
207 psa_information.fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200208 for bits in kt.sizes_to_test():
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000209 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Waleed Elmelegyd7bdbbe2023-07-20 16:26:58 +0000210 size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits)
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000211 test_dependencies = generate_dependencies + [size_dependency]
212 else:
213 test_dependencies = generate_dependencies
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200214 yield test_case_for_key_generation(
215 kt.expression, bits,
Gilles Peskinefdb72232023-06-19 20:46:47 +0200216 psa_information.finish_family_dependencies(test_dependencies, bits),
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200217 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200218 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200219 )
220
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200221 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
222 """Generate test cases that exercise the generation of keys."""
223 for key_type in sorted(self.constructors.key_types):
224 if key_type in self.ECC_KEY_TYPES:
225 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200226 if key_type in self.DH_KEY_TYPES:
227 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200228 kt = crypto_knowledge.KeyType(key_type)
229 yield from self.test_cases_for_key_type_key_generation(kt)
230 for curve_family in sorted(self.constructors.ecc_curves):
231 for constr in self.ECC_KEY_TYPES:
232 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200233 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200234 for dh_family in sorted(self.constructors.dh_groups):
235 for constr in self.DH_KEY_TYPES:
236 kt = crypto_knowledge.KeyType(constr, [dh_family])
237 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200238
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200239class OpFail:
240 """Generate test cases for operations that must fail."""
241 #pylint: disable=too-few-public-methods
242
Gilles Peskinecba28a72022-03-15 17:26:33 +0100243 class Reason(enum.Enum):
244 NOT_SUPPORTED = 0
245 INVALID = 1
246 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200247 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100248
Gilles Peskinefdb72232023-06-19 20:46:47 +0200249 def __init__(self, info: psa_information.Information) -> None:
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200250 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100251 key_type_expressions = self.constructors.generate_expressions(
252 sorted(self.constructors.key_types)
253 )
254 self.key_types = [crypto_knowledge.KeyType(kt_expr)
255 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200256
Gilles Peskinecba28a72022-03-15 17:26:33 +0100257 def make_test_case(
258 self,
259 alg: crypto_knowledge.Algorithm,
260 category: crypto_knowledge.AlgorithmCategory,
261 reason: 'Reason',
262 kt: Optional[crypto_knowledge.KeyType] = None,
263 not_deps: FrozenSet[str] = frozenset(),
264 ) -> test_case.TestCase:
265 """Construct a failure test case for a one-key or keyless operation."""
266 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200267 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100268 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200269 if reason == self.Reason.NOT_SUPPORTED:
270 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
271 for dep in not_deps]
272 pretty_reason = '!' + '&'.join(sorted(short_deps))
273 else:
274 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100275 if kt:
276 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100277 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200278 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100279 key_type = ''
280 pretty_type = ''
281 tc.set_description('PSA {} {}: {}{}'
282 .format(category.name.lower(),
283 pretty_alg,
284 pretty_reason,
285 ' with ' + pretty_type if pretty_type else ''))
Gilles Peskinefdb72232023-06-19 20:46:47 +0200286 dependencies = psa_information.automatic_dependencies(alg.base_expression, key_type)
287 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100288 for i, dep in enumerate(dependencies):
289 if dep in not_deps:
290 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200291 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100292 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000293 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100294 if kt:
295 key_material = kt.key_material(kt.sizes_to_test()[0])
296 arguments += [key_type, test_case.hex_string(key_material)]
297 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200298 if category.is_asymmetric():
299 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100300 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
301 'INVALID_ARGUMENT')
302 arguments.append('PSA_ERROR_' + error)
303 tc.set_arguments(arguments)
304 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200305
Gilles Peskinecba28a72022-03-15 17:26:33 +0100306 def no_key_test_cases(
307 self,
308 alg: crypto_knowledge.Algorithm,
309 category: crypto_knowledge.AlgorithmCategory,
310 ) -> Iterator[test_case.TestCase]:
311 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200312 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100313 # Compatible operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200314 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100315 yield self.make_test_case(alg, category,
316 self.Reason.NOT_SUPPORTED,
317 not_deps=frozenset([dep]))
318 else:
319 # Incompatible operation, supported algorithm
320 yield self.make_test_case(alg, category, self.Reason.INVALID)
321
322 def one_key_test_cases(
323 self,
324 alg: crypto_knowledge.Algorithm,
325 category: crypto_knowledge.AlgorithmCategory,
326 ) -> Iterator[test_case.TestCase]:
327 """Generate failure test cases for one-key operations with the specified algorithm."""
328 for kt in self.key_types:
329 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200330 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100331 # Compatible key and operation, unsupported algorithm
Gilles Peskinefdb72232023-06-19 20:46:47 +0200332 for dep in psa_information.automatic_dependencies(alg.base_expression):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100333 yield self.make_test_case(alg, category,
334 self.Reason.NOT_SUPPORTED,
335 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200336 # Public key for a private-key operation
337 if category.is_asymmetric() and kt.is_public():
338 yield self.make_test_case(alg, category,
339 self.Reason.PUBLIC,
340 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100341 elif key_is_compatible:
342 # Compatible key, incompatible operation, supported algorithm
343 yield self.make_test_case(alg, category,
344 self.Reason.INVALID,
345 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200346 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100347 # Incompatible key, compatible operation, supported algorithm
348 yield self.make_test_case(alg, category,
349 self.Reason.INCOMPATIBLE,
350 kt=kt)
351 else:
352 # Incompatible key and operation. Don't test cases where
353 # multiple things are wrong, to keep the number of test
354 # cases reasonable.
355 pass
356
357 def test_cases_for_algorithm(
358 self,
359 alg: crypto_knowledge.Algorithm,
360 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200361 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100362 for category in crypto_knowledge.AlgorithmCategory:
363 if category == crypto_knowledge.AlgorithmCategory.PAKE:
364 # PAKE operations are not implemented yet
365 pass
366 elif category.requires_key():
367 yield from self.one_key_test_cases(alg, category)
368 else:
369 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200370
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200371 def all_test_cases(self) -> Iterator[test_case.TestCase]:
372 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200373 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100374 for expr in self.constructors.generate_expressions(algorithms):
375 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200376 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200377
378
Gilles Peskine897dff92021-03-10 15:03:44 +0100379class StorageKey(psa_storage.Key):
380 """Representation of a key for storage format testing."""
381
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200382 IMPLICIT_USAGE_FLAGS = {
383 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
384 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
385 } #type: Dict[str, str]
386 """Mapping of usage flags to the flags that they imply."""
387
388 def __init__(
389 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100390 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200391 without_implicit_usage: Optional[bool] = False,
392 **kwargs
393 ) -> None:
394 """Prepare to generate a key.
395
396 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000397 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200398 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100399 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200400 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100401 for flag in sorted(usage_flags):
402 if flag in self.IMPLICIT_USAGE_FLAGS:
403 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
404 if usage_flags:
405 usage_expression = ' | '.join(sorted(usage_flags))
406 else:
407 usage_expression = '0'
408 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200409
410class StorageTestData(StorageKey):
411 """Representation of test case data for storage format testing."""
412
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200413 def __init__(
414 self,
415 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100416 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200417 **kwargs
418 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200419 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200420
Tom Cosgrove1797b052022-12-04 17:19:59 +0000421 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200422 * `expected_usage`: the usage flags generated as the expected usage flags
423 in the test cases. CAn differ from the usage flags
424 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200425 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100426 super().__init__(**kwargs)
427 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100428 if expected_usage is None:
429 self.expected_usage = self.usage #type: psa_storage.Expr
430 elif expected_usage:
431 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
432 else:
433 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200434
Gilles Peskine897dff92021-03-10 15:03:44 +0100435class StorageFormat:
436 """Storage format stability test cases."""
437
Gilles Peskinefdb72232023-06-19 20:46:47 +0200438 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
Gilles Peskine897dff92021-03-10 15:03:44 +0100439 """Prepare to generate test cases for storage format stability.
440
441 * `info`: information about the API. See the `Information` class.
442 * `version`: the storage format version to generate test cases for.
443 * `forward`: if true, generate forward compatibility test cases which
444 save a key and check that its representation is as intended. Otherwise
445 generate backward compatibility test cases which inject a key
446 representation and check that it can be read and used.
447 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200448 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
449 self.version = version #type: int
450 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100451
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100452 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100453 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100454 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100455 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100456 cls,
457 key_type: psa_storage.Expr, bits: int,
458 alg: psa_storage.Expr
459 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100460 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100461
462 Normally only the type and algorithm matter for compatibility, and
463 this is handled in crypto_knowledge.KeyType.can_do(). This function
464 exists to detect exceptional cases. Exceptional cases detected here
465 are not tested in OpFail and should therefore have manually written
466 test cases.
467 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100468 # Some test keys have the RAW_DATA type and attributes that don't
469 # necessarily make sense. We do this to validate numerical
470 # encodings of the attributes.
471 # Raw data keys have no useful exercise anyway so there is no
472 # loss of test coverage.
473 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
474 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100475 # OAEP requires room for two hashes plus wrapping
476 m = cls.RSA_OAEP_RE.match(alg.string)
477 if m:
478 hash_alg = m.group(1)
479 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
480 key_length = (bits + 7) // 8
481 # Leave enough room for at least one byte of plaintext
482 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100483 # There's nothing wrong with ECC keys on Brainpool curves,
484 # but operations with them are very slow. So we only exercise them
485 # with a single algorithm, not with all possible hashes. We do
486 # exercise other curves with all algorithms so test coverage is
487 # perfectly adequate like this.
488 m = cls.BRAINPOOL_RE.match(key_type.string)
489 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
490 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100491 return True
492
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200493 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100494 """Construct a storage format test case for the given key.
495
496 If ``forward`` is true, generate a forward compatibility test case:
497 create a key and validate that it has the expected representation.
498 Otherwise generate a backward compatibility test case: inject the
499 key representation into storage and validate that it can be read
500 correctly.
501 """
502 verb = 'save' if self.forward else 'read'
503 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100504 tc.set_description(verb + ' ' + key.description)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200505 dependencies = psa_information.automatic_dependencies(
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100506 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100507 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100508 )
Gilles Peskinefdb72232023-06-19 20:46:47 +0200509 dependencies = psa_information.finish_family_dependencies(dependencies, key.bits)
510 dependencies += psa_information.generate_key_dependencies(key.description)
511 dependencies = psa_information.fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100512 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100513 tc.set_function('key_storage_' + verb)
514 if self.forward:
515 extra_arguments = []
516 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200517 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100518 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200519 flags.append('TEST_FLAG_EXERCISE')
520 if 'READ_ONLY' in key.lifetime.string:
521 flags.append('TEST_FLAG_READ_ONLY')
522 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100523 tc.set_arguments([key.lifetime.string,
524 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100525 key.expected_usage.string,
526 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100527 '"' + key.material.hex() + '"',
528 '"' + key.hex() + '"',
529 *extra_arguments])
530 return tc
531
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200532 def key_for_lifetime(
533 self,
534 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200535 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200536 """Construct a test key for the given lifetime."""
537 short = lifetime
538 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
539 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100540 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200541 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200542 key = StorageTestData(version=self.version,
543 id=1, lifetime=lifetime,
544 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100545 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200546 material=b'L',
547 description=description)
548 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200549
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200550 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200551 """Generate test keys covering lifetimes."""
552 lifetimes = sorted(self.constructors.lifetimes)
553 expressions = self.constructors.generate_expressions(lifetimes)
554 for lifetime in expressions:
555 # Don't attempt to create or load a volatile key in storage
556 if 'VOLATILE' in lifetime:
557 continue
558 # Don't attempt to create a read-only key in storage,
559 # but do attempt to load one.
560 if 'READ_ONLY' in lifetime and self.forward:
561 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200562 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200563
Gilles Peskinef7614272022-02-24 18:58:08 +0100564 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100565 self,
566 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200567 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100568 test_implicit_usage: Optional[bool] = True
569 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100570 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100571 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100572 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200573 key1 = StorageTestData(version=self.version,
574 id=1, lifetime=0x00000001,
575 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100576 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100577 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100578 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200579 material=b'K',
580 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100581 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100582 usage_expr = key1.expected_usage.string
583 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100584 else:
585 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100586 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100587
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200588 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100589 """Generate test keys covering usage flags."""
590 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100591 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200592 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100593 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200594 for flag1, flag2 in zip(known_flags,
595 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100596 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200597
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200598 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200599 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100600 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200601
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200602 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200603 yield from self.generate_keys_for_usage_flags()
604 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100605
Gilles Peskine7de7c102021-04-29 22:28:07 +0200606 def key_for_type_and_alg(
607 self,
608 kt: crypto_knowledge.KeyType,
609 bits: int,
610 alg: Optional[crypto_knowledge.Algorithm] = None,
611 ) -> StorageTestData:
612 """Construct a test key of the given type.
613
614 If alg is not None, this key allows it.
615 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100616 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100617 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200618 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100619 if alg is not None:
620 alg1 = alg.expression
621 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200622 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100623 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200624 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100625 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200626 key = StorageTestData(version=self.version,
627 id=1, lifetime=0x00000001,
628 type=kt.expression, bits=bits,
629 usage=usage_flags, alg=alg1, alg2=alg2,
630 material=key_material,
631 description=description)
632 return key
633
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100634 def keys_for_type(
635 self,
636 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200637 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200638 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200639 """Generate test keys for the given key type."""
640 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100641 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200642 # Test a non-exercisable key, as well as exercisable keys for
643 # each compatible algorithm.
644 # To do: test reading a key from storage with an incompatible
645 # or unsupported algorithm.
646 yield self.key_for_type_and_alg(kt, bits)
647 compatible_algorithms = [alg for alg in all_algorithms
648 if kt.can_do(alg)]
649 for alg in compatible_algorithms:
650 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100651
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200652 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100653 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200654 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200655 all_algorithms = [crypto_knowledge.Algorithm(alg)
656 for alg in self.constructors.generate_expressions(
657 sorted(self.constructors.algorithms)
658 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200659 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200660 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100661
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200662 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200663 """Generate test keys for the encoding of the specified algorithm."""
664 # These test cases only validate the encoding of algorithms, not
665 # whether the key read from storage is suitable for an operation.
666 # `keys_for_types` generate read tests with an algorithm and a
667 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100668 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100669 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200670 key1 = StorageTestData(version=self.version,
671 id=1, lifetime=0x00000001,
672 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
673 usage=usage, alg=alg, alg2=0,
674 material=b'K',
675 description='alg: ' + descr)
676 yield key1
677 key2 = StorageTestData(version=self.version,
678 id=1, lifetime=0x00000001,
679 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
680 usage=usage, alg=0, alg2=alg,
681 material=b'L',
682 description='alg2: ' + descr)
683 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100684
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200685 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100686 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200687 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200688 for alg in self.constructors.generate_expressions(algorithms):
689 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100690
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200691 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200692 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200693 yield from self.all_keys_for_lifetimes()
694 yield from self.all_keys_for_usage_flags()
695 yield from self.all_keys_for_types()
696 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200697
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200698 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100699 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200700 # First build a list of all keys, then construct all the corresponding
701 # test cases. This allows all required information to be obtained in
702 # one go, which is a significant performance gain as the information
703 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200704 all_keys = list(self.generate_all_keys())
705 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200706 if key.location_value() != 0:
707 # Skip keys with a non-default location, because they
708 # require a driver and we currently have no mechanism to
709 # determine whether a driver is available.
710 continue
711 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100712
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200713class StorageFormatForward(StorageFormat):
714 """Storage format stability test cases for forward compatibility."""
715
Gilles Peskinefdb72232023-06-19 20:46:47 +0200716 def __init__(self, info: psa_information.Information, version: int) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200717 super().__init__(info, version, True)
718
719class StorageFormatV0(StorageFormat):
720 """Storage format stability test cases for version 0 compatibility."""
721
Gilles Peskinefdb72232023-06-19 20:46:47 +0200722 def __init__(self, info: psa_information.Information) -> None:
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200723 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100724
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200725 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200726 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100727 yield from super().all_keys_for_usage_flags()
728 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200729
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200730 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200731 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200732 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200733 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200734 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200735 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200736 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200737 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200738 algorithm and key type combination.
739 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200740 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200741 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100742 usage_flags = ['PSA_KEY_USAGE_EXPORT']
743 material_usage_flags = usage_flags + [implyer_usage]
744 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200745 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200746 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100747 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
748 alg_expression = crypto_knowledge.short_expression(alg, 1)
749 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200750 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200751 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200752 key = StorageTestData(version=self.version,
753 id=1, lifetime=0x00000001,
754 type=key_type.expression, bits=bits,
755 usage=material_usage_flags,
756 expected_usage=expected_usage_flags,
757 without_implicit_usage=True,
758 alg=alg, alg2=alg2,
759 material=key_material,
760 description=description)
761 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200762
763 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200764 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200765 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800766 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200767 # must be filtered. Pair them with keywords created from its names.
768 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
769 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
770 keyword_translation = {
771 'ECDSA': 'ECC',
772 'ED[0-9]*.*' : 'EDWARDS'
773 }
774 exclusive_keywords = {
775 'EDWARDS': 'ECC'
776 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200777 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
778 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200779 alg_with_keys = {} #type: Dict[str, List[str]]
780 translation_table = str.maketrans('(', '_', ')')
781 for alg in algorithms:
782 # Generate keywords from the name of the algorithm
783 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
784 # Translate keywords for better matching with the key types
785 for keyword in alg_keywords.copy():
786 for pattern, replace in keyword_translation.items():
787 if re.match(pattern, keyword):
788 alg_keywords.remove(keyword)
789 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800790 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200791 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
792 continue
793
794 for key_type in key_types:
795 # Generate keywords from the of the key type
796 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
797
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800798 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200799 for keyword1, keyword2 in exclusive_keywords.items():
800 if keyword1 in key_type_keywords:
801 key_type_keywords.remove(keyword2)
802
803 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
804 not key_type_keywords.isdisjoint(alg_keywords):
805 if alg in alg_with_keys:
806 alg_with_keys[alg].append(key_type)
807 else:
808 alg_with_keys[alg] = [key_type]
809 return alg_with_keys
810
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200811 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200812 """Generate test keys for usage flag extensions."""
813 # Generate a key type and algorithm pair for each extendable usage
814 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800815 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200816 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200817
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200818 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
819 for alg in sorted(alg_with_keys):
820 for key_type in sorted(alg_with_keys[alg]):
821 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200822 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100823 if kt.is_public() and '_SIGN_' in usage:
824 # Can't sign with a public key
825 continue
826 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200827
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200828 def generate_all_keys(self) -> Iterator[StorageTestData]:
829 yield from super().generate_all_keys()
830 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200831
Gilles Peskinefdb72232023-06-19 20:46:47 +0200832
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200833class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100834 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100835 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200836 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100837 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200838 'test_suite_psa_crypto_generate_key.generated':
839 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100840 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100841 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200842 'test_suite_psa_crypto_op_fail.generated':
843 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100844 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200845 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100846 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200847 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskinefdb72232023-06-19 20:46:47 +0200848 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
Gilles Peskine0298bda2021-03-10 02:34:37 +0100849
Werner Lewisfbb75e32022-08-24 11:30:03 +0100850 def __init__(self, options):
851 super().__init__(options)
Gilles Peskinefdb72232023-06-19 20:46:47 +0200852 self.info = psa_information.Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100853
Werner Lewisfbb75e32022-08-24 11:30:03 +0100854 def generate_target(self, name: str, *target_args) -> None:
855 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100856
Gilles Peskinefdb72232023-06-19 20:46:47 +0200857
Gilles Peskine09940492021-01-26 22:16:30 +0100858if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200859 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)