blob: 0420f6750789c08811355e809e039f9a0bfeecf2 [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]
127 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100128
Gilles Peskineb94ea512021-03-10 02:12:08 +0100129class Information:
130 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100131
Gilles Peskineb94ea512021-03-10 02:12:08 +0100132 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100133 self.constructors = self.read_psa_interface()
134
135 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100136 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200137 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100138 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200139 # Mbed TLS doesn't support finite-field DH yet and will not support
140 # finite-field DSA. Don't attempt to generate any related test case.
141 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
142 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100143 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
144 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100145
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200146 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100147 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200148 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100149 header_file_names = ['include/psa/crypto_values.h',
150 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200151 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100152 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200153 constructors.parse_header(header_file_name)
154 for test_cases in test_suites:
155 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100156 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200157 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100158 return constructors
159
Gilles Peskine14e428f2021-01-26 22:19:21 +0100160
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200161def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100162 verb: str, key_type: str, bits: int,
163 dependencies: List[str],
164 *args: str,
165 param_descr: str = ''
166) -> test_case.TestCase:
167 """Return one test case exercising a key creation method
168 for an unsupported key type or size.
169 """
170 hack_dependencies_not_implemented(dependencies)
171 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100172 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100173 adverb = 'not' if dependencies else 'never'
174 if param_descr:
175 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200176 tc.set_description('PSA {} {} {}-bit {} supported'
177 .format(verb, short_key_type, bits, adverb))
178 tc.set_dependencies(dependencies)
179 tc.set_function(verb + '_not_supported')
180 tc.set_arguments([key_type] + list(args))
181 return tc
182
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100183class KeyTypeNotSupported:
184 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100185
186 def __init__(self, info: Information) -> None:
187 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100188
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100189 ALWAYS_SUPPORTED = frozenset([
190 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100191 'PSA_KEY_TYPE_PASSWORD',
192 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100193 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200194 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100195 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100196 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100197 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100198 kt: crypto_knowledge.KeyType,
199 param: Optional[int] = None,
200 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100201 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200202 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100203
204 If param is present and not None, emit test cases conditioned on this
205 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200206 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100207 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100208 if kt.name in self.ALWAYS_SUPPORTED:
209 # Don't generate test cases for key types that are always supported.
210 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100211 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100212 import_dependencies = [('!' if param is None else '') +
213 psa_want_symbol(kt.name)]
214 if kt.params is not None:
215 import_dependencies += [('!' if param == i else '') +
216 psa_want_symbol(sym)
217 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 if kt.name.endswith('_PUBLIC_KEY'):
219 generate_dependencies = []
220 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200221 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti7bbd98f2023-06-26 14:06:11 +0200222 import_dependencies = fix_key_pair_dependencies(import_dependencies,
223 'BASIC_IMPORT_EXPORT')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100224 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200225 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100226 'import', kt.expression, bits,
227 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 test_case.hex_string(kt.key_material(bits)),
229 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100230 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100231 if not generate_dependencies and param is not None:
232 # If generation is impossible for this key type, rather than
233 # supported or not depending on implementation capabilities,
234 # only generate the test case once.
235 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100236 # For public key we expect that key generation fails with
237 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100238 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200239 yield test_case_for_key_type_not_supported(
240 'generate', kt.expression, bits,
241 finish_family_dependencies(generate_dependencies, bits),
242 str(bits),
243 param_descr=param_descr,
244 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100245 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100246
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200247 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
248 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
249
Gilles Peskine3d778392021-02-17 15:11:05 +0100250 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100251 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100252 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200253 if key_type in self.ECC_KEY_TYPES:
254 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100255 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100256 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100257 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200258 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100259 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100260 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100261 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100262 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100263 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100264
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200265def test_case_for_key_generation(
266 key_type: str, bits: int,
267 dependencies: List[str],
268 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200269 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200270) -> test_case.TestCase:
271 """Return one test case exercising a key generation.
272 """
273 hack_dependencies_not_implemented(dependencies)
274 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100275 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200276 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200277 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200278 tc.set_dependencies(dependencies)
279 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100280 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200281
282 return tc
283
284class KeyGenerate:
285 """Generate positive and negative (invalid argument) test cases for key generation."""
286
287 def __init__(self, info: Information) -> None:
288 self.constructors = info.constructors
289
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200290 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
291 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
292
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100293 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200294 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200295 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296 ) -> Iterator[test_case.TestCase]:
297 """Return test cases exercising key generation.
298
299 All key types can be generated except for public keys. For public key
300 PSA_ERROR_INVALID_ARGUMENT status is expected.
301 """
302 result = 'PSA_SUCCESS'
303
304 import_dependencies = [psa_want_symbol(kt.name)]
305 if kt.params is not None:
306 import_dependencies += [psa_want_symbol(sym)
307 for i, sym in enumerate(kt.params)]
308 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100309 # The library checks whether the key type is a public key generically,
310 # before it reaches a point where it needs support for the specific key
311 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200312 generate_dependencies = []
313 result = 'PSA_ERROR_INVALID_ARGUMENT'
314 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200315 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200316 for bits in kt.sizes_to_test():
317 yield test_case_for_key_generation(
318 kt.expression, bits,
319 finish_family_dependencies(generate_dependencies, bits),
320 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200321 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200322 )
323
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200324 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
325 """Generate test cases that exercise the generation of keys."""
326 for key_type in sorted(self.constructors.key_types):
327 if key_type in self.ECC_KEY_TYPES:
328 continue
329 kt = crypto_knowledge.KeyType(key_type)
330 yield from self.test_cases_for_key_type_key_generation(kt)
331 for curve_family in sorted(self.constructors.ecc_curves):
332 for constr in self.ECC_KEY_TYPES:
333 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200334 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200335
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200336class OpFail:
337 """Generate test cases for operations that must fail."""
338 #pylint: disable=too-few-public-methods
339
Gilles Peskinecba28a72022-03-15 17:26:33 +0100340 class Reason(enum.Enum):
341 NOT_SUPPORTED = 0
342 INVALID = 1
343 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200344 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100345
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200346 def __init__(self, info: Information) -> None:
347 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100348 key_type_expressions = self.constructors.generate_expressions(
349 sorted(self.constructors.key_types)
350 )
351 self.key_types = [crypto_knowledge.KeyType(kt_expr)
352 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200353
Gilles Peskinecba28a72022-03-15 17:26:33 +0100354 def make_test_case(
355 self,
356 alg: crypto_knowledge.Algorithm,
357 category: crypto_knowledge.AlgorithmCategory,
358 reason: 'Reason',
359 kt: Optional[crypto_knowledge.KeyType] = None,
360 not_deps: FrozenSet[str] = frozenset(),
361 ) -> test_case.TestCase:
362 """Construct a failure test case for a one-key or keyless operation."""
363 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200364 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100365 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200366 if reason == self.Reason.NOT_SUPPORTED:
367 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
368 for dep in not_deps]
369 pretty_reason = '!' + '&'.join(sorted(short_deps))
370 else:
371 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100372 if kt:
373 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100374 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200375 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100376 key_type = ''
377 pretty_type = ''
378 tc.set_description('PSA {} {}: {}{}'
379 .format(category.name.lower(),
380 pretty_alg,
381 pretty_reason,
382 ' with ' + pretty_type if pretty_type else ''))
383 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti24c64e82023-06-26 11:49:02 +0200384 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC_IMPORT_EXPORT')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100385 for i, dep in enumerate(dependencies):
386 if dep in not_deps:
387 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200388 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100389 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000390 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100391 if kt:
392 key_material = kt.key_material(kt.sizes_to_test()[0])
393 arguments += [key_type, test_case.hex_string(key_material)]
394 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200395 if category.is_asymmetric():
396 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100397 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
398 'INVALID_ARGUMENT')
399 arguments.append('PSA_ERROR_' + error)
400 tc.set_arguments(arguments)
401 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200402
Gilles Peskinecba28a72022-03-15 17:26:33 +0100403 def no_key_test_cases(
404 self,
405 alg: crypto_knowledge.Algorithm,
406 category: crypto_knowledge.AlgorithmCategory,
407 ) -> Iterator[test_case.TestCase]:
408 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200409 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100410 # Compatible operation, unsupported algorithm
411 for dep in automatic_dependencies(alg.base_expression):
412 yield self.make_test_case(alg, category,
413 self.Reason.NOT_SUPPORTED,
414 not_deps=frozenset([dep]))
415 else:
416 # Incompatible operation, supported algorithm
417 yield self.make_test_case(alg, category, self.Reason.INVALID)
418
419 def one_key_test_cases(
420 self,
421 alg: crypto_knowledge.Algorithm,
422 category: crypto_knowledge.AlgorithmCategory,
423 ) -> Iterator[test_case.TestCase]:
424 """Generate failure test cases for one-key operations with the specified algorithm."""
425 for kt in self.key_types:
426 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200427 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100428 # Compatible key and operation, unsupported algorithm
429 for dep in automatic_dependencies(alg.base_expression):
430 yield self.make_test_case(alg, category,
431 self.Reason.NOT_SUPPORTED,
432 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200433 # Public key for a private-key operation
434 if category.is_asymmetric() and kt.is_public():
435 yield self.make_test_case(alg, category,
436 self.Reason.PUBLIC,
437 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100438 elif key_is_compatible:
439 # Compatible key, incompatible operation, supported algorithm
440 yield self.make_test_case(alg, category,
441 self.Reason.INVALID,
442 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200443 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100444 # Incompatible key, compatible operation, supported algorithm
445 yield self.make_test_case(alg, category,
446 self.Reason.INCOMPATIBLE,
447 kt=kt)
448 else:
449 # Incompatible key and operation. Don't test cases where
450 # multiple things are wrong, to keep the number of test
451 # cases reasonable.
452 pass
453
454 def test_cases_for_algorithm(
455 self,
456 alg: crypto_knowledge.Algorithm,
457 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200458 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100459 for category in crypto_knowledge.AlgorithmCategory:
460 if category == crypto_knowledge.AlgorithmCategory.PAKE:
461 # PAKE operations are not implemented yet
462 pass
463 elif category.requires_key():
464 yield from self.one_key_test_cases(alg, category)
465 else:
466 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200467
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200468 def all_test_cases(self) -> Iterator[test_case.TestCase]:
469 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200470 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100471 for expr in self.constructors.generate_expressions(algorithms):
472 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200473 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200474
475
Gilles Peskine897dff92021-03-10 15:03:44 +0100476class StorageKey(psa_storage.Key):
477 """Representation of a key for storage format testing."""
478
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200479 IMPLICIT_USAGE_FLAGS = {
480 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
481 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
482 } #type: Dict[str, str]
483 """Mapping of usage flags to the flags that they imply."""
484
485 def __init__(
486 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100487 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200488 without_implicit_usage: Optional[bool] = False,
489 **kwargs
490 ) -> None:
491 """Prepare to generate a key.
492
493 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000494 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200495 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100496 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200497 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100498 for flag in sorted(usage_flags):
499 if flag in self.IMPLICIT_USAGE_FLAGS:
500 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
501 if usage_flags:
502 usage_expression = ' | '.join(sorted(usage_flags))
503 else:
504 usage_expression = '0'
505 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200506
507class StorageTestData(StorageKey):
508 """Representation of test case data for storage format testing."""
509
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200510 def __init__(
511 self,
512 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100513 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200514 **kwargs
515 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200516 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200517
Tom Cosgrove1797b052022-12-04 17:19:59 +0000518 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200519 * `expected_usage`: the usage flags generated as the expected usage flags
520 in the test cases. CAn differ from the usage flags
521 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200522 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100523 super().__init__(**kwargs)
524 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100525 if expected_usage is None:
526 self.expected_usage = self.usage #type: psa_storage.Expr
527 elif expected_usage:
528 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
529 else:
530 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200531
Gilles Peskine897dff92021-03-10 15:03:44 +0100532class StorageFormat:
533 """Storage format stability test cases."""
534
535 def __init__(self, info: Information, version: int, forward: bool) -> None:
536 """Prepare to generate test cases for storage format stability.
537
538 * `info`: information about the API. See the `Information` class.
539 * `version`: the storage format version to generate test cases for.
540 * `forward`: if true, generate forward compatibility test cases which
541 save a key and check that its representation is as intended. Otherwise
542 generate backward compatibility test cases which inject a key
543 representation and check that it can be read and used.
544 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200545 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
546 self.version = version #type: int
547 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100548
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100549 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100550 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100551 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100552 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100553 cls,
554 key_type: psa_storage.Expr, bits: int,
555 alg: psa_storage.Expr
556 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100557 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100558
559 Normally only the type and algorithm matter for compatibility, and
560 this is handled in crypto_knowledge.KeyType.can_do(). This function
561 exists to detect exceptional cases. Exceptional cases detected here
562 are not tested in OpFail and should therefore have manually written
563 test cases.
564 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100565 # Some test keys have the RAW_DATA type and attributes that don't
566 # necessarily make sense. We do this to validate numerical
567 # encodings of the attributes.
568 # Raw data keys have no useful exercise anyway so there is no
569 # loss of test coverage.
570 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
571 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100572 # OAEP requires room for two hashes plus wrapping
573 m = cls.RSA_OAEP_RE.match(alg.string)
574 if m:
575 hash_alg = m.group(1)
576 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
577 key_length = (bits + 7) // 8
578 # Leave enough room for at least one byte of plaintext
579 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100580 # There's nothing wrong with ECC keys on Brainpool curves,
581 # but operations with them are very slow. So we only exercise them
582 # with a single algorithm, not with all possible hashes. We do
583 # exercise other curves with all algorithms so test coverage is
584 # perfectly adequate like this.
585 m = cls.BRAINPOOL_RE.match(key_type.string)
586 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
587 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100588 return True
589
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200590 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100591 """Construct a storage format test case for the given key.
592
593 If ``forward`` is true, generate a forward compatibility test case:
594 create a key and validate that it has the expected representation.
595 Otherwise generate a backward compatibility test case: inject the
596 key representation into storage and validate that it can be read
597 correctly.
598 """
599 verb = 'save' if self.forward else 'read'
600 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100601 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100602 dependencies = automatic_dependencies(
603 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100604 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100605 )
606 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800607 dependencies += generate_key_dependencies(key.description)
Valerio Setti24c64e82023-06-26 11:49:02 +0200608 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC_IMPORT_EXPORT')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100609 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100610 tc.set_function('key_storage_' + verb)
611 if self.forward:
612 extra_arguments = []
613 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200614 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100615 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200616 flags.append('TEST_FLAG_EXERCISE')
617 if 'READ_ONLY' in key.lifetime.string:
618 flags.append('TEST_FLAG_READ_ONLY')
619 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100620 tc.set_arguments([key.lifetime.string,
621 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100622 key.expected_usage.string,
623 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100624 '"' + key.material.hex() + '"',
625 '"' + key.hex() + '"',
626 *extra_arguments])
627 return tc
628
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200629 def key_for_lifetime(
630 self,
631 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200632 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200633 """Construct a test key for the given lifetime."""
634 short = lifetime
635 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
636 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100637 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200638 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200639 key = StorageTestData(version=self.version,
640 id=1, lifetime=lifetime,
641 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100642 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200643 material=b'L',
644 description=description)
645 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200646
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200647 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200648 """Generate test keys covering lifetimes."""
649 lifetimes = sorted(self.constructors.lifetimes)
650 expressions = self.constructors.generate_expressions(lifetimes)
651 for lifetime in expressions:
652 # Don't attempt to create or load a volatile key in storage
653 if 'VOLATILE' in lifetime:
654 continue
655 # Don't attempt to create a read-only key in storage,
656 # but do attempt to load one.
657 if 'READ_ONLY' in lifetime and self.forward:
658 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200659 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200660
Gilles Peskinef7614272022-02-24 18:58:08 +0100661 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100662 self,
663 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200664 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100665 test_implicit_usage: Optional[bool] = True
666 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100667 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100668 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100669 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200670 key1 = StorageTestData(version=self.version,
671 id=1, lifetime=0x00000001,
672 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100673 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100674 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100675 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200676 material=b'K',
677 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100678 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100679 usage_expr = key1.expected_usage.string
680 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100681 else:
682 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100683 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100684
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200685 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100686 """Generate test keys covering usage flags."""
687 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100688 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200689 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100690 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200691 for flag1, flag2 in zip(known_flags,
692 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100693 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200694
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200695 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200696 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100697 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200698
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200699 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200700 yield from self.generate_keys_for_usage_flags()
701 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100702
Gilles Peskine7de7c102021-04-29 22:28:07 +0200703 def key_for_type_and_alg(
704 self,
705 kt: crypto_knowledge.KeyType,
706 bits: int,
707 alg: Optional[crypto_knowledge.Algorithm] = None,
708 ) -> StorageTestData:
709 """Construct a test key of the given type.
710
711 If alg is not None, this key allows it.
712 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100713 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100714 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200715 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100716 if alg is not None:
717 alg1 = alg.expression
718 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200719 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100720 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100722 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200723 key = StorageTestData(version=self.version,
724 id=1, lifetime=0x00000001,
725 type=kt.expression, bits=bits,
726 usage=usage_flags, alg=alg1, alg2=alg2,
727 material=key_material,
728 description=description)
729 return key
730
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100731 def keys_for_type(
732 self,
733 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200734 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200735 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200736 """Generate test keys for the given key type."""
737 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100738 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200739 # Test a non-exercisable key, as well as exercisable keys for
740 # each compatible algorithm.
741 # To do: test reading a key from storage with an incompatible
742 # or unsupported algorithm.
743 yield self.key_for_type_and_alg(kt, bits)
744 compatible_algorithms = [alg for alg in all_algorithms
745 if kt.can_do(alg)]
746 for alg in compatible_algorithms:
747 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100748
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200749 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100750 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200751 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200752 all_algorithms = [crypto_knowledge.Algorithm(alg)
753 for alg in self.constructors.generate_expressions(
754 sorted(self.constructors.algorithms)
755 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200756 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200757 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100758
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200759 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200760 """Generate test keys for the encoding of the specified algorithm."""
761 # These test cases only validate the encoding of algorithms, not
762 # whether the key read from storage is suitable for an operation.
763 # `keys_for_types` generate read tests with an algorithm and a
764 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100765 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100766 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200767 key1 = StorageTestData(version=self.version,
768 id=1, lifetime=0x00000001,
769 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
770 usage=usage, alg=alg, alg2=0,
771 material=b'K',
772 description='alg: ' + descr)
773 yield key1
774 key2 = StorageTestData(version=self.version,
775 id=1, lifetime=0x00000001,
776 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
777 usage=usage, alg=0, alg2=alg,
778 material=b'L',
779 description='alg2: ' + descr)
780 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100781
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200782 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100783 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200784 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200785 for alg in self.constructors.generate_expressions(algorithms):
786 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100787
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200788 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200789 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200790 yield from self.all_keys_for_lifetimes()
791 yield from self.all_keys_for_usage_flags()
792 yield from self.all_keys_for_types()
793 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200794
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200795 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100796 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200797 # First build a list of all keys, then construct all the corresponding
798 # test cases. This allows all required information to be obtained in
799 # one go, which is a significant performance gain as the information
800 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200801 all_keys = list(self.generate_all_keys())
802 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200803 if key.location_value() != 0:
804 # Skip keys with a non-default location, because they
805 # require a driver and we currently have no mechanism to
806 # determine whether a driver is available.
807 continue
808 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100809
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200810class StorageFormatForward(StorageFormat):
811 """Storage format stability test cases for forward compatibility."""
812
813 def __init__(self, info: Information, version: int) -> None:
814 super().__init__(info, version, True)
815
816class StorageFormatV0(StorageFormat):
817 """Storage format stability test cases for version 0 compatibility."""
818
819 def __init__(self, info: Information) -> None:
820 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100821
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200822 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200823 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100824 yield from super().all_keys_for_usage_flags()
825 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200826
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200827 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200828 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200829 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200830 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200831 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200832 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200833 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200834 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200835 algorithm and key type combination.
836 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200837 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200838 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100839 usage_flags = ['PSA_KEY_USAGE_EXPORT']
840 material_usage_flags = usage_flags + [implyer_usage]
841 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200842 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200843 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100844 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
845 alg_expression = crypto_knowledge.short_expression(alg, 1)
846 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200847 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200848 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200849 key = StorageTestData(version=self.version,
850 id=1, lifetime=0x00000001,
851 type=key_type.expression, bits=bits,
852 usage=material_usage_flags,
853 expected_usage=expected_usage_flags,
854 without_implicit_usage=True,
855 alg=alg, alg2=alg2,
856 material=key_material,
857 description=description)
858 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200859
860 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200861 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200862 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800863 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200864 # must be filtered. Pair them with keywords created from its names.
865 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
866 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
867 keyword_translation = {
868 'ECDSA': 'ECC',
869 'ED[0-9]*.*' : 'EDWARDS'
870 }
871 exclusive_keywords = {
872 'EDWARDS': 'ECC'
873 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200874 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
875 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200876 alg_with_keys = {} #type: Dict[str, List[str]]
877 translation_table = str.maketrans('(', '_', ')')
878 for alg in algorithms:
879 # Generate keywords from the name of the algorithm
880 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
881 # Translate keywords for better matching with the key types
882 for keyword in alg_keywords.copy():
883 for pattern, replace in keyword_translation.items():
884 if re.match(pattern, keyword):
885 alg_keywords.remove(keyword)
886 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800887 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200888 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
889 continue
890
891 for key_type in key_types:
892 # Generate keywords from the of the key type
893 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
894
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800895 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200896 for keyword1, keyword2 in exclusive_keywords.items():
897 if keyword1 in key_type_keywords:
898 key_type_keywords.remove(keyword2)
899
900 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
901 not key_type_keywords.isdisjoint(alg_keywords):
902 if alg in alg_with_keys:
903 alg_with_keys[alg].append(key_type)
904 else:
905 alg_with_keys[alg] = [key_type]
906 return alg_with_keys
907
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200908 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200909 """Generate test keys for usage flag extensions."""
910 # Generate a key type and algorithm pair for each extendable usage
911 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800912 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200913 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200914
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200915 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
916 for alg in sorted(alg_with_keys):
917 for key_type in sorted(alg_with_keys[alg]):
918 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200919 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100920 if kt.is_public() and '_SIGN_' in usage:
921 # Can't sign with a public key
922 continue
923 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200924
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200925 def generate_all_keys(self) -> Iterator[StorageTestData]:
926 yield from super().generate_all_keys()
927 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200928
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200929class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100930 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100931 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200932 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100933 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200934 'test_suite_psa_crypto_generate_key.generated':
935 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100936 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100937 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200938 'test_suite_psa_crypto_op_fail.generated':
939 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100940 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200941 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100942 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200943 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100944 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
945
Werner Lewisfbb75e32022-08-24 11:30:03 +0100946 def __init__(self, options):
947 super().__init__(options)
948 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100949
Werner Lewisfbb75e32022-08-24 11:30:03 +0100950 def generate_target(self, name: str, *target_args) -> None:
951 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100952
953if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200954 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)