blob: d1d8abcb2cfa9b2e60e44359ef29cb362e3f9c7e [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 Peskinef8b6b502022-03-15 17:26:33 +010024import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010025import os
26import 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 Peskine14e428f2021-01-26 22:19:21 +010031from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010032from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010033from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010034from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010035
36T = TypeVar('T') #pylint: disable=invalid-name
37
Gilles Peskine14e428f2021-01-26 22:19:21 +010038
Gilles Peskine7f756872021-02-16 12:13:12 +010039def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010040 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
41 if name.startswith('PSA_'):
42 return name[:4] + 'WANT_' + name[4:]
43 else:
44 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
45
Gilles Peskine7f756872021-02-16 12:13:12 +010046def finish_family_dependency(dep: str, bits: int) -> str:
47 """Finish dep if it's a family dependency symbol prefix.
48
49 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
50 qualified by the key size. If dep is such a symbol, finish it by adjusting
51 the prefix and appending the key size. Other symbols are left unchanged.
52 """
53 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
54
55def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
56 """Finish any family dependency symbol prefixes.
57
58 Apply `finish_family_dependency` to each element of `dependencies`.
59 """
60 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010061
Gilles Peskine8a55b432021-04-20 23:23:45 +020062SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
63 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
64 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
65 'PSA_ALG_ANY_HASH', # only in policies
66 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
67 'PSA_ALG_KEY_AGREEMENT', # chaining
68 'PSA_ALG_TRUNCATED_MAC', # modifier
69])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010070def automatic_dependencies(*expressions: str) -> List[str]:
71 """Infer dependencies of a test case by looking for PSA_xxx symbols.
72
73 The arguments are strings which should be C expressions. Do not use
74 string literals or comments as this function is not smart enough to
75 skip them.
76 """
77 used = set()
78 for expr in expressions:
79 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskine8a55b432021-04-20 23:23:45 +020080 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010081 return sorted(psa_want_symbol(name) for name in used)
82
Gilles Peskined169d602021-02-16 14:16:25 +010083# A temporary hack: at the time of writing, not all dependency symbols
84# are implemented yet. Skip test cases for which the dependency symbols are
85# not available. Once all dependency symbols are available, this hack must
86# be removed so that a bug in the dependency symbols proprely leads to a test
87# failure.
88def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
89 return frozenset(symbol
90 for line in open(filename)
91 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010092_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010093def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010094 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
Przemyslaw Stekiel08101082021-10-22 10:39:56 +020095 if _implemented_dependencies is None:
96 _implemented_dependencies = \
97 read_implemented_dependencies('include/psa/crypto_config.h')
98 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010099 for dep in dependencies):
100 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
101
Gilles Peskine14e428f2021-01-26 22:19:21 +0100102
Gilles Peskineb94ea512021-03-10 02:12:08 +0100103class Information:
104 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100105
Gilles Peskineb94ea512021-03-10 02:12:08 +0100106 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100107 self.constructors = self.read_psa_interface()
108
109 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100110 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200111 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100112 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200113 # Mbed TLS doesn't support finite-field DH yet and will not support
114 # finite-field DSA. Don't attempt to generate any related test case.
115 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
116 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100117 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
118 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100119
Gilles Peskineb93f8542021-04-19 13:50:25 +0200120 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100121 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200122 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100123 header_file_names = ['include/psa/crypto_values.h',
124 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200125 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100126 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200127 constructors.parse_header(header_file_name)
128 for test_cases in test_suites:
129 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100130 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200131 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100132 return constructors
133
Gilles Peskine14e428f2021-01-26 22:19:21 +0100134
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200135def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136 verb: str, key_type: str, bits: int,
137 dependencies: List[str],
138 *args: str,
139 param_descr: str = ''
140) -> test_case.TestCase:
141 """Return one test case exercising a key creation method
142 for an unsupported key type or size.
143 """
144 hack_dependencies_not_implemented(dependencies)
145 tc = test_case.TestCase()
146 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
147 adverb = 'not' if dependencies else 'never'
148 if param_descr:
149 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200150 tc.set_description('PSA {} {} {}-bit {} supported'
151 .format(verb, short_key_type, bits, adverb))
152 tc.set_dependencies(dependencies)
153 tc.set_function(verb + '_not_supported')
154 tc.set_arguments([key_type] + list(args))
155 return tc
156
Gilles Peskineb94ea512021-03-10 02:12:08 +0100157class NotSupported:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200158 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100159
160 def __init__(self, info: Information) -> None:
161 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100162
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100163 ALWAYS_SUPPORTED = frozenset([
164 'PSA_KEY_TYPE_DERIVE',
165 'PSA_KEY_TYPE_RAW_DATA',
166 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100167 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100168 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100169 kt: crypto_knowledge.KeyType,
170 param: Optional[int] = None,
171 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100172 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200173 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100174
175 If param is present and not None, emit test cases conditioned on this
176 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200177 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100178 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100179 if kt.name in self.ALWAYS_SUPPORTED:
180 # Don't generate test cases for key types that are always supported.
181 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100182 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100183 import_dependencies = [('!' if param is None else '') +
184 psa_want_symbol(kt.name)]
185 if kt.params is not None:
186 import_dependencies += [('!' if param == i else '') +
187 psa_want_symbol(sym)
188 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100189 if kt.name.endswith('_PUBLIC_KEY'):
190 generate_dependencies = []
191 else:
192 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100193 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200194 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100195 'import', kt.expression, bits,
196 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 test_case.hex_string(kt.key_material(bits)),
198 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100199 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100200 if not generate_dependencies and param is not None:
201 # If generation is impossible for this key type, rather than
202 # supported or not depending on implementation capabilities,
203 # only generate the test case once.
204 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100205 # For public key we expect that key generation fails with
206 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200207 if not kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200208 yield test_case_for_key_type_not_supported(
209 'generate', kt.expression, bits,
210 finish_family_dependencies(generate_dependencies, bits),
211 str(bits),
212 param_descr=param_descr,
213 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100215
Gilles Peskineb93f8542021-04-19 13:50:25 +0200216 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
217 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
218
Gilles Peskine3d778392021-02-17 15:11:05 +0100219 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100220 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200222 if key_type in self.ECC_KEY_TYPES:
223 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100224 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100225 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200227 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 kt = crypto_knowledge.KeyType(constr, [curve_family])
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, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100231 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100232 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100233
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200234def test_case_for_key_generation(
235 key_type: str, bits: int,
236 dependencies: List[str],
237 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200238 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200239) -> test_case.TestCase:
240 """Return one test case exercising a key generation.
241 """
242 hack_dependencies_not_implemented(dependencies)
243 tc = test_case.TestCase()
244 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
245 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200246 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200247 tc.set_dependencies(dependencies)
248 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100249 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200250
251 return tc
252
253class KeyGenerate:
254 """Generate positive and negative (invalid argument) test cases for key generation."""
255
256 def __init__(self, info: Information) -> None:
257 self.constructors = info.constructors
258
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200259 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
260 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
261
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100262 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200263 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200264 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200265 ) -> Iterator[test_case.TestCase]:
266 """Return test cases exercising key generation.
267
268 All key types can be generated except for public keys. For public key
269 PSA_ERROR_INVALID_ARGUMENT status is expected.
270 """
271 result = 'PSA_SUCCESS'
272
273 import_dependencies = [psa_want_symbol(kt.name)]
274 if kt.params is not None:
275 import_dependencies += [psa_want_symbol(sym)
276 for i, sym in enumerate(kt.params)]
277 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100278 # The library checks whether the key type is a public key generically,
279 # before it reaches a point where it needs support for the specific key
280 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200281 generate_dependencies = []
282 result = 'PSA_ERROR_INVALID_ARGUMENT'
283 else:
284 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100285 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200286 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200287 for bits in kt.sizes_to_test():
288 yield test_case_for_key_generation(
289 kt.expression, bits,
290 finish_family_dependencies(generate_dependencies, bits),
291 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200292 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200293 )
294
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200295 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
296 """Generate test cases that exercise the generation of keys."""
297 for key_type in sorted(self.constructors.key_types):
298 if key_type in self.ECC_KEY_TYPES:
299 continue
300 kt = crypto_knowledge.KeyType(key_type)
301 yield from self.test_cases_for_key_type_key_generation(kt)
302 for curve_family in sorted(self.constructors.ecc_curves):
303 for constr in self.ECC_KEY_TYPES:
304 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200305 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200306
Gilles Peskinec05158b2021-04-27 20:40:10 +0200307class OpFail:
308 """Generate test cases for operations that must fail."""
309 #pylint: disable=too-few-public-methods
310
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100311 class Reason(enum.Enum):
312 NOT_SUPPORTED = 0
313 INVALID = 1
314 INCOMPATIBLE = 2
315
Gilles Peskinec05158b2021-04-27 20:40:10 +0200316 def __init__(self, info: Information) -> None:
317 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100318 key_type_expressions = self.constructors.generate_expressions(
319 sorted(self.constructors.key_types)
320 )
321 self.key_types = [crypto_knowledge.KeyType(kt_expr)
322 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200323
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100324 def make_test_case(
325 self,
326 alg: crypto_knowledge.Algorithm,
327 category: crypto_knowledge.AlgorithmCategory,
328 reason: 'Reason',
329 kt: Optional[crypto_knowledge.KeyType] = None,
330 not_deps: FrozenSet[str] = frozenset(),
331 ) -> test_case.TestCase:
332 """Construct a failure test case for a one-key or keyless operation."""
333 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200334 tc = test_case.TestCase()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100335 pretty_alg = re.sub(r'PSA_ALG_', r'', alg.expression)
Gilles Peskined0964452021-04-29 21:35:03 +0200336 if reason == self.Reason.NOT_SUPPORTED:
337 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
338 for dep in not_deps]
339 pretty_reason = '!' + '&'.join(sorted(short_deps))
340 else:
341 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100342 if kt:
343 key_type = kt.expression
344 pretty_type = re.sub(r'PSA_KEY_TYPE_', r'', key_type)
Gilles Peskinea2180472021-04-27 21:03:43 +0200345 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100346 key_type = ''
347 pretty_type = ''
348 tc.set_description('PSA {} {}: {}{}'
349 .format(category.name.lower(),
350 pretty_alg,
351 pretty_reason,
352 ' with ' + pretty_type if pretty_type else ''))
353 dependencies = automatic_dependencies(alg.base_expression, key_type)
354 for i, dep in enumerate(dependencies):
355 if dep in not_deps:
356 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200357 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100358 tc.set_function(category.name.lower() + '_fail')
359 arguments = []
360 if kt:
361 key_material = kt.key_material(kt.sizes_to_test()[0])
362 arguments += [key_type, test_case.hex_string(key_material)]
363 arguments.append(alg.expression)
364 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
365 'INVALID_ARGUMENT')
366 arguments.append('PSA_ERROR_' + error)
367 tc.set_arguments(arguments)
368 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200369
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100370 def no_key_test_cases(
371 self,
372 alg: crypto_knowledge.Algorithm,
373 category: crypto_knowledge.AlgorithmCategory,
374 ) -> Iterator[test_case.TestCase]:
375 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200376 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100377 # Compatible operation, unsupported algorithm
378 for dep in automatic_dependencies(alg.base_expression):
379 yield self.make_test_case(alg, category,
380 self.Reason.NOT_SUPPORTED,
381 not_deps=frozenset([dep]))
382 else:
383 # Incompatible operation, supported algorithm
384 yield self.make_test_case(alg, category, self.Reason.INVALID)
385
386 def one_key_test_cases(
387 self,
388 alg: crypto_knowledge.Algorithm,
389 category: crypto_knowledge.AlgorithmCategory,
390 ) -> Iterator[test_case.TestCase]:
391 """Generate failure test cases for one-key operations with the specified algorithm."""
392 for kt in self.key_types:
393 key_is_compatible = kt.can_do(alg)
394 # To do: public key for a private key operation
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200395 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100396 # Compatible key and operation, unsupported algorithm
397 for dep in automatic_dependencies(alg.base_expression):
398 yield self.make_test_case(alg, category,
399 self.Reason.NOT_SUPPORTED,
400 kt=kt, not_deps=frozenset([dep]))
401 elif key_is_compatible:
402 # Compatible key, incompatible operation, supported algorithm
403 yield self.make_test_case(alg, category,
404 self.Reason.INVALID,
405 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200406 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100407 # Incompatible key, compatible operation, supported algorithm
408 yield self.make_test_case(alg, category,
409 self.Reason.INCOMPATIBLE,
410 kt=kt)
411 else:
412 # Incompatible key and operation. Don't test cases where
413 # multiple things are wrong, to keep the number of test
414 # cases reasonable.
415 pass
416
417 def test_cases_for_algorithm(
418 self,
419 alg: crypto_knowledge.Algorithm,
420 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200421 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100422 for category in crypto_knowledge.AlgorithmCategory:
423 if category == crypto_knowledge.AlgorithmCategory.PAKE:
424 # PAKE operations are not implemented yet
425 pass
426 elif category.requires_key():
427 yield from self.one_key_test_cases(alg, category)
428 else:
429 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200430
Gilles Peskinec05158b2021-04-27 20:40:10 +0200431 def all_test_cases(self) -> Iterator[test_case.TestCase]:
432 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200433 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100434 for expr in self.constructors.generate_expressions(algorithms):
435 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200436 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200437
438
Gilles Peskine897dff92021-03-10 15:03:44 +0100439class StorageKey(psa_storage.Key):
440 """Representation of a key for storage format testing."""
441
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200442 IMPLICIT_USAGE_FLAGS = {
443 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
444 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
445 } #type: Dict[str, str]
446 """Mapping of usage flags to the flags that they imply."""
447
448 def __init__(
449 self,
450 usage: str,
451 without_implicit_usage: Optional[bool] = False,
452 **kwargs
453 ) -> None:
454 """Prepare to generate a key.
455
456 * `usage` : The usage flags used for the key.
457 * `without_implicit_usage`: Flag to defide to apply the usage extension
458 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200459 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200460
461 if not without_implicit_usage:
462 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
463 if self.usage.value() & psa_storage.Expr(flag).value() and \
464 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
465 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
466
467class StorageTestData(StorageKey):
468 """Representation of test case data for storage format testing."""
469
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200470 def __init__(
471 self,
472 description: str,
473 expected_usage: Optional[str] = None,
474 **kwargs
475 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200476 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200477
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200478 * `description` : used for the the test case names
479 * `expected_usage`: the usage flags generated as the expected usage flags
480 in the test cases. CAn differ from the usage flags
481 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200482 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100483 super().__init__(**kwargs)
484 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200485 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200486
Gilles Peskine897dff92021-03-10 15:03:44 +0100487class StorageFormat:
488 """Storage format stability test cases."""
489
490 def __init__(self, info: Information, version: int, forward: bool) -> None:
491 """Prepare to generate test cases for storage format stability.
492
493 * `info`: information about the API. See the `Information` class.
494 * `version`: the storage format version to generate test cases for.
495 * `forward`: if true, generate forward compatibility test cases which
496 save a key and check that its representation is as intended. Otherwise
497 generate backward compatibility test cases which inject a key
498 representation and check that it can be read and used.
499 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200500 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
501 self.version = version #type: int
502 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100503
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200504 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100505 """Construct a storage format test case for the given key.
506
507 If ``forward`` is true, generate a forward compatibility test case:
508 create a key and validate that it has the expected representation.
509 Otherwise generate a backward compatibility test case: inject the
510 key representation into storage and validate that it can be read
511 correctly.
512 """
513 verb = 'save' if self.forward else 'read'
514 tc = test_case.TestCase()
515 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100516 dependencies = automatic_dependencies(
517 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200518 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100519 )
520 dependencies = finish_family_dependencies(dependencies, key.bits)
521 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100522 tc.set_function('key_storage_' + verb)
523 if self.forward:
524 extra_arguments = []
525 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200526 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100527 # Some test keys have the RAW_DATA type and attributes that don't
528 # necessarily make sense. We do this to validate numerical
529 # encodings of the attributes.
530 # Raw data keys have no useful exercise anyway so there is no
531 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200532 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
533 flags.append('TEST_FLAG_EXERCISE')
534 if 'READ_ONLY' in key.lifetime.string:
535 flags.append('TEST_FLAG_READ_ONLY')
536 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100537 tc.set_arguments([key.lifetime.string,
538 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200539 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100540 '"' + key.material.hex() + '"',
541 '"' + key.hex() + '"',
542 *extra_arguments])
543 return tc
544
Gilles Peskineefb584d2021-04-21 22:05:34 +0200545 def key_for_lifetime(
546 self,
547 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200548 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200549 """Construct a test key for the given lifetime."""
550 short = lifetime
551 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
552 r'', short)
553 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
554 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200555 key = StorageTestData(version=self.version,
556 id=1, lifetime=lifetime,
557 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
558 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
559 material=b'L',
560 description=description)
561 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200562
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200563 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200564 """Generate test keys covering lifetimes."""
565 lifetimes = sorted(self.constructors.lifetimes)
566 expressions = self.constructors.generate_expressions(lifetimes)
567 for lifetime in expressions:
568 # Don't attempt to create or load a volatile key in storage
569 if 'VOLATILE' in lifetime:
570 continue
571 # Don't attempt to create a read-only key in storage,
572 # but do attempt to load one.
573 if 'READ_ONLY' in lifetime and self.forward:
574 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200575 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200576
Gilles Peskinea296e482022-02-24 18:58:08 +0100577 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100578 self,
579 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200580 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100581 test_implicit_usage: Optional[bool] = True
582 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100583 """Construct a test key for the given key usage."""
584 usage = ' | '.join(usage_flags) if usage_flags else '0'
585 if short is None:
586 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
Gilles Peskinea296e482022-02-24 18:58:08 +0100587 extra_desc = ' without implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200588 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200589 key1 = StorageTestData(version=self.version,
590 id=1, lifetime=0x00000001,
591 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
592 expected_usage=usage,
Gilles Peskinea296e482022-02-24 18:58:08 +0100593 without_implicit_usage=not test_implicit_usage,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200594 usage=usage, alg=0, alg2=0,
595 material=b'K',
596 description=description)
Gilles Peskinea296e482022-02-24 18:58:08 +0100597 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100598
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200599 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100600 """Generate test keys covering usage flags."""
601 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100602 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200603 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100604 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200605 for flag1, flag2 in zip(known_flags,
606 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100607 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200608
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200609 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200610 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100611 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200612
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200613 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200614 yield from self.generate_keys_for_usage_flags()
615 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100616
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100617 def keys_for_type(
618 self,
619 key_type: str,
620 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200621 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100622 """Generate test keys for the given key type.
623
624 For key types that depend on a parameter (e.g. elliptic curve family),
625 `param` is the parameter to pass to the constructor. Only a single
626 parameter is supported.
627 """
628 kt = crypto_knowledge.KeyType(key_type, params)
629 for bits in kt.sizes_to_test():
630 usage_flags = 'PSA_KEY_USAGE_EXPORT'
631 alg = 0
632 alg2 = 0
633 key_material = kt.key_material(bits)
634 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
635 r'',
636 kt.expression)
637 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200638 key = StorageTestData(version=self.version,
639 id=1, lifetime=0x00000001,
640 type=kt.expression, bits=bits,
641 usage=usage_flags, alg=alg, alg2=alg2,
642 material=key_material,
643 description=description)
644 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100645
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200646 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100647 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200648 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200649 for key_type in self.constructors.generate_expressions(key_types):
650 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100651
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200652 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100653 """Generate test keys for the specified algorithm."""
654 # For now, we don't have information on the compatibility of key
655 # types and algorithms. So we just test the encoding of algorithms,
656 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200657 descr = re.sub(r'PSA_ALG_', r'', alg)
658 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100659 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200660 key1 = StorageTestData(version=self.version,
661 id=1, lifetime=0x00000001,
662 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
663 usage=usage, alg=alg, alg2=0,
664 material=b'K',
665 description='alg: ' + descr)
666 yield key1
667 key2 = StorageTestData(version=self.version,
668 id=1, lifetime=0x00000001,
669 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
670 usage=usage, alg=0, alg2=alg,
671 material=b'L',
672 description='alg2: ' + descr)
673 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100674
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200675 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100676 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200677 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200678 for alg in self.constructors.generate_expressions(algorithms):
679 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100680
gabor-mezei-armea840de2021-06-29 15:42:57 +0200681 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200682 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200683 yield from self.all_keys_for_lifetimes()
684 yield from self.all_keys_for_usage_flags()
685 yield from self.all_keys_for_types()
686 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200687
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200688 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100689 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200690 # First build a list of all keys, then construct all the corresponding
691 # test cases. This allows all required information to be obtained in
692 # one go, which is a significant performance gain as the information
693 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200694 all_keys = list(self.generate_all_keys())
695 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200696 if key.location_value() != 0:
697 # Skip keys with a non-default location, because they
698 # require a driver and we currently have no mechanism to
699 # determine whether a driver is available.
700 continue
701 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100702
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200703class StorageFormatForward(StorageFormat):
704 """Storage format stability test cases for forward compatibility."""
705
706 def __init__(self, info: Information, version: int) -> None:
707 super().__init__(info, version, True)
708
709class StorageFormatV0(StorageFormat):
710 """Storage format stability test cases for version 0 compatibility."""
711
712 def __init__(self, info: Information) -> None:
713 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100714
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200715 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200716 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100717 yield from super().all_keys_for_usage_flags()
718 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200719
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200720 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200721 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200722 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200723 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200724 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200725 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200726 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200727 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200728 algorithm and key type combination.
729 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200730 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200731 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200732 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200733 material_usage_flags = usage_flags + ' | ' + implyer_usage
734 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200735 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200736 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200737 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200738 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
739 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
740 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
741 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200742 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200743 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200744 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200745 key = StorageTestData(version=self.version,
746 id=1, lifetime=0x00000001,
747 type=key_type.expression, bits=bits,
748 usage=material_usage_flags,
749 expected_usage=expected_usage_flags,
750 without_implicit_usage=True,
751 alg=alg, alg2=alg2,
752 material=key_material,
753 description=description)
754 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200755
756 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200757 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200758 """Match possible key types for sign algorithms."""
759 # To create a valid combinaton both the algorithms and key types
760 # must be filtered. Pair them with keywords created from its names.
761 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
762 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
763 keyword_translation = {
764 'ECDSA': 'ECC',
765 'ED[0-9]*.*' : 'EDWARDS'
766 }
767 exclusive_keywords = {
768 'EDWARDS': 'ECC'
769 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200770 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
771 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200772 alg_with_keys = {} #type: Dict[str, List[str]]
773 translation_table = str.maketrans('(', '_', ')')
774 for alg in algorithms:
775 # Generate keywords from the name of the algorithm
776 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
777 # Translate keywords for better matching with the key types
778 for keyword in alg_keywords.copy():
779 for pattern, replace in keyword_translation.items():
780 if re.match(pattern, keyword):
781 alg_keywords.remove(keyword)
782 alg_keywords.add(replace)
783 # Filter out incompatible algortihms
784 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
785 continue
786
787 for key_type in key_types:
788 # Generate keywords from the of the key type
789 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
790
791 # Remove ambigious keywords
792 for keyword1, keyword2 in exclusive_keywords.items():
793 if keyword1 in key_type_keywords:
794 key_type_keywords.remove(keyword2)
795
796 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
797 not key_type_keywords.isdisjoint(alg_keywords):
798 if alg in alg_with_keys:
799 alg_with_keys[alg].append(key_type)
800 else:
801 alg_with_keys[alg] = [key_type]
802 return alg_with_keys
803
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200804 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200805 """Generate test keys for usage flag extensions."""
806 # Generate a key type and algorithm pair for each extendable usage
807 # flag to generate a valid key for exercising. The key is generated
808 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200809 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200810
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200811 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
812 for alg in sorted(alg_with_keys):
813 for key_type in sorted(alg_with_keys[alg]):
814 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200815 kt = crypto_knowledge.KeyType(key_type)
816 if kt.is_valid_for_signature(usage):
817 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200818
gabor-mezei-armea840de2021-06-29 15:42:57 +0200819 def generate_all_keys(self) -> Iterator[StorageTestData]:
820 yield from super().generate_all_keys()
821 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200822
Gilles Peskineb94ea512021-03-10 02:12:08 +0100823class TestGenerator:
824 """Generate test data."""
825
826 def __init__(self, options) -> None:
827 self.test_suite_directory = self.get_option(options, 'directory',
828 'tests/suites')
829 self.info = Information()
830
831 @staticmethod
832 def get_option(options, name: str, default: T) -> T:
833 value = getattr(options, name, None)
834 return default if value is None else value
835
Gilles Peskine0298bda2021-03-10 02:34:37 +0100836 def filename_for(self, basename: str) -> str:
837 """The location of the data file with the specified base name."""
838 return os.path.join(self.test_suite_directory, basename + '.data')
839
Gilles Peskineb94ea512021-03-10 02:12:08 +0100840 def write_test_data_file(self, basename: str,
841 test_cases: Iterable[test_case.TestCase]) -> None:
842 """Write the test cases to a .data file.
843
844 The output file is ``basename + '.data'`` in the test suite directory.
845 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100846 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100847 test_case.write_data_file(filename, test_cases)
848
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200849 # Note that targets whose name containns 'test_format' have their content
850 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100851 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200852 'test_suite_psa_crypto_generate_key.generated':
853 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100854 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100855 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200856 'test_suite_psa_crypto_op_fail.generated':
857 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100858 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200859 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100860 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200861 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100862 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
863
864 def generate_target(self, name: str) -> None:
865 test_cases = self.TARGETS[name](self.info)
866 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100867
Gilles Peskine09940492021-01-26 22:16:30 +0100868def main(args):
869 """Command line entry point."""
870 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100871 parser.add_argument('--list', action='store_true',
872 help='List available targets and exit')
873 parser.add_argument('targets', nargs='*', metavar='TARGET',
874 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100875 options = parser.parse_args(args)
876 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100877 if options.list:
878 for name in sorted(generator.TARGETS):
879 print(generator.filename_for(name))
880 return
881 if options.targets:
882 # Allow "-" as a special case so you can run
883 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
884 # ``$targets`` is empty or not.
885 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
886 for target in options.targets
887 if target != '-']
888 else:
889 options.targets = sorted(generator.TARGETS)
890 for target in options.targets:
891 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100892
893if __name__ == '__main__':
894 main(sys.argv[1:])