blob: 44f3476b68c9576d88f38b52c00fde15dbea4782 [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
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100310 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200311 generate_dependencies.append("MBEDTLS_GENPRIME")
Valerio Setti5d58a552023-06-22 14:02:04 +0200312 # PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE suffix
Valerio Setti5ca80e72023-06-20 19:27:02 +0200313 # to state that they support key generation.
Valerio Setti76882fc2023-06-22 14:46:23 +0200314 generate_dependencies = [re.sub(r'KEY_PAIR\Z', r'KEY_PAIR_GENERATE', dep)
315 for dep in generate_dependencies]
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200316 for bits in kt.sizes_to_test():
317 yield test_case_for_key_generation(
318 kt.expression, bits,
319 finish_family_dependencies(generate_dependencies, bits),
320 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200321 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200322 )
323
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200324 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
325 """Generate test cases that exercise the generation of keys."""
326 for key_type in sorted(self.constructors.key_types):
327 if key_type in self.ECC_KEY_TYPES:
328 continue
329 kt = crypto_knowledge.KeyType(key_type)
330 yield from self.test_cases_for_key_type_key_generation(kt)
331 for curve_family in sorted(self.constructors.ecc_curves):
332 for constr in self.ECC_KEY_TYPES:
333 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200334 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200335
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200336class OpFail:
337 """Generate test cases for operations that must fail."""
338 #pylint: disable=too-few-public-methods
339
Gilles Peskinecba28a72022-03-15 17:26:33 +0100340 class Reason(enum.Enum):
341 NOT_SUPPORTED = 0
342 INVALID = 1
343 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200344 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100345
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200346 def __init__(self, info: Information) -> None:
347 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100348 key_type_expressions = self.constructors.generate_expressions(
349 sorted(self.constructors.key_types)
350 )
351 self.key_types = [crypto_knowledge.KeyType(kt_expr)
352 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200353
Gilles Peskinecba28a72022-03-15 17:26:33 +0100354 def make_test_case(
355 self,
356 alg: crypto_knowledge.Algorithm,
357 category: crypto_knowledge.AlgorithmCategory,
358 reason: 'Reason',
359 kt: Optional[crypto_knowledge.KeyType] = None,
360 not_deps: FrozenSet[str] = frozenset(),
361 ) -> test_case.TestCase:
362 """Construct a failure test case for a one-key or keyless operation."""
363 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200364 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100365 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200366 if reason == self.Reason.NOT_SUPPORTED:
367 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
368 for dep in not_deps]
369 pretty_reason = '!' + '&'.join(sorted(short_deps))
370 else:
371 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100372 if kt:
373 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100374 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200375 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100376 key_type = ''
377 pretty_type = ''
378 tc.set_description('PSA {} {}: {}{}'
379 .format(category.name.lower(),
380 pretty_alg,
381 pretty_reason,
382 ' with ' + pretty_type if pretty_type else ''))
383 dependencies = automatic_dependencies(alg.base_expression, key_type)
384 for i, dep in enumerate(dependencies):
385 if dep in not_deps:
386 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200387 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100388 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000389 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100390 if kt:
391 key_material = kt.key_material(kt.sizes_to_test()[0])
392 arguments += [key_type, test_case.hex_string(key_material)]
393 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200394 if category.is_asymmetric():
395 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100396 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
397 'INVALID_ARGUMENT')
398 arguments.append('PSA_ERROR_' + error)
399 tc.set_arguments(arguments)
400 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200401
Gilles Peskinecba28a72022-03-15 17:26:33 +0100402 def no_key_test_cases(
403 self,
404 alg: crypto_knowledge.Algorithm,
405 category: crypto_knowledge.AlgorithmCategory,
406 ) -> Iterator[test_case.TestCase]:
407 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200408 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100409 # Compatible operation, unsupported algorithm
410 for dep in automatic_dependencies(alg.base_expression):
411 yield self.make_test_case(alg, category,
412 self.Reason.NOT_SUPPORTED,
413 not_deps=frozenset([dep]))
414 else:
415 # Incompatible operation, supported algorithm
416 yield self.make_test_case(alg, category, self.Reason.INVALID)
417
418 def one_key_test_cases(
419 self,
420 alg: crypto_knowledge.Algorithm,
421 category: crypto_knowledge.AlgorithmCategory,
422 ) -> Iterator[test_case.TestCase]:
423 """Generate failure test cases for one-key operations with the specified algorithm."""
424 for kt in self.key_types:
425 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200426 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100427 # Compatible key and operation, unsupported algorithm
428 for dep in automatic_dependencies(alg.base_expression):
429 yield self.make_test_case(alg, category,
430 self.Reason.NOT_SUPPORTED,
431 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200432 # Public key for a private-key operation
433 if category.is_asymmetric() and kt.is_public():
434 yield self.make_test_case(alg, category,
435 self.Reason.PUBLIC,
436 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100437 elif key_is_compatible:
438 # Compatible key, incompatible operation, supported algorithm
439 yield self.make_test_case(alg, category,
440 self.Reason.INVALID,
441 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200442 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100443 # Incompatible key, compatible operation, supported algorithm
444 yield self.make_test_case(alg, category,
445 self.Reason.INCOMPATIBLE,
446 kt=kt)
447 else:
448 # Incompatible key and operation. Don't test cases where
449 # multiple things are wrong, to keep the number of test
450 # cases reasonable.
451 pass
452
453 def test_cases_for_algorithm(
454 self,
455 alg: crypto_knowledge.Algorithm,
456 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200457 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100458 for category in crypto_knowledge.AlgorithmCategory:
459 if category == crypto_knowledge.AlgorithmCategory.PAKE:
460 # PAKE operations are not implemented yet
461 pass
462 elif category.requires_key():
463 yield from self.one_key_test_cases(alg, category)
464 else:
465 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200466
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200467 def all_test_cases(self) -> Iterator[test_case.TestCase]:
468 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200469 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100470 for expr in self.constructors.generate_expressions(algorithms):
471 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200472 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200473
474
Gilles Peskine897dff92021-03-10 15:03:44 +0100475class StorageKey(psa_storage.Key):
476 """Representation of a key for storage format testing."""
477
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200478 IMPLICIT_USAGE_FLAGS = {
479 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
480 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
481 } #type: Dict[str, str]
482 """Mapping of usage flags to the flags that they imply."""
483
484 def __init__(
485 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100486 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200487 without_implicit_usage: Optional[bool] = False,
488 **kwargs
489 ) -> None:
490 """Prepare to generate a key.
491
492 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000493 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100495 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200496 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100497 for flag in sorted(usage_flags):
498 if flag in self.IMPLICIT_USAGE_FLAGS:
499 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
500 if usage_flags:
501 usage_expression = ' | '.join(sorted(usage_flags))
502 else:
503 usage_expression = '0'
504 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200505
506class StorageTestData(StorageKey):
507 """Representation of test case data for storage format testing."""
508
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200509 def __init__(
510 self,
511 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100512 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200513 **kwargs
514 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200515 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200516
Tom Cosgrove1797b052022-12-04 17:19:59 +0000517 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200518 * `expected_usage`: the usage flags generated as the expected usage flags
519 in the test cases. CAn differ from the usage flags
520 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200521 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100522 super().__init__(**kwargs)
523 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100524 if expected_usage is None:
525 self.expected_usage = self.usage #type: psa_storage.Expr
526 elif expected_usage:
527 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
528 else:
529 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200530
Gilles Peskine897dff92021-03-10 15:03:44 +0100531class StorageFormat:
532 """Storage format stability test cases."""
533
534 def __init__(self, info: Information, version: int, forward: bool) -> None:
535 """Prepare to generate test cases for storage format stability.
536
537 * `info`: information about the API. See the `Information` class.
538 * `version`: the storage format version to generate test cases for.
539 * `forward`: if true, generate forward compatibility test cases which
540 save a key and check that its representation is as intended. Otherwise
541 generate backward compatibility test cases which inject a key
542 representation and check that it can be read and used.
543 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200544 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
545 self.version = version #type: int
546 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100547
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100548 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100549 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100550 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100551 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100552 cls,
553 key_type: psa_storage.Expr, bits: int,
554 alg: psa_storage.Expr
555 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100556 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100557
558 Normally only the type and algorithm matter for compatibility, and
559 this is handled in crypto_knowledge.KeyType.can_do(). This function
560 exists to detect exceptional cases. Exceptional cases detected here
561 are not tested in OpFail and should therefore have manually written
562 test cases.
563 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100564 # Some test keys have the RAW_DATA type and attributes that don't
565 # necessarily make sense. We do this to validate numerical
566 # encodings of the attributes.
567 # Raw data keys have no useful exercise anyway so there is no
568 # loss of test coverage.
569 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
570 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100571 # OAEP requires room for two hashes plus wrapping
572 m = cls.RSA_OAEP_RE.match(alg.string)
573 if m:
574 hash_alg = m.group(1)
575 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
576 key_length = (bits + 7) // 8
577 # Leave enough room for at least one byte of plaintext
578 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100579 # There's nothing wrong with ECC keys on Brainpool curves,
580 # but operations with them are very slow. So we only exercise them
581 # with a single algorithm, not with all possible hashes. We do
582 # exercise other curves with all algorithms so test coverage is
583 # perfectly adequate like this.
584 m = cls.BRAINPOOL_RE.match(key_type.string)
585 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
586 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100587 return True
588
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200589 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100590 """Construct a storage format test case for the given key.
591
592 If ``forward`` is true, generate a forward compatibility test case:
593 create a key and validate that it has the expected representation.
594 Otherwise generate a backward compatibility test case: inject the
595 key representation into storage and validate that it can be read
596 correctly.
597 """
598 verb = 'save' if self.forward else 'read'
599 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100600 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100601 dependencies = automatic_dependencies(
602 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100603 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100604 )
605 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800606 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100607 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100608 tc.set_function('key_storage_' + verb)
609 if self.forward:
610 extra_arguments = []
611 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200612 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100613 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200614 flags.append('TEST_FLAG_EXERCISE')
615 if 'READ_ONLY' in key.lifetime.string:
616 flags.append('TEST_FLAG_READ_ONLY')
617 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100618 tc.set_arguments([key.lifetime.string,
619 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100620 key.expected_usage.string,
621 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100622 '"' + key.material.hex() + '"',
623 '"' + key.hex() + '"',
624 *extra_arguments])
625 return tc
626
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200627 def key_for_lifetime(
628 self,
629 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200630 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200631 """Construct a test key for the given lifetime."""
632 short = lifetime
633 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
634 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100635 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200636 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200637 key = StorageTestData(version=self.version,
638 id=1, lifetime=lifetime,
639 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100640 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200641 material=b'L',
642 description=description)
643 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200644
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200645 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200646 """Generate test keys covering lifetimes."""
647 lifetimes = sorted(self.constructors.lifetimes)
648 expressions = self.constructors.generate_expressions(lifetimes)
649 for lifetime in expressions:
650 # Don't attempt to create or load a volatile key in storage
651 if 'VOLATILE' in lifetime:
652 continue
653 # Don't attempt to create a read-only key in storage,
654 # but do attempt to load one.
655 if 'READ_ONLY' in lifetime and self.forward:
656 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200657 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200658
Gilles Peskinef7614272022-02-24 18:58:08 +0100659 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100660 self,
661 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200662 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100663 test_implicit_usage: Optional[bool] = True
664 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100665 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100666 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100667 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200668 key1 = StorageTestData(version=self.version,
669 id=1, lifetime=0x00000001,
670 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100671 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100672 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100673 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200674 material=b'K',
675 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100676 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100677 usage_expr = key1.expected_usage.string
678 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100679 else:
680 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100681 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100682
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200683 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100684 """Generate test keys covering usage flags."""
685 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100686 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200687 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100688 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200689 for flag1, flag2 in zip(known_flags,
690 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100691 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200692
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200693 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200694 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100695 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200696
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200697 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200698 yield from self.generate_keys_for_usage_flags()
699 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100700
Gilles Peskine7de7c102021-04-29 22:28:07 +0200701 def key_for_type_and_alg(
702 self,
703 kt: crypto_knowledge.KeyType,
704 bits: int,
705 alg: Optional[crypto_knowledge.Algorithm] = None,
706 ) -> StorageTestData:
707 """Construct a test key of the given type.
708
709 If alg is not None, this key allows it.
710 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100711 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100712 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200713 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100714 if alg is not None:
715 alg1 = alg.expression
716 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200717 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100718 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200719 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100720 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 key = StorageTestData(version=self.version,
722 id=1, lifetime=0x00000001,
723 type=kt.expression, bits=bits,
724 usage=usage_flags, alg=alg1, alg2=alg2,
725 material=key_material,
726 description=description)
727 return key
728
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100729 def keys_for_type(
730 self,
731 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200732 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200733 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200734 """Generate test keys for the given key type."""
735 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100736 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200737 # Test a non-exercisable key, as well as exercisable keys for
738 # each compatible algorithm.
739 # To do: test reading a key from storage with an incompatible
740 # or unsupported algorithm.
741 yield self.key_for_type_and_alg(kt, bits)
742 compatible_algorithms = [alg for alg in all_algorithms
743 if kt.can_do(alg)]
744 for alg in compatible_algorithms:
745 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100746
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200747 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100748 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200749 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200750 all_algorithms = [crypto_knowledge.Algorithm(alg)
751 for alg in self.constructors.generate_expressions(
752 sorted(self.constructors.algorithms)
753 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200754 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200755 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100756
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200757 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200758 """Generate test keys for the encoding of the specified algorithm."""
759 # These test cases only validate the encoding of algorithms, not
760 # whether the key read from storage is suitable for an operation.
761 # `keys_for_types` generate read tests with an algorithm and a
762 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100763 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100764 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200765 key1 = StorageTestData(version=self.version,
766 id=1, lifetime=0x00000001,
767 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
768 usage=usage, alg=alg, alg2=0,
769 material=b'K',
770 description='alg: ' + descr)
771 yield key1
772 key2 = StorageTestData(version=self.version,
773 id=1, lifetime=0x00000001,
774 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
775 usage=usage, alg=0, alg2=alg,
776 material=b'L',
777 description='alg2: ' + descr)
778 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100779
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200780 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100781 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200782 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200783 for alg in self.constructors.generate_expressions(algorithms):
784 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100785
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200786 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200787 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200788 yield from self.all_keys_for_lifetimes()
789 yield from self.all_keys_for_usage_flags()
790 yield from self.all_keys_for_types()
791 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200792
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200793 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100794 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200795 # First build a list of all keys, then construct all the corresponding
796 # test cases. This allows all required information to be obtained in
797 # one go, which is a significant performance gain as the information
798 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200799 all_keys = list(self.generate_all_keys())
800 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200801 if key.location_value() != 0:
802 # Skip keys with a non-default location, because they
803 # require a driver and we currently have no mechanism to
804 # determine whether a driver is available.
805 continue
806 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100807
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200808class StorageFormatForward(StorageFormat):
809 """Storage format stability test cases for forward compatibility."""
810
811 def __init__(self, info: Information, version: int) -> None:
812 super().__init__(info, version, True)
813
814class StorageFormatV0(StorageFormat):
815 """Storage format stability test cases for version 0 compatibility."""
816
817 def __init__(self, info: Information) -> None:
818 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100819
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200820 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200821 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100822 yield from super().all_keys_for_usage_flags()
823 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200824
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200825 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200826 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200827 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200828 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200829 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200830 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200831 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200832 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200833 algorithm and key type combination.
834 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200835 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200836 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100837 usage_flags = ['PSA_KEY_USAGE_EXPORT']
838 material_usage_flags = usage_flags + [implyer_usage]
839 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200840 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200841 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100842 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
843 alg_expression = crypto_knowledge.short_expression(alg, 1)
844 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200845 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200846 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200847 key = StorageTestData(version=self.version,
848 id=1, lifetime=0x00000001,
849 type=key_type.expression, bits=bits,
850 usage=material_usage_flags,
851 expected_usage=expected_usage_flags,
852 without_implicit_usage=True,
853 alg=alg, alg2=alg2,
854 material=key_material,
855 description=description)
856 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200857
858 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200859 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200860 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800861 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200862 # must be filtered. Pair them with keywords created from its names.
863 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
864 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
865 keyword_translation = {
866 'ECDSA': 'ECC',
867 'ED[0-9]*.*' : 'EDWARDS'
868 }
869 exclusive_keywords = {
870 'EDWARDS': 'ECC'
871 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200872 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
873 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 alg_with_keys = {} #type: Dict[str, List[str]]
875 translation_table = str.maketrans('(', '_', ')')
876 for alg in algorithms:
877 # Generate keywords from the name of the algorithm
878 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
879 # Translate keywords for better matching with the key types
880 for keyword in alg_keywords.copy():
881 for pattern, replace in keyword_translation.items():
882 if re.match(pattern, keyword):
883 alg_keywords.remove(keyword)
884 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800885 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200886 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
887 continue
888
889 for key_type in key_types:
890 # Generate keywords from the of the key type
891 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
892
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800893 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200894 for keyword1, keyword2 in exclusive_keywords.items():
895 if keyword1 in key_type_keywords:
896 key_type_keywords.remove(keyword2)
897
898 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
899 not key_type_keywords.isdisjoint(alg_keywords):
900 if alg in alg_with_keys:
901 alg_with_keys[alg].append(key_type)
902 else:
903 alg_with_keys[alg] = [key_type]
904 return alg_with_keys
905
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200906 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200907 """Generate test keys for usage flag extensions."""
908 # Generate a key type and algorithm pair for each extendable usage
909 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800910 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200911 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200912
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200913 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
914 for alg in sorted(alg_with_keys):
915 for key_type in sorted(alg_with_keys[alg]):
916 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200917 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100918 if kt.is_public() and '_SIGN_' in usage:
919 # Can't sign with a public key
920 continue
921 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200922
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200923 def generate_all_keys(self) -> Iterator[StorageTestData]:
924 yield from super().generate_all_keys()
925 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200926
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200927class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100928 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100929 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200930 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100931 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200932 'test_suite_psa_crypto_generate_key.generated':
933 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100934 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100935 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200936 'test_suite_psa_crypto_op_fail.generated':
937 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100938 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200939 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100940 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200941 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100942 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
943
Werner Lewisfbb75e32022-08-24 11:30:03 +0100944 def __init__(self, options):
945 super().__init__(options)
946 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100947
Werner Lewisfbb75e32022-08-24 11:30:03 +0100948 def generate_target(self, name: str, *target_args) -> None:
949 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100950
951if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200952 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)