blob: f519f7902ee6dd336c12d0199d205225c824376c [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
25import re
Gilles Peskine09940492021-01-26 22:16:30 +010026import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010027from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010028
29import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010030from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010031from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010032from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010034
35T = TypeVar('T') #pylint: disable=invalid-name
36
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Gilles Peskine7f756872021-02-16 12:13:12 +010038def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010039 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
40 if name.startswith('PSA_'):
41 return name[:4] + 'WANT_' + name[4:]
42 else:
43 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
44
Gilles Peskine7f756872021-02-16 12:13:12 +010045def finish_family_dependency(dep: str, bits: int) -> str:
46 """Finish dep if it's a family dependency symbol prefix.
47
48 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
49 qualified by the key size. If dep is such a symbol, finish it by adjusting
50 the prefix and appending the key size. Other symbols are left unchanged.
51 """
52 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
53
54def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
55 """Finish any family dependency symbol prefixes.
56
57 Apply `finish_family_dependency` to each element of `dependencies`.
58 """
59 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010060
Gilles Peskine8a55b432021-04-20 23:23:45 +020061SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
62 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
63 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
64 'PSA_ALG_ANY_HASH', # only in policies
65 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
66 'PSA_ALG_KEY_AGREEMENT', # chaining
67 'PSA_ALG_TRUNCATED_MAC', # modifier
68])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010069def automatic_dependencies(*expressions: str) -> List[str]:
70 """Infer dependencies of a test case by looking for PSA_xxx symbols.
71
72 The arguments are strings which should be C expressions. Do not use
73 string literals or comments as this function is not smart enough to
74 skip them.
75 """
76 used = set()
77 for expr in expressions:
78 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskine8a55b432021-04-20 23:23:45 +020079 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010080 return sorted(psa_want_symbol(name) for name in used)
81
Gilles Peskined169d602021-02-16 14:16:25 +010082# A temporary hack: at the time of writing, not all dependency symbols
83# are implemented yet. Skip test cases for which the dependency symbols are
84# not available. Once all dependency symbols are available, this hack must
85# be removed so that a bug in the dependency symbols proprely leads to a test
86# failure.
87def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
88 return frozenset(symbol
89 for line in open(filename)
90 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskined169d602021-02-16 14:16:25 +010091def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Przemyslaw Stekiel08101082021-10-22 10:39:56 +020092 if _implemented_dependencies is None:
93 _implemented_dependencies = \
94 read_implemented_dependencies('include/psa/crypto_config.h')
95 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010096 for dep in dependencies):
97 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
98
Gilles Peskine14e428f2021-01-26 22:19:21 +010099
Gilles Peskineb94ea512021-03-10 02:12:08 +0100100class Information:
101 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100102
Gilles Peskineb94ea512021-03-10 02:12:08 +0100103 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100104 self.constructors = self.read_psa_interface()
105
106 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100107 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200108 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100109 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200110 # Mbed TLS doesn't support finite-field DH yet and will not support
111 # finite-field DSA. Don't attempt to generate any related test case.
112 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
113 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100114 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
115 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100116
Gilles Peskineb93f8542021-04-19 13:50:25 +0200117 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100118 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200119 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100120 header_file_names = ['include/psa/crypto_values.h',
121 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200122 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100123 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200124 constructors.parse_header(header_file_name)
125 for test_cases in test_suites:
126 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100127 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200128 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100129 return constructors
130
Gilles Peskine14e428f2021-01-26 22:19:21 +0100131
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200132def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100133 verb: str, key_type: str, bits: int,
134 dependencies: List[str],
135 *args: str,
136 param_descr: str = ''
137) -> test_case.TestCase:
138 """Return one test case exercising a key creation method
139 for an unsupported key type or size.
140 """
141 hack_dependencies_not_implemented(dependencies)
142 tc = test_case.TestCase()
143 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
144 adverb = 'not' if dependencies else 'never'
145 if param_descr:
146 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200147 tc.set_description('PSA {} {} {}-bit {} supported'
148 .format(verb, short_key_type, bits, adverb))
149 tc.set_dependencies(dependencies)
150 tc.set_function(verb + '_not_supported')
151 tc.set_arguments([key_type] + list(args))
152 return tc
153
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154class NotSupported:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200155 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100156
157 def __init__(self, info: Information) -> None:
158 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100159
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100160 ALWAYS_SUPPORTED = frozenset([
161 'PSA_KEY_TYPE_DERIVE',
162 'PSA_KEY_TYPE_RAW_DATA',
163 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100164 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100165 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100166 kt: crypto_knowledge.KeyType,
167 param: Optional[int] = None,
168 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100169 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200170 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100171
172 If param is present and not None, emit test cases conditioned on this
173 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200174 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100175 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100176 if kt.name in self.ALWAYS_SUPPORTED:
177 # Don't generate test cases for key types that are always supported.
178 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100179 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100180 import_dependencies = [('!' if param is None else '') +
181 psa_want_symbol(kt.name)]
182 if kt.params is not None:
183 import_dependencies += [('!' if param == i else '') +
184 psa_want_symbol(sym)
185 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100186 if kt.name.endswith('_PUBLIC_KEY'):
187 generate_dependencies = []
188 else:
189 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200191 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100192 'import', kt.expression, bits,
193 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100194 test_case.hex_string(kt.key_material(bits)),
195 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100196 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 if not generate_dependencies and param is not None:
198 # If generation is impossible for this key type, rather than
199 # supported or not depending on implementation capabilities,
200 # only generate the test case once.
201 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100202 # For public key we expect that key generation fails with
203 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200204 if not kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200205 yield test_case_for_key_type_not_supported(
206 'generate', kt.expression, bits,
207 finish_family_dependencies(generate_dependencies, bits),
208 str(bits),
209 param_descr=param_descr,
210 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100211 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212
Gilles Peskineb93f8542021-04-19 13:50:25 +0200213 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
214 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
215
Gilles Peskine3d778392021-02-17 15:11:05 +0100216 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200219 if key_type in self.ECC_KEY_TYPES:
220 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100222 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100223 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200224 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100226 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100228 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100230
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200231def test_case_for_key_generation(
232 key_type: str, bits: int,
233 dependencies: List[str],
234 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200235 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200236) -> test_case.TestCase:
237 """Return one test case exercising a key generation.
238 """
239 hack_dependencies_not_implemented(dependencies)
240 tc = test_case.TestCase()
241 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
242 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200243 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200244 tc.set_dependencies(dependencies)
245 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100246 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200247
248 return tc
249
250class KeyGenerate:
251 """Generate positive and negative (invalid argument) test cases for key generation."""
252
253 def __init__(self, info: Information) -> None:
254 self.constructors = info.constructors
255
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200256 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
257 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
258
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100259 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200260 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200261 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200262 ) -> Iterator[test_case.TestCase]:
263 """Return test cases exercising key generation.
264
265 All key types can be generated except for public keys. For public key
266 PSA_ERROR_INVALID_ARGUMENT status is expected.
267 """
268 result = 'PSA_SUCCESS'
269
270 import_dependencies = [psa_want_symbol(kt.name)]
271 if kt.params is not None:
272 import_dependencies += [psa_want_symbol(sym)
273 for i, sym in enumerate(kt.params)]
274 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100275 # The library checks whether the key type is a public key generically,
276 # before it reaches a point where it needs support for the specific key
277 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200278 generate_dependencies = []
279 result = 'PSA_ERROR_INVALID_ARGUMENT'
280 else:
281 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100282 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200283 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200284 for bits in kt.sizes_to_test():
285 yield test_case_for_key_generation(
286 kt.expression, bits,
287 finish_family_dependencies(generate_dependencies, bits),
288 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200289 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200290 )
291
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200292 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
293 """Generate test cases that exercise the generation of keys."""
294 for key_type in sorted(self.constructors.key_types):
295 if key_type in self.ECC_KEY_TYPES:
296 continue
297 kt = crypto_knowledge.KeyType(key_type)
298 yield from self.test_cases_for_key_type_key_generation(kt)
299 for curve_family in sorted(self.constructors.ecc_curves):
300 for constr in self.ECC_KEY_TYPES:
301 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200302 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200303
Gilles Peskine897dff92021-03-10 15:03:44 +0100304class StorageKey(psa_storage.Key):
305 """Representation of a key for storage format testing."""
306
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200307 IMPLICIT_USAGE_FLAGS = {
308 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
309 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
310 } #type: Dict[str, str]
311 """Mapping of usage flags to the flags that they imply."""
312
313 def __init__(
314 self,
315 usage: str,
316 without_implicit_usage: Optional[bool] = False,
317 **kwargs
318 ) -> None:
319 """Prepare to generate a key.
320
321 * `usage` : The usage flags used for the key.
322 * `without_implicit_usage`: Flag to defide to apply the usage extension
323 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200324 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200325
326 if not without_implicit_usage:
327 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
328 if self.usage.value() & psa_storage.Expr(flag).value() and \
329 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
330 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
331
332class StorageTestData(StorageKey):
333 """Representation of test case data for storage format testing."""
334
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200335 def __init__(
336 self,
337 description: str,
338 expected_usage: Optional[str] = None,
339 **kwargs
340 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200341 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200342
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200343 * `description` : used for the the test case names
344 * `expected_usage`: the usage flags generated as the expected usage flags
345 in the test cases. CAn differ from the usage flags
346 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200347 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100348 super().__init__(**kwargs)
349 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200350 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200351
Gilles Peskine897dff92021-03-10 15:03:44 +0100352class StorageFormat:
353 """Storage format stability test cases."""
354
355 def __init__(self, info: Information, version: int, forward: bool) -> None:
356 """Prepare to generate test cases for storage format stability.
357
358 * `info`: information about the API. See the `Information` class.
359 * `version`: the storage format version to generate test cases for.
360 * `forward`: if true, generate forward compatibility test cases which
361 save a key and check that its representation is as intended. Otherwise
362 generate backward compatibility test cases which inject a key
363 representation and check that it can be read and used.
364 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200365 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
366 self.version = version #type: int
367 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100368
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200369 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100370 """Construct a storage format test case for the given key.
371
372 If ``forward`` is true, generate a forward compatibility test case:
373 create a key and validate that it has the expected representation.
374 Otherwise generate a backward compatibility test case: inject the
375 key representation into storage and validate that it can be read
376 correctly.
377 """
378 verb = 'save' if self.forward else 'read'
379 tc = test_case.TestCase()
380 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100381 dependencies = automatic_dependencies(
382 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200383 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100384 )
385 dependencies = finish_family_dependencies(dependencies, key.bits)
386 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100387 tc.set_function('key_storage_' + verb)
388 if self.forward:
389 extra_arguments = []
390 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200391 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100392 # Some test keys have the RAW_DATA type and attributes that don't
393 # necessarily make sense. We do this to validate numerical
394 # encodings of the attributes.
395 # Raw data keys have no useful exercise anyway so there is no
396 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200397 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
398 flags.append('TEST_FLAG_EXERCISE')
399 if 'READ_ONLY' in key.lifetime.string:
400 flags.append('TEST_FLAG_READ_ONLY')
401 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100402 tc.set_arguments([key.lifetime.string,
403 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200404 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100405 '"' + key.material.hex() + '"',
406 '"' + key.hex() + '"',
407 *extra_arguments])
408 return tc
409
Gilles Peskineefb584d2021-04-21 22:05:34 +0200410 def key_for_lifetime(
411 self,
412 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200413 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200414 """Construct a test key for the given lifetime."""
415 short = lifetime
416 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
417 r'', short)
418 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
419 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200420 key = StorageTestData(version=self.version,
421 id=1, lifetime=lifetime,
422 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
423 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
424 material=b'L',
425 description=description)
426 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200427
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200428 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200429 """Generate test keys covering lifetimes."""
430 lifetimes = sorted(self.constructors.lifetimes)
431 expressions = self.constructors.generate_expressions(lifetimes)
432 for lifetime in expressions:
433 # Don't attempt to create or load a volatile key in storage
434 if 'VOLATILE' in lifetime:
435 continue
436 # Don't attempt to create a read-only key in storage,
437 # but do attempt to load one.
438 if 'READ_ONLY' in lifetime and self.forward:
439 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200440 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200441
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200442 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100443 self,
444 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200445 short: Optional[str] = None,
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200446 test_implicit_usage: Optional[bool] = False
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200447 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100448 """Construct a test key for the given key usage."""
449 usage = ' | '.join(usage_flags) if usage_flags else '0'
450 if short is None:
451 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200452 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200453 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200454 key1 = StorageTestData(version=self.version,
455 id=1, lifetime=0x00000001,
456 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
457 expected_usage=usage,
458 usage=usage, alg=0, alg2=0,
459 material=b'K',
460 description=description)
461 yield key1
462
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200463 if test_implicit_usage:
464 description = 'usage without implication' + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200465 key2 = StorageTestData(version=self.version,
466 id=1, lifetime=0x00000001,
467 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
468 without_implicit_usage=True,
469 usage=usage, alg=0, alg2=0,
470 material=b'K',
471 description=description)
472 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100473
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200474 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100475 """Generate test keys covering usage flags."""
476 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200477 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200478 for usage_flag in known_flags:
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200479 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200480 for flag1, flag2 in zip(known_flags,
481 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200482 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200483
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200484 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200485 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200486 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200487
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200488 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200489 yield from self.generate_keys_for_usage_flags()
490 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100491
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100492 def keys_for_type(
493 self,
494 key_type: str,
495 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200496 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100497 """Generate test keys for the given key type.
498
499 For key types that depend on a parameter (e.g. elliptic curve family),
500 `param` is the parameter to pass to the constructor. Only a single
501 parameter is supported.
502 """
503 kt = crypto_knowledge.KeyType(key_type, params)
504 for bits in kt.sizes_to_test():
505 usage_flags = 'PSA_KEY_USAGE_EXPORT'
506 alg = 0
507 alg2 = 0
508 key_material = kt.key_material(bits)
509 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
510 r'',
511 kt.expression)
512 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200513 key = StorageTestData(version=self.version,
514 id=1, lifetime=0x00000001,
515 type=kt.expression, bits=bits,
516 usage=usage_flags, alg=alg, alg2=alg2,
517 material=key_material,
518 description=description)
519 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100520
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200521 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100522 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200523 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200524 for key_type in self.constructors.generate_expressions(key_types):
525 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100526
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200527 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100528 """Generate test keys for the specified algorithm."""
529 # For now, we don't have information on the compatibility of key
530 # types and algorithms. So we just test the encoding of algorithms,
531 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200532 descr = re.sub(r'PSA_ALG_', r'', alg)
533 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100534 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200535 key1 = StorageTestData(version=self.version,
536 id=1, lifetime=0x00000001,
537 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
538 usage=usage, alg=alg, alg2=0,
539 material=b'K',
540 description='alg: ' + descr)
541 yield key1
542 key2 = StorageTestData(version=self.version,
543 id=1, lifetime=0x00000001,
544 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
545 usage=usage, alg=0, alg2=alg,
546 material=b'L',
547 description='alg2: ' + descr)
548 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100549
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200550 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100551 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200552 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200553 for alg in self.constructors.generate_expressions(algorithms):
554 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100555
gabor-mezei-armea840de2021-06-29 15:42:57 +0200556 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200557 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200558 yield from self.all_keys_for_lifetimes()
559 yield from self.all_keys_for_usage_flags()
560 yield from self.all_keys_for_types()
561 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200562
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200563 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100564 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200565 # First build a list of all keys, then construct all the corresponding
566 # test cases. This allows all required information to be obtained in
567 # one go, which is a significant performance gain as the information
568 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200569 all_keys = list(self.generate_all_keys())
570 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200571 if key.location_value() != 0:
572 # Skip keys with a non-default location, because they
573 # require a driver and we currently have no mechanism to
574 # determine whether a driver is available.
575 continue
576 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100577
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200578class StorageFormatForward(StorageFormat):
579 """Storage format stability test cases for forward compatibility."""
580
581 def __init__(self, info: Information, version: int) -> None:
582 super().__init__(info, version, True)
583
584class StorageFormatV0(StorageFormat):
585 """Storage format stability test cases for version 0 compatibility."""
586
587 def __init__(self, info: Information) -> None:
588 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100589
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200590 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200591 """Generate test keys covering usage flags."""
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200592 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
593 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200594
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200595 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200596 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200597 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200598 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200599 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200600 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200601 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200602 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200603 algorithm and key type combination.
604 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200605 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200606 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200607 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200608 material_usage_flags = usage_flags + ' | ' + implyer_usage
609 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200610 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200611 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200612 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200613 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
614 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
615 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
616 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200617 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200618 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200619 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200620 key = StorageTestData(version=self.version,
621 id=1, lifetime=0x00000001,
622 type=key_type.expression, bits=bits,
623 usage=material_usage_flags,
624 expected_usage=expected_usage_flags,
625 without_implicit_usage=True,
626 alg=alg, alg2=alg2,
627 material=key_material,
628 description=description)
629 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200630
631 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200632 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200633 """Match possible key types for sign algorithms."""
634 # To create a valid combinaton both the algorithms and key types
635 # must be filtered. Pair them with keywords created from its names.
636 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
637 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
638 keyword_translation = {
639 'ECDSA': 'ECC',
640 'ED[0-9]*.*' : 'EDWARDS'
641 }
642 exclusive_keywords = {
643 'EDWARDS': 'ECC'
644 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200645 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
646 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200647 alg_with_keys = {} #type: Dict[str, List[str]]
648 translation_table = str.maketrans('(', '_', ')')
649 for alg in algorithms:
650 # Generate keywords from the name of the algorithm
651 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
652 # Translate keywords for better matching with the key types
653 for keyword in alg_keywords.copy():
654 for pattern, replace in keyword_translation.items():
655 if re.match(pattern, keyword):
656 alg_keywords.remove(keyword)
657 alg_keywords.add(replace)
658 # Filter out incompatible algortihms
659 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
660 continue
661
662 for key_type in key_types:
663 # Generate keywords from the of the key type
664 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
665
666 # Remove ambigious keywords
667 for keyword1, keyword2 in exclusive_keywords.items():
668 if keyword1 in key_type_keywords:
669 key_type_keywords.remove(keyword2)
670
671 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
672 not key_type_keywords.isdisjoint(alg_keywords):
673 if alg in alg_with_keys:
674 alg_with_keys[alg].append(key_type)
675 else:
676 alg_with_keys[alg] = [key_type]
677 return alg_with_keys
678
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200679 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200680 """Generate test keys for usage flag extensions."""
681 # Generate a key type and algorithm pair for each extendable usage
682 # flag to generate a valid key for exercising. The key is generated
683 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200684 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200685
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200686 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
687 for alg in sorted(alg_with_keys):
688 for key_type in sorted(alg_with_keys[alg]):
689 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200690 kt = crypto_knowledge.KeyType(key_type)
691 if kt.is_valid_for_signature(usage):
692 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200693
gabor-mezei-armea840de2021-06-29 15:42:57 +0200694 def generate_all_keys(self) -> Iterator[StorageTestData]:
695 yield from super().generate_all_keys()
696 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200697
Gilles Peskineb94ea512021-03-10 02:12:08 +0100698class TestGenerator:
699 """Generate test data."""
700
701 def __init__(self, options) -> None:
702 self.test_suite_directory = self.get_option(options, 'directory',
703 'tests/suites')
704 self.info = Information()
705
706 @staticmethod
707 def get_option(options, name: str, default: T) -> T:
708 value = getattr(options, name, None)
709 return default if value is None else value
710
Gilles Peskine0298bda2021-03-10 02:34:37 +0100711 def filename_for(self, basename: str) -> str:
712 """The location of the data file with the specified base name."""
713 return os.path.join(self.test_suite_directory, basename + '.data')
714
Gilles Peskineb94ea512021-03-10 02:12:08 +0100715 def write_test_data_file(self, basename: str,
716 test_cases: Iterable[test_case.TestCase]) -> None:
717 """Write the test cases to a .data file.
718
719 The output file is ``basename + '.data'`` in the test suite directory.
720 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100721 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100722 test_case.write_data_file(filename, test_cases)
723
Gilles Peskine0298bda2021-03-10 02:34:37 +0100724 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200725 'test_suite_psa_crypto_generate_key.generated':
726 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100727 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100728 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100729 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200730 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100731 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200732 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100733 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
734
735 def generate_target(self, name: str) -> None:
736 test_cases = self.TARGETS[name](self.info)
737 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100738
Gilles Peskine09940492021-01-26 22:16:30 +0100739def main(args):
740 """Command line entry point."""
741 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100742 parser.add_argument('--list', action='store_true',
743 help='List available targets and exit')
744 parser.add_argument('targets', nargs='*', metavar='TARGET',
745 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100746 options = parser.parse_args(args)
747 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100748 if options.list:
749 for name in sorted(generator.TARGETS):
750 print(generator.filename_for(name))
751 return
752 if options.targets:
753 # Allow "-" as a special case so you can run
754 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
755 # ``$targets`` is empty or not.
756 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
757 for target in options.targets
758 if target != '-']
759 else:
760 options.targets = sorted(generator.TARGETS)
761 for target in options.targets:
762 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100763
764if __name__ == '__main__':
765 main(sys.argv[1:])