blob: 9bee98597fd42679d53e24037e78f7c327cb7365 [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))
91IMPLEMENTED_DEPENDENCIES = read_implemented_dependencies('include/psa/crypto_config.h')
92def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
93 if not all(dep.lstrip('!') in IMPLEMENTED_DEPENDENCIES
94 for dep in dependencies):
95 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
96
Gilles Peskine14e428f2021-01-26 22:19:21 +010097
Gilles Peskineb94ea512021-03-10 02:12:08 +010098class Information:
99 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100100
Gilles Peskineb94ea512021-03-10 02:12:08 +0100101 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100102 self.constructors = self.read_psa_interface()
103
104 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100105 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200106 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100107 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200108 # Mbed TLS doesn't support finite-field DH yet and will not support
109 # finite-field DSA. Don't attempt to generate any related test case.
110 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
111 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100112 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
113 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100114
Gilles Peskineb93f8542021-04-19 13:50:25 +0200115 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100116 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200117 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100118 header_file_names = ['include/psa/crypto_values.h',
119 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200120 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100121 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200122 constructors.parse_header(header_file_name)
123 for test_cases in test_suites:
124 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100125 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200126 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100127 return constructors
128
Gilles Peskine14e428f2021-01-26 22:19:21 +0100129
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200130def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100131 verb: str, key_type: str, bits: int,
132 dependencies: List[str],
133 *args: str,
134 param_descr: str = ''
135) -> test_case.TestCase:
136 """Return one test case exercising a key creation method
137 for an unsupported key type or size.
138 """
139 hack_dependencies_not_implemented(dependencies)
140 tc = test_case.TestCase()
141 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
142 adverb = 'not' if dependencies else 'never'
143 if param_descr:
144 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200145 tc.set_description('PSA {} {} {}-bit {} supported'
146 .format(verb, short_key_type, bits, adverb))
147 tc.set_dependencies(dependencies)
148 tc.set_function(verb + '_not_supported')
149 tc.set_arguments([key_type] + list(args))
150 return tc
151
Gilles Peskineb94ea512021-03-10 02:12:08 +0100152class NotSupported:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200153 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154
155 def __init__(self, info: Information) -> None:
156 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100157
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100158 ALWAYS_SUPPORTED = frozenset([
159 'PSA_KEY_TYPE_DERIVE',
160 'PSA_KEY_TYPE_RAW_DATA',
161 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100162 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100163 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100164 kt: crypto_knowledge.KeyType,
165 param: Optional[int] = None,
166 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100167 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200168 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100169
170 If param is present and not None, emit test cases conditioned on this
171 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200172 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100173 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100174 if kt.name in self.ALWAYS_SUPPORTED:
175 # Don't generate test cases for key types that are always supported.
176 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100177 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100178 import_dependencies = [('!' if param is None else '') +
179 psa_want_symbol(kt.name)]
180 if kt.params is not None:
181 import_dependencies += [('!' if param == i else '') +
182 psa_want_symbol(sym)
183 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100184 if kt.name.endswith('_PUBLIC_KEY'):
185 generate_dependencies = []
186 else:
187 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100188 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200189 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100190 'import', kt.expression, bits,
191 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100192 test_case.hex_string(kt.key_material(bits)),
193 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100194 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100195 if not generate_dependencies and param is not None:
196 # If generation is impossible for this key type, rather than
197 # supported or not depending on implementation capabilities,
198 # only generate the test case once.
199 continue
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200200 # Public key cannot be generated
201 if not kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200202 yield test_case_for_key_type_not_supported(
203 'generate', kt.expression, bits,
204 finish_family_dependencies(generate_dependencies, bits),
205 str(bits),
206 param_descr=param_descr,
207 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100208 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100209
Gilles Peskineb93f8542021-04-19 13:50:25 +0200210 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
211 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
212
Gilles Peskine3d778392021-02-17 15:11:05 +0100213 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100215 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200216 if key_type in self.ECC_KEY_TYPES:
217 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100219 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100220 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200221 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100222 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100225 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100227
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200228def test_case_for_key_generation(
229 key_type: str, bits: int,
230 dependencies: List[str],
231 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200232 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200233) -> test_case.TestCase:
234 """Return one test case exercising a key generation.
235 """
236 hack_dependencies_not_implemented(dependencies)
237 tc = test_case.TestCase()
238 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
239 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200240 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200241 tc.set_dependencies(dependencies)
242 tc.set_function('generate_key')
243 tc.set_arguments([key_type] + list(args))
244 tc.set_result(result)
245
246 return tc
247
248class KeyGenerate:
249 """Generate positive and negative (invalid argument) test cases for key generation."""
250
251 def __init__(self, info: Information) -> None:
252 self.constructors = info.constructors
253
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200254 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
255 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
256
257 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200258 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200259 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200260 ) -> Iterator[test_case.TestCase]:
261 """Return test cases exercising key generation.
262
263 All key types can be generated except for public keys. For public key
264 PSA_ERROR_INVALID_ARGUMENT status is expected.
265 """
266 result = 'PSA_SUCCESS'
267
268 import_dependencies = [psa_want_symbol(kt.name)]
269 if kt.params is not None:
270 import_dependencies += [psa_want_symbol(sym)
271 for i, sym in enumerate(kt.params)]
272 if kt.name.endswith('_PUBLIC_KEY'):
273 generate_dependencies = []
274 result = 'PSA_ERROR_INVALID_ARGUMENT'
275 else:
276 generate_dependencies = import_dependencies
277 for bits in kt.sizes_to_test():
278 yield test_case_for_key_generation(
279 kt.expression, bits,
280 finish_family_dependencies(generate_dependencies, bits),
281 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200282 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200283 )
284
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200285 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
286 """Generate test cases that exercise the generation of keys."""
287 for key_type in sorted(self.constructors.key_types):
288 if key_type in self.ECC_KEY_TYPES:
289 continue
290 kt = crypto_knowledge.KeyType(key_type)
291 yield from self.test_cases_for_key_type_key_generation(kt)
292 for curve_family in sorted(self.constructors.ecc_curves):
293 for constr in self.ECC_KEY_TYPES:
294 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200295 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200296
Gilles Peskine897dff92021-03-10 15:03:44 +0100297class StorageKey(psa_storage.Key):
298 """Representation of a key for storage format testing."""
299
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200300 IMPLICIT_USAGE_FLAGS = {
301 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
302 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
303 } #type: Dict[str, str]
304 """Mapping of usage flags to the flags that they imply."""
305
306 def __init__(
307 self,
308 usage: str,
309 without_implicit_usage: Optional[bool] = False,
310 **kwargs
311 ) -> None:
312 """Prepare to generate a key.
313
314 * `usage` : The usage flags used for the key.
315 * `without_implicit_usage`: Flag to defide to apply the usage extension
316 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200317 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200318
319 if not without_implicit_usage:
320 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
321 if self.usage.value() & psa_storage.Expr(flag).value() and \
322 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
323 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
324
325class StorageTestData(StorageKey):
326 """Representation of test case data for storage format testing."""
327
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200328 def __init__(
329 self,
330 description: str,
331 expected_usage: Optional[str] = None,
332 **kwargs
333 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200334 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200335
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200336 * `description` : used for the the test case names
337 * `expected_usage`: the usage flags generated as the expected usage flags
338 in the test cases. CAn differ from the usage flags
339 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200340 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100341 super().__init__(**kwargs)
342 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200343 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200344
Gilles Peskine897dff92021-03-10 15:03:44 +0100345class StorageFormat:
346 """Storage format stability test cases."""
347
348 def __init__(self, info: Information, version: int, forward: bool) -> None:
349 """Prepare to generate test cases for storage format stability.
350
351 * `info`: information about the API. See the `Information` class.
352 * `version`: the storage format version to generate test cases for.
353 * `forward`: if true, generate forward compatibility test cases which
354 save a key and check that its representation is as intended. Otherwise
355 generate backward compatibility test cases which inject a key
356 representation and check that it can be read and used.
357 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200358 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
359 self.version = version #type: int
360 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100361
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200362 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100363 """Construct a storage format test case for the given key.
364
365 If ``forward`` is true, generate a forward compatibility test case:
366 create a key and validate that it has the expected representation.
367 Otherwise generate a backward compatibility test case: inject the
368 key representation into storage and validate that it can be read
369 correctly.
370 """
371 verb = 'save' if self.forward else 'read'
372 tc = test_case.TestCase()
373 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100374 dependencies = automatic_dependencies(
375 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200376 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100377 )
378 dependencies = finish_family_dependencies(dependencies, key.bits)
379 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100380 tc.set_function('key_storage_' + verb)
381 if self.forward:
382 extra_arguments = []
383 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200384 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100385 # Some test keys have the RAW_DATA type and attributes that don't
386 # necessarily make sense. We do this to validate numerical
387 # encodings of the attributes.
388 # Raw data keys have no useful exercise anyway so there is no
389 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200390 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
391 flags.append('TEST_FLAG_EXERCISE')
392 if 'READ_ONLY' in key.lifetime.string:
393 flags.append('TEST_FLAG_READ_ONLY')
394 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100395 tc.set_arguments([key.lifetime.string,
396 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200397 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100398 '"' + key.material.hex() + '"',
399 '"' + key.hex() + '"',
400 *extra_arguments])
401 return tc
402
Gilles Peskineefb584d2021-04-21 22:05:34 +0200403 def key_for_lifetime(
404 self,
405 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200406 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200407 """Construct a test key for the given lifetime."""
408 short = lifetime
409 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
410 r'', short)
411 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
412 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200413 key = StorageTestData(version=self.version,
414 id=1, lifetime=lifetime,
415 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
416 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
417 material=b'L',
418 description=description)
419 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200420
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200421 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200422 """Generate test keys covering lifetimes."""
423 lifetimes = sorted(self.constructors.lifetimes)
424 expressions = self.constructors.generate_expressions(lifetimes)
425 for lifetime in expressions:
426 # Don't attempt to create or load a volatile key in storage
427 if 'VOLATILE' in lifetime:
428 continue
429 # Don't attempt to create a read-only key in storage,
430 # but do attempt to load one.
431 if 'READ_ONLY' in lifetime and self.forward:
432 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200433 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200434
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200435 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100436 self,
437 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200438 short: Optional[str] = None,
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200439 test_implicit_usage: Optional[bool] = False
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200440 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100441 """Construct a test key for the given key usage."""
442 usage = ' | '.join(usage_flags) if usage_flags else '0'
443 if short is None:
444 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200445 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200446 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200447 key1 = StorageTestData(version=self.version,
448 id=1, lifetime=0x00000001,
449 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
450 expected_usage=usage,
451 usage=usage, alg=0, alg2=0,
452 material=b'K',
453 description=description)
454 yield key1
455
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200456 if test_implicit_usage:
457 description = 'usage without implication' + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200458 key2 = StorageTestData(version=self.version,
459 id=1, lifetime=0x00000001,
460 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
461 without_implicit_usage=True,
462 usage=usage, alg=0, alg2=0,
463 material=b'K',
464 description=description)
465 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100466
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200467 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100468 """Generate test keys covering usage flags."""
469 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200470 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200471 for usage_flag in known_flags:
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200472 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200473 for flag1, flag2 in zip(known_flags,
474 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200475 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200476
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200477 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200478 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200479 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200480
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200481 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200482 yield from self.generate_keys_for_usage_flags()
483 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100484
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100485 def keys_for_type(
486 self,
487 key_type: str,
488 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200489 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100490 """Generate test keys for the given key type.
491
492 For key types that depend on a parameter (e.g. elliptic curve family),
493 `param` is the parameter to pass to the constructor. Only a single
494 parameter is supported.
495 """
496 kt = crypto_knowledge.KeyType(key_type, params)
497 for bits in kt.sizes_to_test():
498 usage_flags = 'PSA_KEY_USAGE_EXPORT'
499 alg = 0
500 alg2 = 0
501 key_material = kt.key_material(bits)
502 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
503 r'',
504 kt.expression)
505 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200506 key = StorageTestData(version=self.version,
507 id=1, lifetime=0x00000001,
508 type=kt.expression, bits=bits,
509 usage=usage_flags, alg=alg, alg2=alg2,
510 material=key_material,
511 description=description)
512 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100513
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200514 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100515 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200516 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200517 for key_type in self.constructors.generate_expressions(key_types):
518 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100519
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200520 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100521 """Generate test keys for the specified algorithm."""
522 # For now, we don't have information on the compatibility of key
523 # types and algorithms. So we just test the encoding of algorithms,
524 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200525 descr = re.sub(r'PSA_ALG_', r'', alg)
526 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100527 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200528 key1 = StorageTestData(version=self.version,
529 id=1, lifetime=0x00000001,
530 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
531 usage=usage, alg=alg, alg2=0,
532 material=b'K',
533 description='alg: ' + descr)
534 yield key1
535 key2 = StorageTestData(version=self.version,
536 id=1, lifetime=0x00000001,
537 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
538 usage=usage, alg=0, alg2=alg,
539 material=b'L',
540 description='alg2: ' + descr)
541 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100542
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200543 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100544 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200545 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200546 for alg in self.constructors.generate_expressions(algorithms):
547 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100548
gabor-mezei-armea840de2021-06-29 15:42:57 +0200549 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200550 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200551 yield from self.all_keys_for_lifetimes()
552 yield from self.all_keys_for_usage_flags()
553 yield from self.all_keys_for_types()
554 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200555
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200556 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100557 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200558 # First build a list of all keys, then construct all the corresponding
559 # test cases. This allows all required information to be obtained in
560 # one go, which is a significant performance gain as the information
561 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200562 all_keys = list(self.generate_all_keys())
563 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200564 if key.location_value() != 0:
565 # Skip keys with a non-default location, because they
566 # require a driver and we currently have no mechanism to
567 # determine whether a driver is available.
568 continue
569 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100570
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200571class StorageFormatForward(StorageFormat):
572 """Storage format stability test cases for forward compatibility."""
573
574 def __init__(self, info: Information, version: int) -> None:
575 super().__init__(info, version, True)
576
577class StorageFormatV0(StorageFormat):
578 """Storage format stability test cases for version 0 compatibility."""
579
580 def __init__(self, info: Information) -> None:
581 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100582
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200583 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200584 """Generate test keys covering usage flags."""
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200585 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
586 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200587
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200588 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200589 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200590 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200591 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200592 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200593 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200594 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200595 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200596 algorithm and key type combination.
597 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200598 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200599 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200600 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200601 material_usage_flags = usage_flags + ' | ' + implyer_usage
602 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200603 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200604 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200605 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200606 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
607 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
608 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
609 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200610 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200611 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200612 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200613 key = StorageTestData(version=self.version,
614 id=1, lifetime=0x00000001,
615 type=key_type.expression, bits=bits,
616 usage=material_usage_flags,
617 expected_usage=expected_usage_flags,
618 without_implicit_usage=True,
619 alg=alg, alg2=alg2,
620 material=key_material,
621 description=description)
622 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200623
624 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200625 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200626 """Match possible key types for sign algorithms."""
627 # To create a valid combinaton both the algorithms and key types
628 # must be filtered. Pair them with keywords created from its names.
629 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
630 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
631 keyword_translation = {
632 'ECDSA': 'ECC',
633 'ED[0-9]*.*' : 'EDWARDS'
634 }
635 exclusive_keywords = {
636 'EDWARDS': 'ECC'
637 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200638 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
639 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200640 alg_with_keys = {} #type: Dict[str, List[str]]
641 translation_table = str.maketrans('(', '_', ')')
642 for alg in algorithms:
643 # Generate keywords from the name of the algorithm
644 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
645 # Translate keywords for better matching with the key types
646 for keyword in alg_keywords.copy():
647 for pattern, replace in keyword_translation.items():
648 if re.match(pattern, keyword):
649 alg_keywords.remove(keyword)
650 alg_keywords.add(replace)
651 # Filter out incompatible algortihms
652 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
653 continue
654
655 for key_type in key_types:
656 # Generate keywords from the of the key type
657 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
658
659 # Remove ambigious keywords
660 for keyword1, keyword2 in exclusive_keywords.items():
661 if keyword1 in key_type_keywords:
662 key_type_keywords.remove(keyword2)
663
664 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
665 not key_type_keywords.isdisjoint(alg_keywords):
666 if alg in alg_with_keys:
667 alg_with_keys[alg].append(key_type)
668 else:
669 alg_with_keys[alg] = [key_type]
670 return alg_with_keys
671
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200672 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200673 """Generate test keys for usage flag extensions."""
674 # Generate a key type and algorithm pair for each extendable usage
675 # flag to generate a valid key for exercising. The key is generated
676 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200677 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200678
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200679 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
680 for alg in sorted(alg_with_keys):
681 for key_type in sorted(alg_with_keys[alg]):
682 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200683 kt = crypto_knowledge.KeyType(key_type)
684 if kt.is_valid_for_signature(usage):
685 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200686
gabor-mezei-armea840de2021-06-29 15:42:57 +0200687 def generate_all_keys(self) -> Iterator[StorageTestData]:
688 yield from super().generate_all_keys()
689 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200690
Gilles Peskineb94ea512021-03-10 02:12:08 +0100691class TestGenerator:
692 """Generate test data."""
693
694 def __init__(self, options) -> None:
695 self.test_suite_directory = self.get_option(options, 'directory',
696 'tests/suites')
697 self.info = Information()
698
699 @staticmethod
700 def get_option(options, name: str, default: T) -> T:
701 value = getattr(options, name, None)
702 return default if value is None else value
703
Gilles Peskine0298bda2021-03-10 02:34:37 +0100704 def filename_for(self, basename: str) -> str:
705 """The location of the data file with the specified base name."""
706 return os.path.join(self.test_suite_directory, basename + '.data')
707
Gilles Peskineb94ea512021-03-10 02:12:08 +0100708 def write_test_data_file(self, basename: str,
709 test_cases: Iterable[test_case.TestCase]) -> None:
710 """Write the test cases to a .data file.
711
712 The output file is ``basename + '.data'`` in the test suite directory.
713 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100714 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100715 test_case.write_data_file(filename, test_cases)
716
Gilles Peskine0298bda2021-03-10 02:34:37 +0100717 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200718 'test_suite_psa_crypto_generate_key.generated':
719 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100720 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100721 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100722 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200723 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100724 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200725 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100726 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
727
728 def generate_target(self, name: str) -> None:
729 test_cases = self.TARGETS[name](self.info)
730 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100731
Gilles Peskine09940492021-01-26 22:16:30 +0100732def main(args):
733 """Command line entry point."""
734 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100735 parser.add_argument('--list', action='store_true',
736 help='List available targets and exit')
737 parser.add_argument('targets', nargs='*', metavar='TARGET',
738 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100739 options = parser.parse_args(args)
740 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100741 if options.list:
742 for name in sorted(generator.TARGETS):
743 print(generator.filename_for(name))
744 return
745 if options.targets:
746 # Allow "-" as a special case so you can run
747 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
748 # ``$targets`` is empty or not.
749 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
750 for target in options.targets
751 if target != '-']
752 else:
753 options.targets = sorted(generator.TARGETS)
754 for target in options.targets:
755 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100756
757if __name__ == '__main__':
758 main(sys.argv[1:])