blob: 9615c29a3f65d8a15f36b567a7f48b9a223ceed0 [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')
99 if not all(dep.lstrip('!') in _implemented_dependencies
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
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136def test_case_for_key_type_not_supported(
137 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
151 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
158class NotSupported:
159 """Generate test cases for when something is not supported."""
160
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]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100174 """Return test cases exercising key creation when the given type is unsupported.
175
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
178 conditioned on the base type not being supported.
179 """
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():
Gilles Peskine3d778392021-02-17 15:11:05 +0100195 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
Gilles Peskine3d778392021-02-17 15:11:05 +0100206 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100207 'generate', kt.expression, bits,
208 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100209 str(bits),
210 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100211 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200214 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
215 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
216
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200220 if key_type in self.ECC_KEY_TYPES:
221 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200225 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100231
232
Gilles Peskine897dff92021-03-10 15:03:44 +0100233class StorageKey(psa_storage.Key):
234 """Representation of a key for storage format testing."""
235
236 def __init__(self, *, description: str, **kwargs) -> None:
237 super().__init__(**kwargs)
238 self.description = description #type: str
239
240class StorageFormat:
241 """Storage format stability test cases."""
242
243 def __init__(self, info: Information, version: int, forward: bool) -> None:
244 """Prepare to generate test cases for storage format stability.
245
246 * `info`: information about the API. See the `Information` class.
247 * `version`: the storage format version to generate test cases for.
248 * `forward`: if true, generate forward compatibility test cases which
249 save a key and check that its representation is as intended. Otherwise
250 generate backward compatibility test cases which inject a key
251 representation and check that it can be read and used.
252 """
253 self.constructors = info.constructors
254 self.version = version
255 self.forward = forward
256
257 def make_test_case(self, key: StorageKey) -> test_case.TestCase:
258 """Construct a storage format test case for the given key.
259
260 If ``forward`` is true, generate a forward compatibility test case:
261 create a key and validate that it has the expected representation.
262 Otherwise generate a backward compatibility test case: inject the
263 key representation into storage and validate that it can be read
264 correctly.
265 """
266 verb = 'save' if self.forward else 'read'
267 tc = test_case.TestCase()
268 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100269 dependencies = automatic_dependencies(
270 key.lifetime.string, key.type.string,
271 key.usage.string, key.alg.string, key.alg2.string,
272 )
273 dependencies = finish_family_dependencies(dependencies, key.bits)
274 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100275 tc.set_function('key_storage_' + verb)
276 if self.forward:
277 extra_arguments = []
278 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200279 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100280 # Some test keys have the RAW_DATA type and attributes that don't
281 # necessarily make sense. We do this to validate numerical
282 # encodings of the attributes.
283 # Raw data keys have no useful exercise anyway so there is no
284 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200285 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
286 flags.append('TEST_FLAG_EXERCISE')
287 if 'READ_ONLY' in key.lifetime.string:
288 flags.append('TEST_FLAG_READ_ONLY')
289 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100290 tc.set_arguments([key.lifetime.string,
291 key.type.string, str(key.bits),
292 key.usage.string, key.alg.string, key.alg2.string,
293 '"' + key.material.hex() + '"',
294 '"' + key.hex() + '"',
295 *extra_arguments])
296 return tc
297
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200298 def key_for_lifetime(
299 self,
300 lifetime: str,
301 ) -> StorageKey:
302 """Construct a test key for the given lifetime."""
303 short = lifetime
304 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
305 r'', short)
306 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
307 description = 'lifetime: ' + short
308 key = StorageKey(version=self.version,
309 id=1, lifetime=lifetime,
310 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
311 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
312 material=b'L',
313 description=description)
314 return key
315
316 def all_keys_for_lifetimes(self) -> Iterator[StorageKey]:
317 """Generate test keys covering lifetimes."""
318 lifetimes = sorted(self.constructors.lifetimes)
319 expressions = self.constructors.generate_expressions(lifetimes)
320 for lifetime in expressions:
321 # Don't attempt to create or load a volatile key in storage
322 if 'VOLATILE' in lifetime:
323 continue
324 # Don't attempt to create a read-only key in storage,
325 # but do attempt to load one.
326 if 'READ_ONLY' in lifetime and self.forward:
327 continue
328 yield self.key_for_lifetime(lifetime)
329
Gilles Peskine897dff92021-03-10 15:03:44 +0100330 def key_for_usage_flags(
331 self,
332 usage_flags: List[str],
333 short: Optional[str] = None
334 ) -> StorageKey:
335 """Construct a test key for the given key usage."""
336 usage = ' | '.join(usage_flags) if usage_flags else '0'
337 if short is None:
338 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
339 description = 'usage: ' + short
340 key = StorageKey(version=self.version,
341 id=1, lifetime=0x00000001,
342 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
343 usage=usage, alg=0, alg2=0,
344 material=b'K',
345 description=description)
346 return key
347
348 def all_keys_for_usage_flags(self) -> Iterator[StorageKey]:
349 """Generate test keys covering usage flags."""
350 known_flags = sorted(self.constructors.key_usage_flags)
351 yield self.key_for_usage_flags(['0'])
352 for usage_flag in known_flags:
353 yield self.key_for_usage_flags([usage_flag])
354 for flag1, flag2 in zip(known_flags,
355 known_flags[1:] + [known_flags[0]]):
356 yield self.key_for_usage_flags([flag1, flag2])
357 yield self.key_for_usage_flags(known_flags, short='all known')
358
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100359 def keys_for_type(
360 self,
361 key_type: str,
362 params: Optional[Iterable[str]] = None
363 ) -> Iterator[StorageKey]:
364 """Generate test keys for the given key type.
365
366 For key types that depend on a parameter (e.g. elliptic curve family),
367 `param` is the parameter to pass to the constructor. Only a single
368 parameter is supported.
369 """
370 kt = crypto_knowledge.KeyType(key_type, params)
371 for bits in kt.sizes_to_test():
372 usage_flags = 'PSA_KEY_USAGE_EXPORT'
373 alg = 0
374 alg2 = 0
375 key_material = kt.key_material(bits)
376 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
377 r'',
378 kt.expression)
379 description = 'type: {} {}-bit'.format(short_expression, bits)
380 key = StorageKey(version=self.version,
381 id=1, lifetime=0x00000001,
382 type=kt.expression, bits=bits,
383 usage=usage_flags, alg=alg, alg2=alg2,
384 material=key_material,
385 description=description)
386 yield key
387
388 def all_keys_for_types(self) -> Iterator[StorageKey]:
389 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200390 key_types = sorted(self.constructors.key_types)
391 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100392 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100393
Gilles Peskined86bc522021-03-10 15:08:57 +0100394 def keys_for_algorithm(self, alg: str) -> Iterator[StorageKey]:
395 """Generate test keys for the specified algorithm."""
396 # For now, we don't have information on the compatibility of key
397 # types and algorithms. So we just test the encoding of algorithms,
398 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200399 descr = re.sub(r'PSA_ALG_', r'', alg)
400 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100401 usage = 'PSA_KEY_USAGE_EXPORT'
402 key1 = StorageKey(version=self.version,
403 id=1, lifetime=0x00000001,
404 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
405 usage=usage, alg=alg, alg2=0,
406 material=b'K',
407 description='alg: ' + descr)
408 yield key1
409 key2 = StorageKey(version=self.version,
410 id=1, lifetime=0x00000001,
411 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
412 usage=usage, alg=0, alg2=alg,
413 material=b'L',
414 description='alg2: ' + descr)
415 yield key2
416
417 def all_keys_for_algorithms(self) -> Iterator[StorageKey]:
418 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200419 algorithms = sorted(self.constructors.algorithms)
420 for alg in self.constructors.generate_expressions(algorithms):
Gilles Peskined86bc522021-03-10 15:08:57 +0100421 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100422
Gilles Peskine897dff92021-03-10 15:03:44 +0100423 def all_test_cases(self) -> Iterator[test_case.TestCase]:
424 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200425 # First build a list of all keys, then construct all the corresponding
426 # test cases. This allows all required information to be obtained in
427 # one go, which is a significant performance gain as the information
428 # includes numerical values obtained by compiling a C program.
429 keys = [] #type: List[StorageKey]
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200430 keys += self.all_keys_for_lifetimes()
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200431 keys += self.all_keys_for_usage_flags()
432 keys += self.all_keys_for_types()
433 keys += self.all_keys_for_algorithms()
434 for key in keys:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200435 if key.location_value() != 0:
436 # Skip keys with a non-default location, because they
437 # require a driver and we currently have no mechanism to
438 # determine whether a driver is available.
439 continue
Gilles Peskined86bc522021-03-10 15:08:57 +0100440 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100441
442
Gilles Peskineb94ea512021-03-10 02:12:08 +0100443class TestGenerator:
444 """Generate test data."""
445
446 def __init__(self, options) -> None:
447 self.test_suite_directory = self.get_option(options, 'directory',
448 'tests/suites')
449 self.info = Information()
450
451 @staticmethod
452 def get_option(options, name: str, default: T) -> T:
453 value = getattr(options, name, None)
454 return default if value is None else value
455
Gilles Peskine0298bda2021-03-10 02:34:37 +0100456 def filename_for(self, basename: str) -> str:
457 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200458 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100459
Gilles Peskineb94ea512021-03-10 02:12:08 +0100460 def write_test_data_file(self, basename: str,
461 test_cases: Iterable[test_case.TestCase]) -> None:
462 """Write the test cases to a .data file.
463
464 The output file is ``basename + '.data'`` in the test suite directory.
465 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100466 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100467 test_case.write_data_file(filename, test_cases)
468
Gilles Peskine0298bda2021-03-10 02:34:37 +0100469 TARGETS = {
470 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100471 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100472 'test_suite_psa_crypto_storage_format.current':
473 lambda info: StorageFormat(info, 0, True).all_test_cases(),
474 'test_suite_psa_crypto_storage_format.v0':
475 lambda info: StorageFormat(info, 0, False).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100476 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
477
478 def generate_target(self, name: str) -> None:
479 test_cases = self.TARGETS[name](self.info)
480 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100481
Gilles Peskine09940492021-01-26 22:16:30 +0100482def main(args):
483 """Command line entry point."""
484 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100485 parser.add_argument('--list', action='store_true',
486 help='List available targets and exit')
487 parser.add_argument('targets', nargs='*', metavar='TARGET',
488 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100489 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200490 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100491 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100492 if options.list:
493 for name in sorted(generator.TARGETS):
494 print(generator.filename_for(name))
495 return
496 if options.targets:
497 # Allow "-" as a special case so you can run
498 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
499 # ``$targets`` is empty or not.
500 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
501 for target in options.targets
502 if target != '-']
503 else:
504 options.targets = sorted(generator.TARGETS)
505 for target in options.targets:
506 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100507
508if __name__ == '__main__':
509 main(sys.argv[1:])