blob: b7495fb5c959d794a822ec12ded722a2fce5b9cd [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))
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010091_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010092def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010093 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
Przemyslaw Stekiel08101082021-10-22 10:39:56 +020094 if _implemented_dependencies is None:
95 _implemented_dependencies = \
96 read_implemented_dependencies('include/psa/crypto_config.h')
97 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010098 for dep in dependencies):
99 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
100
Gilles Peskine14e428f2021-01-26 22:19:21 +0100101
Gilles Peskineb94ea512021-03-10 02:12:08 +0100102class Information:
103 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100104
Gilles Peskineb94ea512021-03-10 02:12:08 +0100105 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100106 self.constructors = self.read_psa_interface()
107
108 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100109 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200110 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100111 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200112 # Mbed TLS doesn't support finite-field DH yet and will not support
113 # finite-field DSA. Don't attempt to generate any related test case.
114 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
115 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100116 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
117 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100118
Gilles Peskineb93f8542021-04-19 13:50:25 +0200119 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100120 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200121 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100122 header_file_names = ['include/psa/crypto_values.h',
123 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200124 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100125 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200126 constructors.parse_header(header_file_name)
127 for test_cases in test_suites:
128 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100129 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200130 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100131 return constructors
132
Gilles Peskine14e428f2021-01-26 22:19:21 +0100133
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200134def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100135 verb: str, key_type: str, bits: int,
136 dependencies: List[str],
137 *args: str,
138 param_descr: str = ''
139) -> test_case.TestCase:
140 """Return one test case exercising a key creation method
141 for an unsupported key type or size.
142 """
143 hack_dependencies_not_implemented(dependencies)
144 tc = test_case.TestCase()
145 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
146 adverb = 'not' if dependencies else 'never'
147 if param_descr:
148 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200149 tc.set_description('PSA {} {} {}-bit {} supported'
150 .format(verb, short_key_type, bits, adverb))
151 tc.set_dependencies(dependencies)
152 tc.set_function(verb + '_not_supported')
153 tc.set_arguments([key_type] + list(args))
154 return tc
155
Gilles Peskineb94ea512021-03-10 02:12:08 +0100156class NotSupported:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200157 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100158
159 def __init__(self, info: Information) -> None:
160 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100161
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100162 ALWAYS_SUPPORTED = frozenset([
163 'PSA_KEY_TYPE_DERIVE',
164 'PSA_KEY_TYPE_RAW_DATA',
165 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100166 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100167 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100168 kt: crypto_knowledge.KeyType,
169 param: Optional[int] = None,
170 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100171 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200172 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100173
174 If param is present and not None, emit test cases conditioned on this
175 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200176 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100177 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100178 if kt.name in self.ALWAYS_SUPPORTED:
179 # Don't generate test cases for key types that are always supported.
180 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100181 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100182 import_dependencies = [('!' if param is None else '') +
183 psa_want_symbol(kt.name)]
184 if kt.params is not None:
185 import_dependencies += [('!' if param == i else '') +
186 psa_want_symbol(sym)
187 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100188 if kt.name.endswith('_PUBLIC_KEY'):
189 generate_dependencies = []
190 else:
191 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100192 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200193 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100194 'import', kt.expression, bits,
195 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100196 test_case.hex_string(kt.key_material(bits)),
197 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100198 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100199 if not generate_dependencies and param is not None:
200 # If generation is impossible for this key type, rather than
201 # supported or not depending on implementation capabilities,
202 # only generate the test case once.
203 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100204 # For public key we expect that key generation fails with
205 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200206 if not kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200207 yield test_case_for_key_type_not_supported(
208 'generate', kt.expression, bits,
209 finish_family_dependencies(generate_dependencies, bits),
210 str(bits),
211 param_descr=param_descr,
212 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214
Gilles Peskineb93f8542021-04-19 13:50:25 +0200215 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
216 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
217
Gilles Peskine3d778392021-02-17 15:11:05 +0100218 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100220 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200221 if key_type in self.ECC_KEY_TYPES:
222 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100223 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100224 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200226 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 kt = crypto_knowledge.KeyType(constr, [curve_family])
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, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100230 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100231 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100232
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200233def test_case_for_key_generation(
234 key_type: str, bits: int,
235 dependencies: List[str],
236 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200237 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200238) -> test_case.TestCase:
239 """Return one test case exercising a key generation.
240 """
241 hack_dependencies_not_implemented(dependencies)
242 tc = test_case.TestCase()
243 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
244 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200245 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200246 tc.set_dependencies(dependencies)
247 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100248 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200249
250 return tc
251
252class KeyGenerate:
253 """Generate positive and negative (invalid argument) test cases for key generation."""
254
255 def __init__(self, info: Information) -> None:
256 self.constructors = info.constructors
257
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200258 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
259 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
260
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100261 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200262 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200263 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200264 ) -> Iterator[test_case.TestCase]:
265 """Return test cases exercising key generation.
266
267 All key types can be generated except for public keys. For public key
268 PSA_ERROR_INVALID_ARGUMENT status is expected.
269 """
270 result = 'PSA_SUCCESS'
271
272 import_dependencies = [psa_want_symbol(kt.name)]
273 if kt.params is not None:
274 import_dependencies += [psa_want_symbol(sym)
275 for i, sym in enumerate(kt.params)]
276 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100277 # The library checks whether the key type is a public key generically,
278 # before it reaches a point where it needs support for the specific key
279 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200280 generate_dependencies = []
281 result = 'PSA_ERROR_INVALID_ARGUMENT'
282 else:
283 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100284 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200285 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200286 for bits in kt.sizes_to_test():
287 yield test_case_for_key_generation(
288 kt.expression, bits,
289 finish_family_dependencies(generate_dependencies, bits),
290 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200291 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200292 )
293
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200294 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
295 """Generate test cases that exercise the generation of keys."""
296 for key_type in sorted(self.constructors.key_types):
297 if key_type in self.ECC_KEY_TYPES:
298 continue
299 kt = crypto_knowledge.KeyType(key_type)
300 yield from self.test_cases_for_key_type_key_generation(kt)
301 for curve_family in sorted(self.constructors.ecc_curves):
302 for constr in self.ECC_KEY_TYPES:
303 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200304 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200305
Gilles Peskinec05158b2021-04-27 20:40:10 +0200306class OpFail:
307 """Generate test cases for operations that must fail."""
308 #pylint: disable=too-few-public-methods
309
310 def __init__(self, info: Information) -> None:
311 self.constructors = info.constructors
312
Gilles Peskinea2180472021-04-27 21:03:43 +0200313 @staticmethod
314 def hash_test_cases(alg: str) -> Iterator[test_case.TestCase]:
315 """Generate hash failure test cases for the specified algorithm."""
316 tc = test_case.TestCase()
317 is_hash = (alg.startswith('PSA_ALG_SHA') or
318 alg.startswith('PSA_ALG_MD') or
319 alg in frozenset(['PSA_ALG_RIPEMD160', 'PSA_ALG_ANY_HASH']))
320 if is_hash:
321 descr = 'not supported'
322 status = 'PSA_ERROR_NOT_SUPPORTED'
323 dependencies = ['!PSA_WANT_' + alg[4:]]
324 else:
325 descr = 'invalid'
326 status = 'PSA_ERROR_INVALID_ARGUMENT'
327 dependencies = automatic_dependencies(alg)
328 tc.set_description('PSA hash {}: {}'
329 .format(descr, re.sub(r'PSA_ALG_', r'', alg)))
330 tc.set_dependencies(dependencies)
331 tc.set_function('hash_fail')
332 tc.set_arguments([alg, status])
333 yield tc
334
335 def test_cases_for_algorithm(self, alg: str) -> Iterator[test_case.TestCase]:
336 """Generate operation failure test cases for the specified algorithm."""
337 yield from self.hash_test_cases(alg)
338
Gilles Peskinec05158b2021-04-27 20:40:10 +0200339 def all_test_cases(self) -> Iterator[test_case.TestCase]:
340 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200341 algorithms = sorted(self.constructors.algorithms)
342 for alg in self.constructors.generate_expressions(algorithms):
343 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200344
345
Gilles Peskine897dff92021-03-10 15:03:44 +0100346class StorageKey(psa_storage.Key):
347 """Representation of a key for storage format testing."""
348
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200349 IMPLICIT_USAGE_FLAGS = {
350 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
351 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
352 } #type: Dict[str, str]
353 """Mapping of usage flags to the flags that they imply."""
354
355 def __init__(
356 self,
357 usage: str,
358 without_implicit_usage: Optional[bool] = False,
359 **kwargs
360 ) -> None:
361 """Prepare to generate a key.
362
363 * `usage` : The usage flags used for the key.
364 * `without_implicit_usage`: Flag to defide to apply the usage extension
365 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200366 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200367
368 if not without_implicit_usage:
369 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
370 if self.usage.value() & psa_storage.Expr(flag).value() and \
371 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
372 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
373
374class StorageTestData(StorageKey):
375 """Representation of test case data for storage format testing."""
376
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200377 def __init__(
378 self,
379 description: str,
380 expected_usage: Optional[str] = None,
381 **kwargs
382 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200383 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200384
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200385 * `description` : used for the the test case names
386 * `expected_usage`: the usage flags generated as the expected usage flags
387 in the test cases. CAn differ from the usage flags
388 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200389 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100390 super().__init__(**kwargs)
391 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200392 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200393
Gilles Peskine897dff92021-03-10 15:03:44 +0100394class StorageFormat:
395 """Storage format stability test cases."""
396
397 def __init__(self, info: Information, version: int, forward: bool) -> None:
398 """Prepare to generate test cases for storage format stability.
399
400 * `info`: information about the API. See the `Information` class.
401 * `version`: the storage format version to generate test cases for.
402 * `forward`: if true, generate forward compatibility test cases which
403 save a key and check that its representation is as intended. Otherwise
404 generate backward compatibility test cases which inject a key
405 representation and check that it can be read and used.
406 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200407 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
408 self.version = version #type: int
409 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100410
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200411 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100412 """Construct a storage format test case for the given key.
413
414 If ``forward`` is true, generate a forward compatibility test case:
415 create a key and validate that it has the expected representation.
416 Otherwise generate a backward compatibility test case: inject the
417 key representation into storage and validate that it can be read
418 correctly.
419 """
420 verb = 'save' if self.forward else 'read'
421 tc = test_case.TestCase()
422 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100423 dependencies = automatic_dependencies(
424 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200425 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100426 )
427 dependencies = finish_family_dependencies(dependencies, key.bits)
428 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100429 tc.set_function('key_storage_' + verb)
430 if self.forward:
431 extra_arguments = []
432 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200433 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100434 # Some test keys have the RAW_DATA type and attributes that don't
435 # necessarily make sense. We do this to validate numerical
436 # encodings of the attributes.
437 # Raw data keys have no useful exercise anyway so there is no
438 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200439 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
440 flags.append('TEST_FLAG_EXERCISE')
441 if 'READ_ONLY' in key.lifetime.string:
442 flags.append('TEST_FLAG_READ_ONLY')
443 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100444 tc.set_arguments([key.lifetime.string,
445 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200446 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100447 '"' + key.material.hex() + '"',
448 '"' + key.hex() + '"',
449 *extra_arguments])
450 return tc
451
Gilles Peskineefb584d2021-04-21 22:05:34 +0200452 def key_for_lifetime(
453 self,
454 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200455 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200456 """Construct a test key for the given lifetime."""
457 short = lifetime
458 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
459 r'', short)
460 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
461 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200462 key = StorageTestData(version=self.version,
463 id=1, lifetime=lifetime,
464 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
465 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
466 material=b'L',
467 description=description)
468 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200469
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200470 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200471 """Generate test keys covering lifetimes."""
472 lifetimes = sorted(self.constructors.lifetimes)
473 expressions = self.constructors.generate_expressions(lifetimes)
474 for lifetime in expressions:
475 # Don't attempt to create or load a volatile key in storage
476 if 'VOLATILE' in lifetime:
477 continue
478 # Don't attempt to create a read-only key in storage,
479 # but do attempt to load one.
480 if 'READ_ONLY' in lifetime and self.forward:
481 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200482 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200483
Gilles Peskinea296e482022-02-24 18:58:08 +0100484 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100485 self,
486 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200487 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100488 test_implicit_usage: Optional[bool] = True
489 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100490 """Construct a test key for the given key usage."""
491 usage = ' | '.join(usage_flags) if usage_flags else '0'
492 if short is None:
493 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
Gilles Peskinea296e482022-02-24 18:58:08 +0100494 extra_desc = ' without implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200495 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200496 key1 = StorageTestData(version=self.version,
497 id=1, lifetime=0x00000001,
498 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
499 expected_usage=usage,
Gilles Peskinea296e482022-02-24 18:58:08 +0100500 without_implicit_usage=not test_implicit_usage,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200501 usage=usage, alg=0, alg2=0,
502 material=b'K',
503 description=description)
Gilles Peskinea296e482022-02-24 18:58:08 +0100504 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100505
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200506 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100507 """Generate test keys covering usage flags."""
508 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100509 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200510 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100511 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200512 for flag1, flag2 in zip(known_flags,
513 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100514 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200515
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200516 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200517 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100518 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200519
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200520 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200521 yield from self.generate_keys_for_usage_flags()
522 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100523
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100524 def keys_for_type(
525 self,
526 key_type: str,
527 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200528 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100529 """Generate test keys for the given key type.
530
531 For key types that depend on a parameter (e.g. elliptic curve family),
532 `param` is the parameter to pass to the constructor. Only a single
533 parameter is supported.
534 """
535 kt = crypto_knowledge.KeyType(key_type, params)
536 for bits in kt.sizes_to_test():
537 usage_flags = 'PSA_KEY_USAGE_EXPORT'
538 alg = 0
539 alg2 = 0
540 key_material = kt.key_material(bits)
541 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
542 r'',
543 kt.expression)
544 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200545 key = StorageTestData(version=self.version,
546 id=1, lifetime=0x00000001,
547 type=kt.expression, bits=bits,
548 usage=usage_flags, alg=alg, alg2=alg2,
549 material=key_material,
550 description=description)
551 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100552
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200553 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100554 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200555 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200556 for key_type in self.constructors.generate_expressions(key_types):
557 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100558
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200559 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100560 """Generate test keys for the specified algorithm."""
561 # For now, we don't have information on the compatibility of key
562 # types and algorithms. So we just test the encoding of algorithms,
563 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200564 descr = re.sub(r'PSA_ALG_', r'', alg)
565 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100566 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200567 key1 = StorageTestData(version=self.version,
568 id=1, lifetime=0x00000001,
569 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
570 usage=usage, alg=alg, alg2=0,
571 material=b'K',
572 description='alg: ' + descr)
573 yield key1
574 key2 = StorageTestData(version=self.version,
575 id=1, lifetime=0x00000001,
576 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
577 usage=usage, alg=0, alg2=alg,
578 material=b'L',
579 description='alg2: ' + descr)
580 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100581
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200582 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100583 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200584 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200585 for alg in self.constructors.generate_expressions(algorithms):
586 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100587
gabor-mezei-armea840de2021-06-29 15:42:57 +0200588 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200589 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200590 yield from self.all_keys_for_lifetimes()
591 yield from self.all_keys_for_usage_flags()
592 yield from self.all_keys_for_types()
593 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200594
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200595 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100596 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200597 # First build a list of all keys, then construct all the corresponding
598 # test cases. This allows all required information to be obtained in
599 # one go, which is a significant performance gain as the information
600 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200601 all_keys = list(self.generate_all_keys())
602 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200603 if key.location_value() != 0:
604 # Skip keys with a non-default location, because they
605 # require a driver and we currently have no mechanism to
606 # determine whether a driver is available.
607 continue
608 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100609
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200610class StorageFormatForward(StorageFormat):
611 """Storage format stability test cases for forward compatibility."""
612
613 def __init__(self, info: Information, version: int) -> None:
614 super().__init__(info, version, True)
615
616class StorageFormatV0(StorageFormat):
617 """Storage format stability test cases for version 0 compatibility."""
618
619 def __init__(self, info: Information) -> None:
620 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100621
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200622 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200623 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100624 yield from super().all_keys_for_usage_flags()
625 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200626
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200627 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200628 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200629 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200630 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200631 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200632 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200633 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200634 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200635 algorithm and key type combination.
636 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200637 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200638 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200639 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200640 material_usage_flags = usage_flags + ' | ' + implyer_usage
641 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200642 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200643 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200644 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200645 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
646 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
647 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
648 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200649 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200650 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200651 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200652 key = StorageTestData(version=self.version,
653 id=1, lifetime=0x00000001,
654 type=key_type.expression, bits=bits,
655 usage=material_usage_flags,
656 expected_usage=expected_usage_flags,
657 without_implicit_usage=True,
658 alg=alg, alg2=alg2,
659 material=key_material,
660 description=description)
661 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200662
663 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200664 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200665 """Match possible key types for sign algorithms."""
666 # To create a valid combinaton both the algorithms and key types
667 # must be filtered. Pair them with keywords created from its names.
668 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
669 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
670 keyword_translation = {
671 'ECDSA': 'ECC',
672 'ED[0-9]*.*' : 'EDWARDS'
673 }
674 exclusive_keywords = {
675 'EDWARDS': 'ECC'
676 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200677 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
678 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200679 alg_with_keys = {} #type: Dict[str, List[str]]
680 translation_table = str.maketrans('(', '_', ')')
681 for alg in algorithms:
682 # Generate keywords from the name of the algorithm
683 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
684 # Translate keywords for better matching with the key types
685 for keyword in alg_keywords.copy():
686 for pattern, replace in keyword_translation.items():
687 if re.match(pattern, keyword):
688 alg_keywords.remove(keyword)
689 alg_keywords.add(replace)
690 # Filter out incompatible algortihms
691 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
692 continue
693
694 for key_type in key_types:
695 # Generate keywords from the of the key type
696 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
697
698 # Remove ambigious keywords
699 for keyword1, keyword2 in exclusive_keywords.items():
700 if keyword1 in key_type_keywords:
701 key_type_keywords.remove(keyword2)
702
703 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
704 not key_type_keywords.isdisjoint(alg_keywords):
705 if alg in alg_with_keys:
706 alg_with_keys[alg].append(key_type)
707 else:
708 alg_with_keys[alg] = [key_type]
709 return alg_with_keys
710
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200711 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200712 """Generate test keys for usage flag extensions."""
713 # Generate a key type and algorithm pair for each extendable usage
714 # flag to generate a valid key for exercising. The key is generated
715 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200716 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200717
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200718 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
719 for alg in sorted(alg_with_keys):
720 for key_type in sorted(alg_with_keys[alg]):
721 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200722 kt = crypto_knowledge.KeyType(key_type)
723 if kt.is_valid_for_signature(usage):
724 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200725
gabor-mezei-armea840de2021-06-29 15:42:57 +0200726 def generate_all_keys(self) -> Iterator[StorageTestData]:
727 yield from super().generate_all_keys()
728 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200729
Gilles Peskineb94ea512021-03-10 02:12:08 +0100730class TestGenerator:
731 """Generate test data."""
732
733 def __init__(self, options) -> None:
734 self.test_suite_directory = self.get_option(options, 'directory',
735 'tests/suites')
736 self.info = Information()
737
738 @staticmethod
739 def get_option(options, name: str, default: T) -> T:
740 value = getattr(options, name, None)
741 return default if value is None else value
742
Gilles Peskine0298bda2021-03-10 02:34:37 +0100743 def filename_for(self, basename: str) -> str:
744 """The location of the data file with the specified base name."""
745 return os.path.join(self.test_suite_directory, basename + '.data')
746
Gilles Peskineb94ea512021-03-10 02:12:08 +0100747 def write_test_data_file(self, basename: str,
748 test_cases: Iterable[test_case.TestCase]) -> None:
749 """Write the test cases to a .data file.
750
751 The output file is ``basename + '.data'`` in the test suite directory.
752 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100753 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100754 test_case.write_data_file(filename, test_cases)
755
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200756 # Note that targets whose name containns 'test_format' have their content
757 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100758 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200759 'test_suite_psa_crypto_generate_key.generated':
760 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100761 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100762 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200763 'test_suite_psa_crypto_op_fail.generated':
764 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100765 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200766 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100767 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200768 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100769 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
770
771 def generate_target(self, name: str) -> None:
772 test_cases = self.TARGETS[name](self.info)
773 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100774
Gilles Peskine09940492021-01-26 22:16:30 +0100775def main(args):
776 """Command line entry point."""
777 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100778 parser.add_argument('--list', action='store_true',
779 help='List available targets and exit')
780 parser.add_argument('targets', nargs='*', metavar='TARGET',
781 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100782 options = parser.parse_args(args)
783 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100784 if options.list:
785 for name in sorted(generator.TARGETS):
786 print(generator.filename_for(name))
787 return
788 if options.targets:
789 # Allow "-" as a special case so you can run
790 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
791 # ``$targets`` is empty or not.
792 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
793 for target in options.targets
794 if target != '-']
795 else:
796 options.targets = sorted(generator.TARGETS)
797 for target in options.targets:
798 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100799
800if __name__ == '__main__':
801 main(sys.argv[1:])