blob: 3c76b0aaaafe33caf9febcfccd52e2ad899d6e73 [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()
Gilles Peskined79aef52022-03-17 23:42:25 +0100146 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100147 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.
Gilles Peskine989c13d2022-03-17 12:52:24 +0100207 if not kt.is_public():
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()
Gilles Peskined79aef52022-03-17 23:42:25 +0100244 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200245 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
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200315 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100316
Gilles Peskinec05158b2021-04-27 20:40:10 +0200317 def __init__(self, info: Information) -> None:
318 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100319 key_type_expressions = self.constructors.generate_expressions(
320 sorted(self.constructors.key_types)
321 )
322 self.key_types = [crypto_knowledge.KeyType(kt_expr)
323 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200324
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100325 def make_test_case(
326 self,
327 alg: crypto_knowledge.Algorithm,
328 category: crypto_knowledge.AlgorithmCategory,
329 reason: 'Reason',
330 kt: Optional[crypto_knowledge.KeyType] = None,
331 not_deps: FrozenSet[str] = frozenset(),
332 ) -> test_case.TestCase:
333 """Construct a failure test case for a one-key or keyless operation."""
334 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200335 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100336 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200337 if reason == self.Reason.NOT_SUPPORTED:
338 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
339 for dep in not_deps]
340 pretty_reason = '!' + '&'.join(sorted(short_deps))
341 else:
342 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100343 if kt:
344 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100345 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200346 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100347 key_type = ''
348 pretty_type = ''
349 tc.set_description('PSA {} {}: {}{}'
350 .format(category.name.lower(),
351 pretty_alg,
352 pretty_reason,
353 ' with ' + pretty_type if pretty_type else ''))
354 dependencies = automatic_dependencies(alg.base_expression, key_type)
355 for i, dep in enumerate(dependencies):
356 if dep in not_deps:
357 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200358 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100359 tc.set_function(category.name.lower() + '_fail')
360 arguments = []
361 if kt:
362 key_material = kt.key_material(kt.sizes_to_test()[0])
363 arguments += [key_type, test_case.hex_string(key_material)]
364 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200365 if category.is_asymmetric():
366 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100367 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
368 'INVALID_ARGUMENT')
369 arguments.append('PSA_ERROR_' + error)
370 tc.set_arguments(arguments)
371 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200372
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100373 def no_key_test_cases(
374 self,
375 alg: crypto_knowledge.Algorithm,
376 category: crypto_knowledge.AlgorithmCategory,
377 ) -> Iterator[test_case.TestCase]:
378 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200379 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100380 # Compatible operation, unsupported algorithm
381 for dep in automatic_dependencies(alg.base_expression):
382 yield self.make_test_case(alg, category,
383 self.Reason.NOT_SUPPORTED,
384 not_deps=frozenset([dep]))
385 else:
386 # Incompatible operation, supported algorithm
387 yield self.make_test_case(alg, category, self.Reason.INVALID)
388
389 def one_key_test_cases(
390 self,
391 alg: crypto_knowledge.Algorithm,
392 category: crypto_knowledge.AlgorithmCategory,
393 ) -> Iterator[test_case.TestCase]:
394 """Generate failure test cases for one-key operations with the specified algorithm."""
395 for kt in self.key_types:
396 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200397 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100398 # Compatible key and operation, unsupported algorithm
399 for dep in automatic_dependencies(alg.base_expression):
400 yield self.make_test_case(alg, category,
401 self.Reason.NOT_SUPPORTED,
402 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200403 # Public key for a private-key operation
404 if category.is_asymmetric() and kt.is_public():
405 yield self.make_test_case(alg, category,
406 self.Reason.PUBLIC,
407 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100408 elif key_is_compatible:
409 # Compatible key, incompatible operation, supported algorithm
410 yield self.make_test_case(alg, category,
411 self.Reason.INVALID,
412 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200413 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100414 # Incompatible key, compatible operation, supported algorithm
415 yield self.make_test_case(alg, category,
416 self.Reason.INCOMPATIBLE,
417 kt=kt)
418 else:
419 # Incompatible key and operation. Don't test cases where
420 # multiple things are wrong, to keep the number of test
421 # cases reasonable.
422 pass
423
424 def test_cases_for_algorithm(
425 self,
426 alg: crypto_knowledge.Algorithm,
427 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200428 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100429 for category in crypto_knowledge.AlgorithmCategory:
430 if category == crypto_knowledge.AlgorithmCategory.PAKE:
431 # PAKE operations are not implemented yet
432 pass
433 elif category.requires_key():
434 yield from self.one_key_test_cases(alg, category)
435 else:
436 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200437
Gilles Peskinec05158b2021-04-27 20:40:10 +0200438 def all_test_cases(self) -> Iterator[test_case.TestCase]:
439 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200440 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100441 for expr in self.constructors.generate_expressions(algorithms):
442 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200443 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200444
445
Gilles Peskine897dff92021-03-10 15:03:44 +0100446class StorageKey(psa_storage.Key):
447 """Representation of a key for storage format testing."""
448
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200449 IMPLICIT_USAGE_FLAGS = {
450 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
451 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
452 } #type: Dict[str, str]
453 """Mapping of usage flags to the flags that they imply."""
454
455 def __init__(
456 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100457 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200458 without_implicit_usage: Optional[bool] = False,
459 **kwargs
460 ) -> None:
461 """Prepare to generate a key.
462
463 * `usage` : The usage flags used for the key.
464 * `without_implicit_usage`: Flag to defide to apply the usage extension
465 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100466 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200467 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100468 for flag in sorted(usage_flags):
469 if flag in self.IMPLICIT_USAGE_FLAGS:
470 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
471 if usage_flags:
472 usage_expression = ' | '.join(sorted(usage_flags))
473 else:
474 usage_expression = '0'
475 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200476
477class StorageTestData(StorageKey):
478 """Representation of test case data for storage format testing."""
479
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200480 def __init__(
481 self,
482 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100483 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200484 **kwargs
485 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200486 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200487
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200488 * `description` : used for the the test case names
489 * `expected_usage`: the usage flags generated as the expected usage flags
490 in the test cases. CAn differ from the usage flags
491 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200492 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100493 super().__init__(**kwargs)
494 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100495 if expected_usage is None:
496 self.expected_usage = self.usage #type: psa_storage.Expr
497 elif expected_usage:
498 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
499 else:
500 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200501
Gilles Peskine897dff92021-03-10 15:03:44 +0100502class StorageFormat:
503 """Storage format stability test cases."""
504
505 def __init__(self, info: Information, version: int, forward: bool) -> None:
506 """Prepare to generate test cases for storage format stability.
507
508 * `info`: information about the API. See the `Information` class.
509 * `version`: the storage format version to generate test cases for.
510 * `forward`: if true, generate forward compatibility test cases which
511 save a key and check that its representation is as intended. Otherwise
512 generate backward compatibility test cases which inject a key
513 representation and check that it can be read and used.
514 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200515 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
516 self.version = version #type: int
517 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100518
Gilles Peskine32611242022-03-19 12:09:13 +0100519 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100520 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100521 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100522 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100523 cls,
524 key_type: psa_storage.Expr, bits: int,
525 alg: psa_storage.Expr
526 ) -> bool:
Gilles Peskine8ddced52022-03-19 15:36:09 +0100527 """Whether to the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100528
529 Normally only the type and algorithm matter for compatibility, and
530 this is handled in crypto_knowledge.KeyType.can_do(). This function
531 exists to detect exceptional cases. Exceptional cases detected here
532 are not tested in OpFail and should therefore have manually written
533 test cases.
534 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100535 # Some test keys have the RAW_DATA type and attributes that don't
536 # necessarily make sense. We do this to validate numerical
537 # encodings of the attributes.
538 # Raw data keys have no useful exercise anyway so there is no
539 # loss of test coverage.
540 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
541 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200542 # Mbed TLS only supports 128-bit keys for RC4.
543 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
544 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100545 # OAEP requires room for two hashes plus wrapping
546 m = cls.RSA_OAEP_RE.match(alg.string)
547 if m:
548 hash_alg = m.group(1)
549 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
550 key_length = (bits + 7) // 8
551 # Leave enough room for at least one byte of plaintext
552 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100553 # There's nothing wrong with ECC keys on Brainpool curves,
554 # but operations with them are very slow. So we only exercise them
555 # with a single algorithm, not with all possible hashes. We do
556 # exercise other curves with all algorithms so test coverage is
557 # perfectly adequate like this.
558 m = cls.BRAINPOOL_RE.match(key_type.string)
559 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
560 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100561 return True
562
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200563 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100564 """Construct a storage format test case for the given key.
565
566 If ``forward`` is true, generate a forward compatibility test case:
567 create a key and validate that it has the expected representation.
568 Otherwise generate a backward compatibility test case: inject the
569 key representation into storage and validate that it can be read
570 correctly.
571 """
572 verb = 'save' if self.forward else 'read'
573 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100574 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100575 dependencies = automatic_dependencies(
576 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100577 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100578 )
579 dependencies = finish_family_dependencies(dependencies, key.bits)
580 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100581 tc.set_function('key_storage_' + verb)
582 if self.forward:
583 extra_arguments = []
584 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200585 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100586 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200587 flags.append('TEST_FLAG_EXERCISE')
588 if 'READ_ONLY' in key.lifetime.string:
589 flags.append('TEST_FLAG_READ_ONLY')
590 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100591 tc.set_arguments([key.lifetime.string,
592 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100593 key.expected_usage.string,
594 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100595 '"' + key.material.hex() + '"',
596 '"' + key.hex() + '"',
597 *extra_arguments])
598 return tc
599
Gilles Peskineefb584d2021-04-21 22:05:34 +0200600 def key_for_lifetime(
601 self,
602 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200603 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200604 """Construct a test key for the given lifetime."""
605 short = lifetime
606 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
607 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100608 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200609 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200610 key = StorageTestData(version=self.version,
611 id=1, lifetime=lifetime,
612 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100613 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200614 material=b'L',
615 description=description)
616 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200617
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200618 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200619 """Generate test keys covering lifetimes."""
620 lifetimes = sorted(self.constructors.lifetimes)
621 expressions = self.constructors.generate_expressions(lifetimes)
622 for lifetime in expressions:
623 # Don't attempt to create or load a volatile key in storage
624 if 'VOLATILE' in lifetime:
625 continue
626 # Don't attempt to create a read-only key in storage,
627 # but do attempt to load one.
628 if 'READ_ONLY' in lifetime and self.forward:
629 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200630 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200631
Gilles Peskinea296e482022-02-24 18:58:08 +0100632 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100633 self,
634 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200635 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100636 test_implicit_usage: Optional[bool] = True
637 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100638 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100639 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100640 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200641 key1 = StorageTestData(version=self.version,
642 id=1, lifetime=0x00000001,
643 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100644 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100645 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100646 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200647 material=b'K',
648 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100649 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100650 usage_expr = key1.expected_usage.string
651 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100652 else:
653 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100654 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100655
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200656 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100657 """Generate test keys covering usage flags."""
658 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100659 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200660 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100661 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200662 for flag1, flag2 in zip(known_flags,
663 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100664 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200665
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200666 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200667 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100668 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200669
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200670 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200671 yield from self.generate_keys_for_usage_flags()
672 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100673
Gilles Peskine6213a002021-04-29 22:28:07 +0200674 def key_for_type_and_alg(
675 self,
676 kt: crypto_knowledge.KeyType,
677 bits: int,
678 alg: Optional[crypto_knowledge.Algorithm] = None,
679 ) -> StorageTestData:
680 """Construct a test key of the given type.
681
682 If alg is not None, this key allows it.
683 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100684 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100685 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200686 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100687 if alg is not None:
688 alg1 = alg.expression
689 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200690 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100691 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200692 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100693 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200694 key = StorageTestData(version=self.version,
695 id=1, lifetime=0x00000001,
696 type=kt.expression, bits=bits,
697 usage=usage_flags, alg=alg1, alg2=alg2,
698 material=key_material,
699 description=description)
700 return key
701
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100702 def keys_for_type(
703 self,
704 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200705 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200706 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200707 """Generate test keys for the given key type."""
708 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100709 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200710 # Test a non-exercisable key, as well as exercisable keys for
711 # each compatible algorithm.
712 # To do: test reading a key from storage with an incompatible
713 # or unsupported algorithm.
714 yield self.key_for_type_and_alg(kt, bits)
715 compatible_algorithms = [alg for alg in all_algorithms
716 if kt.can_do(alg)]
717 for alg in compatible_algorithms:
718 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100719
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200720 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100721 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200722 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200723 all_algorithms = [crypto_knowledge.Algorithm(alg)
724 for alg in self.constructors.generate_expressions(
725 sorted(self.constructors.algorithms)
726 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200727 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200728 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100729
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200730 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200731 """Generate test keys for the encoding of the specified algorithm."""
732 # These test cases only validate the encoding of algorithms, not
733 # whether the key read from storage is suitable for an operation.
734 # `keys_for_types` generate read tests with an algorithm and a
735 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100736 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100737 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200738 key1 = StorageTestData(version=self.version,
739 id=1, lifetime=0x00000001,
740 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
741 usage=usage, alg=alg, alg2=0,
742 material=b'K',
743 description='alg: ' + descr)
744 yield key1
745 key2 = StorageTestData(version=self.version,
746 id=1, lifetime=0x00000001,
747 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
748 usage=usage, alg=0, alg2=alg,
749 material=b'L',
750 description='alg2: ' + descr)
751 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100752
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200753 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100754 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200755 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200756 for alg in self.constructors.generate_expressions(algorithms):
757 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100758
gabor-mezei-armea840de2021-06-29 15:42:57 +0200759 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200760 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200761 yield from self.all_keys_for_lifetimes()
762 yield from self.all_keys_for_usage_flags()
763 yield from self.all_keys_for_types()
764 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200765
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200766 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100767 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200768 # First build a list of all keys, then construct all the corresponding
769 # test cases. This allows all required information to be obtained in
770 # one go, which is a significant performance gain as the information
771 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200772 all_keys = list(self.generate_all_keys())
773 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200774 if key.location_value() != 0:
775 # Skip keys with a non-default location, because they
776 # require a driver and we currently have no mechanism to
777 # determine whether a driver is available.
778 continue
779 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100780
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200781class StorageFormatForward(StorageFormat):
782 """Storage format stability test cases for forward compatibility."""
783
784 def __init__(self, info: Information, version: int) -> None:
785 super().__init__(info, version, True)
786
787class StorageFormatV0(StorageFormat):
788 """Storage format stability test cases for version 0 compatibility."""
789
790 def __init__(self, info: Information) -> None:
791 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100792
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200793 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200794 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100795 yield from super().all_keys_for_usage_flags()
796 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200797
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200798 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200799 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200800 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200801 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200802 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200803 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200804 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200805 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200806 algorithm and key type combination.
807 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200808 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200809 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100810 usage_flags = ['PSA_KEY_USAGE_EXPORT']
811 material_usage_flags = usage_flags + [implyer_usage]
812 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200813 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200814 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100815 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
816 alg_expression = crypto_knowledge.short_expression(alg, 1)
817 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200818 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200819 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200820 key = StorageTestData(version=self.version,
821 id=1, lifetime=0x00000001,
822 type=key_type.expression, bits=bits,
823 usage=material_usage_flags,
824 expected_usage=expected_usage_flags,
825 without_implicit_usage=True,
826 alg=alg, alg2=alg2,
827 material=key_material,
828 description=description)
829 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200830
831 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200832 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200833 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800834 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200835 # must be filtered. Pair them with keywords created from its names.
836 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
837 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
838 keyword_translation = {
839 'ECDSA': 'ECC',
840 'ED[0-9]*.*' : 'EDWARDS'
841 }
842 exclusive_keywords = {
843 'EDWARDS': 'ECC'
844 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200845 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
846 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200847 alg_with_keys = {} #type: Dict[str, List[str]]
848 translation_table = str.maketrans('(', '_', ')')
849 for alg in algorithms:
850 # Generate keywords from the name of the algorithm
851 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
852 # Translate keywords for better matching with the key types
853 for keyword in alg_keywords.copy():
854 for pattern, replace in keyword_translation.items():
855 if re.match(pattern, keyword):
856 alg_keywords.remove(keyword)
857 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800858 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200859 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
860 continue
861
862 for key_type in key_types:
863 # Generate keywords from the of the key type
864 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
865
Shaun Case0e7791f2021-12-20 21:14:10 -0800866 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200867 for keyword1, keyword2 in exclusive_keywords.items():
868 if keyword1 in key_type_keywords:
869 key_type_keywords.remove(keyword2)
870
871 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
872 not key_type_keywords.isdisjoint(alg_keywords):
873 if alg in alg_with_keys:
874 alg_with_keys[alg].append(key_type)
875 else:
876 alg_with_keys[alg] = [key_type]
877 return alg_with_keys
878
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200879 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200880 """Generate test keys for usage flag extensions."""
881 # Generate a key type and algorithm pair for each extendable usage
882 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800883 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200884 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200885
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200886 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
887 for alg in sorted(alg_with_keys):
888 for key_type in sorted(alg_with_keys[alg]):
889 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200890 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100891 if kt.is_public() and '_SIGN_' in usage:
892 # Can't sign with a public key
893 continue
894 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200895
gabor-mezei-armea840de2021-06-29 15:42:57 +0200896 def generate_all_keys(self) -> Iterator[StorageTestData]:
897 yield from super().generate_all_keys()
898 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200899
Gilles Peskineb94ea512021-03-10 02:12:08 +0100900class TestGenerator:
901 """Generate test data."""
902
903 def __init__(self, options) -> None:
904 self.test_suite_directory = self.get_option(options, 'directory',
905 'tests/suites')
906 self.info = Information()
907
908 @staticmethod
909 def get_option(options, name: str, default: T) -> T:
910 value = getattr(options, name, None)
911 return default if value is None else value
912
Gilles Peskine0298bda2021-03-10 02:34:37 +0100913 def filename_for(self, basename: str) -> str:
914 """The location of the data file with the specified base name."""
915 return os.path.join(self.test_suite_directory, basename + '.data')
916
Gilles Peskineb94ea512021-03-10 02:12:08 +0100917 def write_test_data_file(self, basename: str,
918 test_cases: Iterable[test_case.TestCase]) -> None:
919 """Write the test cases to a .data file.
920
921 The output file is ``basename + '.data'`` in the test suite directory.
922 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100923 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100924 test_case.write_data_file(filename, test_cases)
925
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200926 # Note that targets whose name containns 'test_format' have their content
927 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100928 TARGETS = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200929 'test_suite_psa_crypto_generate_key.generated':
930 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100931 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100932 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200933 'test_suite_psa_crypto_op_fail.generated':
934 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100935 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200936 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100937 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200938 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100939 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
940
941 def generate_target(self, name: str) -> None:
942 test_cases = self.TARGETS[name](self.info)
943 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100944
Gilles Peskine09940492021-01-26 22:16:30 +0100945def main(args):
946 """Command line entry point."""
947 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100948 parser.add_argument('--list', action='store_true',
949 help='List available targets and exit')
950 parser.add_argument('targets', nargs='*', metavar='TARGET',
951 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100952 options = parser.parse_args(args)
953 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100954 if options.list:
955 for name in sorted(generator.TARGETS):
956 print(generator.filename_for(name))
957 return
958 if options.targets:
959 # Allow "-" as a special case so you can run
960 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
961 # ``$targets`` is empty or not.
962 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
963 for target in options.targets
964 if target != '-']
965 else:
966 options.targets = sorted(generator.TARGETS)
967 for target in options.targets:
968 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100969
970if __name__ == '__main__':
971 main(sys.argv[1:])