blob: c549fc3efd9c4fa13f3610f44a955e884ccb5979 [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 Setti24c64e82023-06-26 11:49:02 +0200118# PSA_WANT_KEY_TYPE_xxx_KEY_PAIR symbols have a GENERATE suffix to state that
119# they support key generation.
Valerio Setti7bbd98f2023-06-26 14:06:11 +0200120def fix_key_pair_dependencies(dep_list: List[str], usage: str):
Valerio Setti24c64e82023-06-26 11:49:02 +0200121 # Note: this LEGACY replacement for RSA is temporary and it's going to be
122 # aligned with ECC one in #7772.
123 new_list = [re.sub(r'RSA_KEY_PAIR\Z', r'RSA_KEY_PAIR_LEGACY', dep)
124 for dep in dep_list]
Valerio Setti7bbd98f2023-06-26 14:06:11 +0200125 new_list = [re.sub(r'ECC_KEY_PAIR\Z', r'ECC_KEY_PAIR_' + usage, dep)
Valerio Setti24c64e82023-06-26 11:49:02 +0200126 for dep in new_list]
Valerio Setti27c501a2023-06-27 16:58:52 +0200127 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
128 # config_psa.h).
129 if any([re.match(r'[!]?\w+ECC_KEY_PAIR_BASIC', dep) for dep in new_list]):
130 match_pattern = next((dep for dep in new_list
131 if re.match(r'([!]?\w+ECC_KEY_PAIR_BASIC)', dep) is not None), None)
132 new_list.append(re.sub(r'ECC_KEY_PAIR_BASIC', r'ECC_KEY_PAIR_IMPORT', match_pattern))
133 new_list.append(re.sub(r'ECC_KEY_PAIR_BASIC', r'ECC_KEY_PAIR_EXPORT', match_pattern))
134 #if any([re.match(r'!\w+ECC_KEY_PAIR_BASIC\w+', dep) for dep in new_list]):
135 # new_list.append('!PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT')
136 # new_list.append('!PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT')
137 #elif any([re.match(r'\w+ECC_KEY_PAIR\w+', dep) for dep in new_list]):
138 # new_list.append('PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT')
139 # new_list.append('PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT')
Valerio Setti24c64e82023-06-26 11:49:02 +0200140 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100141
Gilles Peskineb94ea512021-03-10 02:12:08 +0100142class Information:
143 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100144
Gilles Peskineb94ea512021-03-10 02:12:08 +0100145 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100146 self.constructors = self.read_psa_interface()
147
148 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100149 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200150 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100151 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200152 # Mbed TLS doesn't support finite-field DH yet and will not support
153 # finite-field DSA. Don't attempt to generate any related test case.
154 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
155 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100156 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
157 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100158
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200159 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100160 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200161 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100162 header_file_names = ['include/psa/crypto_values.h',
163 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200164 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100165 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200166 constructors.parse_header(header_file_name)
167 for test_cases in test_suites:
168 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100169 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200170 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100171 return constructors
172
Gilles Peskine14e428f2021-01-26 22:19:21 +0100173
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200174def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100175 verb: str, key_type: str, bits: int,
176 dependencies: List[str],
177 *args: str,
178 param_descr: str = ''
179) -> test_case.TestCase:
180 """Return one test case exercising a key creation method
181 for an unsupported key type or size.
182 """
183 hack_dependencies_not_implemented(dependencies)
184 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100185 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100186 adverb = 'not' if dependencies else 'never'
187 if param_descr:
188 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200189 tc.set_description('PSA {} {} {}-bit {} supported'
190 .format(verb, short_key_type, bits, adverb))
191 tc.set_dependencies(dependencies)
192 tc.set_function(verb + '_not_supported')
193 tc.set_arguments([key_type] + list(args))
194 return tc
195
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100196class KeyTypeNotSupported:
197 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100198
199 def __init__(self, info: Information) -> None:
200 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100201
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100202 ALWAYS_SUPPORTED = frozenset([
203 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100204 'PSA_KEY_TYPE_PASSWORD',
205 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100206 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200207 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100208 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100209 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100210 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100211 kt: crypto_knowledge.KeyType,
212 param: Optional[int] = None,
213 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100214 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200215 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100216
217 If param is present and not None, emit test cases conditioned on this
218 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200219 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100220 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100221 if kt.name in self.ALWAYS_SUPPORTED:
222 # Don't generate test cases for key types that are always supported.
223 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100224 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 import_dependencies = [('!' if param is None else '') +
226 psa_want_symbol(kt.name)]
227 if kt.params is not None:
228 import_dependencies += [('!' if param == i else '') +
229 psa_want_symbol(sym)
230 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100231 if kt.name.endswith('_PUBLIC_KEY'):
232 generate_dependencies = []
233 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200234 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200235 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100236 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200237 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100238 'import', kt.expression, bits,
239 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100240 test_case.hex_string(kt.key_material(bits)),
241 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100242 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100243 if not generate_dependencies and param is not None:
244 # If generation is impossible for this key type, rather than
245 # supported or not depending on implementation capabilities,
246 # only generate the test case once.
247 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100248 # For public key we expect that key generation fails with
249 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100250 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200251 yield test_case_for_key_type_not_supported(
252 'generate', kt.expression, bits,
253 finish_family_dependencies(generate_dependencies, bits),
254 str(bits),
255 param_descr=param_descr,
256 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100257 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100258
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200259 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
260 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
261
Gilles Peskine3d778392021-02-17 15:11:05 +0100262 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100263 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100264 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200265 if key_type in self.ECC_KEY_TYPES:
266 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100267 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100268 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100269 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200270 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100271 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100272 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100273 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100274 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100275 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100276
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200277def test_case_for_key_generation(
278 key_type: str, bits: int,
279 dependencies: List[str],
280 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200281 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200282) -> test_case.TestCase:
283 """Return one test case exercising a key generation.
284 """
285 hack_dependencies_not_implemented(dependencies)
286 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100287 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200288 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200289 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200290 tc.set_dependencies(dependencies)
291 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100292 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200293
294 return tc
295
296class KeyGenerate:
297 """Generate positive and negative (invalid argument) test cases for key generation."""
298
299 def __init__(self, info: Information) -> None:
300 self.constructors = info.constructors
301
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200302 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
303 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
304
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100305 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200306 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200307 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200308 ) -> Iterator[test_case.TestCase]:
309 """Return test cases exercising key generation.
310
311 All key types can be generated except for public keys. For public key
312 PSA_ERROR_INVALID_ARGUMENT status is expected.
313 """
314 result = 'PSA_SUCCESS'
315
316 import_dependencies = [psa_want_symbol(kt.name)]
317 if kt.params is not None:
318 import_dependencies += [psa_want_symbol(sym)
319 for i, sym in enumerate(kt.params)]
320 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100321 # The library checks whether the key type is a public key generically,
322 # before it reaches a point where it needs support for the specific key
323 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200324 generate_dependencies = []
325 result = 'PSA_ERROR_INVALID_ARGUMENT'
326 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200327 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200328 for bits in kt.sizes_to_test():
329 yield test_case_for_key_generation(
330 kt.expression, bits,
331 finish_family_dependencies(generate_dependencies, bits),
332 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200333 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200334 )
335
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200336 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
337 """Generate test cases that exercise the generation of keys."""
338 for key_type in sorted(self.constructors.key_types):
339 if key_type in self.ECC_KEY_TYPES:
340 continue
341 kt = crypto_knowledge.KeyType(key_type)
342 yield from self.test_cases_for_key_type_key_generation(kt)
343 for curve_family in sorted(self.constructors.ecc_curves):
344 for constr in self.ECC_KEY_TYPES:
345 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200346 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200347
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200348class OpFail:
349 """Generate test cases for operations that must fail."""
350 #pylint: disable=too-few-public-methods
351
Gilles Peskinecba28a72022-03-15 17:26:33 +0100352 class Reason(enum.Enum):
353 NOT_SUPPORTED = 0
354 INVALID = 1
355 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200356 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100357
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200358 def __init__(self, info: Information) -> None:
359 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100360 key_type_expressions = self.constructors.generate_expressions(
361 sorted(self.constructors.key_types)
362 )
363 self.key_types = [crypto_knowledge.KeyType(kt_expr)
364 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200365
Gilles Peskinecba28a72022-03-15 17:26:33 +0100366 def make_test_case(
367 self,
368 alg: crypto_knowledge.Algorithm,
369 category: crypto_knowledge.AlgorithmCategory,
370 reason: 'Reason',
371 kt: Optional[crypto_knowledge.KeyType] = None,
372 not_deps: FrozenSet[str] = frozenset(),
373 ) -> test_case.TestCase:
374 """Construct a failure test case for a one-key or keyless operation."""
375 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200376 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100377 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200378 if reason == self.Reason.NOT_SUPPORTED:
379 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
380 for dep in not_deps]
381 pretty_reason = '!' + '&'.join(sorted(short_deps))
382 else:
383 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100384 if kt:
385 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100386 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200387 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100388 key_type = ''
389 pretty_type = ''
390 tc.set_description('PSA {} {}: {}{}'
391 .format(category.name.lower(),
392 pretty_alg,
393 pretty_reason,
394 ' with ' + pretty_type if pretty_type else ''))
395 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200396 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100397 for i, dep in enumerate(dependencies):
398 if dep in not_deps:
399 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200400 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100401 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000402 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100403 if kt:
404 key_material = kt.key_material(kt.sizes_to_test()[0])
405 arguments += [key_type, test_case.hex_string(key_material)]
406 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200407 if category.is_asymmetric():
408 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100409 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
410 'INVALID_ARGUMENT')
411 arguments.append('PSA_ERROR_' + error)
412 tc.set_arguments(arguments)
413 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200414
Gilles Peskinecba28a72022-03-15 17:26:33 +0100415 def no_key_test_cases(
416 self,
417 alg: crypto_knowledge.Algorithm,
418 category: crypto_knowledge.AlgorithmCategory,
419 ) -> Iterator[test_case.TestCase]:
420 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200421 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100422 # Compatible operation, unsupported algorithm
423 for dep in automatic_dependencies(alg.base_expression):
424 yield self.make_test_case(alg, category,
425 self.Reason.NOT_SUPPORTED,
426 not_deps=frozenset([dep]))
427 else:
428 # Incompatible operation, supported algorithm
429 yield self.make_test_case(alg, category, self.Reason.INVALID)
430
431 def one_key_test_cases(
432 self,
433 alg: crypto_knowledge.Algorithm,
434 category: crypto_knowledge.AlgorithmCategory,
435 ) -> Iterator[test_case.TestCase]:
436 """Generate failure test cases for one-key operations with the specified algorithm."""
437 for kt in self.key_types:
438 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200439 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100440 # Compatible key and operation, unsupported algorithm
441 for dep in automatic_dependencies(alg.base_expression):
442 yield self.make_test_case(alg, category,
443 self.Reason.NOT_SUPPORTED,
444 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200445 # Public key for a private-key operation
446 if category.is_asymmetric() and kt.is_public():
447 yield self.make_test_case(alg, category,
448 self.Reason.PUBLIC,
449 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100450 elif key_is_compatible:
451 # Compatible key, incompatible operation, supported algorithm
452 yield self.make_test_case(alg, category,
453 self.Reason.INVALID,
454 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200455 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100456 # Incompatible key, compatible operation, supported algorithm
457 yield self.make_test_case(alg, category,
458 self.Reason.INCOMPATIBLE,
459 kt=kt)
460 else:
461 # Incompatible key and operation. Don't test cases where
462 # multiple things are wrong, to keep the number of test
463 # cases reasonable.
464 pass
465
466 def test_cases_for_algorithm(
467 self,
468 alg: crypto_knowledge.Algorithm,
469 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200470 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100471 for category in crypto_knowledge.AlgorithmCategory:
472 if category == crypto_knowledge.AlgorithmCategory.PAKE:
473 # PAKE operations are not implemented yet
474 pass
475 elif category.requires_key():
476 yield from self.one_key_test_cases(alg, category)
477 else:
478 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200479
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200480 def all_test_cases(self) -> Iterator[test_case.TestCase]:
481 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200482 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100483 for expr in self.constructors.generate_expressions(algorithms):
484 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200485 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200486
487
Gilles Peskine897dff92021-03-10 15:03:44 +0100488class StorageKey(psa_storage.Key):
489 """Representation of a key for storage format testing."""
490
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200491 IMPLICIT_USAGE_FLAGS = {
492 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
493 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
494 } #type: Dict[str, str]
495 """Mapping of usage flags to the flags that they imply."""
496
497 def __init__(
498 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100499 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200500 without_implicit_usage: Optional[bool] = False,
501 **kwargs
502 ) -> None:
503 """Prepare to generate a key.
504
505 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000506 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200507 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100508 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200509 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100510 for flag in sorted(usage_flags):
511 if flag in self.IMPLICIT_USAGE_FLAGS:
512 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
513 if usage_flags:
514 usage_expression = ' | '.join(sorted(usage_flags))
515 else:
516 usage_expression = '0'
517 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200518
519class StorageTestData(StorageKey):
520 """Representation of test case data for storage format testing."""
521
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200522 def __init__(
523 self,
524 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100525 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200526 **kwargs
527 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200528 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200529
Tom Cosgrove1797b052022-12-04 17:19:59 +0000530 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200531 * `expected_usage`: the usage flags generated as the expected usage flags
532 in the test cases. CAn differ from the usage flags
533 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200534 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100535 super().__init__(**kwargs)
536 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100537 if expected_usage is None:
538 self.expected_usage = self.usage #type: psa_storage.Expr
539 elif expected_usage:
540 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
541 else:
542 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200543
Gilles Peskine897dff92021-03-10 15:03:44 +0100544class StorageFormat:
545 """Storage format stability test cases."""
546
547 def __init__(self, info: Information, version: int, forward: bool) -> None:
548 """Prepare to generate test cases for storage format stability.
549
550 * `info`: information about the API. See the `Information` class.
551 * `version`: the storage format version to generate test cases for.
552 * `forward`: if true, generate forward compatibility test cases which
553 save a key and check that its representation is as intended. Otherwise
554 generate backward compatibility test cases which inject a key
555 representation and check that it can be read and used.
556 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200557 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
558 self.version = version #type: int
559 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100560
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100561 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100562 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100563 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100564 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100565 cls,
566 key_type: psa_storage.Expr, bits: int,
567 alg: psa_storage.Expr
568 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100569 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100570
571 Normally only the type and algorithm matter for compatibility, and
572 this is handled in crypto_knowledge.KeyType.can_do(). This function
573 exists to detect exceptional cases. Exceptional cases detected here
574 are not tested in OpFail and should therefore have manually written
575 test cases.
576 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100577 # Some test keys have the RAW_DATA type and attributes that don't
578 # necessarily make sense. We do this to validate numerical
579 # encodings of the attributes.
580 # Raw data keys have no useful exercise anyway so there is no
581 # loss of test coverage.
582 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
583 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100584 # OAEP requires room for two hashes plus wrapping
585 m = cls.RSA_OAEP_RE.match(alg.string)
586 if m:
587 hash_alg = m.group(1)
588 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
589 key_length = (bits + 7) // 8
590 # Leave enough room for at least one byte of plaintext
591 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100592 # There's nothing wrong with ECC keys on Brainpool curves,
593 # but operations with them are very slow. So we only exercise them
594 # with a single algorithm, not with all possible hashes. We do
595 # exercise other curves with all algorithms so test coverage is
596 # perfectly adequate like this.
597 m = cls.BRAINPOOL_RE.match(key_type.string)
598 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
599 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100600 return True
601
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200602 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100603 """Construct a storage format test case for the given key.
604
605 If ``forward`` is true, generate a forward compatibility test case:
606 create a key and validate that it has the expected representation.
607 Otherwise generate a backward compatibility test case: inject the
608 key representation into storage and validate that it can be read
609 correctly.
610 """
611 verb = 'save' if self.forward else 'read'
612 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100613 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100614 dependencies = automatic_dependencies(
615 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100616 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100617 )
618 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800619 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200620 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100621 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100622 tc.set_function('key_storage_' + verb)
623 if self.forward:
624 extra_arguments = []
625 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200626 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100627 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200628 flags.append('TEST_FLAG_EXERCISE')
629 if 'READ_ONLY' in key.lifetime.string:
630 flags.append('TEST_FLAG_READ_ONLY')
631 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100632 tc.set_arguments([key.lifetime.string,
633 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100634 key.expected_usage.string,
635 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100636 '"' + key.material.hex() + '"',
637 '"' + key.hex() + '"',
638 *extra_arguments])
639 return tc
640
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200641 def key_for_lifetime(
642 self,
643 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200644 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200645 """Construct a test key for the given lifetime."""
646 short = lifetime
647 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
648 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100649 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200650 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 key = StorageTestData(version=self.version,
652 id=1, lifetime=lifetime,
653 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100654 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200655 material=b'L',
656 description=description)
657 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200658
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200659 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200660 """Generate test keys covering lifetimes."""
661 lifetimes = sorted(self.constructors.lifetimes)
662 expressions = self.constructors.generate_expressions(lifetimes)
663 for lifetime in expressions:
664 # Don't attempt to create or load a volatile key in storage
665 if 'VOLATILE' in lifetime:
666 continue
667 # Don't attempt to create a read-only key in storage,
668 # but do attempt to load one.
669 if 'READ_ONLY' in lifetime and self.forward:
670 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200671 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200672
Gilles Peskinef7614272022-02-24 18:58:08 +0100673 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100674 self,
675 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200676 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100677 test_implicit_usage: Optional[bool] = True
678 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100679 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100681 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200682 key1 = StorageTestData(version=self.version,
683 id=1, lifetime=0x00000001,
684 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100685 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100686 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100687 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200688 material=b'K',
689 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100690 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100691 usage_expr = key1.expected_usage.string
692 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100693 else:
694 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100695 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100696
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200697 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100698 """Generate test keys covering usage flags."""
699 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100700 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200701 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100702 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200703 for flag1, flag2 in zip(known_flags,
704 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200706
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200707 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200708 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100709 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200710
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200711 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200712 yield from self.generate_keys_for_usage_flags()
713 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100714
Gilles Peskine7de7c102021-04-29 22:28:07 +0200715 def key_for_type_and_alg(
716 self,
717 kt: crypto_knowledge.KeyType,
718 bits: int,
719 alg: Optional[crypto_knowledge.Algorithm] = None,
720 ) -> StorageTestData:
721 """Construct a test key of the given type.
722
723 If alg is not None, this key allows it.
724 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100725 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100726 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200727 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100728 if alg is not None:
729 alg1 = alg.expression
730 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200731 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100732 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200733 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100734 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 key = StorageTestData(version=self.version,
736 id=1, lifetime=0x00000001,
737 type=kt.expression, bits=bits,
738 usage=usage_flags, alg=alg1, alg2=alg2,
739 material=key_material,
740 description=description)
741 return key
742
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100743 def keys_for_type(
744 self,
745 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200746 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200747 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200748 """Generate test keys for the given key type."""
749 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100750 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 # Test a non-exercisable key, as well as exercisable keys for
752 # each compatible algorithm.
753 # To do: test reading a key from storage with an incompatible
754 # or unsupported algorithm.
755 yield self.key_for_type_and_alg(kt, bits)
756 compatible_algorithms = [alg for alg in all_algorithms
757 if kt.can_do(alg)]
758 for alg in compatible_algorithms:
759 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100760
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200761 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100762 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200763 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200764 all_algorithms = [crypto_knowledge.Algorithm(alg)
765 for alg in self.constructors.generate_expressions(
766 sorted(self.constructors.algorithms)
767 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200768 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200769 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100770
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200771 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200772 """Generate test keys for the encoding of the specified algorithm."""
773 # These test cases only validate the encoding of algorithms, not
774 # whether the key read from storage is suitable for an operation.
775 # `keys_for_types` generate read tests with an algorithm and a
776 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100777 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100778 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200779 key1 = StorageTestData(version=self.version,
780 id=1, lifetime=0x00000001,
781 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
782 usage=usage, alg=alg, alg2=0,
783 material=b'K',
784 description='alg: ' + descr)
785 yield key1
786 key2 = StorageTestData(version=self.version,
787 id=1, lifetime=0x00000001,
788 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
789 usage=usage, alg=0, alg2=alg,
790 material=b'L',
791 description='alg2: ' + descr)
792 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100793
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200794 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100795 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200796 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200797 for alg in self.constructors.generate_expressions(algorithms):
798 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100799
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200800 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200801 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200802 yield from self.all_keys_for_lifetimes()
803 yield from self.all_keys_for_usage_flags()
804 yield from self.all_keys_for_types()
805 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200806
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200807 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100808 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200809 # First build a list of all keys, then construct all the corresponding
810 # test cases. This allows all required information to be obtained in
811 # one go, which is a significant performance gain as the information
812 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200813 all_keys = list(self.generate_all_keys())
814 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200815 if key.location_value() != 0:
816 # Skip keys with a non-default location, because they
817 # require a driver and we currently have no mechanism to
818 # determine whether a driver is available.
819 continue
820 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100821
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200822class StorageFormatForward(StorageFormat):
823 """Storage format stability test cases for forward compatibility."""
824
825 def __init__(self, info: Information, version: int) -> None:
826 super().__init__(info, version, True)
827
828class StorageFormatV0(StorageFormat):
829 """Storage format stability test cases for version 0 compatibility."""
830
831 def __init__(self, info: Information) -> None:
832 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100833
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200834 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200835 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100836 yield from super().all_keys_for_usage_flags()
837 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200838
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200839 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200840 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200841 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200842 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200843 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200844 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200845 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200846 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200847 algorithm and key type combination.
848 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200849 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200850 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100851 usage_flags = ['PSA_KEY_USAGE_EXPORT']
852 material_usage_flags = usage_flags + [implyer_usage]
853 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200854 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200855 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100856 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
857 alg_expression = crypto_knowledge.short_expression(alg, 1)
858 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200859 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200860 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200861 key = StorageTestData(version=self.version,
862 id=1, lifetime=0x00000001,
863 type=key_type.expression, bits=bits,
864 usage=material_usage_flags,
865 expected_usage=expected_usage_flags,
866 without_implicit_usage=True,
867 alg=alg, alg2=alg2,
868 material=key_material,
869 description=description)
870 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200871
872 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200873 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800875 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200876 # must be filtered. Pair them with keywords created from its names.
877 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
878 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
879 keyword_translation = {
880 'ECDSA': 'ECC',
881 'ED[0-9]*.*' : 'EDWARDS'
882 }
883 exclusive_keywords = {
884 'EDWARDS': 'ECC'
885 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200886 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
887 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200888 alg_with_keys = {} #type: Dict[str, List[str]]
889 translation_table = str.maketrans('(', '_', ')')
890 for alg in algorithms:
891 # Generate keywords from the name of the algorithm
892 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
893 # Translate keywords for better matching with the key types
894 for keyword in alg_keywords.copy():
895 for pattern, replace in keyword_translation.items():
896 if re.match(pattern, keyword):
897 alg_keywords.remove(keyword)
898 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800899 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200900 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
901 continue
902
903 for key_type in key_types:
904 # Generate keywords from the of the key type
905 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
906
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800907 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 for keyword1, keyword2 in exclusive_keywords.items():
909 if keyword1 in key_type_keywords:
910 key_type_keywords.remove(keyword2)
911
912 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
913 not key_type_keywords.isdisjoint(alg_keywords):
914 if alg in alg_with_keys:
915 alg_with_keys[alg].append(key_type)
916 else:
917 alg_with_keys[alg] = [key_type]
918 return alg_with_keys
919
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200920 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200921 """Generate test keys for usage flag extensions."""
922 # Generate a key type and algorithm pair for each extendable usage
923 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800924 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200925 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200926
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200927 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
928 for alg in sorted(alg_with_keys):
929 for key_type in sorted(alg_with_keys[alg]):
930 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200931 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100932 if kt.is_public() and '_SIGN_' in usage:
933 # Can't sign with a public key
934 continue
935 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200936
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200937 def generate_all_keys(self) -> Iterator[StorageTestData]:
938 yield from super().generate_all_keys()
939 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200940
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200941class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100942 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100943 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200944 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100945 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200946 'test_suite_psa_crypto_generate_key.generated':
947 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100948 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100949 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200950 'test_suite_psa_crypto_op_fail.generated':
951 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100952 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200953 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100954 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200955 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100956 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
957
Werner Lewisfbb75e32022-08-24 11:30:03 +0100958 def __init__(self, options):
959 super().__init__(options)
960 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100961
Werner Lewisfbb75e32022-08-24 11:30:03 +0100962 def generate_target(self, name: str, *target_args) -> None:
963 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100964
965if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200966 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)