blob: e7819b91c5d205a56fd50cb5fa79cdf9199a7620 [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
23import argparse
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import os
Bence Szépkúti9e84ec72021-05-07 11:49:17 +020025import posixpath
Gilles Peskine14e428f2021-01-26 22:19:21 +010026import re
Gilles Peskine09940492021-01-26 22:16:30 +010027import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010028from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010029
30import scripts_path # pylint: disable=unused-import
Gilles Peskinec86f20a2021-04-22 00:20:47 +020031from mbedtls_dev import build_tree
Gilles Peskine14e428f2021-01-26 22:19:21 +010032from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010033from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010034from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010035from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010036
37T = TypeVar('T') #pylint: disable=invalid-name
38
Gilles Peskine14e428f2021-01-26 22:19:21 +010039
Gilles Peskine7f756872021-02-16 12:13:12 +010040def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010041 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
42 if name.startswith('PSA_'):
43 return name[:4] + 'WANT_' + name[4:]
44 else:
45 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
46
Gilles Peskine7f756872021-02-16 12:13:12 +010047def finish_family_dependency(dep: str, bits: int) -> str:
48 """Finish dep if it's a family dependency symbol prefix.
49
50 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
51 qualified by the key size. If dep is such a symbol, finish it by adjusting
52 the prefix and appending the key size. Other symbols are left unchanged.
53 """
54 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
55
56def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
57 """Finish any family dependency symbol prefixes.
58
59 Apply `finish_family_dependency` to each element of `dependencies`.
60 """
61 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010062
Gilles Peskinec5d086f2021-04-20 23:23:45 +020063SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
64 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
65 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
66 'PSA_ALG_ANY_HASH', # only in policies
67 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
68 'PSA_ALG_KEY_AGREEMENT', # chaining
69 'PSA_ALG_TRUNCATED_MAC', # modifier
70])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010071def automatic_dependencies(*expressions: str) -> List[str]:
72 """Infer dependencies of a test case by looking for PSA_xxx symbols.
73
74 The arguments are strings which should be C expressions. Do not use
75 string literals or comments as this function is not smart enough to
76 skip them.
77 """
78 used = set()
79 for expr in expressions:
80 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020081 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010082 return sorted(psa_want_symbol(name) for name in used)
83
Gilles Peskined169d602021-02-16 14:16:25 +010084# A temporary hack: at the time of writing, not all dependency symbols
85# are implemented yet. Skip test cases for which the dependency symbols are
86# not available. Once all dependency symbols are available, this hack must
87# be removed so that a bug in the dependency symbols proprely leads to a test
88# failure.
89def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
90 return frozenset(symbol
91 for line in open(filename)
92 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020093_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010094def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +020095 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
96 if _implemented_dependencies is None:
97 _implemented_dependencies = \
98 read_implemented_dependencies('include/psa/crypto_config.h')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +020099 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100100 for dep in dependencies):
101 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
102
Gilles Peskine14e428f2021-01-26 22:19:21 +0100103
Gilles Peskineb94ea512021-03-10 02:12:08 +0100104class Information:
105 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100106
Gilles Peskineb94ea512021-03-10 02:12:08 +0100107 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100108 self.constructors = self.read_psa_interface()
109
110 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100111 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200112 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100113 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200114 # Mbed TLS doesn't support finite-field DH yet and will not support
115 # finite-field DSA. Don't attempt to generate any related test case.
116 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
117 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100118 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
119 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100120
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200121 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100122 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200123 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100124 header_file_names = ['include/psa/crypto_values.h',
125 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200126 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100127 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200128 constructors.parse_header(header_file_name)
129 for test_cases in test_suites:
130 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100131 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200132 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100133 return constructors
134
Gilles Peskine14e428f2021-01-26 22:19:21 +0100135
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200136def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100137 verb: str, key_type: str, bits: int,
138 dependencies: List[str],
139 *args: str,
140 param_descr: str = ''
141) -> test_case.TestCase:
142 """Return one test case exercising a key creation method
143 for an unsupported key type or size.
144 """
145 hack_dependencies_not_implemented(dependencies)
146 tc = test_case.TestCase()
147 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
148 adverb = 'not' if dependencies else 'never'
149 if param_descr:
150 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200151 tc.set_description('PSA {} {} {}-bit {} supported'
152 .format(verb, short_key_type, bits, adverb))
153 tc.set_dependencies(dependencies)
154 tc.set_function(verb + '_not_supported')
155 tc.set_arguments([key_type] + list(args))
156 return tc
157
Gilles Peskineb94ea512021-03-10 02:12:08 +0100158class NotSupported:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200159 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100160
161 def __init__(self, info: Information) -> None:
162 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100163
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100164 ALWAYS_SUPPORTED = frozenset([
165 'PSA_KEY_TYPE_DERIVE',
166 'PSA_KEY_TYPE_RAW_DATA',
167 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100168 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100169 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100170 kt: crypto_knowledge.KeyType,
171 param: Optional[int] = None,
172 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100173 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200174 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100175
176 If param is present and not None, emit test cases conditioned on this
177 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200178 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100179 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100180 if kt.name in self.ALWAYS_SUPPORTED:
181 # Don't generate test cases for key types that are always supported.
182 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100183 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100184 import_dependencies = [('!' if param is None else '') +
185 psa_want_symbol(kt.name)]
186 if kt.params is not None:
187 import_dependencies += [('!' if param == i else '') +
188 psa_want_symbol(sym)
189 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 if kt.name.endswith('_PUBLIC_KEY'):
191 generate_dependencies = []
192 else:
193 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100194 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200195 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100196 'import', kt.expression, bits,
197 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100198 test_case.hex_string(kt.key_material(bits)),
199 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100200 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100201 if not generate_dependencies and param is not None:
202 # If generation is impossible for this key type, rather than
203 # supported or not depending on implementation capabilities,
204 # only generate the test case once.
205 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100206 # For public key we expect that key generation fails with
207 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200208 if not kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200209 yield test_case_for_key_type_not_supported(
210 'generate', kt.expression, bits,
211 finish_family_dependencies(generate_dependencies, bits),
212 str(bits),
213 param_descr=param_descr,
214 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100215 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100216
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200217 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
218 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
219
Gilles Peskine3d778392021-02-17 15:11:05 +0100220 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200223 if key_type in self.ECC_KEY_TYPES:
224 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100225 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100226 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200228 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100230 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100231 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100232 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100233 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100234
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200235def test_case_for_key_generation(
236 key_type: str, bits: int,
237 dependencies: List[str],
238 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200239 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200240) -> test_case.TestCase:
241 """Return one test case exercising a key generation.
242 """
243 hack_dependencies_not_implemented(dependencies)
244 tc = test_case.TestCase()
245 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
246 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200247 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200248 tc.set_dependencies(dependencies)
249 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100250 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200251
252 return tc
253
254class KeyGenerate:
255 """Generate positive and negative (invalid argument) test cases for key generation."""
256
257 def __init__(self, info: Information) -> None:
258 self.constructors = info.constructors
259
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200260 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
261 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
262
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100263 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200264 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200265 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200266 ) -> Iterator[test_case.TestCase]:
267 """Return test cases exercising key generation.
268
269 All key types can be generated except for public keys. For public key
270 PSA_ERROR_INVALID_ARGUMENT status is expected.
271 """
272 result = 'PSA_SUCCESS'
273
274 import_dependencies = [psa_want_symbol(kt.name)]
275 if kt.params is not None:
276 import_dependencies += [psa_want_symbol(sym)
277 for i, sym in enumerate(kt.params)]
278 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100279 # The library checks whether the key type is a public key generically,
280 # before it reaches a point where it needs support for the specific key
281 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200282 generate_dependencies = []
283 result = 'PSA_ERROR_INVALID_ARGUMENT'
284 else:
285 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100286 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200287 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200288 for bits in kt.sizes_to_test():
289 yield test_case_for_key_generation(
290 kt.expression, bits,
291 finish_family_dependencies(generate_dependencies, bits),
292 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200293 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200294 )
295
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
297 """Generate test cases that exercise the generation of keys."""
298 for key_type in sorted(self.constructors.key_types):
299 if key_type in self.ECC_KEY_TYPES:
300 continue
301 kt = crypto_knowledge.KeyType(key_type)
302 yield from self.test_cases_for_key_type_key_generation(kt)
303 for curve_family in sorted(self.constructors.ecc_curves):
304 for constr in self.ECC_KEY_TYPES:
305 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200306 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200307
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200308class OpFail:
309 """Generate test cases for operations that must fail."""
310 #pylint: disable=too-few-public-methods
311
312 def __init__(self, info: Information) -> None:
313 self.constructors = info.constructors
314
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200315 @staticmethod
316 def hash_test_cases(alg: str) -> Iterator[test_case.TestCase]:
317 """Generate hash failure test cases for the specified algorithm."""
318 tc = test_case.TestCase()
319 is_hash = (alg.startswith('PSA_ALG_SHA') or
320 alg.startswith('PSA_ALG_MD') or
321 alg in frozenset(['PSA_ALG_RIPEMD160', 'PSA_ALG_ANY_HASH']))
322 if is_hash:
323 descr = 'not supported'
324 status = 'PSA_ERROR_NOT_SUPPORTED'
325 dependencies = ['!PSA_WANT_' + alg[4:]]
326 else:
327 descr = 'invalid'
328 status = 'PSA_ERROR_INVALID_ARGUMENT'
329 dependencies = automatic_dependencies(alg)
330 tc.set_description('PSA hash {}: {}'
331 .format(descr, re.sub(r'PSA_ALG_', r'', alg)))
332 tc.set_dependencies(dependencies)
333 tc.set_function('hash_fail')
334 tc.set_arguments([alg, status])
335 yield tc
336
337 def test_cases_for_algorithm(self, alg: str) -> Iterator[test_case.TestCase]:
338 """Generate operation failure test cases for the specified algorithm."""
339 yield from self.hash_test_cases(alg)
340
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200341 def all_test_cases(self) -> Iterator[test_case.TestCase]:
342 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200343 algorithms = sorted(self.constructors.algorithms)
344 for alg in self.constructors.generate_expressions(algorithms):
345 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200346
347
Gilles Peskine897dff92021-03-10 15:03:44 +0100348class StorageKey(psa_storage.Key):
349 """Representation of a key for storage format testing."""
350
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200351 IMPLICIT_USAGE_FLAGS = {
352 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
353 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
354 } #type: Dict[str, str]
355 """Mapping of usage flags to the flags that they imply."""
356
357 def __init__(
358 self,
359 usage: str,
360 without_implicit_usage: Optional[bool] = False,
361 **kwargs
362 ) -> None:
363 """Prepare to generate a key.
364
365 * `usage` : The usage flags used for the key.
366 * `without_implicit_usage`: Flag to defide to apply the usage extension
367 """
gabor-mezei-arm2c9e54a2021-06-29 17:21:21 +0200368 super().__init__(usage=usage, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200369
370 if not without_implicit_usage:
371 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
372 if self.usage.value() & psa_storage.Expr(flag).value() and \
373 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
374 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
375
376class StorageTestData(StorageKey):
377 """Representation of test case data for storage format testing."""
378
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200379 def __init__(
380 self,
381 description: str,
382 expected_usage: Optional[str] = None,
383 **kwargs
384 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200385 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200386
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200387 * `description` : used for the the test case names
388 * `expected_usage`: the usage flags generated as the expected usage flags
389 in the test cases. CAn differ from the usage flags
390 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200391 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100392 super().__init__(**kwargs)
393 self.description = description #type: str
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200394 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200395
Gilles Peskine897dff92021-03-10 15:03:44 +0100396class StorageFormat:
397 """Storage format stability test cases."""
398
399 def __init__(self, info: Information, version: int, forward: bool) -> None:
400 """Prepare to generate test cases for storage format stability.
401
402 * `info`: information about the API. See the `Information` class.
403 * `version`: the storage format version to generate test cases for.
404 * `forward`: if true, generate forward compatibility test cases which
405 save a key and check that its representation is as intended. Otherwise
406 generate backward compatibility test cases which inject a key
407 representation and check that it can be read and used.
408 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200409 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
410 self.version = version #type: int
411 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100412
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200413 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100414 """Construct a storage format test case for the given key.
415
416 If ``forward`` is true, generate a forward compatibility test case:
417 create a key and validate that it has the expected representation.
418 Otherwise generate a backward compatibility test case: inject the
419 key representation into storage and validate that it can be read
420 correctly.
421 """
422 verb = 'save' if self.forward else 'read'
423 tc = test_case.TestCase()
424 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100425 dependencies = automatic_dependencies(
426 key.lifetime.string, key.type.string,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200427 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100428 )
429 dependencies = finish_family_dependencies(dependencies, key.bits)
430 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100431 tc.set_function('key_storage_' + verb)
432 if self.forward:
433 extra_arguments = []
434 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200435 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100436 # Some test keys have the RAW_DATA type and attributes that don't
437 # necessarily make sense. We do this to validate numerical
438 # encodings of the attributes.
439 # Raw data keys have no useful exercise anyway so there is no
440 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200441 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
442 flags.append('TEST_FLAG_EXERCISE')
443 if 'READ_ONLY' in key.lifetime.string:
444 flags.append('TEST_FLAG_READ_ONLY')
445 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100446 tc.set_arguments([key.lifetime.string,
447 key.type.string, str(key.bits),
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200448 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100449 '"' + key.material.hex() + '"',
450 '"' + key.hex() + '"',
451 *extra_arguments])
452 return tc
453
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200454 def key_for_lifetime(
455 self,
456 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200457 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200458 """Construct a test key for the given lifetime."""
459 short = lifetime
460 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
461 r'', short)
462 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
463 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200464 key = StorageTestData(version=self.version,
465 id=1, lifetime=lifetime,
466 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
467 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
468 material=b'L',
469 description=description)
470 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200471
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200472 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200473 """Generate test keys covering lifetimes."""
474 lifetimes = sorted(self.constructors.lifetimes)
475 expressions = self.constructors.generate_expressions(lifetimes)
476 for lifetime in expressions:
477 # Don't attempt to create or load a volatile key in storage
478 if 'VOLATILE' in lifetime:
479 continue
480 # Don't attempt to create a read-only key in storage,
481 # but do attempt to load one.
482 if 'READ_ONLY' in lifetime and self.forward:
483 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200484 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200485
Gilles Peskinef7614272022-02-24 18:58:08 +0100486 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100487 self,
488 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200489 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100490 test_implicit_usage: Optional[bool] = True
491 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100492 """Construct a test key for the given key usage."""
493 usage = ' | '.join(usage_flags) if usage_flags else '0'
494 if short is None:
495 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
Gilles Peskinef7614272022-02-24 18:58:08 +0100496 extra_desc = ' without implication' if test_implicit_usage else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200497 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200498 key1 = StorageTestData(version=self.version,
499 id=1, lifetime=0x00000001,
500 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
501 expected_usage=usage,
Gilles Peskinef7614272022-02-24 18:58:08 +0100502 without_implicit_usage=not test_implicit_usage,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200503 usage=usage, alg=0, alg2=0,
504 material=b'K',
505 description=description)
Gilles Peskinef7614272022-02-24 18:58:08 +0100506 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100507
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200508 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100509 """Generate test keys covering usage flags."""
510 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100511 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200512 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100513 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200514 for flag1, flag2 in zip(known_flags,
515 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100516 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200517
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200518 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200519 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100520 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200521
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200522 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200523 yield from self.generate_keys_for_usage_flags()
524 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100525
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100526 def keys_for_type(
527 self,
528 key_type: str,
529 params: Optional[Iterable[str]] = None
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200530 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100531 """Generate test keys for the given key type.
532
533 For key types that depend on a parameter (e.g. elliptic curve family),
534 `param` is the parameter to pass to the constructor. Only a single
535 parameter is supported.
536 """
537 kt = crypto_knowledge.KeyType(key_type, params)
538 for bits in kt.sizes_to_test():
539 usage_flags = 'PSA_KEY_USAGE_EXPORT'
540 alg = 0
541 alg2 = 0
542 key_material = kt.key_material(bits)
543 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
544 r'',
545 kt.expression)
546 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200547 key = StorageTestData(version=self.version,
548 id=1, lifetime=0x00000001,
549 type=kt.expression, bits=bits,
550 usage=usage_flags, alg=alg, alg2=alg2,
551 material=key_material,
552 description=description)
553 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100554
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200555 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100556 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200557 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200558 for key_type in self.constructors.generate_expressions(key_types):
559 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100560
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200561 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100562 """Generate test keys for the specified algorithm."""
563 # For now, we don't have information on the compatibility of key
564 # types and algorithms. So we just test the encoding of algorithms,
565 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200566 descr = re.sub(r'PSA_ALG_', r'', alg)
567 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100568 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200569 key1 = StorageTestData(version=self.version,
570 id=1, lifetime=0x00000001,
571 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
572 usage=usage, alg=alg, alg2=0,
573 material=b'K',
574 description='alg: ' + descr)
575 yield key1
576 key2 = StorageTestData(version=self.version,
577 id=1, lifetime=0x00000001,
578 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
579 usage=usage, alg=0, alg2=alg,
580 material=b'L',
581 description='alg2: ' + descr)
582 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100583
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200584 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100585 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200586 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200587 for alg in self.constructors.generate_expressions(algorithms):
588 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100589
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200590 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200591 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200592 yield from self.all_keys_for_lifetimes()
593 yield from self.all_keys_for_usage_flags()
594 yield from self.all_keys_for_types()
595 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200596
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200597 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100598 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200599 # First build a list of all keys, then construct all the corresponding
600 # test cases. This allows all required information to be obtained in
601 # one go, which is a significant performance gain as the information
602 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200603 all_keys = list(self.generate_all_keys())
604 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200605 if key.location_value() != 0:
606 # Skip keys with a non-default location, because they
607 # require a driver and we currently have no mechanism to
608 # determine whether a driver is available.
609 continue
610 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100611
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200612class StorageFormatForward(StorageFormat):
613 """Storage format stability test cases for forward compatibility."""
614
615 def __init__(self, info: Information, version: int) -> None:
616 super().__init__(info, version, True)
617
618class StorageFormatV0(StorageFormat):
619 """Storage format stability test cases for version 0 compatibility."""
620
621 def __init__(self, info: Information) -> None:
622 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100623
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200624 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200625 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100626 yield from super().all_keys_for_usage_flags()
627 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200628
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200629 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200630 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200631 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200632 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200633 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200634 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200635 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200636 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200637 algorithm and key type combination.
638 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200639 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200640 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200641 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200642 material_usage_flags = usage_flags + ' | ' + implyer_usage
643 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200644 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200645 key_material = key_type.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200646 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200647 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
648 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
649 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
650 r'',
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200651 key_type.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200652 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200653 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200654 key = StorageTestData(version=self.version,
655 id=1, lifetime=0x00000001,
656 type=key_type.expression, bits=bits,
657 usage=material_usage_flags,
658 expected_usage=expected_usage_flags,
659 without_implicit_usage=True,
660 alg=alg, alg2=alg2,
661 material=key_material,
662 description=description)
663 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200664
665 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200666 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200667 """Match possible key types for sign algorithms."""
668 # To create a valid combinaton both the algorithms and key types
669 # must be filtered. Pair them with keywords created from its names.
670 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
671 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
672 keyword_translation = {
673 'ECDSA': 'ECC',
674 'ED[0-9]*.*' : 'EDWARDS'
675 }
676 exclusive_keywords = {
677 'EDWARDS': 'ECC'
678 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200679 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
680 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200681 alg_with_keys = {} #type: Dict[str, List[str]]
682 translation_table = str.maketrans('(', '_', ')')
683 for alg in algorithms:
684 # Generate keywords from the name of the algorithm
685 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
686 # Translate keywords for better matching with the key types
687 for keyword in alg_keywords.copy():
688 for pattern, replace in keyword_translation.items():
689 if re.match(pattern, keyword):
690 alg_keywords.remove(keyword)
691 alg_keywords.add(replace)
692 # Filter out incompatible algortihms
693 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
694 continue
695
696 for key_type in key_types:
697 # Generate keywords from the of the key type
698 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
699
700 # Remove ambigious keywords
701 for keyword1, keyword2 in exclusive_keywords.items():
702 if keyword1 in key_type_keywords:
703 key_type_keywords.remove(keyword2)
704
705 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
706 not key_type_keywords.isdisjoint(alg_keywords):
707 if alg in alg_with_keys:
708 alg_with_keys[alg].append(key_type)
709 else:
710 alg_with_keys[alg] = [key_type]
711 return alg_with_keys
712
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200713 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200714 """Generate test keys for usage flag extensions."""
715 # Generate a key type and algorithm pair for each extendable usage
716 # flag to generate a valid key for exercising. The key is generated
717 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200718 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200719
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200720 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
721 for alg in sorted(alg_with_keys):
722 for key_type in sorted(alg_with_keys[alg]):
723 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200724 kt = crypto_knowledge.KeyType(key_type)
725 if kt.is_valid_for_signature(usage):
726 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200727
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200728 def generate_all_keys(self) -> Iterator[StorageTestData]:
729 yield from super().generate_all_keys()
730 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200731
Gilles Peskineb94ea512021-03-10 02:12:08 +0100732class TestGenerator:
733 """Generate test data."""
734
735 def __init__(self, options) -> None:
736 self.test_suite_directory = self.get_option(options, 'directory',
737 'tests/suites')
738 self.info = Information()
739
740 @staticmethod
741 def get_option(options, name: str, default: T) -> T:
742 value = getattr(options, name, None)
743 return default if value is None else value
744
Gilles Peskine0298bda2021-03-10 02:34:37 +0100745 def filename_for(self, basename: str) -> str:
746 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200747 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100748
Gilles Peskineb94ea512021-03-10 02:12:08 +0100749 def write_test_data_file(self, basename: str,
750 test_cases: Iterable[test_case.TestCase]) -> None:
751 """Write the test cases to a .data file.
752
753 The output file is ``basename + '.data'`` in the test suite directory.
754 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100755 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100756 test_case.write_data_file(filename, test_cases)
757
Gilles Peskine92165362021-04-23 16:37:12 +0200758 # Note that targets whose name containns 'test_format' have their content
759 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100760 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200761 'test_suite_psa_crypto_generate_key.generated':
762 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100763 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100764 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200765 'test_suite_psa_crypto_op_fail.generated':
766 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100767 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200768 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100769 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200770 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100771 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
772
773 def generate_target(self, name: str) -> None:
774 test_cases = self.TARGETS[name](self.info)
775 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100776
Gilles Peskine09940492021-01-26 22:16:30 +0100777def main(args):
778 """Command line entry point."""
779 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100780 parser.add_argument('--list', action='store_true',
781 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100782 parser.add_argument('--list-for-cmake', action='store_true',
783 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200784 parser.add_argument('--directory', metavar='DIR',
785 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100786 parser.add_argument('targets', nargs='*', metavar='TARGET',
787 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100788 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200789 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100790 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100791 if options.list:
792 for name in sorted(generator.TARGETS):
793 print(generator.filename_for(name))
794 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100795 # List in a cmake list format (i.e. ';'-separated)
796 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100797 print(';'.join(generator.filename_for(name)
798 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100799 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100800 if options.targets:
801 # Allow "-" as a special case so you can run
802 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
803 # ``$targets`` is empty or not.
804 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
805 for target in options.targets
806 if target != '-']
807 else:
808 options.targets = sorted(generator.TARGETS)
809 for target in options.targets:
810 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100811
812if __name__ == '__main__':
813 main(sys.argv[1:])