blob: b15a7eb989f1ae92307cd3add728b8276acfdec4 [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 Peskinecba28a72022-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 Lewisfbb75e32022-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
Gilles Peskine64f2efd2022-09-16 21:41:47 +020033from mbedtls_dev import test_data_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."""
Valerio Setti64f790f2023-05-26 13:50:20 +020038 # PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR symbols are deprecated and they should
39 # be replaced soon with newer PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR_yyy in
40 # library's code and tests. Until this happen though, they have been
41 # renamed to temporary internal symbols
42 # MBEDTLS_PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR_LEGACY so this is what must
43 # be used in tests' dependencies.
44 if name.endswith('RSA_KEY_PAIR') or name.endswith('ECC_KEY_PAIR'):
45 return 'MBEDTLS_' + name[:4] + 'WANT_' + name[4:] + '_LEGACY'
Gilles Peskineaf172842021-01-27 18:24:48 +010046 if name.startswith('PSA_'):
47 return name[:4] + 'WANT_' + name[4:]
48 else:
49 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
50
Gilles Peskine7f756872021-02-16 12:13:12 +010051def finish_family_dependency(dep: str, bits: int) -> str:
52 """Finish dep if it's a family dependency symbol prefix.
53
54 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
55 qualified by the key size. If dep is such a symbol, finish it by adjusting
56 the prefix and appending the key size. Other symbols are left unchanged.
57 """
58 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
59
60def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
61 """Finish any family dependency symbol prefixes.
62
63 Apply `finish_family_dependency` to each element of `dependencies`.
64 """
65 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010066
Gilles Peskinec5d086f2021-04-20 23:23:45 +020067SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
68 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
69 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
70 'PSA_ALG_ANY_HASH', # only in policies
71 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
72 'PSA_ALG_KEY_AGREEMENT', # chaining
73 'PSA_ALG_TRUNCATED_MAC', # modifier
74])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010075def automatic_dependencies(*expressions: str) -> List[str]:
76 """Infer dependencies of a test case by looking for PSA_xxx symbols.
77
78 The arguments are strings which should be C expressions. Do not use
79 string literals or comments as this function is not smart enough to
80 skip them.
81 """
82 used = set()
83 for expr in expressions:
84 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020085 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010086 return sorted(psa_want_symbol(name) for name in used)
87
Yanray Wang3f417442023-04-21 14:29:16 +080088# Define set of regular expressions and dependencies to optionally append
89# extra dependencies for test case.
90AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
91AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
92
93DEPENDENCY_FROM_KEY = {
94 AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
95}#type: Dict[str, List[str]]
Yanray Wang5dd429c2023-05-10 09:58:46 +080096def generate_key_dependencies(description: str) -> List[str]:
Yanray Wang3f417442023-04-21 14:29:16 +080097 """Return additional dependencies based on pairs of REGEX and dependencies.
98 """
99 deps = []
100 for regex, dep in DEPENDENCY_FROM_KEY.items():
101 if re.search(regex, description):
102 deps += dep
103
104 return deps
105
Gilles Peskined169d602021-02-16 14:16:25 +0100106# A temporary hack: at the time of writing, not all dependency symbols
107# are implemented yet. Skip test cases for which the dependency symbols are
108# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove1797b052022-12-04 17:19:59 +0000109# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +0100110# failure.
111def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
112 return frozenset(symbol
113 for line in open(filename)
114 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200115_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +0100116def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200117 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
118 if _implemented_dependencies is None:
119 _implemented_dependencies = \
120 read_implemented_dependencies('include/psa/crypto_config.h')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200121 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100122 for dep in dependencies):
123 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
124
Gilles Peskine14e428f2021-01-26 22:19:21 +0100125
Gilles Peskineb94ea512021-03-10 02:12:08 +0100126class Information:
127 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100128
Gilles Peskineb94ea512021-03-10 02:12:08 +0100129 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100130 self.constructors = self.read_psa_interface()
131
132 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100133 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200134 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100135 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200136 # Mbed TLS doesn't support finite-field DH yet and will not support
137 # finite-field DSA. Don't attempt to generate any related test case.
138 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
139 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100140 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
141 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100142
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200143 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100144 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200145 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100146 header_file_names = ['include/psa/crypto_values.h',
147 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200148 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100149 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200150 constructors.parse_header(header_file_name)
151 for test_cases in test_suites:
152 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100153 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200154 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100155 return constructors
156
Gilles Peskine14e428f2021-01-26 22:19:21 +0100157
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200158def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100159 verb: str, key_type: str, bits: int,
160 dependencies: List[str],
161 *args: str,
162 param_descr: str = ''
163) -> test_case.TestCase:
164 """Return one test case exercising a key creation method
165 for an unsupported key type or size.
166 """
167 hack_dependencies_not_implemented(dependencies)
168 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100169 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100170 adverb = 'not' if dependencies else 'never'
171 if param_descr:
172 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200173 tc.set_description('PSA {} {} {}-bit {} supported'
174 .format(verb, short_key_type, bits, adverb))
175 tc.set_dependencies(dependencies)
176 tc.set_function(verb + '_not_supported')
177 tc.set_arguments([key_type] + list(args))
178 return tc
179
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100180class KeyTypeNotSupported:
181 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100182
183 def __init__(self, info: Information) -> None:
184 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100185
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100186 ALWAYS_SUPPORTED = frozenset([
187 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100188 'PSA_KEY_TYPE_PASSWORD',
189 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100190 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200191 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100192 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100193 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100194 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100195 kt: crypto_knowledge.KeyType,
196 param: Optional[int] = None,
197 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100198 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200199 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100200
201 If param is present and not None, emit test cases conditioned on this
202 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200203 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100204 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100205 if kt.name in self.ALWAYS_SUPPORTED:
206 # Don't generate test cases for key types that are always supported.
207 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100208 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100209 import_dependencies = [('!' if param is None else '') +
210 psa_want_symbol(kt.name)]
211 if kt.params is not None:
212 import_dependencies += [('!' if param == i else '') +
213 psa_want_symbol(sym)
214 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100215 if kt.name.endswith('_PUBLIC_KEY'):
216 generate_dependencies = []
217 else:
218 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200220 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100221 'import', kt.expression, bits,
222 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100223 test_case.hex_string(kt.key_material(bits)),
224 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100225 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 if not generate_dependencies and param is not None:
227 # If generation is impossible for this key type, rather than
228 # supported or not depending on implementation capabilities,
229 # only generate the test case once.
230 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100231 # For public key we expect that key generation fails with
232 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100233 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200234 yield test_case_for_key_type_not_supported(
235 'generate', kt.expression, bits,
236 finish_family_dependencies(generate_dependencies, bits),
237 str(bits),
238 param_descr=param_descr,
239 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100240 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100241
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200242 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
243 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
244
Gilles Peskine3d778392021-02-17 15:11:05 +0100245 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100246 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100247 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200248 if key_type in self.ECC_KEY_TYPES:
249 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100250 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100251 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100252 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200253 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100254 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100255 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100256 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100257 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100258 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100259
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200260def test_case_for_key_generation(
261 key_type: str, bits: int,
262 dependencies: List[str],
263 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200264 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200265) -> test_case.TestCase:
266 """Return one test case exercising a key generation.
267 """
268 hack_dependencies_not_implemented(dependencies)
269 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100270 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200271 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200272 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200273 tc.set_dependencies(dependencies)
274 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100275 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200276
277 return tc
278
279class KeyGenerate:
280 """Generate positive and negative (invalid argument) test cases for key generation."""
281
282 def __init__(self, info: Information) -> None:
283 self.constructors = info.constructors
284
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200285 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
286 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
287
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100288 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200289 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200290 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200291 ) -> Iterator[test_case.TestCase]:
292 """Return test cases exercising key generation.
293
294 All key types can be generated except for public keys. For public key
295 PSA_ERROR_INVALID_ARGUMENT status is expected.
296 """
297 result = 'PSA_SUCCESS'
298
299 import_dependencies = [psa_want_symbol(kt.name)]
300 if kt.params is not None:
301 import_dependencies += [psa_want_symbol(sym)
302 for i, sym in enumerate(kt.params)]
303 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100304 # The library checks whether the key type is a public key generically,
305 # before it reaches a point where it needs support for the specific key
306 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200307 generate_dependencies = []
308 result = 'PSA_ERROR_INVALID_ARGUMENT'
309 else:
310 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100311 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200312 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313 for bits in kt.sizes_to_test():
314 yield test_case_for_key_generation(
315 kt.expression, bits,
316 finish_family_dependencies(generate_dependencies, bits),
317 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200318 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200319 )
320
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200321 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
322 """Generate test cases that exercise the generation of keys."""
323 for key_type in sorted(self.constructors.key_types):
324 if key_type in self.ECC_KEY_TYPES:
325 continue
326 kt = crypto_knowledge.KeyType(key_type)
327 yield from self.test_cases_for_key_type_key_generation(kt)
328 for curve_family in sorted(self.constructors.ecc_curves):
329 for constr in self.ECC_KEY_TYPES:
330 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200331 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200332
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200333class OpFail:
334 """Generate test cases for operations that must fail."""
335 #pylint: disable=too-few-public-methods
336
Gilles Peskinecba28a72022-03-15 17:26:33 +0100337 class Reason(enum.Enum):
338 NOT_SUPPORTED = 0
339 INVALID = 1
340 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200341 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100342
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200343 def __init__(self, info: Information) -> None:
344 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100345 key_type_expressions = self.constructors.generate_expressions(
346 sorted(self.constructors.key_types)
347 )
348 self.key_types = [crypto_knowledge.KeyType(kt_expr)
349 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200350
Gilles Peskinecba28a72022-03-15 17:26:33 +0100351 def make_test_case(
352 self,
353 alg: crypto_knowledge.Algorithm,
354 category: crypto_knowledge.AlgorithmCategory,
355 reason: 'Reason',
356 kt: Optional[crypto_knowledge.KeyType] = None,
357 not_deps: FrozenSet[str] = frozenset(),
358 ) -> test_case.TestCase:
359 """Construct a failure test case for a one-key or keyless operation."""
360 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200361 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100362 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200363 if reason == self.Reason.NOT_SUPPORTED:
364 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
365 for dep in not_deps]
366 pretty_reason = '!' + '&'.join(sorted(short_deps))
367 else:
368 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100369 if kt:
370 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100371 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200372 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100373 key_type = ''
374 pretty_type = ''
375 tc.set_description('PSA {} {}: {}{}'
376 .format(category.name.lower(),
377 pretty_alg,
378 pretty_reason,
379 ' with ' + pretty_type if pretty_type else ''))
380 dependencies = automatic_dependencies(alg.base_expression, key_type)
381 for i, dep in enumerate(dependencies):
382 if dep in not_deps:
383 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200384 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100385 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000386 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100387 if kt:
388 key_material = kt.key_material(kt.sizes_to_test()[0])
389 arguments += [key_type, test_case.hex_string(key_material)]
390 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200391 if category.is_asymmetric():
392 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100393 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
394 'INVALID_ARGUMENT')
395 arguments.append('PSA_ERROR_' + error)
396 tc.set_arguments(arguments)
397 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200398
Gilles Peskinecba28a72022-03-15 17:26:33 +0100399 def no_key_test_cases(
400 self,
401 alg: crypto_knowledge.Algorithm,
402 category: crypto_knowledge.AlgorithmCategory,
403 ) -> Iterator[test_case.TestCase]:
404 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200405 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100406 # Compatible operation, unsupported algorithm
407 for dep in automatic_dependencies(alg.base_expression):
408 yield self.make_test_case(alg, category,
409 self.Reason.NOT_SUPPORTED,
410 not_deps=frozenset([dep]))
411 else:
412 # Incompatible operation, supported algorithm
413 yield self.make_test_case(alg, category, self.Reason.INVALID)
414
415 def one_key_test_cases(
416 self,
417 alg: crypto_knowledge.Algorithm,
418 category: crypto_knowledge.AlgorithmCategory,
419 ) -> Iterator[test_case.TestCase]:
420 """Generate failure test cases for one-key operations with the specified algorithm."""
421 for kt in self.key_types:
422 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200423 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100424 # Compatible key and operation, unsupported algorithm
425 for dep in automatic_dependencies(alg.base_expression):
426 yield self.make_test_case(alg, category,
427 self.Reason.NOT_SUPPORTED,
428 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200429 # Public key for a private-key operation
430 if category.is_asymmetric() and kt.is_public():
431 yield self.make_test_case(alg, category,
432 self.Reason.PUBLIC,
433 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100434 elif key_is_compatible:
435 # Compatible key, incompatible operation, supported algorithm
436 yield self.make_test_case(alg, category,
437 self.Reason.INVALID,
438 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200439 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100440 # Incompatible key, compatible operation, supported algorithm
441 yield self.make_test_case(alg, category,
442 self.Reason.INCOMPATIBLE,
443 kt=kt)
444 else:
445 # Incompatible key and operation. Don't test cases where
446 # multiple things are wrong, to keep the number of test
447 # cases reasonable.
448 pass
449
450 def test_cases_for_algorithm(
451 self,
452 alg: crypto_knowledge.Algorithm,
453 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200454 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100455 for category in crypto_knowledge.AlgorithmCategory:
456 if category == crypto_knowledge.AlgorithmCategory.PAKE:
457 # PAKE operations are not implemented yet
458 pass
459 elif category.requires_key():
460 yield from self.one_key_test_cases(alg, category)
461 else:
462 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200463
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200464 def all_test_cases(self) -> Iterator[test_case.TestCase]:
465 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200466 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100467 for expr in self.constructors.generate_expressions(algorithms):
468 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200469 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200470
471
Gilles Peskine897dff92021-03-10 15:03:44 +0100472class StorageKey(psa_storage.Key):
473 """Representation of a key for storage format testing."""
474
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200475 IMPLICIT_USAGE_FLAGS = {
476 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
477 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
478 } #type: Dict[str, str]
479 """Mapping of usage flags to the flags that they imply."""
480
481 def __init__(
482 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100483 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200484 without_implicit_usage: Optional[bool] = False,
485 **kwargs
486 ) -> None:
487 """Prepare to generate a key.
488
489 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000490 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200491 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100492 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200493 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100494 for flag in sorted(usage_flags):
495 if flag in self.IMPLICIT_USAGE_FLAGS:
496 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
497 if usage_flags:
498 usage_expression = ' | '.join(sorted(usage_flags))
499 else:
500 usage_expression = '0'
501 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200502
503class StorageTestData(StorageKey):
504 """Representation of test case data for storage format testing."""
505
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200506 def __init__(
507 self,
508 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100509 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200510 **kwargs
511 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200512 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200513
Tom Cosgrove1797b052022-12-04 17:19:59 +0000514 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200515 * `expected_usage`: the usage flags generated as the expected usage flags
516 in the test cases. CAn differ from the usage flags
517 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200518 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100519 super().__init__(**kwargs)
520 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100521 if expected_usage is None:
522 self.expected_usage = self.usage #type: psa_storage.Expr
523 elif expected_usage:
524 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
525 else:
526 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200527
Gilles Peskine897dff92021-03-10 15:03:44 +0100528class StorageFormat:
529 """Storage format stability test cases."""
530
531 def __init__(self, info: Information, version: int, forward: bool) -> None:
532 """Prepare to generate test cases for storage format stability.
533
534 * `info`: information about the API. See the `Information` class.
535 * `version`: the storage format version to generate test cases for.
536 * `forward`: if true, generate forward compatibility test cases which
537 save a key and check that its representation is as intended. Otherwise
538 generate backward compatibility test cases which inject a key
539 representation and check that it can be read and used.
540 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200541 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
542 self.version = version #type: int
543 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100544
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100545 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100546 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100547 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100548 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100549 cls,
550 key_type: psa_storage.Expr, bits: int,
551 alg: psa_storage.Expr
552 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100553 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100554
555 Normally only the type and algorithm matter for compatibility, and
556 this is handled in crypto_knowledge.KeyType.can_do(). This function
557 exists to detect exceptional cases. Exceptional cases detected here
558 are not tested in OpFail and should therefore have manually written
559 test cases.
560 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100561 # Some test keys have the RAW_DATA type and attributes that don't
562 # necessarily make sense. We do this to validate numerical
563 # encodings of the attributes.
564 # Raw data keys have no useful exercise anyway so there is no
565 # loss of test coverage.
566 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
567 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100568 # OAEP requires room for two hashes plus wrapping
569 m = cls.RSA_OAEP_RE.match(alg.string)
570 if m:
571 hash_alg = m.group(1)
572 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
573 key_length = (bits + 7) // 8
574 # Leave enough room for at least one byte of plaintext
575 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100576 # There's nothing wrong with ECC keys on Brainpool curves,
577 # but operations with them are very slow. So we only exercise them
578 # with a single algorithm, not with all possible hashes. We do
579 # exercise other curves with all algorithms so test coverage is
580 # perfectly adequate like this.
581 m = cls.BRAINPOOL_RE.match(key_type.string)
582 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
583 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100584 return True
585
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200586 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100587 """Construct a storage format test case for the given key.
588
589 If ``forward`` is true, generate a forward compatibility test case:
590 create a key and validate that it has the expected representation.
591 Otherwise generate a backward compatibility test case: inject the
592 key representation into storage and validate that it can be read
593 correctly.
594 """
595 verb = 'save' if self.forward else 'read'
596 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100597 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100598 dependencies = automatic_dependencies(
599 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100600 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100601 )
602 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800603 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100604 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100605 tc.set_function('key_storage_' + verb)
606 if self.forward:
607 extra_arguments = []
608 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200609 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100610 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200611 flags.append('TEST_FLAG_EXERCISE')
612 if 'READ_ONLY' in key.lifetime.string:
613 flags.append('TEST_FLAG_READ_ONLY')
614 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100615 tc.set_arguments([key.lifetime.string,
616 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100617 key.expected_usage.string,
618 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100619 '"' + key.material.hex() + '"',
620 '"' + key.hex() + '"',
621 *extra_arguments])
622 return tc
623
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200624 def key_for_lifetime(
625 self,
626 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200627 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200628 """Construct a test key for the given lifetime."""
629 short = lifetime
630 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
631 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100632 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200633 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200634 key = StorageTestData(version=self.version,
635 id=1, lifetime=lifetime,
636 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100637 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200638 material=b'L',
639 description=description)
640 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200641
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200642 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200643 """Generate test keys covering lifetimes."""
644 lifetimes = sorted(self.constructors.lifetimes)
645 expressions = self.constructors.generate_expressions(lifetimes)
646 for lifetime in expressions:
647 # Don't attempt to create or load a volatile key in storage
648 if 'VOLATILE' in lifetime:
649 continue
650 # Don't attempt to create a read-only key in storage,
651 # but do attempt to load one.
652 if 'READ_ONLY' in lifetime and self.forward:
653 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200654 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200655
Gilles Peskinef7614272022-02-24 18:58:08 +0100656 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100657 self,
658 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200659 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100660 test_implicit_usage: Optional[bool] = True
661 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100662 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100663 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100664 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200665 key1 = StorageTestData(version=self.version,
666 id=1, lifetime=0x00000001,
667 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100668 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100669 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100670 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200671 material=b'K',
672 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100673 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100674 usage_expr = key1.expected_usage.string
675 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100676 else:
677 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100678 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100679
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200680 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100681 """Generate test keys covering usage flags."""
682 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100683 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200684 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100685 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200686 for flag1, flag2 in zip(known_flags,
687 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100688 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200689
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200690 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200691 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100692 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200693
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200694 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200695 yield from self.generate_keys_for_usage_flags()
696 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100697
Gilles Peskine7de7c102021-04-29 22:28:07 +0200698 def key_for_type_and_alg(
699 self,
700 kt: crypto_knowledge.KeyType,
701 bits: int,
702 alg: Optional[crypto_knowledge.Algorithm] = None,
703 ) -> StorageTestData:
704 """Construct a test key of the given type.
705
706 If alg is not None, this key allows it.
707 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100708 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100709 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200710 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100711 if alg is not None:
712 alg1 = alg.expression
713 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200714 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100715 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200716 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100717 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200718 key = StorageTestData(version=self.version,
719 id=1, lifetime=0x00000001,
720 type=kt.expression, bits=bits,
721 usage=usage_flags, alg=alg1, alg2=alg2,
722 material=key_material,
723 description=description)
724 return key
725
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100726 def keys_for_type(
727 self,
728 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200729 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200730 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200731 """Generate test keys for the given key type."""
732 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100733 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200734 # Test a non-exercisable key, as well as exercisable keys for
735 # each compatible algorithm.
736 # To do: test reading a key from storage with an incompatible
737 # or unsupported algorithm.
738 yield self.key_for_type_and_alg(kt, bits)
739 compatible_algorithms = [alg for alg in all_algorithms
740 if kt.can_do(alg)]
741 for alg in compatible_algorithms:
742 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100743
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200744 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100745 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200746 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200747 all_algorithms = [crypto_knowledge.Algorithm(alg)
748 for alg in self.constructors.generate_expressions(
749 sorted(self.constructors.algorithms)
750 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200751 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200752 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100753
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200754 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200755 """Generate test keys for the encoding of the specified algorithm."""
756 # These test cases only validate the encoding of algorithms, not
757 # whether the key read from storage is suitable for an operation.
758 # `keys_for_types` generate read tests with an algorithm and a
759 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100760 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100761 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200762 key1 = StorageTestData(version=self.version,
763 id=1, lifetime=0x00000001,
764 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
765 usage=usage, alg=alg, alg2=0,
766 material=b'K',
767 description='alg: ' + descr)
768 yield key1
769 key2 = StorageTestData(version=self.version,
770 id=1, lifetime=0x00000001,
771 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
772 usage=usage, alg=0, alg2=alg,
773 material=b'L',
774 description='alg2: ' + descr)
775 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100776
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200777 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100778 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200779 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200780 for alg in self.constructors.generate_expressions(algorithms):
781 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100782
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200783 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200784 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200785 yield from self.all_keys_for_lifetimes()
786 yield from self.all_keys_for_usage_flags()
787 yield from self.all_keys_for_types()
788 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200789
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200790 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100791 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200792 # First build a list of all keys, then construct all the corresponding
793 # test cases. This allows all required information to be obtained in
794 # one go, which is a significant performance gain as the information
795 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200796 all_keys = list(self.generate_all_keys())
797 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200798 if key.location_value() != 0:
799 # Skip keys with a non-default location, because they
800 # require a driver and we currently have no mechanism to
801 # determine whether a driver is available.
802 continue
803 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100804
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200805class StorageFormatForward(StorageFormat):
806 """Storage format stability test cases for forward compatibility."""
807
808 def __init__(self, info: Information, version: int) -> None:
809 super().__init__(info, version, True)
810
811class StorageFormatV0(StorageFormat):
812 """Storage format stability test cases for version 0 compatibility."""
813
814 def __init__(self, info: Information) -> None:
815 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100816
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200817 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200818 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100819 yield from super().all_keys_for_usage_flags()
820 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200821
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200822 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200823 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200824 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200825 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200826 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200827 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200828 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200829 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200830 algorithm and key type combination.
831 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200832 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200833 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100834 usage_flags = ['PSA_KEY_USAGE_EXPORT']
835 material_usage_flags = usage_flags + [implyer_usage]
836 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200837 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200838 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100839 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
840 alg_expression = crypto_knowledge.short_expression(alg, 1)
841 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200842 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200843 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200844 key = StorageTestData(version=self.version,
845 id=1, lifetime=0x00000001,
846 type=key_type.expression, bits=bits,
847 usage=material_usage_flags,
848 expected_usage=expected_usage_flags,
849 without_implicit_usage=True,
850 alg=alg, alg2=alg2,
851 material=key_material,
852 description=description)
853 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200854
855 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200856 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200857 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800858 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200859 # must be filtered. Pair them with keywords created from its names.
860 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
861 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
862 keyword_translation = {
863 'ECDSA': 'ECC',
864 'ED[0-9]*.*' : 'EDWARDS'
865 }
866 exclusive_keywords = {
867 'EDWARDS': 'ECC'
868 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200869 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
870 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200871 alg_with_keys = {} #type: Dict[str, List[str]]
872 translation_table = str.maketrans('(', '_', ')')
873 for alg in algorithms:
874 # Generate keywords from the name of the algorithm
875 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
876 # Translate keywords for better matching with the key types
877 for keyword in alg_keywords.copy():
878 for pattern, replace in keyword_translation.items():
879 if re.match(pattern, keyword):
880 alg_keywords.remove(keyword)
881 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800882 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200883 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
884 continue
885
886 for key_type in key_types:
887 # Generate keywords from the of the key type
888 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
889
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800890 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200891 for keyword1, keyword2 in exclusive_keywords.items():
892 if keyword1 in key_type_keywords:
893 key_type_keywords.remove(keyword2)
894
895 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
896 not key_type_keywords.isdisjoint(alg_keywords):
897 if alg in alg_with_keys:
898 alg_with_keys[alg].append(key_type)
899 else:
900 alg_with_keys[alg] = [key_type]
901 return alg_with_keys
902
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200903 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200904 """Generate test keys for usage flag extensions."""
905 # Generate a key type and algorithm pair for each extendable usage
906 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800907 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200909
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200910 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
911 for alg in sorted(alg_with_keys):
912 for key_type in sorted(alg_with_keys[alg]):
913 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200914 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100915 if kt.is_public() and '_SIGN_' in usage:
916 # Can't sign with a public key
917 continue
918 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200919
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200920 def generate_all_keys(self) -> Iterator[StorageTestData]:
921 yield from super().generate_all_keys()
922 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200923
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200924class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100925 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100926 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200927 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100928 targets = {
Przemyslaw Stekiel1b0978b2021-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 Peskine0e9e4422022-12-15 22:14:28 +0100932 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-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-arma4102cb2021-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-arma4102cb2021-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
Werner Lewisfbb75e32022-08-24 11:30:03 +0100941 def __init__(self, options):
942 super().__init__(options)
943 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100944
Werner Lewisfbb75e32022-08-24 11:30:03 +0100945 def generate_target(self, name: str, *target_args) -> None:
946 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100947
948if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200949 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)