blob: f5d83c6fb36d7cebd5763bb0e49c724af36be164 [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
Valerio Setti0c42c432023-06-29 12:10:52 +0200118def tweak_key_pair_dependency(dep: str, usage: str):
Valerio Settieabfef32023-06-30 11:09:43 +0200119 """
120 This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
121 symbols according to the required usage.
122 """
Valerio Setti0c42c432023-06-29 12:10:52 +0200123 ret_list = list()
Valerio Setti42796e22023-07-10 12:24:34 +0200124 if dep.endswith('KEY_PAIR'):
Valerio Setti0c42c432023-06-29 12:10:52 +0200125 if usage == "BASIC":
126 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
127 # config_psa.h).
Valerio Setti42796e22023-07-10 12:24:34 +0200128 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_BASIC', dep))
129 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_IMPORT', dep))
130 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_EXPORT', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200131 elif usage == "GENERATE":
Valerio Setti42796e22023-07-10 12:24:34 +0200132 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_GENERATE', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200133 else:
134 # No replacement to do in this case
135 ret_list.append(dep)
136 return ret_list
137
138def fix_key_pair_dependencies(dep_list: List[str], usage: str):
139 new_list = [new_deps
140 for dep in dep_list
141 for new_deps in tweak_key_pair_dependency(dep, usage)]
142
Valerio Setti24c64e82023-06-26 11:49:02 +0200143 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100144
Gilles Peskineb94ea512021-03-10 02:12:08 +0100145class Information:
146 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100147
Gilles Peskineb94ea512021-03-10 02:12:08 +0100148 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100149 self.constructors = self.read_psa_interface()
150
151 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100152 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200153 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100154 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200155 # Mbed TLS doesn't support finite-field DH yet and will not support
156 # finite-field DSA. Don't attempt to generate any related test case.
157 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
158 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100159 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
160 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100161
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200162 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100163 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200164 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100165 header_file_names = ['include/psa/crypto_values.h',
166 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200167 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100168 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200169 constructors.parse_header(header_file_name)
170 for test_cases in test_suites:
171 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100172 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200173 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100174 return constructors
175
Gilles Peskine14e428f2021-01-26 22:19:21 +0100176
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200177def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100178 verb: str, key_type: str, bits: int,
179 dependencies: List[str],
180 *args: str,
181 param_descr: str = ''
182) -> test_case.TestCase:
183 """Return one test case exercising a key creation method
184 for an unsupported key type or size.
185 """
186 hack_dependencies_not_implemented(dependencies)
187 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100188 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100189 adverb = 'not' if dependencies else 'never'
190 if param_descr:
191 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200192 tc.set_description('PSA {} {} {}-bit {} supported'
193 .format(verb, short_key_type, bits, adverb))
194 tc.set_dependencies(dependencies)
195 tc.set_function(verb + '_not_supported')
196 tc.set_arguments([key_type] + list(args))
197 return tc
198
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100199class KeyTypeNotSupported:
200 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100201
202 def __init__(self, info: Information) -> None:
203 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100204
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100205 ALWAYS_SUPPORTED = frozenset([
206 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100207 'PSA_KEY_TYPE_PASSWORD',
208 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100209 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200210 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100211 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100213 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100214 kt: crypto_knowledge.KeyType,
215 param: Optional[int] = None,
216 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200218 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100219
220 If param is present and not None, emit test cases conditioned on this
221 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200222 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100223 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100224 if kt.name in self.ALWAYS_SUPPORTED:
225 # Don't generate test cases for key types that are always supported.
226 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 import_dependencies = [('!' if param is None else '') +
229 psa_want_symbol(kt.name)]
230 if kt.params is not None:
231 import_dependencies += [('!' if param == i else '') +
232 psa_want_symbol(sym)
233 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100234 if kt.name.endswith('_PUBLIC_KEY'):
235 generate_dependencies = []
236 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200237 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200238 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100239 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200240 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100241 'import', kt.expression, bits,
242 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100243 test_case.hex_string(kt.key_material(bits)),
244 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100245 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100246 if not generate_dependencies and param is not None:
247 # If generation is impossible for this key type, rather than
248 # supported or not depending on implementation capabilities,
249 # only generate the test case once.
250 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100251 # For public key we expect that key generation fails with
252 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100253 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200254 yield test_case_for_key_type_not_supported(
255 'generate', kt.expression, bits,
256 finish_family_dependencies(generate_dependencies, bits),
257 str(bits),
258 param_descr=param_descr,
259 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100260 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100261
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200262 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
263 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
264
Gilles Peskine3d778392021-02-17 15:11:05 +0100265 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100266 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100267 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200268 if key_type in self.ECC_KEY_TYPES:
269 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100270 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100271 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100272 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200273 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100274 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100275 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100276 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100277 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100278 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100279
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200280def test_case_for_key_generation(
281 key_type: str, bits: int,
282 dependencies: List[str],
283 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200284 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200285) -> test_case.TestCase:
286 """Return one test case exercising a key generation.
287 """
288 hack_dependencies_not_implemented(dependencies)
289 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100290 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200291 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200292 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200293 tc.set_dependencies(dependencies)
294 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100295 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296
297 return tc
298
299class KeyGenerate:
300 """Generate positive and negative (invalid argument) test cases for key generation."""
301
302 def __init__(self, info: Information) -> None:
303 self.constructors = info.constructors
304
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200305 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
306 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
307
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100308 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200309 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200310 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200311 ) -> Iterator[test_case.TestCase]:
312 """Return test cases exercising key generation.
313
314 All key types can be generated except for public keys. For public key
315 PSA_ERROR_INVALID_ARGUMENT status is expected.
316 """
317 result = 'PSA_SUCCESS'
318
319 import_dependencies = [psa_want_symbol(kt.name)]
320 if kt.params is not None:
321 import_dependencies += [psa_want_symbol(sym)
322 for i, sym in enumerate(kt.params)]
323 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100324 # The library checks whether the key type is a public key generically,
325 # before it reaches a point where it needs support for the specific key
326 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200327 generate_dependencies = []
328 result = 'PSA_ERROR_INVALID_ARGUMENT'
329 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200330 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200331 for bits in kt.sizes_to_test():
332 yield test_case_for_key_generation(
333 kt.expression, bits,
334 finish_family_dependencies(generate_dependencies, bits),
335 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200336 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200337 )
338
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200339 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
340 """Generate test cases that exercise the generation of keys."""
341 for key_type in sorted(self.constructors.key_types):
342 if key_type in self.ECC_KEY_TYPES:
343 continue
344 kt = crypto_knowledge.KeyType(key_type)
345 yield from self.test_cases_for_key_type_key_generation(kt)
346 for curve_family in sorted(self.constructors.ecc_curves):
347 for constr in self.ECC_KEY_TYPES:
348 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200349 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200350
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200351class OpFail:
352 """Generate test cases for operations that must fail."""
353 #pylint: disable=too-few-public-methods
354
Gilles Peskinecba28a72022-03-15 17:26:33 +0100355 class Reason(enum.Enum):
356 NOT_SUPPORTED = 0
357 INVALID = 1
358 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200359 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100360
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200361 def __init__(self, info: Information) -> None:
362 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100363 key_type_expressions = self.constructors.generate_expressions(
364 sorted(self.constructors.key_types)
365 )
366 self.key_types = [crypto_knowledge.KeyType(kt_expr)
367 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200368
Gilles Peskinecba28a72022-03-15 17:26:33 +0100369 def make_test_case(
370 self,
371 alg: crypto_knowledge.Algorithm,
372 category: crypto_knowledge.AlgorithmCategory,
373 reason: 'Reason',
374 kt: Optional[crypto_knowledge.KeyType] = None,
375 not_deps: FrozenSet[str] = frozenset(),
376 ) -> test_case.TestCase:
377 """Construct a failure test case for a one-key or keyless operation."""
378 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200379 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100380 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200381 if reason == self.Reason.NOT_SUPPORTED:
382 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
383 for dep in not_deps]
384 pretty_reason = '!' + '&'.join(sorted(short_deps))
385 else:
386 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100387 if kt:
388 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100389 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200390 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100391 key_type = ''
392 pretty_type = ''
393 tc.set_description('PSA {} {}: {}{}'
394 .format(category.name.lower(),
395 pretty_alg,
396 pretty_reason,
397 ' with ' + pretty_type if pretty_type else ''))
398 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200399 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100400 for i, dep in enumerate(dependencies):
401 if dep in not_deps:
402 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200403 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100404 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000405 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100406 if kt:
407 key_material = kt.key_material(kt.sizes_to_test()[0])
408 arguments += [key_type, test_case.hex_string(key_material)]
409 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200410 if category.is_asymmetric():
411 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100412 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
413 'INVALID_ARGUMENT')
414 arguments.append('PSA_ERROR_' + error)
415 tc.set_arguments(arguments)
416 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200417
Gilles Peskinecba28a72022-03-15 17:26:33 +0100418 def no_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 keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200424 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100425 # Compatible operation, unsupported algorithm
426 for dep in automatic_dependencies(alg.base_expression):
427 yield self.make_test_case(alg, category,
428 self.Reason.NOT_SUPPORTED,
429 not_deps=frozenset([dep]))
430 else:
431 # Incompatible operation, supported algorithm
432 yield self.make_test_case(alg, category, self.Reason.INVALID)
433
434 def one_key_test_cases(
435 self,
436 alg: crypto_knowledge.Algorithm,
437 category: crypto_knowledge.AlgorithmCategory,
438 ) -> Iterator[test_case.TestCase]:
439 """Generate failure test cases for one-key operations with the specified algorithm."""
440 for kt in self.key_types:
441 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200442 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100443 # Compatible key and operation, unsupported algorithm
444 for dep in automatic_dependencies(alg.base_expression):
445 yield self.make_test_case(alg, category,
446 self.Reason.NOT_SUPPORTED,
447 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200448 # Public key for a private-key operation
449 if category.is_asymmetric() and kt.is_public():
450 yield self.make_test_case(alg, category,
451 self.Reason.PUBLIC,
452 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100453 elif key_is_compatible:
454 # Compatible key, incompatible operation, supported algorithm
455 yield self.make_test_case(alg, category,
456 self.Reason.INVALID,
457 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200458 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100459 # Incompatible key, compatible operation, supported algorithm
460 yield self.make_test_case(alg, category,
461 self.Reason.INCOMPATIBLE,
462 kt=kt)
463 else:
464 # Incompatible key and operation. Don't test cases where
465 # multiple things are wrong, to keep the number of test
466 # cases reasonable.
467 pass
468
469 def test_cases_for_algorithm(
470 self,
471 alg: crypto_knowledge.Algorithm,
472 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200473 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100474 for category in crypto_knowledge.AlgorithmCategory:
475 if category == crypto_knowledge.AlgorithmCategory.PAKE:
476 # PAKE operations are not implemented yet
477 pass
478 elif category.requires_key():
479 yield from self.one_key_test_cases(alg, category)
480 else:
481 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200482
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200483 def all_test_cases(self) -> Iterator[test_case.TestCase]:
484 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200485 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100486 for expr in self.constructors.generate_expressions(algorithms):
487 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200488 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200489
490
Gilles Peskine897dff92021-03-10 15:03:44 +0100491class StorageKey(psa_storage.Key):
492 """Representation of a key for storage format testing."""
493
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494 IMPLICIT_USAGE_FLAGS = {
495 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
496 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
497 } #type: Dict[str, str]
498 """Mapping of usage flags to the flags that they imply."""
499
500 def __init__(
501 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100502 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200503 without_implicit_usage: Optional[bool] = False,
504 **kwargs
505 ) -> None:
506 """Prepare to generate a key.
507
508 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000509 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200510 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100511 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200512 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100513 for flag in sorted(usage_flags):
514 if flag in self.IMPLICIT_USAGE_FLAGS:
515 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
516 if usage_flags:
517 usage_expression = ' | '.join(sorted(usage_flags))
518 else:
519 usage_expression = '0'
520 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200521
522class StorageTestData(StorageKey):
523 """Representation of test case data for storage format testing."""
524
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200525 def __init__(
526 self,
527 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100528 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200529 **kwargs
530 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200531 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200532
Tom Cosgrove1797b052022-12-04 17:19:59 +0000533 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200534 * `expected_usage`: the usage flags generated as the expected usage flags
535 in the test cases. CAn differ from the usage flags
536 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200537 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100538 super().__init__(**kwargs)
539 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100540 if expected_usage is None:
541 self.expected_usage = self.usage #type: psa_storage.Expr
542 elif expected_usage:
543 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
544 else:
545 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200546
Gilles Peskine897dff92021-03-10 15:03:44 +0100547class StorageFormat:
548 """Storage format stability test cases."""
549
550 def __init__(self, info: Information, version: int, forward: bool) -> None:
551 """Prepare to generate test cases for storage format stability.
552
553 * `info`: information about the API. See the `Information` class.
554 * `version`: the storage format version to generate test cases for.
555 * `forward`: if true, generate forward compatibility test cases which
556 save a key and check that its representation is as intended. Otherwise
557 generate backward compatibility test cases which inject a key
558 representation and check that it can be read and used.
559 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200560 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
561 self.version = version #type: int
562 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100563
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100564 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100565 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100566 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100567 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100568 cls,
569 key_type: psa_storage.Expr, bits: int,
570 alg: psa_storage.Expr
571 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100572 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100573
574 Normally only the type and algorithm matter for compatibility, and
575 this is handled in crypto_knowledge.KeyType.can_do(). This function
576 exists to detect exceptional cases. Exceptional cases detected here
577 are not tested in OpFail and should therefore have manually written
578 test cases.
579 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100580 # Some test keys have the RAW_DATA type and attributes that don't
581 # necessarily make sense. We do this to validate numerical
582 # encodings of the attributes.
583 # Raw data keys have no useful exercise anyway so there is no
584 # loss of test coverage.
585 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
586 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100587 # OAEP requires room for two hashes plus wrapping
588 m = cls.RSA_OAEP_RE.match(alg.string)
589 if m:
590 hash_alg = m.group(1)
591 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
592 key_length = (bits + 7) // 8
593 # Leave enough room for at least one byte of plaintext
594 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100595 # There's nothing wrong with ECC keys on Brainpool curves,
596 # but operations with them are very slow. So we only exercise them
597 # with a single algorithm, not with all possible hashes. We do
598 # exercise other curves with all algorithms so test coverage is
599 # perfectly adequate like this.
600 m = cls.BRAINPOOL_RE.match(key_type.string)
601 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
602 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100603 return True
604
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200605 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100606 """Construct a storage format test case for the given key.
607
608 If ``forward`` is true, generate a forward compatibility test case:
609 create a key and validate that it has the expected representation.
610 Otherwise generate a backward compatibility test case: inject the
611 key representation into storage and validate that it can be read
612 correctly.
613 """
614 verb = 'save' if self.forward else 'read'
615 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100616 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100617 dependencies = automatic_dependencies(
618 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100619 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100620 )
621 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800622 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200623 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100624 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100625 tc.set_function('key_storage_' + verb)
626 if self.forward:
627 extra_arguments = []
628 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200629 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100630 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200631 flags.append('TEST_FLAG_EXERCISE')
632 if 'READ_ONLY' in key.lifetime.string:
633 flags.append('TEST_FLAG_READ_ONLY')
634 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100635 tc.set_arguments([key.lifetime.string,
636 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100637 key.expected_usage.string,
638 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100639 '"' + key.material.hex() + '"',
640 '"' + key.hex() + '"',
641 *extra_arguments])
642 return tc
643
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200644 def key_for_lifetime(
645 self,
646 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200647 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200648 """Construct a test key for the given lifetime."""
649 short = lifetime
650 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
651 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100652 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200653 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200654 key = StorageTestData(version=self.version,
655 id=1, lifetime=lifetime,
656 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100657 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200658 material=b'L',
659 description=description)
660 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200661
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200662 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200663 """Generate test keys covering lifetimes."""
664 lifetimes = sorted(self.constructors.lifetimes)
665 expressions = self.constructors.generate_expressions(lifetimes)
666 for lifetime in expressions:
667 # Don't attempt to create or load a volatile key in storage
668 if 'VOLATILE' in lifetime:
669 continue
670 # Don't attempt to create a read-only key in storage,
671 # but do attempt to load one.
672 if 'READ_ONLY' in lifetime and self.forward:
673 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200674 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200675
Gilles Peskinef7614272022-02-24 18:58:08 +0100676 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100677 self,
678 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200679 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 test_implicit_usage: Optional[bool] = True
681 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100682 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100683 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100684 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200685 key1 = StorageTestData(version=self.version,
686 id=1, lifetime=0x00000001,
687 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100688 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100689 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100690 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200691 material=b'K',
692 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100693 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100694 usage_expr = key1.expected_usage.string
695 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100696 else:
697 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100698 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100699
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200700 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100701 """Generate test keys covering usage flags."""
702 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100703 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200704 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200706 for flag1, flag2 in zip(known_flags,
707 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100708 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200709
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200710 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200711 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100712 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200713
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200714 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200715 yield from self.generate_keys_for_usage_flags()
716 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100717
Gilles Peskine7de7c102021-04-29 22:28:07 +0200718 def key_for_type_and_alg(
719 self,
720 kt: crypto_knowledge.KeyType,
721 bits: int,
722 alg: Optional[crypto_knowledge.Algorithm] = None,
723 ) -> StorageTestData:
724 """Construct a test key of the given type.
725
726 If alg is not None, this key allows it.
727 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100728 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100729 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200730 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100731 if alg is not None:
732 alg1 = alg.expression
733 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200734 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100735 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200736 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100737 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200738 key = StorageTestData(version=self.version,
739 id=1, lifetime=0x00000001,
740 type=kt.expression, bits=bits,
741 usage=usage_flags, alg=alg1, alg2=alg2,
742 material=key_material,
743 description=description)
744 return key
745
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100746 def keys_for_type(
747 self,
748 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200749 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200750 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 """Generate test keys for the given key type."""
752 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100753 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200754 # Test a non-exercisable key, as well as exercisable keys for
755 # each compatible algorithm.
756 # To do: test reading a key from storage with an incompatible
757 # or unsupported algorithm.
758 yield self.key_for_type_and_alg(kt, bits)
759 compatible_algorithms = [alg for alg in all_algorithms
760 if kt.can_do(alg)]
761 for alg in compatible_algorithms:
762 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100763
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200764 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100765 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200766 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200767 all_algorithms = [crypto_knowledge.Algorithm(alg)
768 for alg in self.constructors.generate_expressions(
769 sorted(self.constructors.algorithms)
770 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200771 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200772 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100773
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200774 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200775 """Generate test keys for the encoding of the specified algorithm."""
776 # These test cases only validate the encoding of algorithms, not
777 # whether the key read from storage is suitable for an operation.
778 # `keys_for_types` generate read tests with an algorithm and a
779 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100780 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100781 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200782 key1 = StorageTestData(version=self.version,
783 id=1, lifetime=0x00000001,
784 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
785 usage=usage, alg=alg, alg2=0,
786 material=b'K',
787 description='alg: ' + descr)
788 yield key1
789 key2 = StorageTestData(version=self.version,
790 id=1, lifetime=0x00000001,
791 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
792 usage=usage, alg=0, alg2=alg,
793 material=b'L',
794 description='alg2: ' + descr)
795 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100796
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200797 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100798 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200799 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200800 for alg in self.constructors.generate_expressions(algorithms):
801 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100802
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200803 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200804 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200805 yield from self.all_keys_for_lifetimes()
806 yield from self.all_keys_for_usage_flags()
807 yield from self.all_keys_for_types()
808 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200809
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200810 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100811 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200812 # First build a list of all keys, then construct all the corresponding
813 # test cases. This allows all required information to be obtained in
814 # one go, which is a significant performance gain as the information
815 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200816 all_keys = list(self.generate_all_keys())
817 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200818 if key.location_value() != 0:
819 # Skip keys with a non-default location, because they
820 # require a driver and we currently have no mechanism to
821 # determine whether a driver is available.
822 continue
823 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100824
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200825class StorageFormatForward(StorageFormat):
826 """Storage format stability test cases for forward compatibility."""
827
828 def __init__(self, info: Information, version: int) -> None:
829 super().__init__(info, version, True)
830
831class StorageFormatV0(StorageFormat):
832 """Storage format stability test cases for version 0 compatibility."""
833
834 def __init__(self, info: Information) -> None:
835 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100836
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200837 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200838 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100839 yield from super().all_keys_for_usage_flags()
840 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200841
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200842 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200843 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200844 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200845 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200846 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200847 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200848 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200849 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200850 algorithm and key type combination.
851 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200852 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200853 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100854 usage_flags = ['PSA_KEY_USAGE_EXPORT']
855 material_usage_flags = usage_flags + [implyer_usage]
856 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200857 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200858 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100859 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
860 alg_expression = crypto_knowledge.short_expression(alg, 1)
861 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200862 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200863 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200864 key = StorageTestData(version=self.version,
865 id=1, lifetime=0x00000001,
866 type=key_type.expression, bits=bits,
867 usage=material_usage_flags,
868 expected_usage=expected_usage_flags,
869 without_implicit_usage=True,
870 alg=alg, alg2=alg2,
871 material=key_material,
872 description=description)
873 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874
875 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200876 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200877 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800878 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200879 # must be filtered. Pair them with keywords created from its names.
880 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
881 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
882 keyword_translation = {
883 'ECDSA': 'ECC',
884 'ED[0-9]*.*' : 'EDWARDS'
885 }
886 exclusive_keywords = {
887 'EDWARDS': 'ECC'
888 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200889 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
890 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200891 alg_with_keys = {} #type: Dict[str, List[str]]
892 translation_table = str.maketrans('(', '_', ')')
893 for alg in algorithms:
894 # Generate keywords from the name of the algorithm
895 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
896 # Translate keywords for better matching with the key types
897 for keyword in alg_keywords.copy():
898 for pattern, replace in keyword_translation.items():
899 if re.match(pattern, keyword):
900 alg_keywords.remove(keyword)
901 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800902 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200903 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
904 continue
905
906 for key_type in key_types:
907 # Generate keywords from the of the key type
908 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
909
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800910 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200911 for keyword1, keyword2 in exclusive_keywords.items():
912 if keyword1 in key_type_keywords:
913 key_type_keywords.remove(keyword2)
914
915 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
916 not key_type_keywords.isdisjoint(alg_keywords):
917 if alg in alg_with_keys:
918 alg_with_keys[alg].append(key_type)
919 else:
920 alg_with_keys[alg] = [key_type]
921 return alg_with_keys
922
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200923 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200924 """Generate test keys for usage flag extensions."""
925 # Generate a key type and algorithm pair for each extendable usage
926 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800927 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200928 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200929
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200930 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
931 for alg in sorted(alg_with_keys):
932 for key_type in sorted(alg_with_keys[alg]):
933 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200934 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100935 if kt.is_public() and '_SIGN_' in usage:
936 # Can't sign with a public key
937 continue
938 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200939
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200940 def generate_all_keys(self) -> Iterator[StorageTestData]:
941 yield from super().generate_all_keys()
942 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200943
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200944class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100945 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100946 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200947 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100948 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200949 'test_suite_psa_crypto_generate_key.generated':
950 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100951 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100952 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200953 'test_suite_psa_crypto_op_fail.generated':
954 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100955 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200956 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100957 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200958 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100959 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
960
Werner Lewisfbb75e32022-08-24 11:30:03 +0100961 def __init__(self, options):
962 super().__init__(options)
963 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100964
Werner Lewisfbb75e32022-08-24 11:30:03 +0100965 def generate_target(self, name: str, *target_args) -> None:
966 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100967
968if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200969 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)