blob: ab3fe07cf894112605333b49eeb08e51b17583bf [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 Setti76882fc2023-06-22 14:46:23 +0200211 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE and
212 # IMPORT suffixes to state that they support key generation and
213 # import, respectively.
214 generate_dependencies = [re.sub(r'KEY_PAIR\Z', r'KEY_PAIR_GENERATE', dep)
215 for dep in import_dependencies]
216 import_dependencies = [re.sub(r'KEY_PAIR\Z', r'KEY_PAIR_IMPORT', dep)
217 for dep in import_dependencies]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200219 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100220 'import', kt.expression, bits,
221 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100222 test_case.hex_string(kt.key_material(bits)),
223 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100224 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 if not generate_dependencies and param is not None:
226 # If generation is impossible for this key type, rather than
227 # supported or not depending on implementation capabilities,
228 # only generate the test case once.
229 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100230 # For public key we expect that key generation fails with
231 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100232 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200233 yield test_case_for_key_type_not_supported(
234 'generate', kt.expression, bits,
235 finish_family_dependencies(generate_dependencies, bits),
236 str(bits),
237 param_descr=param_descr,
238 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100239 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100240
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200241 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
242 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
243
Gilles Peskine3d778392021-02-17 15:11:05 +0100244 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100245 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100246 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200247 if key_type in self.ECC_KEY_TYPES:
248 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100249 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100250 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100251 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200252 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100253 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100254 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100255 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100256 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100257 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100258
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200259def test_case_for_key_generation(
260 key_type: str, bits: int,
261 dependencies: List[str],
262 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200263 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200264) -> test_case.TestCase:
265 """Return one test case exercising a key generation.
266 """
267 hack_dependencies_not_implemented(dependencies)
268 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100269 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200270 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200271 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200272 tc.set_dependencies(dependencies)
273 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100274 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200275
276 return tc
277
278class KeyGenerate:
279 """Generate positive and negative (invalid argument) test cases for key generation."""
280
281 def __init__(self, info: Information) -> None:
282 self.constructors = info.constructors
283
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200284 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
285 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
286
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100287 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200288 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200289 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200290 ) -> Iterator[test_case.TestCase]:
291 """Return test cases exercising key generation.
292
293 All key types can be generated except for public keys. For public key
294 PSA_ERROR_INVALID_ARGUMENT status is expected.
295 """
296 result = 'PSA_SUCCESS'
297
298 import_dependencies = [psa_want_symbol(kt.name)]
299 if kt.params is not None:
300 import_dependencies += [psa_want_symbol(sym)
301 for i, sym in enumerate(kt.params)]
302 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100303 # The library checks whether the key type is a public key generically,
304 # before it reaches a point where it needs support for the specific key
305 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200306 generate_dependencies = []
307 result = 'PSA_ERROR_INVALID_ARGUMENT'
308 else:
309 generate_dependencies = import_dependencies
Valerio Setti5d58a552023-06-22 14:02:04 +0200310 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE suffix
Valerio Setti5ca80e72023-06-20 19:27:02 +0200311 # to state that they support key generation.
Valerio Setti76882fc2023-06-22 14:46:23 +0200312 generate_dependencies = [re.sub(r'KEY_PAIR\Z', r'KEY_PAIR_GENERATE', dep)
313 for dep in generate_dependencies]
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200314 for bits in kt.sizes_to_test():
315 yield test_case_for_key_generation(
316 kt.expression, bits,
317 finish_family_dependencies(generate_dependencies, bits),
318 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200319 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200320 )
321
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200322 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
323 """Generate test cases that exercise the generation of keys."""
324 for key_type in sorted(self.constructors.key_types):
325 if key_type in self.ECC_KEY_TYPES:
326 continue
327 kt = crypto_knowledge.KeyType(key_type)
328 yield from self.test_cases_for_key_type_key_generation(kt)
329 for curve_family in sorted(self.constructors.ecc_curves):
330 for constr in self.ECC_KEY_TYPES:
331 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200332 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200333
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200334class OpFail:
335 """Generate test cases for operations that must fail."""
336 #pylint: disable=too-few-public-methods
337
Gilles Peskinecba28a72022-03-15 17:26:33 +0100338 class Reason(enum.Enum):
339 NOT_SUPPORTED = 0
340 INVALID = 1
341 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200342 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100343
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200344 def __init__(self, info: Information) -> None:
345 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100346 key_type_expressions = self.constructors.generate_expressions(
347 sorted(self.constructors.key_types)
348 )
349 self.key_types = [crypto_knowledge.KeyType(kt_expr)
350 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200351
Gilles Peskinecba28a72022-03-15 17:26:33 +0100352 def make_test_case(
353 self,
354 alg: crypto_knowledge.Algorithm,
355 category: crypto_knowledge.AlgorithmCategory,
356 reason: 'Reason',
357 kt: Optional[crypto_knowledge.KeyType] = None,
358 not_deps: FrozenSet[str] = frozenset(),
359 ) -> test_case.TestCase:
360 """Construct a failure test case for a one-key or keyless operation."""
361 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200362 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100363 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200364 if reason == self.Reason.NOT_SUPPORTED:
365 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
366 for dep in not_deps]
367 pretty_reason = '!' + '&'.join(sorted(short_deps))
368 else:
369 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100370 if kt:
371 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100372 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200373 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100374 key_type = ''
375 pretty_type = ''
376 tc.set_description('PSA {} {}: {}{}'
377 .format(category.name.lower(),
378 pretty_alg,
379 pretty_reason,
380 ' with ' + pretty_type if pretty_type else ''))
381 dependencies = automatic_dependencies(alg.base_expression, key_type)
382 for i, dep in enumerate(dependencies):
383 if dep in not_deps:
384 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200385 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100386 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000387 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100388 if kt:
389 key_material = kt.key_material(kt.sizes_to_test()[0])
390 arguments += [key_type, test_case.hex_string(key_material)]
391 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200392 if category.is_asymmetric():
393 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100394 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
395 'INVALID_ARGUMENT')
396 arguments.append('PSA_ERROR_' + error)
397 tc.set_arguments(arguments)
398 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200399
Gilles Peskinecba28a72022-03-15 17:26:33 +0100400 def no_key_test_cases(
401 self,
402 alg: crypto_knowledge.Algorithm,
403 category: crypto_knowledge.AlgorithmCategory,
404 ) -> Iterator[test_case.TestCase]:
405 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200406 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100407 # Compatible operation, unsupported algorithm
408 for dep in automatic_dependencies(alg.base_expression):
409 yield self.make_test_case(alg, category,
410 self.Reason.NOT_SUPPORTED,
411 not_deps=frozenset([dep]))
412 else:
413 # Incompatible operation, supported algorithm
414 yield self.make_test_case(alg, category, self.Reason.INVALID)
415
416 def one_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 one-key operations with the specified algorithm."""
422 for kt in self.key_types:
423 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200424 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100425 # Compatible key and operation, unsupported algorithm
426 for dep in automatic_dependencies(alg.base_expression):
427 yield self.make_test_case(alg, category,
428 self.Reason.NOT_SUPPORTED,
429 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200430 # Public key for a private-key operation
431 if category.is_asymmetric() and kt.is_public():
432 yield self.make_test_case(alg, category,
433 self.Reason.PUBLIC,
434 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100435 elif key_is_compatible:
436 # Compatible key, incompatible operation, supported algorithm
437 yield self.make_test_case(alg, category,
438 self.Reason.INVALID,
439 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200440 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100441 # Incompatible key, compatible operation, supported algorithm
442 yield self.make_test_case(alg, category,
443 self.Reason.INCOMPATIBLE,
444 kt=kt)
445 else:
446 # Incompatible key and operation. Don't test cases where
447 # multiple things are wrong, to keep the number of test
448 # cases reasonable.
449 pass
450
451 def test_cases_for_algorithm(
452 self,
453 alg: crypto_knowledge.Algorithm,
454 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200455 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100456 for category in crypto_knowledge.AlgorithmCategory:
457 if category == crypto_knowledge.AlgorithmCategory.PAKE:
458 # PAKE operations are not implemented yet
459 pass
460 elif category.requires_key():
461 yield from self.one_key_test_cases(alg, category)
462 else:
463 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200464
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200465 def all_test_cases(self) -> Iterator[test_case.TestCase]:
466 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200467 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100468 for expr in self.constructors.generate_expressions(algorithms):
469 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200470 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200471
472
Gilles Peskine897dff92021-03-10 15:03:44 +0100473class StorageKey(psa_storage.Key):
474 """Representation of a key for storage format testing."""
475
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200476 IMPLICIT_USAGE_FLAGS = {
477 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
478 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
479 } #type: Dict[str, str]
480 """Mapping of usage flags to the flags that they imply."""
481
482 def __init__(
483 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100484 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200485 without_implicit_usage: Optional[bool] = False,
486 **kwargs
487 ) -> None:
488 """Prepare to generate a key.
489
490 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000491 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200492 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100493 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100495 for flag in sorted(usage_flags):
496 if flag in self.IMPLICIT_USAGE_FLAGS:
497 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
498 if usage_flags:
499 usage_expression = ' | '.join(sorted(usage_flags))
500 else:
501 usage_expression = '0'
502 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200503
504class StorageTestData(StorageKey):
505 """Representation of test case data for storage format testing."""
506
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200507 def __init__(
508 self,
509 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100510 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200511 **kwargs
512 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200513 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200514
Tom Cosgrove1797b052022-12-04 17:19:59 +0000515 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200516 * `expected_usage`: the usage flags generated as the expected usage flags
517 in the test cases. CAn differ from the usage flags
518 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200519 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100520 super().__init__(**kwargs)
521 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100522 if expected_usage is None:
523 self.expected_usage = self.usage #type: psa_storage.Expr
524 elif expected_usage:
525 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
526 else:
527 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200528
Gilles Peskine897dff92021-03-10 15:03:44 +0100529class StorageFormat:
530 """Storage format stability test cases."""
531
532 def __init__(self, info: Information, version: int, forward: bool) -> None:
533 """Prepare to generate test cases for storage format stability.
534
535 * `info`: information about the API. See the `Information` class.
536 * `version`: the storage format version to generate test cases for.
537 * `forward`: if true, generate forward compatibility test cases which
538 save a key and check that its representation is as intended. Otherwise
539 generate backward compatibility test cases which inject a key
540 representation and check that it can be read and used.
541 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200542 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
543 self.version = version #type: int
544 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100545
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100546 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100547 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100548 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100549 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100550 cls,
551 key_type: psa_storage.Expr, bits: int,
552 alg: psa_storage.Expr
553 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100554 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100555
556 Normally only the type and algorithm matter for compatibility, and
557 this is handled in crypto_knowledge.KeyType.can_do(). This function
558 exists to detect exceptional cases. Exceptional cases detected here
559 are not tested in OpFail and should therefore have manually written
560 test cases.
561 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100562 # Some test keys have the RAW_DATA type and attributes that don't
563 # necessarily make sense. We do this to validate numerical
564 # encodings of the attributes.
565 # Raw data keys have no useful exercise anyway so there is no
566 # loss of test coverage.
567 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
568 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100569 # OAEP requires room for two hashes plus wrapping
570 m = cls.RSA_OAEP_RE.match(alg.string)
571 if m:
572 hash_alg = m.group(1)
573 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
574 key_length = (bits + 7) // 8
575 # Leave enough room for at least one byte of plaintext
576 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100577 # There's nothing wrong with ECC keys on Brainpool curves,
578 # but operations with them are very slow. So we only exercise them
579 # with a single algorithm, not with all possible hashes. We do
580 # exercise other curves with all algorithms so test coverage is
581 # perfectly adequate like this.
582 m = cls.BRAINPOOL_RE.match(key_type.string)
583 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
584 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100585 return True
586
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200587 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100588 """Construct a storage format test case for the given key.
589
590 If ``forward`` is true, generate a forward compatibility test case:
591 create a key and validate that it has the expected representation.
592 Otherwise generate a backward compatibility test case: inject the
593 key representation into storage and validate that it can be read
594 correctly.
595 """
596 verb = 'save' if self.forward else 'read'
597 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100598 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100599 dependencies = automatic_dependencies(
600 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100601 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100602 )
603 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800604 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100605 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100606 tc.set_function('key_storage_' + verb)
607 if self.forward:
608 extra_arguments = []
609 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200610 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100611 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200612 flags.append('TEST_FLAG_EXERCISE')
613 if 'READ_ONLY' in key.lifetime.string:
614 flags.append('TEST_FLAG_READ_ONLY')
615 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100616 tc.set_arguments([key.lifetime.string,
617 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100618 key.expected_usage.string,
619 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100620 '"' + key.material.hex() + '"',
621 '"' + key.hex() + '"',
622 *extra_arguments])
623 return tc
624
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200625 def key_for_lifetime(
626 self,
627 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200628 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200629 """Construct a test key for the given lifetime."""
630 short = lifetime
631 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
632 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100633 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200634 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200635 key = StorageTestData(version=self.version,
636 id=1, lifetime=lifetime,
637 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100638 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200639 material=b'L',
640 description=description)
641 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200642
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200643 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200644 """Generate test keys covering lifetimes."""
645 lifetimes = sorted(self.constructors.lifetimes)
646 expressions = self.constructors.generate_expressions(lifetimes)
647 for lifetime in expressions:
648 # Don't attempt to create or load a volatile key in storage
649 if 'VOLATILE' in lifetime:
650 continue
651 # Don't attempt to create a read-only key in storage,
652 # but do attempt to load one.
653 if 'READ_ONLY' in lifetime and self.forward:
654 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200655 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200656
Gilles Peskinef7614272022-02-24 18:58:08 +0100657 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100658 self,
659 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200660 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100661 test_implicit_usage: Optional[bool] = True
662 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100663 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100664 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100665 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200666 key1 = StorageTestData(version=self.version,
667 id=1, lifetime=0x00000001,
668 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100669 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100670 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100671 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200672 material=b'K',
673 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100674 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100675 usage_expr = key1.expected_usage.string
676 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100677 else:
678 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100679 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100680
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200681 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100682 """Generate test keys covering usage flags."""
683 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100684 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200685 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100686 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200687 for flag1, flag2 in zip(known_flags,
688 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100689 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200690
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200691 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200692 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100693 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200694
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200695 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200696 yield from self.generate_keys_for_usage_flags()
697 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100698
Gilles Peskine7de7c102021-04-29 22:28:07 +0200699 def key_for_type_and_alg(
700 self,
701 kt: crypto_knowledge.KeyType,
702 bits: int,
703 alg: Optional[crypto_knowledge.Algorithm] = None,
704 ) -> StorageTestData:
705 """Construct a test key of the given type.
706
707 If alg is not None, this key allows it.
708 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100709 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100710 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200711 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100712 if alg is not None:
713 alg1 = alg.expression
714 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200715 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100716 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200717 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100718 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200719 key = StorageTestData(version=self.version,
720 id=1, lifetime=0x00000001,
721 type=kt.expression, bits=bits,
722 usage=usage_flags, alg=alg1, alg2=alg2,
723 material=key_material,
724 description=description)
725 return key
726
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100727 def keys_for_type(
728 self,
729 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200730 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200731 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200732 """Generate test keys for the given key type."""
733 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100734 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 # Test a non-exercisable key, as well as exercisable keys for
736 # each compatible algorithm.
737 # To do: test reading a key from storage with an incompatible
738 # or unsupported algorithm.
739 yield self.key_for_type_and_alg(kt, bits)
740 compatible_algorithms = [alg for alg in all_algorithms
741 if kt.can_do(alg)]
742 for alg in compatible_algorithms:
743 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100744
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200745 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100746 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200747 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200748 all_algorithms = [crypto_knowledge.Algorithm(alg)
749 for alg in self.constructors.generate_expressions(
750 sorted(self.constructors.algorithms)
751 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200752 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200753 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100754
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200755 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200756 """Generate test keys for the encoding of the specified algorithm."""
757 # These test cases only validate the encoding of algorithms, not
758 # whether the key read from storage is suitable for an operation.
759 # `keys_for_types` generate read tests with an algorithm and a
760 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100761 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100762 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200763 key1 = StorageTestData(version=self.version,
764 id=1, lifetime=0x00000001,
765 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
766 usage=usage, alg=alg, alg2=0,
767 material=b'K',
768 description='alg: ' + descr)
769 yield key1
770 key2 = StorageTestData(version=self.version,
771 id=1, lifetime=0x00000001,
772 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
773 usage=usage, alg=0, alg2=alg,
774 material=b'L',
775 description='alg2: ' + descr)
776 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100777
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200778 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100779 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200780 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200781 for alg in self.constructors.generate_expressions(algorithms):
782 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100783
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200784 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200785 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200786 yield from self.all_keys_for_lifetimes()
787 yield from self.all_keys_for_usage_flags()
788 yield from self.all_keys_for_types()
789 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200790
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200791 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100792 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200793 # First build a list of all keys, then construct all the corresponding
794 # test cases. This allows all required information to be obtained in
795 # one go, which is a significant performance gain as the information
796 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200797 all_keys = list(self.generate_all_keys())
798 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200799 if key.location_value() != 0:
800 # Skip keys with a non-default location, because they
801 # require a driver and we currently have no mechanism to
802 # determine whether a driver is available.
803 continue
804 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100805
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200806class StorageFormatForward(StorageFormat):
807 """Storage format stability test cases for forward compatibility."""
808
809 def __init__(self, info: Information, version: int) -> None:
810 super().__init__(info, version, True)
811
812class StorageFormatV0(StorageFormat):
813 """Storage format stability test cases for version 0 compatibility."""
814
815 def __init__(self, info: Information) -> None:
816 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100817
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200818 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200819 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100820 yield from super().all_keys_for_usage_flags()
821 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200822
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200823 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200824 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200825 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200826 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200827 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200828 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200829 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200830 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200831 algorithm and key type combination.
832 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200833 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200834 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100835 usage_flags = ['PSA_KEY_USAGE_EXPORT']
836 material_usage_flags = usage_flags + [implyer_usage]
837 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200838 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200839 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100840 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
841 alg_expression = crypto_knowledge.short_expression(alg, 1)
842 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200843 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200844 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200845 key = StorageTestData(version=self.version,
846 id=1, lifetime=0x00000001,
847 type=key_type.expression, bits=bits,
848 usage=material_usage_flags,
849 expected_usage=expected_usage_flags,
850 without_implicit_usage=True,
851 alg=alg, alg2=alg2,
852 material=key_material,
853 description=description)
854 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200855
856 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200857 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200858 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800859 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200860 # must be filtered. Pair them with keywords created from its names.
861 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
862 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
863 keyword_translation = {
864 'ECDSA': 'ECC',
865 'ED[0-9]*.*' : 'EDWARDS'
866 }
867 exclusive_keywords = {
868 'EDWARDS': 'ECC'
869 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200870 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
871 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200872 alg_with_keys = {} #type: Dict[str, List[str]]
873 translation_table = str.maketrans('(', '_', ')')
874 for alg in algorithms:
875 # Generate keywords from the name of the algorithm
876 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
877 # Translate keywords for better matching with the key types
878 for keyword in alg_keywords.copy():
879 for pattern, replace in keyword_translation.items():
880 if re.match(pattern, keyword):
881 alg_keywords.remove(keyword)
882 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800883 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200884 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
885 continue
886
887 for key_type in key_types:
888 # Generate keywords from the of the key type
889 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
890
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800891 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200892 for keyword1, keyword2 in exclusive_keywords.items():
893 if keyword1 in key_type_keywords:
894 key_type_keywords.remove(keyword2)
895
896 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
897 not key_type_keywords.isdisjoint(alg_keywords):
898 if alg in alg_with_keys:
899 alg_with_keys[alg].append(key_type)
900 else:
901 alg_with_keys[alg] = [key_type]
902 return alg_with_keys
903
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200904 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200905 """Generate test keys for usage flag extensions."""
906 # Generate a key type and algorithm pair for each extendable usage
907 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800908 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200909 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200910
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200911 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
912 for alg in sorted(alg_with_keys):
913 for key_type in sorted(alg_with_keys[alg]):
914 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200915 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100916 if kt.is_public() and '_SIGN_' in usage:
917 # Can't sign with a public key
918 continue
919 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200920
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200921 def generate_all_keys(self) -> Iterator[StorageTestData]:
922 yield from super().generate_all_keys()
923 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200924
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200925class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100926 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100927 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200928 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100929 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200930 'test_suite_psa_crypto_generate_key.generated':
931 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100932 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100933 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200934 'test_suite_psa_crypto_op_fail.generated':
935 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100936 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200937 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100938 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200939 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100940 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
941
Werner Lewisfbb75e32022-08-24 11:30:03 +0100942 def __init__(self, options):
943 super().__init__(options)
944 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100945
Werner Lewisfbb75e32022-08-24 11:30:03 +0100946 def generate_target(self, name: str, *target_args) -> None:
947 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100948
949if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200950 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)