blob: 6002c4ac741812b3db1f7880ccc3d968044aea8d [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]]
88def generate_key_dependencis(description: str) -> List[str]:
89 """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')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200113 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100114 for dep in dependencies):
115 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
116
Gilles Peskine14e428f2021-01-26 22:19:21 +0100117
Gilles Peskineb94ea512021-03-10 02:12:08 +0100118class Information:
119 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100120
Gilles Peskineb94ea512021-03-10 02:12:08 +0100121 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100122 self.constructors = self.read_psa_interface()
123
124 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100125 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200126 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100127 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200128 # Mbed TLS doesn't support finite-field DH yet and will not support
129 # finite-field DSA. Don't attempt to generate any related test case.
130 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
131 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100132 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
133 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100134
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200135 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100136 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200137 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100138 header_file_names = ['include/psa/crypto_values.h',
139 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200140 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100141 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200142 constructors.parse_header(header_file_name)
143 for test_cases in test_suites:
144 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100145 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200146 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100147 return constructors
148
Gilles Peskine14e428f2021-01-26 22:19:21 +0100149
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200150def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100151 verb: str, key_type: str, bits: int,
152 dependencies: List[str],
153 *args: str,
154 param_descr: str = ''
155) -> test_case.TestCase:
156 """Return one test case exercising a key creation method
157 for an unsupported key type or size.
158 """
159 hack_dependencies_not_implemented(dependencies)
160 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100161 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100162 adverb = 'not' if dependencies else 'never'
163 if param_descr:
164 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200165 tc.set_description('PSA {} {} {}-bit {} supported'
166 .format(verb, short_key_type, bits, adverb))
167 tc.set_dependencies(dependencies)
168 tc.set_function(verb + '_not_supported')
169 tc.set_arguments([key_type] + list(args))
170 return tc
171
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100172class KeyTypeNotSupported:
173 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100174
175 def __init__(self, info: Information) -> None:
176 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100177
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100178 ALWAYS_SUPPORTED = frozenset([
179 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100180 'PSA_KEY_TYPE_PASSWORD',
181 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100182 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200183 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100184 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100185 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100186 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100187 kt: crypto_knowledge.KeyType,
188 param: Optional[int] = None,
189 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100190 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200191 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100192
193 If param is present and not None, emit test cases conditioned on this
194 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200195 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100196 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100197 if kt.name in self.ALWAYS_SUPPORTED:
198 # Don't generate test cases for key types that are always supported.
199 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100200 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100201 import_dependencies = [('!' if param is None else '') +
202 psa_want_symbol(kt.name)]
203 if kt.params is not None:
204 import_dependencies += [('!' if param == i else '') +
205 psa_want_symbol(sym)
206 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100207 if kt.name.endswith('_PUBLIC_KEY'):
208 generate_dependencies = []
209 else:
210 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100211 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200212 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100213 'import', kt.expression, bits,
214 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100215 test_case.hex_string(kt.key_material(bits)),
216 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100218 if not generate_dependencies and param is not None:
219 # If generation is impossible for this key type, rather than
220 # supported or not depending on implementation capabilities,
221 # only generate the test case once.
222 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100223 # For public key we expect that key generation fails with
224 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100225 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200226 yield test_case_for_key_type_not_supported(
227 'generate', kt.expression, bits,
228 finish_family_dependencies(generate_dependencies, bits),
229 str(bits),
230 param_descr=param_descr,
231 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100232 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100233
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200234 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
235 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
236
Gilles Peskine3d778392021-02-17 15:11:05 +0100237 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100238 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100239 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200240 if key_type in self.ECC_KEY_TYPES:
241 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100242 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100243 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100244 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200245 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100246 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100247 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100248 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100249 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100250 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100251
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200252def test_case_for_key_generation(
253 key_type: str, bits: int,
254 dependencies: List[str],
255 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200256 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200257) -> test_case.TestCase:
258 """Return one test case exercising a key generation.
259 """
260 hack_dependencies_not_implemented(dependencies)
261 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100262 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200263 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200264 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200265 tc.set_dependencies(dependencies)
266 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100267 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200268
269 return tc
270
271class KeyGenerate:
272 """Generate positive and negative (invalid argument) test cases for key generation."""
273
274 def __init__(self, info: Information) -> None:
275 self.constructors = info.constructors
276
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200277 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
278 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
279
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100280 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200281 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200282 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200283 ) -> Iterator[test_case.TestCase]:
284 """Return test cases exercising key generation.
285
286 All key types can be generated except for public keys. For public key
287 PSA_ERROR_INVALID_ARGUMENT status is expected.
288 """
289 result = 'PSA_SUCCESS'
290
291 import_dependencies = [psa_want_symbol(kt.name)]
292 if kt.params is not None:
293 import_dependencies += [psa_want_symbol(sym)
294 for i, sym in enumerate(kt.params)]
295 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100296 # The library checks whether the key type is a public key generically,
297 # before it reaches a point where it needs support for the specific key
298 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200299 generate_dependencies = []
300 result = 'PSA_ERROR_INVALID_ARGUMENT'
301 else:
302 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100303 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200304 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200305 for bits in kt.sizes_to_test():
306 yield test_case_for_key_generation(
307 kt.expression, bits,
308 finish_family_dependencies(generate_dependencies, bits),
309 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200310 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200311 )
312
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
314 """Generate test cases that exercise the generation of keys."""
315 for key_type in sorted(self.constructors.key_types):
316 if key_type in self.ECC_KEY_TYPES:
317 continue
318 kt = crypto_knowledge.KeyType(key_type)
319 yield from self.test_cases_for_key_type_key_generation(kt)
320 for curve_family in sorted(self.constructors.ecc_curves):
321 for constr in self.ECC_KEY_TYPES:
322 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200323 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200324
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200325class OpFail:
326 """Generate test cases for operations that must fail."""
327 #pylint: disable=too-few-public-methods
328
Gilles Peskinecba28a72022-03-15 17:26:33 +0100329 class Reason(enum.Enum):
330 NOT_SUPPORTED = 0
331 INVALID = 1
332 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200333 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100334
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200335 def __init__(self, info: Information) -> None:
336 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100337 key_type_expressions = self.constructors.generate_expressions(
338 sorted(self.constructors.key_types)
339 )
340 self.key_types = [crypto_knowledge.KeyType(kt_expr)
341 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200342
Gilles Peskinecba28a72022-03-15 17:26:33 +0100343 def make_test_case(
344 self,
345 alg: crypto_knowledge.Algorithm,
346 category: crypto_knowledge.AlgorithmCategory,
347 reason: 'Reason',
348 kt: Optional[crypto_knowledge.KeyType] = None,
349 not_deps: FrozenSet[str] = frozenset(),
350 ) -> test_case.TestCase:
351 """Construct a failure test case for a one-key or keyless operation."""
352 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200353 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100354 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200355 if reason == self.Reason.NOT_SUPPORTED:
356 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
357 for dep in not_deps]
358 pretty_reason = '!' + '&'.join(sorted(short_deps))
359 else:
360 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100361 if kt:
362 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100363 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200364 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100365 key_type = ''
366 pretty_type = ''
367 tc.set_description('PSA {} {}: {}{}'
368 .format(category.name.lower(),
369 pretty_alg,
370 pretty_reason,
371 ' with ' + pretty_type if pretty_type else ''))
372 dependencies = automatic_dependencies(alg.base_expression, key_type)
373 for i, dep in enumerate(dependencies):
374 if dep in not_deps:
375 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200376 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100377 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000378 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100379 if kt:
380 key_material = kt.key_material(kt.sizes_to_test()[0])
381 arguments += [key_type, test_case.hex_string(key_material)]
382 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200383 if category.is_asymmetric():
384 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100385 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
386 'INVALID_ARGUMENT')
387 arguments.append('PSA_ERROR_' + error)
388 tc.set_arguments(arguments)
389 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200390
Gilles Peskinecba28a72022-03-15 17:26:33 +0100391 def no_key_test_cases(
392 self,
393 alg: crypto_knowledge.Algorithm,
394 category: crypto_knowledge.AlgorithmCategory,
395 ) -> Iterator[test_case.TestCase]:
396 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200397 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100398 # Compatible operation, unsupported algorithm
399 for dep in automatic_dependencies(alg.base_expression):
400 yield self.make_test_case(alg, category,
401 self.Reason.NOT_SUPPORTED,
402 not_deps=frozenset([dep]))
403 else:
404 # Incompatible operation, supported algorithm
405 yield self.make_test_case(alg, category, self.Reason.INVALID)
406
407 def one_key_test_cases(
408 self,
409 alg: crypto_knowledge.Algorithm,
410 category: crypto_knowledge.AlgorithmCategory,
411 ) -> Iterator[test_case.TestCase]:
412 """Generate failure test cases for one-key operations with the specified algorithm."""
413 for kt in self.key_types:
414 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200415 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100416 # Compatible key and operation, unsupported algorithm
417 for dep in automatic_dependencies(alg.base_expression):
418 yield self.make_test_case(alg, category,
419 self.Reason.NOT_SUPPORTED,
420 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200421 # Public key for a private-key operation
422 if category.is_asymmetric() and kt.is_public():
423 yield self.make_test_case(alg, category,
424 self.Reason.PUBLIC,
425 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100426 elif key_is_compatible:
427 # Compatible key, incompatible operation, supported algorithm
428 yield self.make_test_case(alg, category,
429 self.Reason.INVALID,
430 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200431 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100432 # Incompatible key, compatible operation, supported algorithm
433 yield self.make_test_case(alg, category,
434 self.Reason.INCOMPATIBLE,
435 kt=kt)
436 else:
437 # Incompatible key and operation. Don't test cases where
438 # multiple things are wrong, to keep the number of test
439 # cases reasonable.
440 pass
441
442 def test_cases_for_algorithm(
443 self,
444 alg: crypto_knowledge.Algorithm,
445 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200446 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100447 for category in crypto_knowledge.AlgorithmCategory:
448 if category == crypto_knowledge.AlgorithmCategory.PAKE:
449 # PAKE operations are not implemented yet
450 pass
451 elif category.requires_key():
452 yield from self.one_key_test_cases(alg, category)
453 else:
454 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200455
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200456 def all_test_cases(self) -> Iterator[test_case.TestCase]:
457 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200458 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100459 for expr in self.constructors.generate_expressions(algorithms):
460 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200461 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200462
463
Gilles Peskine897dff92021-03-10 15:03:44 +0100464class StorageKey(psa_storage.Key):
465 """Representation of a key for storage format testing."""
466
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200467 IMPLICIT_USAGE_FLAGS = {
468 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
469 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
470 } #type: Dict[str, str]
471 """Mapping of usage flags to the flags that they imply."""
472
473 def __init__(
474 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100475 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200476 without_implicit_usage: Optional[bool] = False,
477 **kwargs
478 ) -> None:
479 """Prepare to generate a key.
480
481 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000482 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200483 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100484 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200485 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100486 for flag in sorted(usage_flags):
487 if flag in self.IMPLICIT_USAGE_FLAGS:
488 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
489 if usage_flags:
490 usage_expression = ' | '.join(sorted(usage_flags))
491 else:
492 usage_expression = '0'
493 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494
495class StorageTestData(StorageKey):
496 """Representation of test case data for storage format testing."""
497
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200498 def __init__(
499 self,
500 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100501 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200502 **kwargs
503 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200504 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200505
Tom Cosgrove1797b052022-12-04 17:19:59 +0000506 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200507 * `expected_usage`: the usage flags generated as the expected usage flags
508 in the test cases. CAn differ from the usage flags
509 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200510 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100511 super().__init__(**kwargs)
512 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100513 if expected_usage is None:
514 self.expected_usage = self.usage #type: psa_storage.Expr
515 elif expected_usage:
516 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
517 else:
518 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200519
Gilles Peskine897dff92021-03-10 15:03:44 +0100520class StorageFormat:
521 """Storage format stability test cases."""
522
523 def __init__(self, info: Information, version: int, forward: bool) -> None:
524 """Prepare to generate test cases for storage format stability.
525
526 * `info`: information about the API. See the `Information` class.
527 * `version`: the storage format version to generate test cases for.
528 * `forward`: if true, generate forward compatibility test cases which
529 save a key and check that its representation is as intended. Otherwise
530 generate backward compatibility test cases which inject a key
531 representation and check that it can be read and used.
532 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200533 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
534 self.version = version #type: int
535 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100536
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100537 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100538 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100539 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100540 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100541 cls,
542 key_type: psa_storage.Expr, bits: int,
543 alg: psa_storage.Expr
544 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100545 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100546
547 Normally only the type and algorithm matter for compatibility, and
548 this is handled in crypto_knowledge.KeyType.can_do(). This function
549 exists to detect exceptional cases. Exceptional cases detected here
550 are not tested in OpFail and should therefore have manually written
551 test cases.
552 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100553 # Some test keys have the RAW_DATA type and attributes that don't
554 # necessarily make sense. We do this to validate numerical
555 # encodings of the attributes.
556 # Raw data keys have no useful exercise anyway so there is no
557 # loss of test coverage.
558 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
559 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100560 # OAEP requires room for two hashes plus wrapping
561 m = cls.RSA_OAEP_RE.match(alg.string)
562 if m:
563 hash_alg = m.group(1)
564 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
565 key_length = (bits + 7) // 8
566 # Leave enough room for at least one byte of plaintext
567 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100568 # There's nothing wrong with ECC keys on Brainpool curves,
569 # but operations with them are very slow. So we only exercise them
570 # with a single algorithm, not with all possible hashes. We do
571 # exercise other curves with all algorithms so test coverage is
572 # perfectly adequate like this.
573 m = cls.BRAINPOOL_RE.match(key_type.string)
574 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
575 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100576 return True
577
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200578 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100579 """Construct a storage format test case for the given key.
580
581 If ``forward`` is true, generate a forward compatibility test case:
582 create a key and validate that it has the expected representation.
583 Otherwise generate a backward compatibility test case: inject the
584 key representation into storage and validate that it can be read
585 correctly.
586 """
587 verb = 'save' if self.forward else 'read'
588 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100589 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100590 dependencies = automatic_dependencies(
591 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100592 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100593 )
594 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang3f417442023-04-21 14:29:16 +0800595 dependencies += generate_key_dependencis(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100596 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100597 tc.set_function('key_storage_' + verb)
598 if self.forward:
599 extra_arguments = []
600 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200601 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100602 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200603 flags.append('TEST_FLAG_EXERCISE')
604 if 'READ_ONLY' in key.lifetime.string:
605 flags.append('TEST_FLAG_READ_ONLY')
606 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100607 tc.set_arguments([key.lifetime.string,
608 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100609 key.expected_usage.string,
610 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100611 '"' + key.material.hex() + '"',
612 '"' + key.hex() + '"',
613 *extra_arguments])
614 return tc
615
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200616 def key_for_lifetime(
617 self,
618 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200619 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200620 """Construct a test key for the given lifetime."""
621 short = lifetime
622 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
623 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100624 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200625 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200626 key = StorageTestData(version=self.version,
627 id=1, lifetime=lifetime,
628 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100629 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200630 material=b'L',
631 description=description)
632 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200633
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200634 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200635 """Generate test keys covering lifetimes."""
636 lifetimes = sorted(self.constructors.lifetimes)
637 expressions = self.constructors.generate_expressions(lifetimes)
638 for lifetime in expressions:
639 # Don't attempt to create or load a volatile key in storage
640 if 'VOLATILE' in lifetime:
641 continue
642 # Don't attempt to create a read-only key in storage,
643 # but do attempt to load one.
644 if 'READ_ONLY' in lifetime and self.forward:
645 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200646 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200647
Gilles Peskinef7614272022-02-24 18:58:08 +0100648 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100649 self,
650 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200651 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100652 test_implicit_usage: Optional[bool] = True
653 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100654 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100655 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100656 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200657 key1 = StorageTestData(version=self.version,
658 id=1, lifetime=0x00000001,
659 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100660 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100661 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100662 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200663 material=b'K',
664 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100665 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100666 usage_expr = key1.expected_usage.string
667 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100668 else:
669 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100670 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100671
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200672 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100673 """Generate test keys covering usage flags."""
674 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100675 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200676 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100677 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200678 for flag1, flag2 in zip(known_flags,
679 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200681
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200682 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200683 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100684 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200685
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200686 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200687 yield from self.generate_keys_for_usage_flags()
688 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100689
Gilles Peskine7de7c102021-04-29 22:28:07 +0200690 def key_for_type_and_alg(
691 self,
692 kt: crypto_knowledge.KeyType,
693 bits: int,
694 alg: Optional[crypto_knowledge.Algorithm] = None,
695 ) -> StorageTestData:
696 """Construct a test key of the given type.
697
698 If alg is not None, this key allows it.
699 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100700 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100701 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200702 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100703 if alg is not None:
704 alg1 = alg.expression
705 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200706 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100707 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200708 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100709 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200710 key = StorageTestData(version=self.version,
711 id=1, lifetime=0x00000001,
712 type=kt.expression, bits=bits,
713 usage=usage_flags, alg=alg1, alg2=alg2,
714 material=key_material,
715 description=description)
716 return key
717
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100718 def keys_for_type(
719 self,
720 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200722 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200723 """Generate test keys for the given key type."""
724 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100725 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200726 # Test a non-exercisable key, as well as exercisable keys for
727 # each compatible algorithm.
728 # To do: test reading a key from storage with an incompatible
729 # or unsupported algorithm.
730 yield self.key_for_type_and_alg(kt, bits)
731 compatible_algorithms = [alg for alg in all_algorithms
732 if kt.can_do(alg)]
733 for alg in compatible_algorithms:
734 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100735
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200736 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100737 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200738 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200739 all_algorithms = [crypto_knowledge.Algorithm(alg)
740 for alg in self.constructors.generate_expressions(
741 sorted(self.constructors.algorithms)
742 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200743 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200744 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100745
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200746 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200747 """Generate test keys for the encoding of the specified algorithm."""
748 # These test cases only validate the encoding of algorithms, not
749 # whether the key read from storage is suitable for an operation.
750 # `keys_for_types` generate read tests with an algorithm and a
751 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100752 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100753 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200754 key1 = StorageTestData(version=self.version,
755 id=1, lifetime=0x00000001,
756 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
757 usage=usage, alg=alg, alg2=0,
758 material=b'K',
759 description='alg: ' + descr)
760 yield key1
761 key2 = StorageTestData(version=self.version,
762 id=1, lifetime=0x00000001,
763 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
764 usage=usage, alg=0, alg2=alg,
765 material=b'L',
766 description='alg2: ' + descr)
767 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100768
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200769 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100770 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200771 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200772 for alg in self.constructors.generate_expressions(algorithms):
773 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100774
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200775 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200776 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200777 yield from self.all_keys_for_lifetimes()
778 yield from self.all_keys_for_usage_flags()
779 yield from self.all_keys_for_types()
780 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200781
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200782 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100783 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200784 # First build a list of all keys, then construct all the corresponding
785 # test cases. This allows all required information to be obtained in
786 # one go, which is a significant performance gain as the information
787 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200788 all_keys = list(self.generate_all_keys())
789 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200790 if key.location_value() != 0:
791 # Skip keys with a non-default location, because they
792 # require a driver and we currently have no mechanism to
793 # determine whether a driver is available.
794 continue
795 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100796
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200797class StorageFormatForward(StorageFormat):
798 """Storage format stability test cases for forward compatibility."""
799
800 def __init__(self, info: Information, version: int) -> None:
801 super().__init__(info, version, True)
802
803class StorageFormatV0(StorageFormat):
804 """Storage format stability test cases for version 0 compatibility."""
805
806 def __init__(self, info: Information) -> None:
807 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100808
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200809 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200810 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100811 yield from super().all_keys_for_usage_flags()
812 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200813
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200814 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200815 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200816 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200817 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200818 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200819 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200820 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200821 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200822 algorithm and key type combination.
823 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200824 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200825 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100826 usage_flags = ['PSA_KEY_USAGE_EXPORT']
827 material_usage_flags = usage_flags + [implyer_usage]
828 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200829 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200830 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100831 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
832 alg_expression = crypto_knowledge.short_expression(alg, 1)
833 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200834 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200835 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200836 key = StorageTestData(version=self.version,
837 id=1, lifetime=0x00000001,
838 type=key_type.expression, bits=bits,
839 usage=material_usage_flags,
840 expected_usage=expected_usage_flags,
841 without_implicit_usage=True,
842 alg=alg, alg2=alg2,
843 material=key_material,
844 description=description)
845 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200846
847 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200848 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200849 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800850 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200851 # must be filtered. Pair them with keywords created from its names.
852 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
853 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
854 keyword_translation = {
855 'ECDSA': 'ECC',
856 'ED[0-9]*.*' : 'EDWARDS'
857 }
858 exclusive_keywords = {
859 'EDWARDS': 'ECC'
860 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200861 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
862 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200863 alg_with_keys = {} #type: Dict[str, List[str]]
864 translation_table = str.maketrans('(', '_', ')')
865 for alg in algorithms:
866 # Generate keywords from the name of the algorithm
867 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
868 # Translate keywords for better matching with the key types
869 for keyword in alg_keywords.copy():
870 for pattern, replace in keyword_translation.items():
871 if re.match(pattern, keyword):
872 alg_keywords.remove(keyword)
873 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800874 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200875 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
876 continue
877
878 for key_type in key_types:
879 # Generate keywords from the of the key type
880 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
881
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800882 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200883 for keyword1, keyword2 in exclusive_keywords.items():
884 if keyword1 in key_type_keywords:
885 key_type_keywords.remove(keyword2)
886
887 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
888 not key_type_keywords.isdisjoint(alg_keywords):
889 if alg in alg_with_keys:
890 alg_with_keys[alg].append(key_type)
891 else:
892 alg_with_keys[alg] = [key_type]
893 return alg_with_keys
894
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200895 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200896 """Generate test keys for usage flag extensions."""
897 # Generate a key type and algorithm pair for each extendable usage
898 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800899 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200900 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200901
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200902 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
903 for alg in sorted(alg_with_keys):
904 for key_type in sorted(alg_with_keys[alg]):
905 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200906 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100907 if kt.is_public() and '_SIGN_' in usage:
908 # Can't sign with a public key
909 continue
910 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200911
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200912 def generate_all_keys(self) -> Iterator[StorageTestData]:
913 yield from super().generate_all_keys()
914 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200915
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200916class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100917 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100918 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200919 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100920 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200921 'test_suite_psa_crypto_generate_key.generated':
922 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100923 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100924 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200925 'test_suite_psa_crypto_op_fail.generated':
926 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100927 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200928 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100929 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200930 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100931 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
932
Werner Lewisfbb75e32022-08-24 11:30:03 +0100933 def __init__(self, options):
934 super().__init__(options)
935 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100936
Werner Lewisfbb75e32022-08-24 11:30:03 +0100937 def generate_target(self, name: str, *target_args) -> None:
938 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100939
940if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200941 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)