blob: 3a31aab9c8f9becd03d45a5f69140c39288844cf [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."""
38 if name.startswith('PSA_'):
39 return name[:4] + 'WANT_' + name[4:]
40 else:
41 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
42
Gilles Peskine7f756872021-02-16 12:13:12 +010043def finish_family_dependency(dep: str, bits: int) -> str:
44 """Finish dep if it's a family dependency symbol prefix.
45
46 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
47 qualified by the key size. If dep is such a symbol, finish it by adjusting
48 the prefix and appending the key size. Other symbols are left unchanged.
49 """
50 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
51
52def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
53 """Finish any family dependency symbol prefixes.
54
55 Apply `finish_family_dependency` to each element of `dependencies`.
56 """
57 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010058
Gilles Peskinec5d086f2021-04-20 23:23:45 +020059SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
60 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
61 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
62 'PSA_ALG_ANY_HASH', # only in policies
63 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
64 'PSA_ALG_KEY_AGREEMENT', # chaining
65 'PSA_ALG_TRUNCATED_MAC', # modifier
66])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010067def automatic_dependencies(*expressions: str) -> List[str]:
68 """Infer dependencies of a test case by looking for PSA_xxx symbols.
69
70 The arguments are strings which should be C expressions. Do not use
71 string literals or comments as this function is not smart enough to
72 skip them.
73 """
74 used = set()
75 for expr in expressions:
76 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020077 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010078 return sorted(psa_want_symbol(name) for name in used)
79
Yanray Wang3f417442023-04-21 14:29:16 +080080# Define set of regular expressions and dependencies to optionally append
81# extra dependencies for test case.
82AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
83AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
84
85DEPENDENCY_FROM_KEY = {
86 AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
87}#type: Dict[str, List[str]]
Yanray Wang5dd429c2023-05-10 09:58:46 +080088def generate_key_dependencies(description: str) -> List[str]:
Yanray Wang3f417442023-04-21 14:29:16 +080089 """Return additional dependencies based on pairs of REGEX and dependencies.
90 """
91 deps = []
92 for regex, dep in DEPENDENCY_FROM_KEY.items():
93 if re.search(regex, description):
94 deps += dep
95
96 return deps
97
Gilles Peskined169d602021-02-16 14:16:25 +010098# A temporary hack: at the time of writing, not all dependency symbols
99# are implemented yet. Skip test cases for which the dependency symbols are
100# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove1797b052022-12-04 17:19:59 +0000101# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +0100102# failure.
103def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
104 return frozenset(symbol
105 for line in open(filename)
106 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200107_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +0100108def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200109 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
110 if _implemented_dependencies is None:
111 _implemented_dependencies = \
112 read_implemented_dependencies('include/psa/crypto_config.h')
Valerio Setti323ad1c2023-05-26 17:47:55 +0200113 if not all((dep.lstrip('!') in _implemented_dependencies or
Valerio Setti5ca80e72023-06-20 19:27:02 +0200114 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100115 for dep in dependencies):
116 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
117
Gilles Peskine14e428f2021-01-26 22:19:21 +0100118
Gilles Peskineb94ea512021-03-10 02:12:08 +0100119class Information:
120 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100121
Gilles Peskineb94ea512021-03-10 02:12:08 +0100122 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100123 self.constructors = self.read_psa_interface()
124
125 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100126 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200127 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100128 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200129 # Mbed TLS doesn't support finite-field DH yet and will not support
130 # finite-field DSA. Don't attempt to generate any related test case.
131 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
132 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100133 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
134 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100135
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200136 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100137 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200138 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100139 header_file_names = ['include/psa/crypto_values.h',
140 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200141 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100142 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200143 constructors.parse_header(header_file_name)
144 for test_cases in test_suites:
145 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100146 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200147 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100148 return constructors
149
Gilles Peskine14e428f2021-01-26 22:19:21 +0100150
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200151def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100152 verb: str, key_type: str, bits: int,
153 dependencies: List[str],
154 *args: str,
155 param_descr: str = ''
156) -> test_case.TestCase:
157 """Return one test case exercising a key creation method
158 for an unsupported key type or size.
159 """
160 hack_dependencies_not_implemented(dependencies)
161 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100162 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100163 adverb = 'not' if dependencies else 'never'
164 if param_descr:
165 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200166 tc.set_description('PSA {} {} {}-bit {} supported'
167 .format(verb, short_key_type, bits, adverb))
168 tc.set_dependencies(dependencies)
169 tc.set_function(verb + '_not_supported')
170 tc.set_arguments([key_type] + list(args))
171 return tc
172
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100173class KeyTypeNotSupported:
174 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100175
176 def __init__(self, info: Information) -> None:
177 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100178
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100179 ALWAYS_SUPPORTED = frozenset([
180 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100181 'PSA_KEY_TYPE_PASSWORD',
182 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100183 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200184 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100185 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100186 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100187 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100188 kt: crypto_knowledge.KeyType,
189 param: Optional[int] = None,
190 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100191 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200192 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100193
194 If param is present and not None, emit test cases conditioned on this
195 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200196 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100198 if kt.name in self.ALWAYS_SUPPORTED:
199 # Don't generate test cases for key types that are always supported.
200 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100201 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100202 import_dependencies = [('!' if param is None else '') +
203 psa_want_symbol(kt.name)]
204 if kt.params is not None:
205 import_dependencies += [('!' if param == i else '') +
206 psa_want_symbol(sym)
207 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100208 if kt.name.endswith('_PUBLIC_KEY'):
209 generate_dependencies = []
210 else:
Valerio Setti5ca80e72023-06-20 19:27:02 +0200211 # Create a separate list so that we can work on them independently
212 # in the following.
213 generate_dependencies = [dep for dep in import_dependencies]
Valerio Setti5d58a552023-06-22 14:02:04 +0200214 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE and
Valerio Setti5ca80e72023-06-20 19:27:02 +0200215 # IMPORT suffixes to state that they support key generation and
216 # import, respectively.
217 for dep in import_dependencies:
218 if dep.endswith('KEY_PAIR'):
219 import_dependencies.remove(dep)
220 import_dependencies.append(dep + "_IMPORT")
221 for dep in generate_dependencies:
222 if dep.endswith('KEY_PAIR'):
223 generate_dependencies.remove(dep)
224 generate_dependencies.append(dep + "_GENERATE")
Gilles Peskine14e428f2021-01-26 22:19:21 +0100225 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200226 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100227 'import', kt.expression, bits,
228 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 test_case.hex_string(kt.key_material(bits)),
230 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100231 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100232 if not generate_dependencies and param is not None:
233 # If generation is impossible for this key type, rather than
234 # supported or not depending on implementation capabilities,
235 # only generate the test case once.
236 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100237 # For public key we expect that key generation fails with
238 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100239 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200240 yield test_case_for_key_type_not_supported(
241 'generate', kt.expression, bits,
242 finish_family_dependencies(generate_dependencies, bits),
243 str(bits),
244 param_descr=param_descr,
245 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100246 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100247
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200248 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
249 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
250
Gilles Peskine3d778392021-02-17 15:11:05 +0100251 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100252 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100253 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200254 if key_type in self.ECC_KEY_TYPES:
255 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100256 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100257 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100258 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200259 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100260 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100261 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100262 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100263 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100264 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100265
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200266def test_case_for_key_generation(
267 key_type: str, bits: int,
268 dependencies: List[str],
269 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200270 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200271) -> test_case.TestCase:
272 """Return one test case exercising a key generation.
273 """
274 hack_dependencies_not_implemented(dependencies)
275 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100276 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200277 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200278 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200279 tc.set_dependencies(dependencies)
280 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100281 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200282
283 return tc
284
285class KeyGenerate:
286 """Generate positive and negative (invalid argument) test cases for key generation."""
287
288 def __init__(self, info: Information) -> None:
289 self.constructors = info.constructors
290
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200291 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
292 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
293
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100294 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200295 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200296 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200297 ) -> Iterator[test_case.TestCase]:
298 """Return test cases exercising key generation.
299
300 All key types can be generated except for public keys. For public key
301 PSA_ERROR_INVALID_ARGUMENT status is expected.
302 """
303 result = 'PSA_SUCCESS'
304
305 import_dependencies = [psa_want_symbol(kt.name)]
306 if kt.params is not None:
307 import_dependencies += [psa_want_symbol(sym)
308 for i, sym in enumerate(kt.params)]
309 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100310 # The library checks whether the key type is a public key generically,
311 # before it reaches a point where it needs support for the specific key
312 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313 generate_dependencies = []
314 result = 'PSA_ERROR_INVALID_ARGUMENT'
315 else:
316 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100317 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200318 generate_dependencies.append("MBEDTLS_GENPRIME")
Valerio Setti5d58a552023-06-22 14:02:04 +0200319 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE suffix
Valerio Setti5ca80e72023-06-20 19:27:02 +0200320 # to state that they support key generation.
321 if kt.name == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
322 generate_dependencies.remove(psa_want_symbol(kt.name))
323 generate_dependencies.append(psa_want_symbol(kt.name) + "_GENERATE")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200324 for bits in kt.sizes_to_test():
325 yield test_case_for_key_generation(
326 kt.expression, bits,
327 finish_family_dependencies(generate_dependencies, bits),
328 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200329 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200330 )
331
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200332 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
333 """Generate test cases that exercise the generation of keys."""
334 for key_type in sorted(self.constructors.key_types):
335 if key_type in self.ECC_KEY_TYPES:
336 continue
337 kt = crypto_knowledge.KeyType(key_type)
338 yield from self.test_cases_for_key_type_key_generation(kt)
339 for curve_family in sorted(self.constructors.ecc_curves):
340 for constr in self.ECC_KEY_TYPES:
341 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200342 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200343
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200344class OpFail:
345 """Generate test cases for operations that must fail."""
346 #pylint: disable=too-few-public-methods
347
Gilles Peskinecba28a72022-03-15 17:26:33 +0100348 class Reason(enum.Enum):
349 NOT_SUPPORTED = 0
350 INVALID = 1
351 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200352 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100353
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200354 def __init__(self, info: Information) -> None:
355 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100356 key_type_expressions = self.constructors.generate_expressions(
357 sorted(self.constructors.key_types)
358 )
359 self.key_types = [crypto_knowledge.KeyType(kt_expr)
360 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200361
Gilles Peskinecba28a72022-03-15 17:26:33 +0100362 def make_test_case(
363 self,
364 alg: crypto_knowledge.Algorithm,
365 category: crypto_knowledge.AlgorithmCategory,
366 reason: 'Reason',
367 kt: Optional[crypto_knowledge.KeyType] = None,
368 not_deps: FrozenSet[str] = frozenset(),
369 ) -> test_case.TestCase:
370 """Construct a failure test case for a one-key or keyless operation."""
371 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200372 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100373 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200374 if reason == self.Reason.NOT_SUPPORTED:
375 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
376 for dep in not_deps]
377 pretty_reason = '!' + '&'.join(sorted(short_deps))
378 else:
379 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100380 if kt:
381 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100382 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200383 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100384 key_type = ''
385 pretty_type = ''
386 tc.set_description('PSA {} {}: {}{}'
387 .format(category.name.lower(),
388 pretty_alg,
389 pretty_reason,
390 ' with ' + pretty_type if pretty_type else ''))
391 dependencies = automatic_dependencies(alg.base_expression, key_type)
392 for i, dep in enumerate(dependencies):
393 if dep in not_deps:
394 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200395 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100396 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000397 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100398 if kt:
399 key_material = kt.key_material(kt.sizes_to_test()[0])
400 arguments += [key_type, test_case.hex_string(key_material)]
401 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200402 if category.is_asymmetric():
403 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100404 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
405 'INVALID_ARGUMENT')
406 arguments.append('PSA_ERROR_' + error)
407 tc.set_arguments(arguments)
408 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200409
Gilles Peskinecba28a72022-03-15 17:26:33 +0100410 def no_key_test_cases(
411 self,
412 alg: crypto_knowledge.Algorithm,
413 category: crypto_knowledge.AlgorithmCategory,
414 ) -> Iterator[test_case.TestCase]:
415 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200416 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100417 # Compatible operation, unsupported algorithm
418 for dep in automatic_dependencies(alg.base_expression):
419 yield self.make_test_case(alg, category,
420 self.Reason.NOT_SUPPORTED,
421 not_deps=frozenset([dep]))
422 else:
423 # Incompatible operation, supported algorithm
424 yield self.make_test_case(alg, category, self.Reason.INVALID)
425
426 def one_key_test_cases(
427 self,
428 alg: crypto_knowledge.Algorithm,
429 category: crypto_knowledge.AlgorithmCategory,
430 ) -> Iterator[test_case.TestCase]:
431 """Generate failure test cases for one-key operations with the specified algorithm."""
432 for kt in self.key_types:
433 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200434 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100435 # Compatible key and operation, unsupported algorithm
436 for dep in automatic_dependencies(alg.base_expression):
437 yield self.make_test_case(alg, category,
438 self.Reason.NOT_SUPPORTED,
439 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200440 # Public key for a private-key operation
441 if category.is_asymmetric() and kt.is_public():
442 yield self.make_test_case(alg, category,
443 self.Reason.PUBLIC,
444 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100445 elif key_is_compatible:
446 # Compatible key, incompatible operation, supported algorithm
447 yield self.make_test_case(alg, category,
448 self.Reason.INVALID,
449 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200450 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100451 # Incompatible key, compatible operation, supported algorithm
452 yield self.make_test_case(alg, category,
453 self.Reason.INCOMPATIBLE,
454 kt=kt)
455 else:
456 # Incompatible key and operation. Don't test cases where
457 # multiple things are wrong, to keep the number of test
458 # cases reasonable.
459 pass
460
461 def test_cases_for_algorithm(
462 self,
463 alg: crypto_knowledge.Algorithm,
464 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200465 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100466 for category in crypto_knowledge.AlgorithmCategory:
467 if category == crypto_knowledge.AlgorithmCategory.PAKE:
468 # PAKE operations are not implemented yet
469 pass
470 elif category.requires_key():
471 yield from self.one_key_test_cases(alg, category)
472 else:
473 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200474
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200475 def all_test_cases(self) -> Iterator[test_case.TestCase]:
476 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200477 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100478 for expr in self.constructors.generate_expressions(algorithms):
479 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200480 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200481
482
Gilles Peskine897dff92021-03-10 15:03:44 +0100483class StorageKey(psa_storage.Key):
484 """Representation of a key for storage format testing."""
485
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200486 IMPLICIT_USAGE_FLAGS = {
487 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
488 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
489 } #type: Dict[str, str]
490 """Mapping of usage flags to the flags that they imply."""
491
492 def __init__(
493 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100494 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200495 without_implicit_usage: Optional[bool] = False,
496 **kwargs
497 ) -> None:
498 """Prepare to generate a key.
499
500 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000501 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200502 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100503 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200504 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100505 for flag in sorted(usage_flags):
506 if flag in self.IMPLICIT_USAGE_FLAGS:
507 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
508 if usage_flags:
509 usage_expression = ' | '.join(sorted(usage_flags))
510 else:
511 usage_expression = '0'
512 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200513
514class StorageTestData(StorageKey):
515 """Representation of test case data for storage format testing."""
516
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200517 def __init__(
518 self,
519 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100520 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200521 **kwargs
522 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200523 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200524
Tom Cosgrove1797b052022-12-04 17:19:59 +0000525 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200526 * `expected_usage`: the usage flags generated as the expected usage flags
527 in the test cases. CAn differ from the usage flags
528 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200529 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100530 super().__init__(**kwargs)
531 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100532 if expected_usage is None:
533 self.expected_usage = self.usage #type: psa_storage.Expr
534 elif expected_usage:
535 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
536 else:
537 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200538
Gilles Peskine897dff92021-03-10 15:03:44 +0100539class StorageFormat:
540 """Storage format stability test cases."""
541
542 def __init__(self, info: Information, version: int, forward: bool) -> None:
543 """Prepare to generate test cases for storage format stability.
544
545 * `info`: information about the API. See the `Information` class.
546 * `version`: the storage format version to generate test cases for.
547 * `forward`: if true, generate forward compatibility test cases which
548 save a key and check that its representation is as intended. Otherwise
549 generate backward compatibility test cases which inject a key
550 representation and check that it can be read and used.
551 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200552 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
553 self.version = version #type: int
554 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100555
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100556 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100557 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100558 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100559 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100560 cls,
561 key_type: psa_storage.Expr, bits: int,
562 alg: psa_storage.Expr
563 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100564 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100565
566 Normally only the type and algorithm matter for compatibility, and
567 this is handled in crypto_knowledge.KeyType.can_do(). This function
568 exists to detect exceptional cases. Exceptional cases detected here
569 are not tested in OpFail and should therefore have manually written
570 test cases.
571 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100572 # Some test keys have the RAW_DATA type and attributes that don't
573 # necessarily make sense. We do this to validate numerical
574 # encodings of the attributes.
575 # Raw data keys have no useful exercise anyway so there is no
576 # loss of test coverage.
577 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
578 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100579 # OAEP requires room for two hashes plus wrapping
580 m = cls.RSA_OAEP_RE.match(alg.string)
581 if m:
582 hash_alg = m.group(1)
583 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
584 key_length = (bits + 7) // 8
585 # Leave enough room for at least one byte of plaintext
586 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100587 # There's nothing wrong with ECC keys on Brainpool curves,
588 # but operations with them are very slow. So we only exercise them
589 # with a single algorithm, not with all possible hashes. We do
590 # exercise other curves with all algorithms so test coverage is
591 # perfectly adequate like this.
592 m = cls.BRAINPOOL_RE.match(key_type.string)
593 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
594 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100595 return True
596
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200597 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100598 """Construct a storage format test case for the given key.
599
600 If ``forward`` is true, generate a forward compatibility test case:
601 create a key and validate that it has the expected representation.
602 Otherwise generate a backward compatibility test case: inject the
603 key representation into storage and validate that it can be read
604 correctly.
605 """
606 verb = 'save' if self.forward else 'read'
607 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100608 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100609 dependencies = automatic_dependencies(
610 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100611 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100612 )
613 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800614 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100615 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100616 tc.set_function('key_storage_' + verb)
617 if self.forward:
618 extra_arguments = []
619 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200620 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100621 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200622 flags.append('TEST_FLAG_EXERCISE')
623 if 'READ_ONLY' in key.lifetime.string:
624 flags.append('TEST_FLAG_READ_ONLY')
625 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100626 tc.set_arguments([key.lifetime.string,
627 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100628 key.expected_usage.string,
629 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100630 '"' + key.material.hex() + '"',
631 '"' + key.hex() + '"',
632 *extra_arguments])
633 return tc
634
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200635 def key_for_lifetime(
636 self,
637 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200638 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200639 """Construct a test key for the given lifetime."""
640 short = lifetime
641 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
642 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100643 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200644 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200645 key = StorageTestData(version=self.version,
646 id=1, lifetime=lifetime,
647 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100648 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200649 material=b'L',
650 description=description)
651 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200652
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200653 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200654 """Generate test keys covering lifetimes."""
655 lifetimes = sorted(self.constructors.lifetimes)
656 expressions = self.constructors.generate_expressions(lifetimes)
657 for lifetime in expressions:
658 # Don't attempt to create or load a volatile key in storage
659 if 'VOLATILE' in lifetime:
660 continue
661 # Don't attempt to create a read-only key in storage,
662 # but do attempt to load one.
663 if 'READ_ONLY' in lifetime and self.forward:
664 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200665 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200666
Gilles Peskinef7614272022-02-24 18:58:08 +0100667 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100668 self,
669 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200670 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100671 test_implicit_usage: Optional[bool] = True
672 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100673 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100674 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100675 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200676 key1 = StorageTestData(version=self.version,
677 id=1, lifetime=0x00000001,
678 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100679 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100681 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200682 material=b'K',
683 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100684 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100685 usage_expr = key1.expected_usage.string
686 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100687 else:
688 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100689 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100690
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200691 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100692 """Generate test keys covering usage flags."""
693 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100694 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200695 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100696 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200697 for flag1, flag2 in zip(known_flags,
698 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100699 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200700
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200701 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200702 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100703 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200704
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200705 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200706 yield from self.generate_keys_for_usage_flags()
707 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100708
Gilles Peskine7de7c102021-04-29 22:28:07 +0200709 def key_for_type_and_alg(
710 self,
711 kt: crypto_knowledge.KeyType,
712 bits: int,
713 alg: Optional[crypto_knowledge.Algorithm] = None,
714 ) -> StorageTestData:
715 """Construct a test key of the given type.
716
717 If alg is not None, this key allows it.
718 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100719 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100720 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100722 if alg is not None:
723 alg1 = alg.expression
724 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200725 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100726 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200727 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100728 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200729 key = StorageTestData(version=self.version,
730 id=1, lifetime=0x00000001,
731 type=kt.expression, bits=bits,
732 usage=usage_flags, alg=alg1, alg2=alg2,
733 material=key_material,
734 description=description)
735 return key
736
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100737 def keys_for_type(
738 self,
739 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200740 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200741 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200742 """Generate test keys for the given key type."""
743 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100744 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200745 # Test a non-exercisable key, as well as exercisable keys for
746 # each compatible algorithm.
747 # To do: test reading a key from storage with an incompatible
748 # or unsupported algorithm.
749 yield self.key_for_type_and_alg(kt, bits)
750 compatible_algorithms = [alg for alg in all_algorithms
751 if kt.can_do(alg)]
752 for alg in compatible_algorithms:
753 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100754
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200755 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100756 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200757 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200758 all_algorithms = [crypto_knowledge.Algorithm(alg)
759 for alg in self.constructors.generate_expressions(
760 sorted(self.constructors.algorithms)
761 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200762 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200763 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100764
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200765 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200766 """Generate test keys for the encoding of the specified algorithm."""
767 # These test cases only validate the encoding of algorithms, not
768 # whether the key read from storage is suitable for an operation.
769 # `keys_for_types` generate read tests with an algorithm and a
770 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100771 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100772 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200773 key1 = StorageTestData(version=self.version,
774 id=1, lifetime=0x00000001,
775 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
776 usage=usage, alg=alg, alg2=0,
777 material=b'K',
778 description='alg: ' + descr)
779 yield key1
780 key2 = StorageTestData(version=self.version,
781 id=1, lifetime=0x00000001,
782 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
783 usage=usage, alg=0, alg2=alg,
784 material=b'L',
785 description='alg2: ' + descr)
786 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100787
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200788 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100789 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200790 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200791 for alg in self.constructors.generate_expressions(algorithms):
792 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100793
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200794 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200795 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200796 yield from self.all_keys_for_lifetimes()
797 yield from self.all_keys_for_usage_flags()
798 yield from self.all_keys_for_types()
799 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200800
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200801 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100802 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200803 # First build a list of all keys, then construct all the corresponding
804 # test cases. This allows all required information to be obtained in
805 # one go, which is a significant performance gain as the information
806 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200807 all_keys = list(self.generate_all_keys())
808 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200809 if key.location_value() != 0:
810 # Skip keys with a non-default location, because they
811 # require a driver and we currently have no mechanism to
812 # determine whether a driver is available.
813 continue
814 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100815
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200816class StorageFormatForward(StorageFormat):
817 """Storage format stability test cases for forward compatibility."""
818
819 def __init__(self, info: Information, version: int) -> None:
820 super().__init__(info, version, True)
821
822class StorageFormatV0(StorageFormat):
823 """Storage format stability test cases for version 0 compatibility."""
824
825 def __init__(self, info: Information) -> None:
826 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100827
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200828 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200829 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100830 yield from super().all_keys_for_usage_flags()
831 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200832
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200833 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200834 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200835 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200836 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200837 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200838 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200839 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200840 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200841 algorithm and key type combination.
842 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200843 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200844 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100845 usage_flags = ['PSA_KEY_USAGE_EXPORT']
846 material_usage_flags = usage_flags + [implyer_usage]
847 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200848 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200849 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100850 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
851 alg_expression = crypto_knowledge.short_expression(alg, 1)
852 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200853 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200854 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200855 key = StorageTestData(version=self.version,
856 id=1, lifetime=0x00000001,
857 type=key_type.expression, bits=bits,
858 usage=material_usage_flags,
859 expected_usage=expected_usage_flags,
860 without_implicit_usage=True,
861 alg=alg, alg2=alg2,
862 material=key_material,
863 description=description)
864 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200865
866 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200867 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200868 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800869 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200870 # must be filtered. Pair them with keywords created from its names.
871 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
872 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
873 keyword_translation = {
874 'ECDSA': 'ECC',
875 'ED[0-9]*.*' : 'EDWARDS'
876 }
877 exclusive_keywords = {
878 'EDWARDS': 'ECC'
879 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200880 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
881 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200882 alg_with_keys = {} #type: Dict[str, List[str]]
883 translation_table = str.maketrans('(', '_', ')')
884 for alg in algorithms:
885 # Generate keywords from the name of the algorithm
886 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
887 # Translate keywords for better matching with the key types
888 for keyword in alg_keywords.copy():
889 for pattern, replace in keyword_translation.items():
890 if re.match(pattern, keyword):
891 alg_keywords.remove(keyword)
892 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800893 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200894 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
895 continue
896
897 for key_type in key_types:
898 # Generate keywords from the of the key type
899 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
900
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800901 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200902 for keyword1, keyword2 in exclusive_keywords.items():
903 if keyword1 in key_type_keywords:
904 key_type_keywords.remove(keyword2)
905
906 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
907 not key_type_keywords.isdisjoint(alg_keywords):
908 if alg in alg_with_keys:
909 alg_with_keys[alg].append(key_type)
910 else:
911 alg_with_keys[alg] = [key_type]
912 return alg_with_keys
913
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200914 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200915 """Generate test keys for usage flag extensions."""
916 # Generate a key type and algorithm pair for each extendable usage
917 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800918 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200919 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200920
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200921 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
922 for alg in sorted(alg_with_keys):
923 for key_type in sorted(alg_with_keys[alg]):
924 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200925 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100926 if kt.is_public() and '_SIGN_' in usage:
927 # Can't sign with a public key
928 continue
929 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200930
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200931 def generate_all_keys(self) -> Iterator[StorageTestData]:
932 yield from super().generate_all_keys()
933 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200934
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200935class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100936 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100937 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200938 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100939 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200940 'test_suite_psa_crypto_generate_key.generated':
941 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100942 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100943 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200944 'test_suite_psa_crypto_op_fail.generated':
945 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100946 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200947 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100948 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200949 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100950 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
951
Werner Lewisfbb75e32022-08-24 11:30:03 +0100952 def __init__(self, options):
953 super().__init__(options)
954 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100955
Werner Lewisfbb75e32022-08-24 11:30:03 +0100956 def generate_target(self, name: str, *target_args) -> None:
957 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100958
959if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200960 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)