blob: 1f516c9928fe6f4555f4f38e78c14e43b9ea2f9f [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
Gilles Peskinef8b6b502022-03-15 17:26:33 +010023import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import re
Gilles Peskine09940492021-01-26 22:16:30 +010025import sys
Werner Lewisdcad1e92022-08-24 11:30:03 +010026from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010027
28import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010029from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010030from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010031from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010032from mbedtls_dev import test_case
Werner Lewisdcad1e92022-08-24 11:30:03 +010033from mbedtls_dev import test_generation
Gilles Peskine09940492021-01-26 22:16:30 +010034
Gilles Peskine14e428f2021-01-26 22:19:21 +010035
Gilles Peskine7f756872021-02-16 12:13:12 +010036def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010037 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
38 if name.startswith('PSA_'):
39 return name[:4] + 'WANT_' + name[4:]
40 else:
41 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
42
Gilles Peskine7f756872021-02-16 12:13:12 +010043def finish_family_dependency(dep: str, bits: int) -> str:
44 """Finish dep if it's a family dependency symbol prefix.
45
46 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
47 qualified by the key size. If dep is such a symbol, finish it by adjusting
48 the prefix and appending the key size. Other symbols are left unchanged.
49 """
50 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
51
52def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
53 """Finish any family dependency symbol prefixes.
54
55 Apply `finish_family_dependency` to each element of `dependencies`.
56 """
57 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010058
Gilles Peskine8a55b432021-04-20 23:23:45 +020059SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
60 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
61 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
62 'PSA_ALG_ANY_HASH', # only in policies
63 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
64 'PSA_ALG_KEY_AGREEMENT', # chaining
65 'PSA_ALG_TRUNCATED_MAC', # modifier
66])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010067def automatic_dependencies(*expressions: str) -> List[str]:
68 """Infer dependencies of a test case by looking for PSA_xxx symbols.
69
70 The arguments are strings which should be C expressions. Do not use
71 string literals or comments as this function is not smart enough to
72 skip them.
73 """
74 used = set()
75 for expr in expressions:
76 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskine8a55b432021-04-20 23:23:45 +020077 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010078 return sorted(psa_want_symbol(name) for name in used)
79
Gilles Peskined169d602021-02-16 14:16:25 +010080# A temporary hack: at the time of writing, not all dependency symbols
81# are implemented yet. Skip test cases for which the dependency symbols are
82# not available. Once all dependency symbols are available, this hack must
83# be removed so that a bug in the dependency symbols proprely leads to a test
84# failure.
85def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
86 return frozenset(symbol
87 for line in open(filename)
88 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010089_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010090def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Przemyslaw Stekiel29275932021-11-09 12:06:26 +010091 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
Przemyslaw Stekiel08101082021-10-22 10:39:56 +020092 if _implemented_dependencies is None:
93 _implemented_dependencies = \
94 read_implemented_dependencies('include/psa/crypto_config.h')
95 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010096 for dep in dependencies):
97 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
98
Gilles Peskine14e428f2021-01-26 22:19:21 +010099
Gilles Peskineb94ea512021-03-10 02:12:08 +0100100class Information:
101 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100102
Gilles Peskineb94ea512021-03-10 02:12:08 +0100103 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100104 self.constructors = self.read_psa_interface()
105
106 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100107 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200108 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100109 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200110 # Mbed TLS doesn't support finite-field DH yet and will not support
111 # finite-field DSA. Don't attempt to generate any related test case.
112 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
113 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100114 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
115 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100116
Gilles Peskineb93f8542021-04-19 13:50:25 +0200117 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100118 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200119 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100120 header_file_names = ['include/psa/crypto_values.h',
121 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200122 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100123 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200124 constructors.parse_header(header_file_name)
125 for test_cases in test_suites:
126 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100127 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200128 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100129 return constructors
130
Gilles Peskine14e428f2021-01-26 22:19:21 +0100131
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200132def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100133 verb: str, key_type: str, bits: int,
134 dependencies: List[str],
135 *args: str,
136 param_descr: str = ''
137) -> test_case.TestCase:
138 """Return one test case exercising a key creation method
139 for an unsupported key type or size.
140 """
141 hack_dependencies_not_implemented(dependencies)
142 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100143 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100144 adverb = 'not' if dependencies else 'never'
145 if param_descr:
146 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200147 tc.set_description('PSA {} {} {}-bit {} supported'
148 .format(verb, short_key_type, bits, adverb))
149 tc.set_dependencies(dependencies)
150 tc.set_function(verb + '_not_supported')
151 tc.set_arguments([key_type] + list(args))
152 return tc
153
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154class NotSupported:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200155 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100156
157 def __init__(self, info: Information) -> None:
158 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100159
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100160 ALWAYS_SUPPORTED = frozenset([
161 'PSA_KEY_TYPE_DERIVE',
162 'PSA_KEY_TYPE_RAW_DATA',
163 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100164 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100165 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100166 kt: crypto_knowledge.KeyType,
167 param: Optional[int] = None,
168 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100169 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200170 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100171
172 If param is present and not None, emit test cases conditioned on this
173 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel32a8b842021-10-18 14:58:20 +0200174 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100175 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100176 if kt.name in self.ALWAYS_SUPPORTED:
177 # Don't generate test cases for key types that are always supported.
178 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100179 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100180 import_dependencies = [('!' if param is None else '') +
181 psa_want_symbol(kt.name)]
182 if kt.params is not None:
183 import_dependencies += [('!' if param == i else '') +
184 psa_want_symbol(sym)
185 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100186 if kt.name.endswith('_PUBLIC_KEY'):
187 generate_dependencies = []
188 else:
189 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200191 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100192 'import', kt.expression, bits,
193 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100194 test_case.hex_string(kt.key_material(bits)),
195 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100196 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 if not generate_dependencies and param is not None:
198 # If generation is impossible for this key type, rather than
199 # supported or not depending on implementation capabilities,
200 # only generate the test case once.
201 continue
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100202 # For public key we expect that key generation fails with
203 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskine989c13d2022-03-17 12:52:24 +0100204 if not kt.is_public():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200205 yield test_case_for_key_type_not_supported(
206 'generate', kt.expression, bits,
207 finish_family_dependencies(generate_dependencies, bits),
208 str(bits),
209 param_descr=param_descr,
210 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100211 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212
Gilles Peskineb93f8542021-04-19 13:50:25 +0200213 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
214 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
215
Gilles Peskine3d778392021-02-17 15:11:05 +0100216 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200219 if key_type in self.ECC_KEY_TYPES:
220 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100222 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100223 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200224 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100226 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100228 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100230
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200231def test_case_for_key_generation(
232 key_type: str, bits: int,
233 dependencies: List[str],
234 *args: str,
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200235 result: str = ''
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200236) -> test_case.TestCase:
237 """Return one test case exercising a key generation.
238 """
239 hack_dependencies_not_implemented(dependencies)
240 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100241 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200242 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200243 .format(short_key_type, bits))
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200244 tc.set_dependencies(dependencies)
245 tc.set_function('generate_key')
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100246 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200247
248 return tc
249
250class KeyGenerate:
251 """Generate positive and negative (invalid argument) test cases for key generation."""
252
253 def __init__(self, info: Information) -> None:
254 self.constructors = info.constructors
255
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200256 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
257 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
258
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100259 @staticmethod
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200260 def test_cases_for_key_type_key_generation(
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200261 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200262 ) -> Iterator[test_case.TestCase]:
263 """Return test cases exercising key generation.
264
265 All key types can be generated except for public keys. For public key
266 PSA_ERROR_INVALID_ARGUMENT status is expected.
267 """
268 result = 'PSA_SUCCESS'
269
270 import_dependencies = [psa_want_symbol(kt.name)]
271 if kt.params is not None:
272 import_dependencies += [psa_want_symbol(sym)
273 for i, sym in enumerate(kt.params)]
274 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100275 # The library checks whether the key type is a public key generically,
276 # before it reaches a point where it needs support for the specific key
277 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200278 generate_dependencies = []
279 result = 'PSA_ERROR_INVALID_ARGUMENT'
280 else:
281 generate_dependencies = import_dependencies
Przemyslaw Stekiel1ab3a5c2021-11-02 10:50:44 +0100282 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekiel08101082021-10-22 10:39:56 +0200283 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200284 for bits in kt.sizes_to_test():
285 yield test_case_for_key_generation(
286 kt.expression, bits,
287 finish_family_dependencies(generate_dependencies, bits),
288 str(bits),
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200289 result
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200290 )
291
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200292 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
293 """Generate test cases that exercise the generation of keys."""
294 for key_type in sorted(self.constructors.key_types):
295 if key_type in self.ECC_KEY_TYPES:
296 continue
297 kt = crypto_knowledge.KeyType(key_type)
298 yield from self.test_cases_for_key_type_key_generation(kt)
299 for curve_family in sorted(self.constructors.ecc_curves):
300 for constr in self.ECC_KEY_TYPES:
301 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekielc03b7c52021-10-20 11:59:50 +0200302 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200303
Gilles Peskinec05158b2021-04-27 20:40:10 +0200304class OpFail:
305 """Generate test cases for operations that must fail."""
306 #pylint: disable=too-few-public-methods
307
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100308 class Reason(enum.Enum):
309 NOT_SUPPORTED = 0
310 INVALID = 1
311 INCOMPATIBLE = 2
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200312 PUBLIC = 3
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100313
Gilles Peskinec05158b2021-04-27 20:40:10 +0200314 def __init__(self, info: Information) -> None:
315 self.constructors = info.constructors
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100316 key_type_expressions = self.constructors.generate_expressions(
317 sorted(self.constructors.key_types)
318 )
319 self.key_types = [crypto_knowledge.KeyType(kt_expr)
320 for kt_expr in key_type_expressions]
Gilles Peskinec05158b2021-04-27 20:40:10 +0200321
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100322 def make_test_case(
323 self,
324 alg: crypto_knowledge.Algorithm,
325 category: crypto_knowledge.AlgorithmCategory,
326 reason: 'Reason',
327 kt: Optional[crypto_knowledge.KeyType] = None,
328 not_deps: FrozenSet[str] = frozenset(),
329 ) -> test_case.TestCase:
330 """Construct a failure test case for a one-key or keyless operation."""
331 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskinea2180472021-04-27 21:03:43 +0200332 tc = test_case.TestCase()
Gilles Peskined79aef52022-03-17 23:42:25 +0100333 pretty_alg = alg.short_expression()
Gilles Peskined0964452021-04-29 21:35:03 +0200334 if reason == self.Reason.NOT_SUPPORTED:
335 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
336 for dep in not_deps]
337 pretty_reason = '!' + '&'.join(sorted(short_deps))
338 else:
339 pretty_reason = reason.name.lower()
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100340 if kt:
341 key_type = kt.expression
Gilles Peskined79aef52022-03-17 23:42:25 +0100342 pretty_type = kt.short_expression()
Gilles Peskinea2180472021-04-27 21:03:43 +0200343 else:
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100344 key_type = ''
345 pretty_type = ''
346 tc.set_description('PSA {} {}: {}{}'
347 .format(category.name.lower(),
348 pretty_alg,
349 pretty_reason,
350 ' with ' + pretty_type if pretty_type else ''))
351 dependencies = automatic_dependencies(alg.base_expression, key_type)
352 for i, dep in enumerate(dependencies):
353 if dep in not_deps:
354 dependencies[i] = '!' + dep
Gilles Peskinea2180472021-04-27 21:03:43 +0200355 tc.set_dependencies(dependencies)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100356 tc.set_function(category.name.lower() + '_fail')
357 arguments = []
358 if kt:
359 key_material = kt.key_material(kt.sizes_to_test()[0])
360 arguments += [key_type, test_case.hex_string(key_material)]
361 arguments.append(alg.expression)
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200362 if category.is_asymmetric():
363 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100364 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
365 'INVALID_ARGUMENT')
366 arguments.append('PSA_ERROR_' + error)
367 tc.set_arguments(arguments)
368 return tc
Gilles Peskinea2180472021-04-27 21:03:43 +0200369
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100370 def no_key_test_cases(
371 self,
372 alg: crypto_knowledge.Algorithm,
373 category: crypto_knowledge.AlgorithmCategory,
374 ) -> Iterator[test_case.TestCase]:
375 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200376 if alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100377 # Compatible operation, unsupported algorithm
378 for dep in automatic_dependencies(alg.base_expression):
379 yield self.make_test_case(alg, category,
380 self.Reason.NOT_SUPPORTED,
381 not_deps=frozenset([dep]))
382 else:
383 # Incompatible operation, supported algorithm
384 yield self.make_test_case(alg, category, self.Reason.INVALID)
385
386 def one_key_test_cases(
387 self,
388 alg: crypto_knowledge.Algorithm,
389 category: crypto_knowledge.AlgorithmCategory,
390 ) -> Iterator[test_case.TestCase]:
391 """Generate failure test cases for one-key operations with the specified algorithm."""
392 for kt in self.key_types:
393 key_is_compatible = kt.can_do(alg)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200394 if key_is_compatible and alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100395 # Compatible key and operation, unsupported algorithm
396 for dep in automatic_dependencies(alg.base_expression):
397 yield self.make_test_case(alg, category,
398 self.Reason.NOT_SUPPORTED,
399 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200400 # Public key for a private-key operation
401 if category.is_asymmetric() and kt.is_public():
402 yield self.make_test_case(alg, category,
403 self.Reason.PUBLIC,
404 kt=kt)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100405 elif key_is_compatible:
406 # Compatible key, incompatible operation, supported algorithm
407 yield self.make_test_case(alg, category,
408 self.Reason.INVALID,
409 kt=kt)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200410 elif alg.can_do(category):
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100411 # Incompatible key, compatible operation, supported algorithm
412 yield self.make_test_case(alg, category,
413 self.Reason.INCOMPATIBLE,
414 kt=kt)
415 else:
416 # Incompatible key and operation. Don't test cases where
417 # multiple things are wrong, to keep the number of test
418 # cases reasonable.
419 pass
420
421 def test_cases_for_algorithm(
422 self,
423 alg: crypto_knowledge.Algorithm,
424 ) -> Iterator[test_case.TestCase]:
Gilles Peskinea2180472021-04-27 21:03:43 +0200425 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100426 for category in crypto_knowledge.AlgorithmCategory:
427 if category == crypto_knowledge.AlgorithmCategory.PAKE:
428 # PAKE operations are not implemented yet
429 pass
430 elif category.requires_key():
431 yield from self.one_key_test_cases(alg, category)
432 else:
433 yield from self.no_key_test_cases(alg, category)
Gilles Peskinea2180472021-04-27 21:03:43 +0200434
Gilles Peskinec05158b2021-04-27 20:40:10 +0200435 def all_test_cases(self) -> Iterator[test_case.TestCase]:
436 """Generate all test cases for operations that must fail."""
Gilles Peskinea2180472021-04-27 21:03:43 +0200437 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinef8b6b502022-03-15 17:26:33 +0100438 for expr in self.constructors.generate_expressions(algorithms):
439 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskinea2180472021-04-27 21:03:43 +0200440 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec05158b2021-04-27 20:40:10 +0200441
442
Gilles Peskine897dff92021-03-10 15:03:44 +0100443class StorageKey(psa_storage.Key):
444 """Representation of a key for storage format testing."""
445
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200446 IMPLICIT_USAGE_FLAGS = {
447 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
448 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
449 } #type: Dict[str, str]
450 """Mapping of usage flags to the flags that they imply."""
451
452 def __init__(
453 self,
Gilles Peskined9af9782022-03-17 22:32:59 +0100454 usage: Iterable[str],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200455 without_implicit_usage: Optional[bool] = False,
456 **kwargs
457 ) -> None:
458 """Prepare to generate a key.
459
460 * `usage` : The usage flags used for the key.
461 * `without_implicit_usage`: Flag to defide to apply the usage extension
462 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100463 usage_flags = set(usage)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200464 if not without_implicit_usage:
Gilles Peskined9af9782022-03-17 22:32:59 +0100465 for flag in sorted(usage_flags):
466 if flag in self.IMPLICIT_USAGE_FLAGS:
467 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
468 if usage_flags:
469 usage_expression = ' | '.join(sorted(usage_flags))
470 else:
471 usage_expression = '0'
472 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200473
474class StorageTestData(StorageKey):
475 """Representation of test case data for storage format testing."""
476
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200477 def __init__(
478 self,
479 description: str,
Gilles Peskined9af9782022-03-17 22:32:59 +0100480 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200481 **kwargs
482 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200483 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200484
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200485 * `description` : used for the the test case names
486 * `expected_usage`: the usage flags generated as the expected usage flags
487 in the test cases. CAn differ from the usage flags
488 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200489 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100490 super().__init__(**kwargs)
491 self.description = description #type: str
Gilles Peskined9af9782022-03-17 22:32:59 +0100492 if expected_usage is None:
493 self.expected_usage = self.usage #type: psa_storage.Expr
494 elif expected_usage:
495 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
496 else:
497 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200498
Gilles Peskine897dff92021-03-10 15:03:44 +0100499class StorageFormat:
500 """Storage format stability test cases."""
501
502 def __init__(self, info: Information, version: int, forward: bool) -> None:
503 """Prepare to generate test cases for storage format stability.
504
505 * `info`: information about the API. See the `Information` class.
506 * `version`: the storage format version to generate test cases for.
507 * `forward`: if true, generate forward compatibility test cases which
508 save a key and check that its representation is as intended. Otherwise
509 generate backward compatibility test cases which inject a key
510 representation and check that it can be read and used.
511 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200512 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
513 self.version = version #type: int
514 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100515
Gilles Peskine32611242022-03-19 12:09:13 +0100516 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine8ddced52022-03-19 15:36:09 +0100517 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine32611242022-03-19 12:09:13 +0100518 @classmethod
Gilles Peskine8ddced52022-03-19 15:36:09 +0100519 def exercise_key_with_algorithm(
Gilles Peskine32611242022-03-19 12:09:13 +0100520 cls,
521 key_type: psa_storage.Expr, bits: int,
522 alg: psa_storage.Expr
523 ) -> bool:
Gilles Peskine8ddced52022-03-19 15:36:09 +0100524 """Whether to the given key with the given algorithm.
Gilles Peskine32611242022-03-19 12:09:13 +0100525
526 Normally only the type and algorithm matter for compatibility, and
527 this is handled in crypto_knowledge.KeyType.can_do(). This function
528 exists to detect exceptional cases. Exceptional cases detected here
529 are not tested in OpFail and should therefore have manually written
530 test cases.
531 """
Gilles Peskine8ddced52022-03-19 15:36:09 +0100532 # Some test keys have the RAW_DATA type and attributes that don't
533 # necessarily make sense. We do this to validate numerical
534 # encodings of the attributes.
535 # Raw data keys have no useful exercise anyway so there is no
536 # loss of test coverage.
537 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
538 return False
Gilles Peskinec7686002022-04-20 16:31:37 +0200539 # Mbed TLS only supports 128-bit keys for RC4.
540 if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
541 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100542 # OAEP requires room for two hashes plus wrapping
543 m = cls.RSA_OAEP_RE.match(alg.string)
544 if m:
545 hash_alg = m.group(1)
546 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
547 key_length = (bits + 7) // 8
548 # Leave enough room for at least one byte of plaintext
549 return key_length > 2 * hash_length + 2
Gilles Peskine8ddced52022-03-19 15:36:09 +0100550 # There's nothing wrong with ECC keys on Brainpool curves,
551 # but operations with them are very slow. So we only exercise them
552 # with a single algorithm, not with all possible hashes. We do
553 # exercise other curves with all algorithms so test coverage is
554 # perfectly adequate like this.
555 m = cls.BRAINPOOL_RE.match(key_type.string)
556 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
557 return False
Gilles Peskine32611242022-03-19 12:09:13 +0100558 return True
559
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200560 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100561 """Construct a storage format test case for the given key.
562
563 If ``forward`` is true, generate a forward compatibility test case:
564 create a key and validate that it has the expected representation.
565 Otherwise generate a backward compatibility test case: inject the
566 key representation into storage and validate that it can be read
567 correctly.
568 """
569 verb = 'save' if self.forward else 'read'
570 tc = test_case.TestCase()
Gilles Peskine930ccef2022-03-18 00:02:15 +0100571 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100572 dependencies = automatic_dependencies(
573 key.lifetime.string, key.type.string,
Gilles Peskined9af9782022-03-17 22:32:59 +0100574 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100575 )
576 dependencies = finish_family_dependencies(dependencies, key.bits)
577 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100578 tc.set_function('key_storage_' + verb)
579 if self.forward:
580 extra_arguments = []
581 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200582 flags = []
Gilles Peskine8ddced52022-03-19 15:36:09 +0100583 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine643eb832021-04-21 20:11:33 +0200584 flags.append('TEST_FLAG_EXERCISE')
585 if 'READ_ONLY' in key.lifetime.string:
586 flags.append('TEST_FLAG_READ_ONLY')
587 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100588 tc.set_arguments([key.lifetime.string,
589 key.type.string, str(key.bits),
Gilles Peskined9af9782022-03-17 22:32:59 +0100590 key.expected_usage.string,
591 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100592 '"' + key.material.hex() + '"',
593 '"' + key.hex() + '"',
594 *extra_arguments])
595 return tc
596
Gilles Peskineefb584d2021-04-21 22:05:34 +0200597 def key_for_lifetime(
598 self,
599 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200600 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200601 """Construct a test key for the given lifetime."""
602 short = lifetime
603 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
604 r'', short)
Gilles Peskined79aef52022-03-17 23:42:25 +0100605 short = crypto_knowledge.short_expression(short)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200606 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200607 key = StorageTestData(version=self.version,
608 id=1, lifetime=lifetime,
609 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100610 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200611 material=b'L',
612 description=description)
613 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200614
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200615 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200616 """Generate test keys covering lifetimes."""
617 lifetimes = sorted(self.constructors.lifetimes)
618 expressions = self.constructors.generate_expressions(lifetimes)
619 for lifetime in expressions:
620 # Don't attempt to create or load a volatile key in storage
621 if 'VOLATILE' in lifetime:
622 continue
623 # Don't attempt to create a read-only key in storage,
624 # but do attempt to load one.
625 if 'READ_ONLY' in lifetime and self.forward:
626 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200627 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200628
Gilles Peskinea296e482022-02-24 18:58:08 +0100629 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100630 self,
631 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200632 short: Optional[str] = None,
Gilles Peskinea296e482022-02-24 18:58:08 +0100633 test_implicit_usage: Optional[bool] = True
634 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100635 """Construct a test key for the given key usage."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100636 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskined9af9782022-03-17 22:32:59 +0100637 description = 'usage' + extra_desc + ': '
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200638 key1 = StorageTestData(version=self.version,
639 id=1, lifetime=0x00000001,
640 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskined9af9782022-03-17 22:32:59 +0100641 expected_usage=usage_flags,
Gilles Peskinea296e482022-02-24 18:58:08 +0100642 without_implicit_usage=not test_implicit_usage,
Gilles Peskined9af9782022-03-17 22:32:59 +0100643 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200644 material=b'K',
645 description=description)
Gilles Peskined9af9782022-03-17 22:32:59 +0100646 if short is None:
Gilles Peskined79aef52022-03-17 23:42:25 +0100647 usage_expr = key1.expected_usage.string
648 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskined9af9782022-03-17 22:32:59 +0100649 else:
650 key1.description += short
Gilles Peskinea296e482022-02-24 18:58:08 +0100651 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100652
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200653 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100654 """Generate test keys covering usage flags."""
655 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100656 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200657 for usage_flag in known_flags:
Gilles Peskinea296e482022-02-24 18:58:08 +0100658 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200659 for flag1, flag2 in zip(known_flags,
660 known_flags[1:] + [known_flags[0]]):
Gilles Peskinea296e482022-02-24 18:58:08 +0100661 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200662
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200663 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200664 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinea296e482022-02-24 18:58:08 +0100665 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200666
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200667 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200668 yield from self.generate_keys_for_usage_flags()
669 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100670
Gilles Peskine6213a002021-04-29 22:28:07 +0200671 def key_for_type_and_alg(
672 self,
673 kt: crypto_knowledge.KeyType,
674 bits: int,
675 alg: Optional[crypto_knowledge.Algorithm] = None,
676 ) -> StorageTestData:
677 """Construct a test key of the given type.
678
679 If alg is not None, this key allows it.
680 """
Gilles Peskined9af9782022-03-17 22:32:59 +0100681 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine0de11432022-03-18 09:58:09 +0100682 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine6213a002021-04-29 22:28:07 +0200683 alg2 = 0
Gilles Peskine0de11432022-03-18 09:58:09 +0100684 if alg is not None:
685 alg1 = alg.expression
686 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine6213a002021-04-29 22:28:07 +0200687 key_material = kt.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100688 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine6213a002021-04-29 22:28:07 +0200689 if alg is not None:
Gilles Peskine930ccef2022-03-18 00:02:15 +0100690 description += ', ' + alg.short_expression(1)
Gilles Peskine6213a002021-04-29 22:28:07 +0200691 key = StorageTestData(version=self.version,
692 id=1, lifetime=0x00000001,
693 type=kt.expression, bits=bits,
694 usage=usage_flags, alg=alg1, alg2=alg2,
695 material=key_material,
696 description=description)
697 return key
698
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100699 def keys_for_type(
700 self,
701 key_type: str,
Gilles Peskine6213a002021-04-29 22:28:07 +0200702 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200703 ) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200704 """Generate test keys for the given key type."""
705 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100706 for bits in kt.sizes_to_test():
Gilles Peskine6213a002021-04-29 22:28:07 +0200707 # Test a non-exercisable key, as well as exercisable keys for
708 # each compatible algorithm.
709 # To do: test reading a key from storage with an incompatible
710 # or unsupported algorithm.
711 yield self.key_for_type_and_alg(kt, bits)
712 compatible_algorithms = [alg for alg in all_algorithms
713 if kt.can_do(alg)]
714 for alg in compatible_algorithms:
715 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100716
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200717 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100718 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200719 key_types = sorted(self.constructors.key_types)
Gilles Peskine6213a002021-04-29 22:28:07 +0200720 all_algorithms = [crypto_knowledge.Algorithm(alg)
721 for alg in self.constructors.generate_expressions(
722 sorted(self.constructors.algorithms)
723 )]
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200724 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine6213a002021-04-29 22:28:07 +0200725 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100726
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200727 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine6213a002021-04-29 22:28:07 +0200728 """Generate test keys for the encoding of the specified algorithm."""
729 # These test cases only validate the encoding of algorithms, not
730 # whether the key read from storage is suitable for an operation.
731 # `keys_for_types` generate read tests with an algorithm and a
732 # compatible key.
Gilles Peskine930ccef2022-03-18 00:02:15 +0100733 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskined9af9782022-03-17 22:32:59 +0100734 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200735 key1 = StorageTestData(version=self.version,
736 id=1, lifetime=0x00000001,
737 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
738 usage=usage, alg=alg, alg2=0,
739 material=b'K',
740 description='alg: ' + descr)
741 yield key1
742 key2 = StorageTestData(version=self.version,
743 id=1, lifetime=0x00000001,
744 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
745 usage=usage, alg=0, alg2=alg,
746 material=b'L',
747 description='alg2: ' + descr)
748 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100749
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200750 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100751 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200752 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200753 for alg in self.constructors.generate_expressions(algorithms):
754 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100755
gabor-mezei-armea840de2021-06-29 15:42:57 +0200756 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200757 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200758 yield from self.all_keys_for_lifetimes()
759 yield from self.all_keys_for_usage_flags()
760 yield from self.all_keys_for_types()
761 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200762
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200763 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100764 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200765 # First build a list of all keys, then construct all the corresponding
766 # test cases. This allows all required information to be obtained in
767 # one go, which is a significant performance gain as the information
768 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200769 all_keys = list(self.generate_all_keys())
770 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200771 if key.location_value() != 0:
772 # Skip keys with a non-default location, because they
773 # require a driver and we currently have no mechanism to
774 # determine whether a driver is available.
775 continue
776 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100777
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200778class StorageFormatForward(StorageFormat):
779 """Storage format stability test cases for forward compatibility."""
780
781 def __init__(self, info: Information, version: int) -> None:
782 super().__init__(info, version, True)
783
784class StorageFormatV0(StorageFormat):
785 """Storage format stability test cases for version 0 compatibility."""
786
787 def __init__(self, info: Information) -> None:
788 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100789
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200790 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200791 """Generate test keys covering usage flags."""
Gilles Peskinea296e482022-02-24 18:58:08 +0100792 yield from super().all_keys_for_usage_flags()
793 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200794
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200795 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200796 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200797 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200798 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200799 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200800 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200801 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200802 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200803 algorithm and key type combination.
804 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200805 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200806 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskined9af9782022-03-17 22:32:59 +0100807 usage_flags = ['PSA_KEY_USAGE_EXPORT']
808 material_usage_flags = usage_flags + [implyer_usage]
809 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200810 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200811 key_material = key_type.key_material(bits)
Gilles Peskine930ccef2022-03-18 00:02:15 +0100812 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
813 alg_expression = crypto_knowledge.short_expression(alg, 1)
814 key_type_expression = key_type.short_expression(1)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200815 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200816 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200817 key = StorageTestData(version=self.version,
818 id=1, lifetime=0x00000001,
819 type=key_type.expression, bits=bits,
820 usage=material_usage_flags,
821 expected_usage=expected_usage_flags,
822 without_implicit_usage=True,
823 alg=alg, alg2=alg2,
824 material=key_material,
825 description=description)
826 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200827
828 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200829 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200830 """Match possible key types for sign algorithms."""
Shaun Case0e7791f2021-12-20 21:14:10 -0800831 # To create a valid combination both the algorithms and key types
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200832 # must be filtered. Pair them with keywords created from its names.
833 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
834 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
835 keyword_translation = {
836 'ECDSA': 'ECC',
837 'ED[0-9]*.*' : 'EDWARDS'
838 }
839 exclusive_keywords = {
840 'EDWARDS': 'ECC'
841 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200842 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
843 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200844 alg_with_keys = {} #type: Dict[str, List[str]]
845 translation_table = str.maketrans('(', '_', ')')
846 for alg in algorithms:
847 # Generate keywords from the name of the algorithm
848 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
849 # Translate keywords for better matching with the key types
850 for keyword in alg_keywords.copy():
851 for pattern, replace in keyword_translation.items():
852 if re.match(pattern, keyword):
853 alg_keywords.remove(keyword)
854 alg_keywords.add(replace)
Shaun Case0e7791f2021-12-20 21:14:10 -0800855 # Filter out incompatible algorithms
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200856 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
857 continue
858
859 for key_type in key_types:
860 # Generate keywords from the of the key type
861 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
862
Shaun Case0e7791f2021-12-20 21:14:10 -0800863 # Remove ambiguous keywords
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200864 for keyword1, keyword2 in exclusive_keywords.items():
865 if keyword1 in key_type_keywords:
866 key_type_keywords.remove(keyword2)
867
868 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
869 not key_type_keywords.isdisjoint(alg_keywords):
870 if alg in alg_with_keys:
871 alg_with_keys[alg].append(key_type)
872 else:
873 alg_with_keys[alg] = [key_type]
874 return alg_with_keys
875
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200876 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200877 """Generate test keys for usage flag extensions."""
878 # Generate a key type and algorithm pair for each extendable usage
879 # flag to generate a valid key for exercising. The key is generated
Shaun Case0e7791f2021-12-20 21:14:10 -0800880 # without usage extension to check the extension compatibility.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200881 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200882
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200883 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
884 for alg in sorted(alg_with_keys):
885 for key_type in sorted(alg_with_keys[alg]):
886 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200887 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine989c13d2022-03-17 12:52:24 +0100888 if kt.is_public() and '_SIGN_' in usage:
889 # Can't sign with a public key
890 continue
891 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200892
gabor-mezei-armea840de2021-06-29 15:42:57 +0200893 def generate_all_keys(self) -> Iterator[StorageTestData]:
894 yield from super().generate_all_keys()
895 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200896
Werner Lewisdcad1e92022-08-24 11:30:03 +0100897class PSATestGenerator(test_generation.TestGenerator):
898 """Test generator subclass including PSA targets and info."""
Dave Rodgmanbeb5ad72022-04-22 14:52:41 +0100899 # Note that targets whose names contain 'test_format' have their content
Gilles Peskinecfd4fae2021-04-23 16:37:12 +0200900 # validated by `abi_check.py`.
Werner Lewis0d07e862022-09-02 11:56:34 +0100901 targets = {
Przemyslaw Stekiel997caf82021-10-15 15:21:51 +0200902 'test_suite_psa_crypto_generate_key.generated':
903 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100904 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100905 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec05158b2021-04-27 20:40:10 +0200906 'test_suite_psa_crypto_op_fail.generated':
907 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100908 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200909 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100910 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200911 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100912 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
913
Werner Lewisdcad1e92022-08-24 11:30:03 +0100914 def __init__(self, options):
915 super().__init__(options)
916 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100917
Werner Lewisdcad1e92022-08-24 11:30:03 +0100918 def generate_target(self, name: str, *target_args) -> None:
919 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100920
921if __name__ == '__main__':
Werner Lewisdcad1e92022-08-24 11:30:03 +0100922 test_generation.main(sys.argv[1:], PSATestGenerator)