blob: fe4649ee20be8c0a104daf56c6db9e5446ddd1ae [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.
Gilles Peskineaf172842021-01-27 18:24:48 +010044 if name.startswith('PSA_'):
45 return name[:4] + 'WANT_' + name[4:]
46 else:
47 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
48
Gilles Peskine7f756872021-02-16 12:13:12 +010049def finish_family_dependency(dep: str, bits: int) -> str:
50 """Finish dep if it's a family dependency symbol prefix.
51
52 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
53 qualified by the key size. If dep is such a symbol, finish it by adjusting
54 the prefix and appending the key size. Other symbols are left unchanged.
55 """
56 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
57
58def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
59 """Finish any family dependency symbol prefixes.
60
61 Apply `finish_family_dependency` to each element of `dependencies`.
62 """
63 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010064
Gilles Peskinec5d086f2021-04-20 23:23:45 +020065SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
66 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
67 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
68 'PSA_ALG_ANY_HASH', # only in policies
69 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
70 'PSA_ALG_KEY_AGREEMENT', # chaining
71 'PSA_ALG_TRUNCATED_MAC', # modifier
72])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010073def automatic_dependencies(*expressions: str) -> List[str]:
74 """Infer dependencies of a test case by looking for PSA_xxx symbols.
75
76 The arguments are strings which should be C expressions. Do not use
77 string literals or comments as this function is not smart enough to
78 skip them.
79 """
80 used = set()
81 for expr in expressions:
82 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020083 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010084 return sorted(psa_want_symbol(name) for name in used)
85
Yanray Wang3f417442023-04-21 14:29:16 +080086# Define set of regular expressions and dependencies to optionally append
87# extra dependencies for test case.
88AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
89AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
90
91DEPENDENCY_FROM_KEY = {
92 AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
93}#type: Dict[str, List[str]]
Yanray Wang5dd429c2023-05-10 09:58:46 +080094def generate_key_dependencies(description: str) -> List[str]:
Yanray Wang3f417442023-04-21 14:29:16 +080095 """Return additional dependencies based on pairs of REGEX and dependencies.
96 """
97 deps = []
98 for regex, dep in DEPENDENCY_FROM_KEY.items():
99 if re.search(regex, description):
100 deps += dep
101
102 return deps
103
Gilles Peskined169d602021-02-16 14:16:25 +0100104# A temporary hack: at the time of writing, not all dependency symbols
105# are implemented yet. Skip test cases for which the dependency symbols are
106# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove1797b052022-12-04 17:19:59 +0000107# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +0100108# failure.
109def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
110 return frozenset(symbol
111 for line in open(filename)
112 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200113_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +0100114def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200115 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
116 if _implemented_dependencies is None:
117 _implemented_dependencies = \
118 read_implemented_dependencies('include/psa/crypto_config.h')
Valerio Setti323ad1c2023-05-26 17:47:55 +0200119 if not all((dep.lstrip('!') in _implemented_dependencies or
Valerio Setti5ca80e72023-06-20 19:27:02 +0200120 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100121 for dep in dependencies):
122 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
123
Gilles Peskine14e428f2021-01-26 22:19:21 +0100124
Gilles Peskineb94ea512021-03-10 02:12:08 +0100125class Information:
126 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100127
Gilles Peskineb94ea512021-03-10 02:12:08 +0100128 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100129 self.constructors = self.read_psa_interface()
130
131 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100132 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200133 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100134 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200135 # Mbed TLS doesn't support finite-field DH yet and will not support
136 # finite-field DSA. Don't attempt to generate any related test case.
137 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
138 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100139 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
140 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100141
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200142 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100143 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200144 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100145 header_file_names = ['include/psa/crypto_values.h',
146 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200147 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100148 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200149 constructors.parse_header(header_file_name)
150 for test_cases in test_suites:
151 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100152 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200153 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100154 return constructors
155
Gilles Peskine14e428f2021-01-26 22:19:21 +0100156
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200157def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100158 verb: str, key_type: str, bits: int,
159 dependencies: List[str],
160 *args: str,
161 param_descr: str = ''
162) -> test_case.TestCase:
163 """Return one test case exercising a key creation method
164 for an unsupported key type or size.
165 """
166 hack_dependencies_not_implemented(dependencies)
167 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100168 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100169 adverb = 'not' if dependencies else 'never'
170 if param_descr:
171 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200172 tc.set_description('PSA {} {} {}-bit {} supported'
173 .format(verb, short_key_type, bits, adverb))
174 tc.set_dependencies(dependencies)
175 tc.set_function(verb + '_not_supported')
176 tc.set_arguments([key_type] + list(args))
177 return tc
178
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100179class KeyTypeNotSupported:
180 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100181
182 def __init__(self, info: Information) -> None:
183 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100184
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100185 ALWAYS_SUPPORTED = frozenset([
186 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100187 'PSA_KEY_TYPE_PASSWORD',
188 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100189 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200190 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100191 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100192 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100193 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100194 kt: crypto_knowledge.KeyType,
195 param: Optional[int] = None,
196 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100197 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200198 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100199
200 If param is present and not None, emit test cases conditioned on this
201 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200202 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100203 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100204 if kt.name in self.ALWAYS_SUPPORTED:
205 # Don't generate test cases for key types that are always supported.
206 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100207 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100208 import_dependencies = [('!' if param is None else '') +
209 psa_want_symbol(kt.name)]
210 if kt.params is not None:
211 import_dependencies += [('!' if param == i else '') +
212 psa_want_symbol(sym)
213 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214 if kt.name.endswith('_PUBLIC_KEY'):
215 generate_dependencies = []
216 else:
Valerio Setti5ca80e72023-06-20 19:27:02 +0200217 # Create a separate list so that we can work on them independently
218 # in the following.
219 generate_dependencies = [dep for dep in import_dependencies]
220 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have now a GENERATE and
221 # IMPORT suffixes to state that they support key generation and
222 # import, respectively.
223 for dep in import_dependencies:
224 if dep.endswith('KEY_PAIR'):
225 import_dependencies.remove(dep)
226 import_dependencies.append(dep + "_IMPORT")
227 for dep in generate_dependencies:
228 if dep.endswith('KEY_PAIR'):
229 generate_dependencies.remove(dep)
230 generate_dependencies.append(dep + "_GENERATE")
Gilles Peskine14e428f2021-01-26 22:19:21 +0100231 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200232 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100233 'import', kt.expression, bits,
234 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100235 test_case.hex_string(kt.key_material(bits)),
236 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100237 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100238 if not generate_dependencies and param is not None:
239 # If generation is impossible for this key type, rather than
240 # supported or not depending on implementation capabilities,
241 # only generate the test case once.
242 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100243 # For public key we expect that key generation fails with
244 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100245 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200246 yield test_case_for_key_type_not_supported(
247 'generate', kt.expression, bits,
248 finish_family_dependencies(generate_dependencies, bits),
249 str(bits),
250 param_descr=param_descr,
251 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100252 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100253
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200254 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
255 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
256
Gilles Peskine3d778392021-02-17 15:11:05 +0100257 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100258 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100259 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200260 if key_type in self.ECC_KEY_TYPES:
261 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100262 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100263 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100264 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200265 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100266 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100267 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100268 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100269 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100270 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100271
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200272def test_case_for_key_generation(
273 key_type: str, bits: int,
274 dependencies: List[str],
275 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200276 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200277) -> test_case.TestCase:
278 """Return one test case exercising a key generation.
279 """
280 hack_dependencies_not_implemented(dependencies)
281 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100282 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200283 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200284 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200285 tc.set_dependencies(dependencies)
286 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100287 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200288
289 return tc
290
291class KeyGenerate:
292 """Generate positive and negative (invalid argument) test cases for key generation."""
293
294 def __init__(self, info: Information) -> None:
295 self.constructors = info.constructors
296
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200297 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
298 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
299
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100300 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200301 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200302 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200303 ) -> Iterator[test_case.TestCase]:
304 """Return test cases exercising key generation.
305
306 All key types can be generated except for public keys. For public key
307 PSA_ERROR_INVALID_ARGUMENT status is expected.
308 """
309 result = 'PSA_SUCCESS'
310
311 import_dependencies = [psa_want_symbol(kt.name)]
312 if kt.params is not None:
313 import_dependencies += [psa_want_symbol(sym)
314 for i, sym in enumerate(kt.params)]
315 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100316 # The library checks whether the key type is a public key generically,
317 # before it reaches a point where it needs support for the specific key
318 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200319 generate_dependencies = []
320 result = 'PSA_ERROR_INVALID_ARGUMENT'
321 else:
322 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100323 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200324 generate_dependencies.append("MBEDTLS_GENPRIME")
Valerio Setti5ca80e72023-06-20 19:27:02 +0200325 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have now a GENERATE suffix
326 # to state that they support key generation.
327 if kt.name == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
328 generate_dependencies.remove(psa_want_symbol(kt.name))
329 generate_dependencies.append(psa_want_symbol(kt.name) + "_GENERATE")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200330 for bits in kt.sizes_to_test():
331 yield test_case_for_key_generation(
332 kt.expression, bits,
333 finish_family_dependencies(generate_dependencies, bits),
334 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200335 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200336 )
337
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200338 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
339 """Generate test cases that exercise the generation of keys."""
340 for key_type in sorted(self.constructors.key_types):
341 if key_type in self.ECC_KEY_TYPES:
342 continue
343 kt = crypto_knowledge.KeyType(key_type)
344 yield from self.test_cases_for_key_type_key_generation(kt)
345 for curve_family in sorted(self.constructors.ecc_curves):
346 for constr in self.ECC_KEY_TYPES:
347 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200348 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200349
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200350class OpFail:
351 """Generate test cases for operations that must fail."""
352 #pylint: disable=too-few-public-methods
353
Gilles Peskinecba28a72022-03-15 17:26:33 +0100354 class Reason(enum.Enum):
355 NOT_SUPPORTED = 0
356 INVALID = 1
357 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200358 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100359
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200360 def __init__(self, info: Information) -> None:
361 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100362 key_type_expressions = self.constructors.generate_expressions(
363 sorted(self.constructors.key_types)
364 )
365 self.key_types = [crypto_knowledge.KeyType(kt_expr)
366 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200367
Gilles Peskinecba28a72022-03-15 17:26:33 +0100368 def make_test_case(
369 self,
370 alg: crypto_knowledge.Algorithm,
371 category: crypto_knowledge.AlgorithmCategory,
372 reason: 'Reason',
373 kt: Optional[crypto_knowledge.KeyType] = None,
374 not_deps: FrozenSet[str] = frozenset(),
375 ) -> test_case.TestCase:
376 """Construct a failure test case for a one-key or keyless operation."""
377 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200378 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100379 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200380 if reason == self.Reason.NOT_SUPPORTED:
381 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
382 for dep in not_deps]
383 pretty_reason = '!' + '&'.join(sorted(short_deps))
384 else:
385 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100386 if kt:
387 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100388 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200389 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100390 key_type = ''
391 pretty_type = ''
392 tc.set_description('PSA {} {}: {}{}'
393 .format(category.name.lower(),
394 pretty_alg,
395 pretty_reason,
396 ' with ' + pretty_type if pretty_type else ''))
397 dependencies = automatic_dependencies(alg.base_expression, key_type)
398 for i, dep in enumerate(dependencies):
399 if dep in not_deps:
400 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200401 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100402 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000403 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100404 if kt:
405 key_material = kt.key_material(kt.sizes_to_test()[0])
406 arguments += [key_type, test_case.hex_string(key_material)]
407 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200408 if category.is_asymmetric():
409 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100410 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
411 'INVALID_ARGUMENT')
412 arguments.append('PSA_ERROR_' + error)
413 tc.set_arguments(arguments)
414 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200415
Gilles Peskinecba28a72022-03-15 17:26:33 +0100416 def no_key_test_cases(
417 self,
418 alg: crypto_knowledge.Algorithm,
419 category: crypto_knowledge.AlgorithmCategory,
420 ) -> Iterator[test_case.TestCase]:
421 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200422 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100423 # Compatible operation, unsupported algorithm
424 for dep in automatic_dependencies(alg.base_expression):
425 yield self.make_test_case(alg, category,
426 self.Reason.NOT_SUPPORTED,
427 not_deps=frozenset([dep]))
428 else:
429 # Incompatible operation, supported algorithm
430 yield self.make_test_case(alg, category, self.Reason.INVALID)
431
432 def one_key_test_cases(
433 self,
434 alg: crypto_knowledge.Algorithm,
435 category: crypto_knowledge.AlgorithmCategory,
436 ) -> Iterator[test_case.TestCase]:
437 """Generate failure test cases for one-key operations with the specified algorithm."""
438 for kt in self.key_types:
439 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200440 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100441 # Compatible key and operation, unsupported algorithm
442 for dep in automatic_dependencies(alg.base_expression):
443 yield self.make_test_case(alg, category,
444 self.Reason.NOT_SUPPORTED,
445 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200446 # Public key for a private-key operation
447 if category.is_asymmetric() and kt.is_public():
448 yield self.make_test_case(alg, category,
449 self.Reason.PUBLIC,
450 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100451 elif key_is_compatible:
452 # Compatible key, incompatible operation, supported algorithm
453 yield self.make_test_case(alg, category,
454 self.Reason.INVALID,
455 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200456 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100457 # Incompatible key, compatible operation, supported algorithm
458 yield self.make_test_case(alg, category,
459 self.Reason.INCOMPATIBLE,
460 kt=kt)
461 else:
462 # Incompatible key and operation. Don't test cases where
463 # multiple things are wrong, to keep the number of test
464 # cases reasonable.
465 pass
466
467 def test_cases_for_algorithm(
468 self,
469 alg: crypto_knowledge.Algorithm,
470 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200471 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100472 for category in crypto_knowledge.AlgorithmCategory:
473 if category == crypto_knowledge.AlgorithmCategory.PAKE:
474 # PAKE operations are not implemented yet
475 pass
476 elif category.requires_key():
477 yield from self.one_key_test_cases(alg, category)
478 else:
479 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200480
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200481 def all_test_cases(self) -> Iterator[test_case.TestCase]:
482 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200483 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100484 for expr in self.constructors.generate_expressions(algorithms):
485 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200486 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200487
488
Gilles Peskine897dff92021-03-10 15:03:44 +0100489class StorageKey(psa_storage.Key):
490 """Representation of a key for storage format testing."""
491
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200492 IMPLICIT_USAGE_FLAGS = {
493 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
494 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
495 } #type: Dict[str, str]
496 """Mapping of usage flags to the flags that they imply."""
497
498 def __init__(
499 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100500 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200501 without_implicit_usage: Optional[bool] = False,
502 **kwargs
503 ) -> None:
504 """Prepare to generate a key.
505
506 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000507 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200508 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100509 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200510 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100511 for flag in sorted(usage_flags):
512 if flag in self.IMPLICIT_USAGE_FLAGS:
513 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
514 if usage_flags:
515 usage_expression = ' | '.join(sorted(usage_flags))
516 else:
517 usage_expression = '0'
518 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200519
520class StorageTestData(StorageKey):
521 """Representation of test case data for storage format testing."""
522
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200523 def __init__(
524 self,
525 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100526 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200527 **kwargs
528 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200529 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200530
Tom Cosgrove1797b052022-12-04 17:19:59 +0000531 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200532 * `expected_usage`: the usage flags generated as the expected usage flags
533 in the test cases. CAn differ from the usage flags
534 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200535 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100536 super().__init__(**kwargs)
537 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100538 if expected_usage is None:
539 self.expected_usage = self.usage #type: psa_storage.Expr
540 elif expected_usage:
541 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
542 else:
543 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200544
Gilles Peskine897dff92021-03-10 15:03:44 +0100545class StorageFormat:
546 """Storage format stability test cases."""
547
548 def __init__(self, info: Information, version: int, forward: bool) -> None:
549 """Prepare to generate test cases for storage format stability.
550
551 * `info`: information about the API. See the `Information` class.
552 * `version`: the storage format version to generate test cases for.
553 * `forward`: if true, generate forward compatibility test cases which
554 save a key and check that its representation is as intended. Otherwise
555 generate backward compatibility test cases which inject a key
556 representation and check that it can be read and used.
557 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200558 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
559 self.version = version #type: int
560 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100561
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100562 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100563 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100564 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100565 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100566 cls,
567 key_type: psa_storage.Expr, bits: int,
568 alg: psa_storage.Expr
569 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100570 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100571
572 Normally only the type and algorithm matter for compatibility, and
573 this is handled in crypto_knowledge.KeyType.can_do(). This function
574 exists to detect exceptional cases. Exceptional cases detected here
575 are not tested in OpFail and should therefore have manually written
576 test cases.
577 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100578 # Some test keys have the RAW_DATA type and attributes that don't
579 # necessarily make sense. We do this to validate numerical
580 # encodings of the attributes.
581 # Raw data keys have no useful exercise anyway so there is no
582 # loss of test coverage.
583 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
584 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100585 # OAEP requires room for two hashes plus wrapping
586 m = cls.RSA_OAEP_RE.match(alg.string)
587 if m:
588 hash_alg = m.group(1)
589 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
590 key_length = (bits + 7) // 8
591 # Leave enough room for at least one byte of plaintext
592 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100593 # There's nothing wrong with ECC keys on Brainpool curves,
594 # but operations with them are very slow. So we only exercise them
595 # with a single algorithm, not with all possible hashes. We do
596 # exercise other curves with all algorithms so test coverage is
597 # perfectly adequate like this.
598 m = cls.BRAINPOOL_RE.match(key_type.string)
599 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
600 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100601 return True
602
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200603 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100604 """Construct a storage format test case for the given key.
605
606 If ``forward`` is true, generate a forward compatibility test case:
607 create a key and validate that it has the expected representation.
608 Otherwise generate a backward compatibility test case: inject the
609 key representation into storage and validate that it can be read
610 correctly.
611 """
612 verb = 'save' if self.forward else 'read'
613 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100614 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100615 dependencies = automatic_dependencies(
616 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100617 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100618 )
619 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800620 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100621 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100622 tc.set_function('key_storage_' + verb)
623 if self.forward:
624 extra_arguments = []
625 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200626 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100627 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200628 flags.append('TEST_FLAG_EXERCISE')
629 if 'READ_ONLY' in key.lifetime.string:
630 flags.append('TEST_FLAG_READ_ONLY')
631 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100632 tc.set_arguments([key.lifetime.string,
633 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100634 key.expected_usage.string,
635 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100636 '"' + key.material.hex() + '"',
637 '"' + key.hex() + '"',
638 *extra_arguments])
639 return tc
640
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200641 def key_for_lifetime(
642 self,
643 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200644 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200645 """Construct a test key for the given lifetime."""
646 short = lifetime
647 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
648 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100649 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200650 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 key = StorageTestData(version=self.version,
652 id=1, lifetime=lifetime,
653 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100654 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200655 material=b'L',
656 description=description)
657 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200658
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200659 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200660 """Generate test keys covering lifetimes."""
661 lifetimes = sorted(self.constructors.lifetimes)
662 expressions = self.constructors.generate_expressions(lifetimes)
663 for lifetime in expressions:
664 # Don't attempt to create or load a volatile key in storage
665 if 'VOLATILE' in lifetime:
666 continue
667 # Don't attempt to create a read-only key in storage,
668 # but do attempt to load one.
669 if 'READ_ONLY' in lifetime and self.forward:
670 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200671 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200672
Gilles Peskinef7614272022-02-24 18:58:08 +0100673 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100674 self,
675 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200676 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100677 test_implicit_usage: Optional[bool] = True
678 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100679 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100681 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200682 key1 = StorageTestData(version=self.version,
683 id=1, lifetime=0x00000001,
684 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100685 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100686 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100687 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200688 material=b'K',
689 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100690 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100691 usage_expr = key1.expected_usage.string
692 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100693 else:
694 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100695 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100696
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200697 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100698 """Generate test keys covering usage flags."""
699 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100700 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200701 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100702 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200703 for flag1, flag2 in zip(known_flags,
704 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200706
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200707 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200708 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100709 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200710
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200711 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200712 yield from self.generate_keys_for_usage_flags()
713 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100714
Gilles Peskine7de7c102021-04-29 22:28:07 +0200715 def key_for_type_and_alg(
716 self,
717 kt: crypto_knowledge.KeyType,
718 bits: int,
719 alg: Optional[crypto_knowledge.Algorithm] = None,
720 ) -> StorageTestData:
721 """Construct a test key of the given type.
722
723 If alg is not None, this key allows it.
724 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100725 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100726 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200727 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100728 if alg is not None:
729 alg1 = alg.expression
730 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200731 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100732 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200733 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100734 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 key = StorageTestData(version=self.version,
736 id=1, lifetime=0x00000001,
737 type=kt.expression, bits=bits,
738 usage=usage_flags, alg=alg1, alg2=alg2,
739 material=key_material,
740 description=description)
741 return key
742
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100743 def keys_for_type(
744 self,
745 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200746 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200747 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200748 """Generate test keys for the given key type."""
749 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100750 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 # Test a non-exercisable key, as well as exercisable keys for
752 # each compatible algorithm.
753 # To do: test reading a key from storage with an incompatible
754 # or unsupported algorithm.
755 yield self.key_for_type_and_alg(kt, bits)
756 compatible_algorithms = [alg for alg in all_algorithms
757 if kt.can_do(alg)]
758 for alg in compatible_algorithms:
759 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100760
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200761 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100762 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200763 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200764 all_algorithms = [crypto_knowledge.Algorithm(alg)
765 for alg in self.constructors.generate_expressions(
766 sorted(self.constructors.algorithms)
767 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200768 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200769 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100770
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200771 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200772 """Generate test keys for the encoding of the specified algorithm."""
773 # These test cases only validate the encoding of algorithms, not
774 # whether the key read from storage is suitable for an operation.
775 # `keys_for_types` generate read tests with an algorithm and a
776 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100777 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100778 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200779 key1 = StorageTestData(version=self.version,
780 id=1, lifetime=0x00000001,
781 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
782 usage=usage, alg=alg, alg2=0,
783 material=b'K',
784 description='alg: ' + descr)
785 yield key1
786 key2 = StorageTestData(version=self.version,
787 id=1, lifetime=0x00000001,
788 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
789 usage=usage, alg=0, alg2=alg,
790 material=b'L',
791 description='alg2: ' + descr)
792 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100793
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200794 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100795 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200796 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200797 for alg in self.constructors.generate_expressions(algorithms):
798 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100799
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200800 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200801 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200802 yield from self.all_keys_for_lifetimes()
803 yield from self.all_keys_for_usage_flags()
804 yield from self.all_keys_for_types()
805 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200806
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200807 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100808 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200809 # First build a list of all keys, then construct all the corresponding
810 # test cases. This allows all required information to be obtained in
811 # one go, which is a significant performance gain as the information
812 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200813 all_keys = list(self.generate_all_keys())
814 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200815 if key.location_value() != 0:
816 # Skip keys with a non-default location, because they
817 # require a driver and we currently have no mechanism to
818 # determine whether a driver is available.
819 continue
820 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100821
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200822class StorageFormatForward(StorageFormat):
823 """Storage format stability test cases for forward compatibility."""
824
825 def __init__(self, info: Information, version: int) -> None:
826 super().__init__(info, version, True)
827
828class StorageFormatV0(StorageFormat):
829 """Storage format stability test cases for version 0 compatibility."""
830
831 def __init__(self, info: Information) -> None:
832 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100833
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200834 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200835 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100836 yield from super().all_keys_for_usage_flags()
837 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200838
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200839 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200840 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200841 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200842 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200843 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200844 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200845 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200846 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200847 algorithm and key type combination.
848 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200849 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200850 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100851 usage_flags = ['PSA_KEY_USAGE_EXPORT']
852 material_usage_flags = usage_flags + [implyer_usage]
853 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200854 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200855 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100856 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
857 alg_expression = crypto_knowledge.short_expression(alg, 1)
858 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200859 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200860 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200861 key = StorageTestData(version=self.version,
862 id=1, lifetime=0x00000001,
863 type=key_type.expression, bits=bits,
864 usage=material_usage_flags,
865 expected_usage=expected_usage_flags,
866 without_implicit_usage=True,
867 alg=alg, alg2=alg2,
868 material=key_material,
869 description=description)
870 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200871
872 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200873 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800875 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200876 # must be filtered. Pair them with keywords created from its names.
877 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
878 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
879 keyword_translation = {
880 'ECDSA': 'ECC',
881 'ED[0-9]*.*' : 'EDWARDS'
882 }
883 exclusive_keywords = {
884 'EDWARDS': 'ECC'
885 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200886 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
887 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200888 alg_with_keys = {} #type: Dict[str, List[str]]
889 translation_table = str.maketrans('(', '_', ')')
890 for alg in algorithms:
891 # Generate keywords from the name of the algorithm
892 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
893 # Translate keywords for better matching with the key types
894 for keyword in alg_keywords.copy():
895 for pattern, replace in keyword_translation.items():
896 if re.match(pattern, keyword):
897 alg_keywords.remove(keyword)
898 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800899 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200900 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
901 continue
902
903 for key_type in key_types:
904 # Generate keywords from the of the key type
905 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
906
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800907 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 for keyword1, keyword2 in exclusive_keywords.items():
909 if keyword1 in key_type_keywords:
910 key_type_keywords.remove(keyword2)
911
912 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
913 not key_type_keywords.isdisjoint(alg_keywords):
914 if alg in alg_with_keys:
915 alg_with_keys[alg].append(key_type)
916 else:
917 alg_with_keys[alg] = [key_type]
918 return alg_with_keys
919
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200920 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200921 """Generate test keys for usage flag extensions."""
922 # Generate a key type and algorithm pair for each extendable usage
923 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800924 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200925 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200926
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200927 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
928 for alg in sorted(alg_with_keys):
929 for key_type in sorted(alg_with_keys[alg]):
930 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200931 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100932 if kt.is_public() and '_SIGN_' in usage:
933 # Can't sign with a public key
934 continue
935 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200936
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200937 def generate_all_keys(self) -> Iterator[StorageTestData]:
938 yield from super().generate_all_keys()
939 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200940
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200941class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100942 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100943 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200944 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100945 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200946 'test_suite_psa_crypto_generate_key.generated':
947 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100948 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100949 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200950 'test_suite_psa_crypto_op_fail.generated':
951 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100952 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200953 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100954 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200955 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100956 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
957
Werner Lewisfbb75e32022-08-24 11:30:03 +0100958 def __init__(self, options):
959 super().__init__(options)
960 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100961
Werner Lewisfbb75e32022-08-24 11:30:03 +0100962 def generate_target(self, name: str, *target_args) -> None:
963 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100964
965if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200966 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)