blob: 955a5f35f92dfbec631b96d2404b815a46465fc1 [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
Gilles Peskine0298bda2021-03-10 02:34:37 +01003
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Gilles Peskine09940492021-01-26 22:16:30 +01006"""
7
8# Copyright The Mbed TLS Contributors
Dave Rodgman7ff79652023-11-03 12:04:52 +00009# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine09940492021-01-26 22:16:30 +010010
Gilles Peskinef8b6b502022-03-15 17:26:33 +010011import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010012import re
Gilles Peskine09940492021-01-26 22:16:30 +010013import sys
Werner Lewisdcad1e92022-08-24 11:30:03 +010014from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010015
16import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010017from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010018from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010019from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010020from mbedtls_dev import test_case
Gilles Peskine69feebd2022-09-16 21:41:47 +020021from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010022
Gilles Peskine14e428f2021-01-26 22:19:21 +010023
Gilles Peskine7f756872021-02-16 12:13:12 +010024def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010025 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
26 if name.startswith('PSA_'):
27 return name[:4] + 'WANT_' + name[4:]
28 else:
29 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
30
Gilles Peskine7f756872021-02-16 12:13:12 +010031def finish_family_dependency(dep: str, bits: int) -> str:
32 """Finish dep if it's a family dependency symbol prefix.
33
34 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
35 qualified by the key size. If dep is such a symbol, finish it by adjusting
36 the prefix and appending the key size. Other symbols are left unchanged.
37 """
38 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
39
40def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
41 """Finish any family dependency symbol prefixes.
42
43 Apply `finish_family_dependency` to each element of `dependencies`.
44 """
45 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010046
Gilles Peskine8a55b432021-04-20 23:23:45 +020047SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
48 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
49 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
50 'PSA_ALG_ANY_HASH', # only in policies
51 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
52 'PSA_ALG_KEY_AGREEMENT', # chaining
53 'PSA_ALG_TRUNCATED_MAC', # modifier
54])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010055def automatic_dependencies(*expressions: str) -> List[str]:
56 """Infer dependencies of a test case by looking for PSA_xxx symbols.
57
58 The arguments are strings which should be C expressions. Do not use
59 string literals or comments as this function is not smart enough to
60 skip them.
61 """
62 used = set()
63 for expr in expressions:
64 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskine8a55b432021-04-20 23:23:45 +020065 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010066 return sorted(psa_want_symbol(name) for name in used)
67
Gilles Peskined169d602021-02-16 14:16:25 +010068# A temporary hack: at the time of writing, not all dependency symbols
69# are implemented yet. Skip test cases for which the dependency symbols are
70# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove49f99bc2022-12-04 16:44:21 +000071# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +010072# failure.
73def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
74 return frozenset(symbol
75 for line in open(filename)
76 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010077_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010078def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010079 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
Przemyslaw Stekiel08101082021-10-22 10:39:56 +020080 if _implemented_dependencies is None:
81 _implemented_dependencies = \
82 read_implemented_dependencies('include/psa/crypto_config.h')
83 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010084 for dep in dependencies):
85 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
86
Gilles Peskine14e428f2021-01-26 22:19:21 +010087
Gilles Peskineb94ea512021-03-10 02:12:08 +010088class Information:
89 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +010090
Gilles Peskineb94ea512021-03-10 02:12:08 +010091 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +010092 self.constructors = self.read_psa_interface()
93
94 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +010095 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +020096 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +010097 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +020098 # Mbed TLS doesn't support finite-field DH yet and will not support
99 # finite-field DSA. Don't attempt to generate any related test case.
100 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
101 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100102 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
103 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100104
Gilles Peskineb93f8542021-04-19 13:50:25 +0200105 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100106 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200107 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100108 header_file_names = ['include/psa/crypto_values.h',
109 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200110 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100111 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200112 constructors.parse_header(header_file_name)
113 for test_cases in test_suites:
114 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100115 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200116 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100117 return constructors
118
Gilles Peskine14e428f2021-01-26 22:19:21 +0100119
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200120def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100121 verb: str, key_type: str, bits: int,
122 dependencies: List[str],
123 *args: str,
124 param_descr: str = ''
125) -> test_case.TestCase:
126 """Return one test case exercising a key creation method
127 for an unsupported key type or size.
128 """
129 hack_dependencies_not_implemented(dependencies)
130 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100131 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100132 adverb = 'not' if dependencies else 'never'
133 if param_descr:
134 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200135 tc.set_description('PSA {} {} {}-bit {} supported'
136 .format(verb, short_key_type, bits, adverb))
137 tc.set_dependencies(dependencies)
138 tc.set_function(verb + '_not_supported')
139 tc.set_arguments([key_type] + list(args))
140 return tc
141
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100142class KeyTypeNotSupported:
143 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100144
145 def __init__(self, info: Information) -> None:
146 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100147
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100148 ALWAYS_SUPPORTED = frozenset([
149 'PSA_KEY_TYPE_DERIVE',
150 'PSA_KEY_TYPE_RAW_DATA',
151 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100152 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100153 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100154 kt: crypto_knowledge.KeyType,
155 param: Optional[int] = None,
156 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100157 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200158 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100159
160 If param is present and not None, emit test cases conditioned on this
161 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200162 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100163 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100164 if kt.name in self.ALWAYS_SUPPORTED:
165 # Don't generate test cases for key types that are always supported.
166 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100167 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100168 import_dependencies = [('!' if param is None else '') +
169 psa_want_symbol(kt.name)]
170 if kt.params is not None:
171 import_dependencies += [('!' if param == i else '') +
172 psa_want_symbol(sym)
173 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100174 if kt.name.endswith('_PUBLIC_KEY'):
175 generate_dependencies = []
176 else:
177 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100178 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200179 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100180 'import', kt.expression, bits,
181 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100182 test_case.hex_string(kt.key_material(bits)),
183 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100184 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100185 if not generate_dependencies and param is not None:
186 # If generation is impossible for this key type, rather than
187 # supported or not depending on implementation capabilities,
188 # only generate the test case once.
189 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100190 # For public key we expect that key generation fails with
191 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +0100192 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200193 yield test_case_for_key_type_not_supported(
194 'generate', kt.expression, bits,
195 finish_family_dependencies(generate_dependencies, bits),
196 str(bits),
197 param_descr=param_descr,
198 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100199 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100200
Gilles Peskineb93f8542021-04-19 13:50:25 +0200201 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
202 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
203
Gilles Peskine3d778392021-02-17 15:11:05 +0100204 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100205 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100206 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200207 if key_type in self.ECC_KEY_TYPES:
208 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100209 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100210 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100211 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200212 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100213 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100214 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100215 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100216 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100217 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100218
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200219def test_case_for_key_generation(
220 key_type: str, bits: int,
221 dependencies: List[str],
222 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200223 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200224) -> test_case.TestCase:
225 """Return one test case exercising a key generation.
226 """
227 hack_dependencies_not_implemented(dependencies)
228 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100229 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200230 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200231 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200232 tc.set_dependencies(dependencies)
233 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100234 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200235
236 return tc
237
238class KeyGenerate:
239 """Generate positive and negative (invalid argument) test cases for key generation."""
240
241 def __init__(self, info: Information) -> None:
242 self.constructors = info.constructors
243
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200244 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
245 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
246
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100247 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200248 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200249 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200250 ) -> Iterator[test_case.TestCase]:
251 """Return test cases exercising key generation.
252
253 All key types can be generated except for public keys. For public key
254 PSA_ERROR_INVALID_ARGUMENT status is expected.
255 """
256 result = 'PSA_SUCCESS'
257
258 import_dependencies = [psa_want_symbol(kt.name)]
259 if kt.params is not None:
260 import_dependencies += [psa_want_symbol(sym)
261 for i, sym in enumerate(kt.params)]
262 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100263 # The library checks whether the key type is a public key generically,
264 # before it reaches a point where it needs support for the specific key
265 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200266 generate_dependencies = []
267 result = 'PSA_ERROR_INVALID_ARGUMENT'
268 else:
269 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100270 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200271 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200272 for bits in kt.sizes_to_test():
273 yield test_case_for_key_generation(
274 kt.expression, bits,
275 finish_family_dependencies(generate_dependencies, bits),
276 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200277 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200278 )
279
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200280 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
281 """Generate test cases that exercise the generation of keys."""
282 for key_type in sorted(self.constructors.key_types):
283 if key_type in self.ECC_KEY_TYPES:
284 continue
285 kt = crypto_knowledge.KeyType(key_type)
286 yield from self.test_cases_for_key_type_key_generation(kt)
287 for curve_family in sorted(self.constructors.ecc_curves):
288 for constr in self.ECC_KEY_TYPES:
289 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200290 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200291
Gilles Peskinec05158b2021-04-27 20:40:10 +0200292class OpFail:
293 """Generate test cases for operations that must fail."""
294 #pylint: disable=too-few-public-methods
295
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100296 class Reason(enum.Enum):
297 NOT_SUPPORTED = 0
298 INVALID = 1
299 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200300 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100301
Gilles Peskinec05158b2021-04-27 20:40:10 +0200302 def __init__(self, info: Information) -> None:
303 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100304 key_type_expressions = self.constructors.generate_expressions(
305 sorted(self.constructors.key_types)
306 )
307 self.key_types = [crypto_knowledge.KeyType(kt_expr)
308 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200309
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100310 def make_test_case(
311 self,
312 alg: crypto_knowledge.Algorithm,
313 category: crypto_knowledge.AlgorithmCategory,
314 reason: 'Reason',
315 kt: Optional[crypto_knowledge.KeyType] = None,
316 not_deps: FrozenSet[str] = frozenset(),
317 ) -> test_case.TestCase:
318 """Construct a failure test case for a one-key or keyless operation."""
319 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200320 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100321 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200322 if reason == self.Reason.NOT_SUPPORTED:
323 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
324 for dep in not_deps]
325 pretty_reason = '!' + '&'.join(sorted(short_deps))
326 else:
327 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100328 if kt:
329 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100330 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200331 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100332 key_type = ''
333 pretty_type = ''
334 tc.set_description('PSA {} {}: {}{}'
335 .format(category.name.lower(),
336 pretty_alg,
337 pretty_reason,
338 ' with ' + pretty_type if pretty_type else ''))
339 dependencies = automatic_dependencies(alg.base_expression, key_type)
340 for i, dep in enumerate(dependencies):
341 if dep in not_deps:
342 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200343 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100344 tc.set_function(category.name.lower() + '_fail')
David Horstmann4fc7e0e2023-01-24 18:53:15 +0000345 arguments = [] # type: List[str]
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100346 if kt:
347 key_material = kt.key_material(kt.sizes_to_test()[0])
348 arguments += [key_type, test_case.hex_string(key_material)]
349 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200350 if category.is_asymmetric():
351 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100352 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
353 'INVALID_ARGUMENT')
354 arguments.append('PSA_ERROR_' + error)
355 tc.set_arguments(arguments)
356 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200357
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100358 def no_key_test_cases(
359 self,
360 alg: crypto_knowledge.Algorithm,
361 category: crypto_knowledge.AlgorithmCategory,
362 ) -> Iterator[test_case.TestCase]:
363 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200364 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100365 # Compatible operation, unsupported algorithm
366 for dep in automatic_dependencies(alg.base_expression):
367 yield self.make_test_case(alg, category,
368 self.Reason.NOT_SUPPORTED,
369 not_deps=frozenset([dep]))
370 else:
371 # Incompatible operation, supported algorithm
372 yield self.make_test_case(alg, category, self.Reason.INVALID)
373
374 def one_key_test_cases(
375 self,
376 alg: crypto_knowledge.Algorithm,
377 category: crypto_knowledge.AlgorithmCategory,
378 ) -> Iterator[test_case.TestCase]:
379 """Generate failure test cases for one-key operations with the specified algorithm."""
380 for kt in self.key_types:
381 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200382 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100383 # Compatible key and operation, unsupported algorithm
384 for dep in automatic_dependencies(alg.base_expression):
385 yield self.make_test_case(alg, category,
386 self.Reason.NOT_SUPPORTED,
387 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200388 # Public key for a private-key operation
389 if category.is_asymmetric() and kt.is_public():
390 yield self.make_test_case(alg, category,
391 self.Reason.PUBLIC,
392 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100393 elif key_is_compatible:
394 # Compatible key, incompatible operation, supported algorithm
395 yield self.make_test_case(alg, category,
396 self.Reason.INVALID,
397 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200398 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100399 # Incompatible key, compatible operation, supported algorithm
400 yield self.make_test_case(alg, category,
401 self.Reason.INCOMPATIBLE,
402 kt=kt)
403 else:
404 # Incompatible key and operation. Don't test cases where
405 # multiple things are wrong, to keep the number of test
406 # cases reasonable.
407 pass
408
409 def test_cases_for_algorithm(
410 self,
411 alg: crypto_knowledge.Algorithm,
412 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200413 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100414 for category in crypto_knowledge.AlgorithmCategory:
415 if category == crypto_knowledge.AlgorithmCategory.PAKE:
416 # PAKE operations are not implemented yet
417 pass
418 elif category.requires_key():
419 yield from self.one_key_test_cases(alg, category)
420 else:
421 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200422
Gilles Peskinec05158b2021-04-27 20:40:10 +0200423 def all_test_cases(self) -> Iterator[test_case.TestCase]:
424 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200425 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100426 for expr in self.constructors.generate_expressions(algorithms):
427 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200428 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200429
430
Gilles Peskine897dff92021-03-10 15:03:44 +0100431class StorageKey(psa_storage.Key):
432 """Representation of a key for storage format testing."""
433
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200434 IMPLICIT_USAGE_FLAGS = {
435 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
436 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
437 } #type: Dict[str, str]
438 """Mapping of usage flags to the flags that they imply."""
439
440 def __init__(
441 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100442 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200443 without_implicit_usage: Optional[bool] = False,
444 **kwargs
445 ) -> None:
446 """Prepare to generate a key.
447
448 * `usage` : The usage flags used for the key.
Tom Cosgrove49f99bc2022-12-04 16:44:21 +0000449 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200450 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100451 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200452 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100453 for flag in sorted(usage_flags):
454 if flag in self.IMPLICIT_USAGE_FLAGS:
455 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
456 if usage_flags:
457 usage_expression = ' | '.join(sorted(usage_flags))
458 else:
459 usage_expression = '0'
460 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200461
462class StorageTestData(StorageKey):
463 """Representation of test case data for storage format testing."""
464
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200465 def __init__(
466 self,
467 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100468 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200469 **kwargs
470 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200471 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200472
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200473 * `description` : used for the the test case names
474 * `expected_usage`: the usage flags generated as the expected usage flags
475 in the test cases. CAn differ from the usage flags
476 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200477 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100478 super().__init__(**kwargs)
479 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100480 if expected_usage is None:
481 self.expected_usage = self.usage #type: psa_storage.Expr
482 elif expected_usage:
483 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
484 else:
485 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200486
Gilles Peskine897dff92021-03-10 15:03:44 +0100487class StorageFormat:
488 """Storage format stability test cases."""
489
490 def __init__(self, info: Information, version: int, forward: bool) -> None:
491 """Prepare to generate test cases for storage format stability.
492
493 * `info`: information about the API. See the `Information` class.
494 * `version`: the storage format version to generate test cases for.
495 * `forward`: if true, generate forward compatibility test cases which
496 save a key and check that its representation is as intended. Otherwise
497 generate backward compatibility test cases which inject a key
498 representation and check that it can be read and used.
499 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200500 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
501 self.version = version #type: int
502 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100503
Gilles Peskine32611242022-03-19 12:09:13 +0100504 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100505 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100506 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100507 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100508 cls,
509 key_type: psa_storage.Expr, bits: int,
510 alg: psa_storage.Expr
511 ) -> bool:
Gilles Peskine1efe7fd2022-12-15 23:03:19 +0100512 """Whether to exercise the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100513
514 Normally only the type and algorithm matter for compatibility, and
515 this is handled in crypto_knowledge.KeyType.can_do(). This function
516 exists to detect exceptional cases. Exceptional cases detected here
517 are not tested in OpFail and should therefore have manually written
518 test cases.
519 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100520 # Some test keys have the RAW_DATA type and attributes that don't
521 # necessarily make sense. We do this to validate numerical
522 # encodings of the attributes.
523 # Raw data keys have no useful exercise anyway so there is no
524 # loss of test coverage.
525 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
526 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200527 # Mbed TLS only supports 128-bit keys for RC4.
528 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
529 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100530 # OAEP requires room for two hashes plus wrapping
531 m = cls.RSA_OAEP_RE.match(alg.string)
532 if m:
533 hash_alg = m.group(1)
534 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
535 key_length = (bits + 7) // 8
536 # Leave enough room for at least one byte of plaintext
537 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100538 # There's nothing wrong with ECC keys on Brainpool curves,
539 # but operations with them are very slow. So we only exercise them
540 # with a single algorithm, not with all possible hashes. We do
541 # exercise other curves with all algorithms so test coverage is
542 # perfectly adequate like this.
543 m = cls.BRAINPOOL_RE.match(key_type.string)
544 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
545 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100546 return True
547
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200548 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100549 """Construct a storage format test case for the given key.
550
551 If ``forward`` is true, generate a forward compatibility test case:
552 create a key and validate that it has the expected representation.
553 Otherwise generate a backward compatibility test case: inject the
554 key representation into storage and validate that it can be read
555 correctly.
556 """
557 verb = 'save' if self.forward else 'read'
558 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100559 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100560 dependencies = automatic_dependencies(
561 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100562 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100563 )
564 dependencies = finish_family_dependencies(dependencies, key.bits)
565 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100566 tc.set_function('key_storage_' + verb)
567 if self.forward:
568 extra_arguments = []
569 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200570 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100571 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200572 flags.append('TEST_FLAG_EXERCISE')
573 if 'READ_ONLY' in key.lifetime.string:
574 flags.append('TEST_FLAG_READ_ONLY')
575 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100576 tc.set_arguments([key.lifetime.string,
577 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100578 key.expected_usage.string,
579 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100580 '"' + key.material.hex() + '"',
581 '"' + key.hex() + '"',
582 *extra_arguments])
583 return tc
584
Gilles Peskineefb584d2021-04-21 22:05:34 +0200585 def key_for_lifetime(
586 self,
587 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200588 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200589 """Construct a test key for the given lifetime."""
590 short = lifetime
591 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
592 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100593 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200594 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200595 key = StorageTestData(version=self.version,
596 id=1, lifetime=lifetime,
597 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100598 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200599 material=b'L',
600 description=description)
601 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200602
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200603 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200604 """Generate test keys covering lifetimes."""
605 lifetimes = sorted(self.constructors.lifetimes)
606 expressions = self.constructors.generate_expressions(lifetimes)
607 for lifetime in expressions:
608 # Don't attempt to create or load a volatile key in storage
609 if 'VOLATILE' in lifetime:
610 continue
611 # Don't attempt to create a read-only key in storage,
612 # but do attempt to load one.
613 if 'READ_ONLY' in lifetime and self.forward:
614 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200615 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200616
Gilles Peskinea296e482022-02-24 18:58:08 +0100617 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100618 self,
619 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200620 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100621 test_implicit_usage: Optional[bool] = True
622 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100623 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100624 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100625 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200626 key1 = StorageTestData(version=self.version,
627 id=1, lifetime=0x00000001,
628 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100629 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100630 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100631 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200632 material=b'K',
633 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100634 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100635 usage_expr = key1.expected_usage.string
636 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100637 else:
638 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100639 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100640
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200641 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100642 """Generate test keys covering usage flags."""
643 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100644 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200645 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100646 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200647 for flag1, flag2 in zip(known_flags,
648 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100649 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200650
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200651 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200652 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100653 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200654
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200655 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200656 yield from self.generate_keys_for_usage_flags()
657 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100658
Gilles Peskine6213a002021-04-29 22:28:07 +0200659 def key_for_type_and_alg(
660 self,
661 kt: crypto_knowledge.KeyType,
662 bits: int,
663 alg: Optional[crypto_knowledge.Algorithm] = None,
664 ) -> StorageTestData:
665 """Construct a test key of the given type.
666
667 If alg is not None, this key allows it.
668 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100669 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100670 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200671 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100672 if alg is not None:
673 alg1 = alg.expression
674 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200675 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100676 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200677 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100678 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200679 key = StorageTestData(version=self.version,
680 id=1, lifetime=0x00000001,
681 type=kt.expression, bits=bits,
682 usage=usage_flags, alg=alg1, alg2=alg2,
683 material=key_material,
684 description=description)
685 return key
686
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100687 def keys_for_type(
688 self,
689 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200690 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200691 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200692 """Generate test keys for the given key type."""
693 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100694 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200695 # Test a non-exercisable key, as well as exercisable keys for
696 # each compatible algorithm.
697 # To do: test reading a key from storage with an incompatible
698 # or unsupported algorithm.
699 yield self.key_for_type_and_alg(kt, bits)
700 compatible_algorithms = [alg for alg in all_algorithms
701 if kt.can_do(alg)]
702 for alg in compatible_algorithms:
703 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100704
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200705 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100706 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200707 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200708 all_algorithms = [crypto_knowledge.Algorithm(alg)
709 for alg in self.constructors.generate_expressions(
710 sorted(self.constructors.algorithms)
711 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200712 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200713 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100714
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200715 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200716 """Generate test keys for the encoding of the specified algorithm."""
717 # These test cases only validate the encoding of algorithms, not
718 # whether the key read from storage is suitable for an operation.
719 # `keys_for_types` generate read tests with an algorithm and a
720 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100721 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100722 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200723 key1 = StorageTestData(version=self.version,
724 id=1, lifetime=0x00000001,
725 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
726 usage=usage, alg=alg, alg2=0,
727 material=b'K',
728 description='alg: ' + descr)
729 yield key1
730 key2 = StorageTestData(version=self.version,
731 id=1, lifetime=0x00000001,
732 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
733 usage=usage, alg=0, alg2=alg,
734 material=b'L',
735 description='alg2: ' + descr)
736 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100737
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200738 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100739 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200740 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200741 for alg in self.constructors.generate_expressions(algorithms):
742 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100743
gabor-mezei-armea840de2021-06-29 15:42:57 +0200744 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200745 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200746 yield from self.all_keys_for_lifetimes()
747 yield from self.all_keys_for_usage_flags()
748 yield from self.all_keys_for_types()
749 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200750
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200751 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100752 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200753 # First build a list of all keys, then construct all the corresponding
754 # test cases. This allows all required information to be obtained in
755 # one go, which is a significant performance gain as the information
756 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200757 all_keys = list(self.generate_all_keys())
758 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200759 if key.location_value() != 0:
760 # Skip keys with a non-default location, because they
761 # require a driver and we currently have no mechanism to
762 # determine whether a driver is available.
763 continue
764 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100765
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200766class StorageFormatForward(StorageFormat):
767 """Storage format stability test cases for forward compatibility."""
768
769 def __init__(self, info: Information, version: int) -> None:
770 super().__init__(info, version, True)
771
772class StorageFormatV0(StorageFormat):
773 """Storage format stability test cases for version 0 compatibility."""
774
775 def __init__(self, info: Information) -> None:
776 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100777
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200778 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200779 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100780 yield from super().all_keys_for_usage_flags()
781 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200782
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200783 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200784 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200785 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200786 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200787 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200788 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200789 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200790 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200791 algorithm and key type combination.
792 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200793 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200794 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100795 usage_flags = ['PSA_KEY_USAGE_EXPORT']
796 material_usage_flags = usage_flags + [implyer_usage]
797 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200798 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200799 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100800 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
801 alg_expression = crypto_knowledge.short_expression(alg, 1)
802 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200803 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200804 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200805 key = StorageTestData(version=self.version,
806 id=1, lifetime=0x00000001,
807 type=key_type.expression, bits=bits,
808 usage=material_usage_flags,
809 expected_usage=expected_usage_flags,
810 without_implicit_usage=True,
811 alg=alg, alg2=alg2,
812 material=key_material,
813 description=description)
814 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200815
816 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200817 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200818 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800819 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200820 # must be filtered. Pair them with keywords created from its names.
821 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
822 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
823 keyword_translation = {
824 'ECDSA': 'ECC',
825 'ED[0-9]*.*' : 'EDWARDS'
826 }
827 exclusive_keywords = {
828 'EDWARDS': 'ECC'
829 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200830 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
831 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200832 alg_with_keys = {} #type: Dict[str, List[str]]
833 translation_table = str.maketrans('(', '_', ')')
834 for alg in algorithms:
835 # Generate keywords from the name of the algorithm
836 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
837 # Translate keywords for better matching with the key types
838 for keyword in alg_keywords.copy():
839 for pattern, replace in keyword_translation.items():
840 if re.match(pattern, keyword):
841 alg_keywords.remove(keyword)
842 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800843 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200844 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
845 continue
846
847 for key_type in key_types:
848 # Generate keywords from the of the key type
849 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
850
Shaun Case0e7791f2021-12-20 21:14:10 -0800851 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200852 for keyword1, keyword2 in exclusive_keywords.items():
853 if keyword1 in key_type_keywords:
854 key_type_keywords.remove(keyword2)
855
856 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
857 not key_type_keywords.isdisjoint(alg_keywords):
858 if alg in alg_with_keys:
859 alg_with_keys[alg].append(key_type)
860 else:
861 alg_with_keys[alg] = [key_type]
862 return alg_with_keys
863
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200864 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200865 """Generate test keys for usage flag extensions."""
866 # Generate a key type and algorithm pair for each extendable usage
867 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800868 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200869 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200870
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200871 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
872 for alg in sorted(alg_with_keys):
873 for key_type in sorted(alg_with_keys[alg]):
874 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200875 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100876 if kt.is_public() and '_SIGN_' in usage:
877 # Can't sign with a public key
878 continue
879 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200880
gabor-mezei-armea840de2021-06-29 15:42:57 +0200881 def generate_all_keys(self) -> Iterator[StorageTestData]:
882 yield from super().generate_all_keys()
883 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200884
Gilles Peskine69feebd2022-09-16 21:41:47 +0200885class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisdcad1e92022-08-24 11:30:03 +0100886 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100887 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200888 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100889 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200890 'test_suite_psa_crypto_generate_key.generated':
891 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100892 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine4fa76bd2022-12-15 22:14:28 +0100893 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200894 'test_suite_psa_crypto_op_fail.generated':
895 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100896 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200897 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100898 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200899 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100900 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
901
Werner Lewisdcad1e92022-08-24 11:30:03 +0100902 def __init__(self, options):
903 super().__init__(options)
904 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100905
Werner Lewisdcad1e92022-08-24 11:30:03 +0100906 def generate_target(self, name: str, *target_args) -> None:
907 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100908
909if __name__ == '__main__':
Gilles Peskine69feebd2022-09-16 21:41:47 +0200910 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)