blob: 6d108e0fb156874c617359a97150529c2f8a227e [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)
336 pretty_reason = reason.name.lower()
337 if kt:
338 key_type = kt.expression
339 pretty_type = re.sub(r'PSA_KEY_TYPE_', r'', key_type)
Gilles Peskinea2180472021-04-27 21:03:43 +0200340 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100341 key_type = ''
342 pretty_type = ''
343 tc.set_description('PSA {} {}: {}{}'
344 .format(category.name.lower(),
345 pretty_alg,
346 pretty_reason,
347 ' with ' + pretty_type if pretty_type else ''))
348 dependencies = automatic_dependencies(alg.base_expression, key_type)
349 for i, dep in enumerate(dependencies):
350 if dep in not_deps:
351 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200352 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100353 tc.set_function(category.name.lower() + '_fail')
354 arguments = []
355 if kt:
356 key_material = kt.key_material(kt.sizes_to_test()[0])
357 arguments += [key_type, test_case.hex_string(key_material)]
358 arguments.append(alg.expression)
359 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
360 'INVALID_ARGUMENT')
361 arguments.append('PSA_ERROR_' + error)
362 tc.set_arguments(arguments)
363 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200364
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100365 def no_key_test_cases(
366 self,
367 alg: crypto_knowledge.Algorithm,
368 category: crypto_knowledge.AlgorithmCategory,
369 ) -> Iterator[test_case.TestCase]:
370 """Generate failure test cases for keyless operations with the specified algorithm."""
371 if category == alg.category:
372 # Compatible operation, unsupported algorithm
373 for dep in automatic_dependencies(alg.base_expression):
374 yield self.make_test_case(alg, category,
375 self.Reason.NOT_SUPPORTED,
376 not_deps=frozenset([dep]))
377 else:
378 # Incompatible operation, supported algorithm
379 yield self.make_test_case(alg, category, self.Reason.INVALID)
380
381 def one_key_test_cases(
382 self,
383 alg: crypto_knowledge.Algorithm,
384 category: crypto_knowledge.AlgorithmCategory,
385 ) -> Iterator[test_case.TestCase]:
386 """Generate failure test cases for one-key operations with the specified algorithm."""
387 for kt in self.key_types:
388 key_is_compatible = kt.can_do(alg)
389 # To do: public key for a private key operation
390 if key_is_compatible and category == alg.category:
391 # Compatible key and operation, unsupported algorithm
392 for dep in automatic_dependencies(alg.base_expression):
393 yield self.make_test_case(alg, category,
394 self.Reason.NOT_SUPPORTED,
395 kt=kt, not_deps=frozenset([dep]))
396 elif key_is_compatible:
397 # Compatible key, incompatible operation, supported algorithm
398 yield self.make_test_case(alg, category,
399 self.Reason.INVALID,
400 kt=kt)
401 elif category == alg.category:
402 # Incompatible key, compatible operation, supported algorithm
403 yield self.make_test_case(alg, category,
404 self.Reason.INCOMPATIBLE,
405 kt=kt)
406 else:
407 # Incompatible key and operation. Don't test cases where
408 # multiple things are wrong, to keep the number of test
409 # cases reasonable.
410 pass
411
412 def test_cases_for_algorithm(
413 self,
414 alg: crypto_knowledge.Algorithm,
415 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200416 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100417 for category in crypto_knowledge.AlgorithmCategory:
418 if category == crypto_knowledge.AlgorithmCategory.PAKE:
419 # PAKE operations are not implemented yet
420 pass
421 elif category.requires_key():
422 yield from self.one_key_test_cases(alg, category)
423 else:
424 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200425
Gilles Peskinec05158b2021-04-27 20:40:10 +0200426 def all_test_cases(self) -> Iterator[test_case.TestCase]:
427 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200428 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100429 for expr in self.constructors.generate_expressions(algorithms):
430 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200431 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200432
433
Gilles Peskine897dff92021-03-10 15:03:44 +0100434class StorageKey(psa_storage.Key):
435 """Representation of a key for storage format testing."""
436
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200437 IMPLICIT_USAGE_FLAGS = {
438 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
439 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
440 } #type: Dict[str, str]
441 """Mapping of usage flags to the flags that they imply."""
442
443 def __init__(
444 self,
445 usage: str,
446 without_implicit_usage: Optional[bool] = False,
447 **kwargs
448 ) -> None:
449 """Prepare to generate a key.
450
451 * `usage` : The usage flags used for the key.
452 * `without_implicit_usage`: Flag to defide to apply the usage extension
453 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200454 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200455
456 if not without_implicit_usage:
457 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
458 if self.usage.value() & psa_storage.Expr(flag).value() and \
459 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
460 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
461
462class StorageTestData(StorageKey):
463 """Representation of test case data for storage format testing."""
464
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200465 def __init__(
466 self,
467 description: str,
468 expected_usage: Optional[str] = None,
469 **kwargs
470 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200471 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200472
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200473 * `description` : used for the the test case names
474 * `expected_usage`: the usage flags generated as the expected usage flags
475 in the test cases. CAn differ from the usage flags
476 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200477 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100478 super().__init__(**kwargs)
479 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200480 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200481
Gilles Peskine897dff92021-03-10 15:03:44 +0100482class StorageFormat:
483 """Storage format stability test cases."""
484
485 def __init__(self, info: Information, version: int, forward: bool) -> None:
486 """Prepare to generate test cases for storage format stability.
487
488 * `info`: information about the API. See the `Information` class.
489 * `version`: the storage format version to generate test cases for.
490 * `forward`: if true, generate forward compatibility test cases which
491 save a key and check that its representation is as intended. Otherwise
492 generate backward compatibility test cases which inject a key
493 representation and check that it can be read and used.
494 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200495 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
496 self.version = version #type: int
497 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100498
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200499 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100500 """Construct a storage format test case for the given key.
501
502 If ``forward`` is true, generate a forward compatibility test case:
503 create a key and validate that it has the expected representation.
504 Otherwise generate a backward compatibility test case: inject the
505 key representation into storage and validate that it can be read
506 correctly.
507 """
508 verb = 'save' if self.forward else 'read'
509 tc = test_case.TestCase()
510 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100511 dependencies = automatic_dependencies(
512 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200513 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100514 )
515 dependencies = finish_family_dependencies(dependencies, key.bits)
516 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100517 tc.set_function('key_storage_' + verb)
518 if self.forward:
519 extra_arguments = []
520 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200521 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100522 # Some test keys have the RAW_DATA type and attributes that don't
523 # necessarily make sense. We do this to validate numerical
524 # encodings of the attributes.
525 # Raw data keys have no useful exercise anyway so there is no
526 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200527 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
528 flags.append('TEST_FLAG_EXERCISE')
529 if 'READ_ONLY' in key.lifetime.string:
530 flags.append('TEST_FLAG_READ_ONLY')
531 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100532 tc.set_arguments([key.lifetime.string,
533 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200534 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100535 '"' + key.material.hex() + '"',
536 '"' + key.hex() + '"',
537 *extra_arguments])
538 return tc
539
Gilles Peskineefb584d2021-04-21 22:05:34 +0200540 def key_for_lifetime(
541 self,
542 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200543 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200544 """Construct a test key for the given lifetime."""
545 short = lifetime
546 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
547 r'', short)
548 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
549 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200550 key = StorageTestData(version=self.version,
551 id=1, lifetime=lifetime,
552 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
553 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
554 material=b'L',
555 description=description)
556 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200557
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200558 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200559 """Generate test keys covering lifetimes."""
560 lifetimes = sorted(self.constructors.lifetimes)
561 expressions = self.constructors.generate_expressions(lifetimes)
562 for lifetime in expressions:
563 # Don't attempt to create or load a volatile key in storage
564 if 'VOLATILE' in lifetime:
565 continue
566 # Don't attempt to create a read-only key in storage,
567 # but do attempt to load one.
568 if 'READ_ONLY' in lifetime and self.forward:
569 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200570 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200571
Gilles Peskinea296e482022-02-24 18:58:08 +0100572 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100573 self,
574 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200575 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100576 test_implicit_usage: Optional[bool] = True
577 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100578 """Construct a test key for the given key usage."""
579 usage = ' | '.join(usage_flags) if usage_flags else '0'
580 if short is None:
581 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
Gilles Peskinea296e482022-02-24 18:58:08 +0100582 extra_desc = ' without implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200583 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200584 key1 = StorageTestData(version=self.version,
585 id=1, lifetime=0x00000001,
586 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
587 expected_usage=usage,
Gilles Peskinea296e482022-02-24 18:58:08 +0100588 without_implicit_usage=not test_implicit_usage,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200589 usage=usage, alg=0, alg2=0,
590 material=b'K',
591 description=description)
Gilles Peskinea296e482022-02-24 18:58:08 +0100592 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100593
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200594 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100595 """Generate test keys covering usage flags."""
596 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100597 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200598 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100599 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200600 for flag1, flag2 in zip(known_flags,
601 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100602 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200603
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200604 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200605 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100606 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200607
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200608 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200609 yield from self.generate_keys_for_usage_flags()
610 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100611
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100612 def keys_for_type(
613 self,
614 key_type: str,
615 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200616 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100617 """Generate test keys for the given key type.
618
619 For key types that depend on a parameter (e.g. elliptic curve family),
620 `param` is the parameter to pass to the constructor. Only a single
621 parameter is supported.
622 """
623 kt = crypto_knowledge.KeyType(key_type, params)
624 for bits in kt.sizes_to_test():
625 usage_flags = 'PSA_KEY_USAGE_EXPORT'
626 alg = 0
627 alg2 = 0
628 key_material = kt.key_material(bits)
629 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
630 r'',
631 kt.expression)
632 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200633 key = StorageTestData(version=self.version,
634 id=1, lifetime=0x00000001,
635 type=kt.expression, bits=bits,
636 usage=usage_flags, alg=alg, alg2=alg2,
637 material=key_material,
638 description=description)
639 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100640
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200641 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100642 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200643 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200644 for key_type in self.constructors.generate_expressions(key_types):
645 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100646
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200647 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100648 """Generate test keys for the specified algorithm."""
649 # For now, we don't have information on the compatibility of key
650 # types and algorithms. So we just test the encoding of algorithms,
651 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200652 descr = re.sub(r'PSA_ALG_', r'', alg)
653 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100654 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200655 key1 = StorageTestData(version=self.version,
656 id=1, lifetime=0x00000001,
657 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
658 usage=usage, alg=alg, alg2=0,
659 material=b'K',
660 description='alg: ' + descr)
661 yield key1
662 key2 = StorageTestData(version=self.version,
663 id=1, lifetime=0x00000001,
664 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
665 usage=usage, alg=0, alg2=alg,
666 material=b'L',
667 description='alg2: ' + descr)
668 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100669
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200670 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100671 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200672 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200673 for alg in self.constructors.generate_expressions(algorithms):
674 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100675
gabor-mezei-armea840de2021-06-29 15:42:57 +0200676 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200677 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200678 yield from self.all_keys_for_lifetimes()
679 yield from self.all_keys_for_usage_flags()
680 yield from self.all_keys_for_types()
681 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200682
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200683 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100684 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200685 # First build a list of all keys, then construct all the corresponding
686 # test cases. This allows all required information to be obtained in
687 # one go, which is a significant performance gain as the information
688 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200689 all_keys = list(self.generate_all_keys())
690 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200691 if key.location_value() != 0:
692 # Skip keys with a non-default location, because they
693 # require a driver and we currently have no mechanism to
694 # determine whether a driver is available.
695 continue
696 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100697
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200698class StorageFormatForward(StorageFormat):
699 """Storage format stability test cases for forward compatibility."""
700
701 def __init__(self, info: Information, version: int) -> None:
702 super().__init__(info, version, True)
703
704class StorageFormatV0(StorageFormat):
705 """Storage format stability test cases for version 0 compatibility."""
706
707 def __init__(self, info: Information) -> None:
708 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100709
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200710 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200711 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100712 yield from super().all_keys_for_usage_flags()
713 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200714
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200715 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200716 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200717 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200718 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200719 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200720 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200721 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200722 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200723 algorithm and key type combination.
724 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200725 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200726 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200727 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200728 material_usage_flags = usage_flags + ' | ' + implyer_usage
729 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200730 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200731 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200732 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200733 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
734 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
735 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
736 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200737 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200738 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200739 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200740 key = StorageTestData(version=self.version,
741 id=1, lifetime=0x00000001,
742 type=key_type.expression, bits=bits,
743 usage=material_usage_flags,
744 expected_usage=expected_usage_flags,
745 without_implicit_usage=True,
746 alg=alg, alg2=alg2,
747 material=key_material,
748 description=description)
749 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200750
751 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200752 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200753 """Match possible key types for sign algorithms."""
754 # To create a valid combinaton both the algorithms and key types
755 # must be filtered. Pair them with keywords created from its names.
756 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
757 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
758 keyword_translation = {
759 'ECDSA': 'ECC',
760 'ED[0-9]*.*' : 'EDWARDS'
761 }
762 exclusive_keywords = {
763 'EDWARDS': 'ECC'
764 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200765 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
766 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200767 alg_with_keys = {} #type: Dict[str, List[str]]
768 translation_table = str.maketrans('(', '_', ')')
769 for alg in algorithms:
770 # Generate keywords from the name of the algorithm
771 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
772 # Translate keywords for better matching with the key types
773 for keyword in alg_keywords.copy():
774 for pattern, replace in keyword_translation.items():
775 if re.match(pattern, keyword):
776 alg_keywords.remove(keyword)
777 alg_keywords.add(replace)
778 # Filter out incompatible algortihms
779 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
780 continue
781
782 for key_type in key_types:
783 # Generate keywords from the of the key type
784 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
785
786 # Remove ambigious keywords
787 for keyword1, keyword2 in exclusive_keywords.items():
788 if keyword1 in key_type_keywords:
789 key_type_keywords.remove(keyword2)
790
791 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
792 not key_type_keywords.isdisjoint(alg_keywords):
793 if alg in alg_with_keys:
794 alg_with_keys[alg].append(key_type)
795 else:
796 alg_with_keys[alg] = [key_type]
797 return alg_with_keys
798
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200799 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200800 """Generate test keys for usage flag extensions."""
801 # Generate a key type and algorithm pair for each extendable usage
802 # flag to generate a valid key for exercising. The key is generated
803 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200804 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200805
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200806 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
807 for alg in sorted(alg_with_keys):
808 for key_type in sorted(alg_with_keys[alg]):
809 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200810 kt = crypto_knowledge.KeyType(key_type)
811 if kt.is_valid_for_signature(usage):
812 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200813
gabor-mezei-armea840de2021-06-29 15:42:57 +0200814 def generate_all_keys(self) -> Iterator[StorageTestData]:
815 yield from super().generate_all_keys()
816 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200817
Gilles Peskineb94ea512021-03-10 02:12:08 +0100818class TestGenerator:
819 """Generate test data."""
820
821 def __init__(self, options) -> None:
822 self.test_suite_directory = self.get_option(options, 'directory',
823 'tests/suites')
824 self.info = Information()
825
826 @staticmethod
827 def get_option(options, name: str, default: T) -> T:
828 value = getattr(options, name, None)
829 return default if value is None else value
830
Gilles Peskine0298bda2021-03-10 02:34:37 +0100831 def filename_for(self, basename: str) -> str:
832 """The location of the data file with the specified base name."""
833 return os.path.join(self.test_suite_directory, basename + '.data')
834
Gilles Peskineb94ea512021-03-10 02:12:08 +0100835 def write_test_data_file(self, basename: str,
836 test_cases: Iterable[test_case.TestCase]) -> None:
837 """Write the test cases to a .data file.
838
839 The output file is ``basename + '.data'`` in the test suite directory.
840 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100841 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100842 test_case.write_data_file(filename, test_cases)
843
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200844 # Note that targets whose name containns 'test_format' have their content
845 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100846 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200847 'test_suite_psa_crypto_generate_key.generated':
848 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100849 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100850 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200851 'test_suite_psa_crypto_op_fail.generated':
852 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100853 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200854 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100855 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200856 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100857 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
858
859 def generate_target(self, name: str) -> None:
860 test_cases = self.TARGETS[name](self.info)
861 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100862
Gilles Peskine09940492021-01-26 22:16:30 +0100863def main(args):
864 """Command line entry point."""
865 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100866 parser.add_argument('--list', action='store_true',
867 help='List available targets and exit')
868 parser.add_argument('targets', nargs='*', metavar='TARGET',
869 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100870 options = parser.parse_args(args)
871 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100872 if options.list:
873 for name in sorted(generator.TARGETS):
874 print(generator.filename_for(name))
875 return
876 if options.targets:
877 # Allow "-" as a special case so you can run
878 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
879 # ``$targets`` is empty or not.
880 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
881 for target in options.targets
882 if target != '-']
883 else:
884 options.targets = sorted(generator.TARGETS)
885 for target in options.targets:
886 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100887
888if __name__ == '__main__':
889 main(sys.argv[1:])