blob: 044042bf0aa73a883c99bfbb1e5e99ae90667216 [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.
120def fix_key_pair_dependencies(dep_list: str, type: str):
121 # 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]
125 new_list = [re.sub(r'ECC_KEY_PAIR\Z', r'ECC_KEY_PAIR_' + type, dep)
126 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')
222 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC_IMPORT_EXPORT')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100223 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200224 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100225 'import', kt.expression, bits,
226 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 test_case.hex_string(kt.key_material(bits)),
228 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 if not generate_dependencies and param is not None:
231 # If generation is impossible for this key type, rather than
232 # supported or not depending on implementation capabilities,
233 # only generate the test case once.
234 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100235 # For public key we expect that key generation fails with
236 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100237 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200238 yield test_case_for_key_type_not_supported(
239 'generate', kt.expression, bits,
240 finish_family_dependencies(generate_dependencies, bits),
241 str(bits),
242 param_descr=param_descr,
243 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100244 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100245
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200246 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
247 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
248
Gilles Peskine3d778392021-02-17 15:11:05 +0100249 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100250 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100251 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200252 if key_type in self.ECC_KEY_TYPES:
253 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100254 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100255 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100256 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200257 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100258 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100259 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100260 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100261 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100262 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100263
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200264def test_case_for_key_generation(
265 key_type: str, bits: int,
266 dependencies: List[str],
267 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200268 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200269) -> test_case.TestCase:
270 """Return one test case exercising a key generation.
271 """
272 hack_dependencies_not_implemented(dependencies)
273 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100274 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200275 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200276 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200277 tc.set_dependencies(dependencies)
278 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100279 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200280
281 return tc
282
283class KeyGenerate:
284 """Generate positive and negative (invalid argument) test cases for key generation."""
285
286 def __init__(self, info: Information) -> None:
287 self.constructors = info.constructors
288
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200289 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
290 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
291
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100292 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200293 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200294 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200295 ) -> Iterator[test_case.TestCase]:
296 """Return test cases exercising key generation.
297
298 All key types can be generated except for public keys. For public key
299 PSA_ERROR_INVALID_ARGUMENT status is expected.
300 """
301 result = 'PSA_SUCCESS'
302
303 import_dependencies = [psa_want_symbol(kt.name)]
304 if kt.params is not None:
305 import_dependencies += [psa_want_symbol(sym)
306 for i, sym in enumerate(kt.params)]
307 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100308 # The library checks whether the key type is a public key generically,
309 # before it reaches a point where it needs support for the specific key
310 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200311 generate_dependencies = []
312 result = 'PSA_ERROR_INVALID_ARGUMENT'
313 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200314 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200315 for bits in kt.sizes_to_test():
316 yield test_case_for_key_generation(
317 kt.expression, bits,
318 finish_family_dependencies(generate_dependencies, bits),
319 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200320 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200321 )
322
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200323 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
324 """Generate test cases that exercise the generation of keys."""
325 for key_type in sorted(self.constructors.key_types):
326 if key_type in self.ECC_KEY_TYPES:
327 continue
328 kt = crypto_knowledge.KeyType(key_type)
329 yield from self.test_cases_for_key_type_key_generation(kt)
330 for curve_family in sorted(self.constructors.ecc_curves):
331 for constr in self.ECC_KEY_TYPES:
332 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200333 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200334
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200335class OpFail:
336 """Generate test cases for operations that must fail."""
337 #pylint: disable=too-few-public-methods
338
Gilles Peskinecba28a72022-03-15 17:26:33 +0100339 class Reason(enum.Enum):
340 NOT_SUPPORTED = 0
341 INVALID = 1
342 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200343 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100344
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200345 def __init__(self, info: Information) -> None:
346 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100347 key_type_expressions = self.constructors.generate_expressions(
348 sorted(self.constructors.key_types)
349 )
350 self.key_types = [crypto_knowledge.KeyType(kt_expr)
351 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200352
Gilles Peskinecba28a72022-03-15 17:26:33 +0100353 def make_test_case(
354 self,
355 alg: crypto_knowledge.Algorithm,
356 category: crypto_knowledge.AlgorithmCategory,
357 reason: 'Reason',
358 kt: Optional[crypto_knowledge.KeyType] = None,
359 not_deps: FrozenSet[str] = frozenset(),
360 ) -> test_case.TestCase:
361 """Construct a failure test case for a one-key or keyless operation."""
362 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200363 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100364 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200365 if reason == self.Reason.NOT_SUPPORTED:
366 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
367 for dep in not_deps]
368 pretty_reason = '!' + '&'.join(sorted(short_deps))
369 else:
370 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100371 if kt:
372 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100373 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200374 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100375 key_type = ''
376 pretty_type = ''
377 tc.set_description('PSA {} {}: {}{}'
378 .format(category.name.lower(),
379 pretty_alg,
380 pretty_reason,
381 ' with ' + pretty_type if pretty_type else ''))
382 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti24c64e82023-06-26 11:49:02 +0200383 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC_IMPORT_EXPORT')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100384 for i, dep in enumerate(dependencies):
385 if dep in not_deps:
386 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200387 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100388 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000389 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100390 if kt:
391 key_material = kt.key_material(kt.sizes_to_test()[0])
392 arguments += [key_type, test_case.hex_string(key_material)]
393 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200394 if category.is_asymmetric():
395 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100396 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
397 'INVALID_ARGUMENT')
398 arguments.append('PSA_ERROR_' + error)
399 tc.set_arguments(arguments)
400 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200401
Gilles Peskinecba28a72022-03-15 17:26:33 +0100402 def no_key_test_cases(
403 self,
404 alg: crypto_knowledge.Algorithm,
405 category: crypto_knowledge.AlgorithmCategory,
406 ) -> Iterator[test_case.TestCase]:
407 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200408 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100409 # Compatible operation, unsupported algorithm
410 for dep in automatic_dependencies(alg.base_expression):
411 yield self.make_test_case(alg, category,
412 self.Reason.NOT_SUPPORTED,
413 not_deps=frozenset([dep]))
414 else:
415 # Incompatible operation, supported algorithm
416 yield self.make_test_case(alg, category, self.Reason.INVALID)
417
418 def one_key_test_cases(
419 self,
420 alg: crypto_knowledge.Algorithm,
421 category: crypto_knowledge.AlgorithmCategory,
422 ) -> Iterator[test_case.TestCase]:
423 """Generate failure test cases for one-key operations with the specified algorithm."""
424 for kt in self.key_types:
425 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200426 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100427 # Compatible key and operation, unsupported algorithm
428 for dep in automatic_dependencies(alg.base_expression):
429 yield self.make_test_case(alg, category,
430 self.Reason.NOT_SUPPORTED,
431 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200432 # Public key for a private-key operation
433 if category.is_asymmetric() and kt.is_public():
434 yield self.make_test_case(alg, category,
435 self.Reason.PUBLIC,
436 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100437 elif key_is_compatible:
438 # Compatible key, incompatible operation, supported algorithm
439 yield self.make_test_case(alg, category,
440 self.Reason.INVALID,
441 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200442 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100443 # Incompatible key, compatible operation, supported algorithm
444 yield self.make_test_case(alg, category,
445 self.Reason.INCOMPATIBLE,
446 kt=kt)
447 else:
448 # Incompatible key and operation. Don't test cases where
449 # multiple things are wrong, to keep the number of test
450 # cases reasonable.
451 pass
452
453 def test_cases_for_algorithm(
454 self,
455 alg: crypto_knowledge.Algorithm,
456 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200457 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100458 for category in crypto_knowledge.AlgorithmCategory:
459 if category == crypto_knowledge.AlgorithmCategory.PAKE:
460 # PAKE operations are not implemented yet
461 pass
462 elif category.requires_key():
463 yield from self.one_key_test_cases(alg, category)
464 else:
465 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200466
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200467 def all_test_cases(self) -> Iterator[test_case.TestCase]:
468 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200469 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100470 for expr in self.constructors.generate_expressions(algorithms):
471 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200472 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200473
474
Gilles Peskine897dff92021-03-10 15:03:44 +0100475class StorageKey(psa_storage.Key):
476 """Representation of a key for storage format testing."""
477
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200478 IMPLICIT_USAGE_FLAGS = {
479 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
480 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
481 } #type: Dict[str, str]
482 """Mapping of usage flags to the flags that they imply."""
483
484 def __init__(
485 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100486 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200487 without_implicit_usage: Optional[bool] = False,
488 **kwargs
489 ) -> None:
490 """Prepare to generate a key.
491
492 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000493 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200494 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100495 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200496 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100497 for flag in sorted(usage_flags):
498 if flag in self.IMPLICIT_USAGE_FLAGS:
499 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
500 if usage_flags:
501 usage_expression = ' | '.join(sorted(usage_flags))
502 else:
503 usage_expression = '0'
504 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200505
506class StorageTestData(StorageKey):
507 """Representation of test case data for storage format testing."""
508
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200509 def __init__(
510 self,
511 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100512 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200513 **kwargs
514 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200515 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200516
Tom Cosgrove1797b052022-12-04 17:19:59 +0000517 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200518 * `expected_usage`: the usage flags generated as the expected usage flags
519 in the test cases. CAn differ from the usage flags
520 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200521 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100522 super().__init__(**kwargs)
523 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100524 if expected_usage is None:
525 self.expected_usage = self.usage #type: psa_storage.Expr
526 elif expected_usage:
527 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
528 else:
529 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200530
Gilles Peskine897dff92021-03-10 15:03:44 +0100531class StorageFormat:
532 """Storage format stability test cases."""
533
534 def __init__(self, info: Information, version: int, forward: bool) -> None:
535 """Prepare to generate test cases for storage format stability.
536
537 * `info`: information about the API. See the `Information` class.
538 * `version`: the storage format version to generate test cases for.
539 * `forward`: if true, generate forward compatibility test cases which
540 save a key and check that its representation is as intended. Otherwise
541 generate backward compatibility test cases which inject a key
542 representation and check that it can be read and used.
543 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200544 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
545 self.version = version #type: int
546 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100547
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100548 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100549 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100550 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100551 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100552 cls,
553 key_type: psa_storage.Expr, bits: int,
554 alg: psa_storage.Expr
555 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100556 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100557
558 Normally only the type and algorithm matter for compatibility, and
559 this is handled in crypto_knowledge.KeyType.can_do(). This function
560 exists to detect exceptional cases. Exceptional cases detected here
561 are not tested in OpFail and should therefore have manually written
562 test cases.
563 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100564 # Some test keys have the RAW_DATA type and attributes that don't
565 # necessarily make sense. We do this to validate numerical
566 # encodings of the attributes.
567 # Raw data keys have no useful exercise anyway so there is no
568 # loss of test coverage.
569 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
570 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100571 # OAEP requires room for two hashes plus wrapping
572 m = cls.RSA_OAEP_RE.match(alg.string)
573 if m:
574 hash_alg = m.group(1)
575 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
576 key_length = (bits + 7) // 8
577 # Leave enough room for at least one byte of plaintext
578 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100579 # There's nothing wrong with ECC keys on Brainpool curves,
580 # but operations with them are very slow. So we only exercise them
581 # with a single algorithm, not with all possible hashes. We do
582 # exercise other curves with all algorithms so test coverage is
583 # perfectly adequate like this.
584 m = cls.BRAINPOOL_RE.match(key_type.string)
585 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
586 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100587 return True
588
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200589 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100590 """Construct a storage format test case for the given key.
591
592 If ``forward`` is true, generate a forward compatibility test case:
593 create a key and validate that it has the expected representation.
594 Otherwise generate a backward compatibility test case: inject the
595 key representation into storage and validate that it can be read
596 correctly.
597 """
598 verb = 'save' if self.forward else 'read'
599 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100600 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100601 dependencies = automatic_dependencies(
602 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100603 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100604 )
605 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800606 dependencies += generate_key_dependencies(key.description)
Valerio Setti24c64e82023-06-26 11:49:02 +0200607 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC_IMPORT_EXPORT')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100608 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100609 tc.set_function('key_storage_' + verb)
610 if self.forward:
611 extra_arguments = []
612 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200613 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100614 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200615 flags.append('TEST_FLAG_EXERCISE')
616 if 'READ_ONLY' in key.lifetime.string:
617 flags.append('TEST_FLAG_READ_ONLY')
618 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100619 tc.set_arguments([key.lifetime.string,
620 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100621 key.expected_usage.string,
622 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100623 '"' + key.material.hex() + '"',
624 '"' + key.hex() + '"',
625 *extra_arguments])
626 return tc
627
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200628 def key_for_lifetime(
629 self,
630 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200631 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200632 """Construct a test key for the given lifetime."""
633 short = lifetime
634 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
635 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100636 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200637 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200638 key = StorageTestData(version=self.version,
639 id=1, lifetime=lifetime,
640 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100641 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200642 material=b'L',
643 description=description)
644 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200645
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200646 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200647 """Generate test keys covering lifetimes."""
648 lifetimes = sorted(self.constructors.lifetimes)
649 expressions = self.constructors.generate_expressions(lifetimes)
650 for lifetime in expressions:
651 # Don't attempt to create or load a volatile key in storage
652 if 'VOLATILE' in lifetime:
653 continue
654 # Don't attempt to create a read-only key in storage,
655 # but do attempt to load one.
656 if 'READ_ONLY' in lifetime and self.forward:
657 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200658 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200659
Gilles Peskinef7614272022-02-24 18:58:08 +0100660 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100661 self,
662 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200663 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100664 test_implicit_usage: Optional[bool] = True
665 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100666 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100667 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100668 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200669 key1 = StorageTestData(version=self.version,
670 id=1, lifetime=0x00000001,
671 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100672 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100673 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100674 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200675 material=b'K',
676 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100677 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100678 usage_expr = key1.expected_usage.string
679 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100680 else:
681 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100682 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100683
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200684 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100685 """Generate test keys covering usage flags."""
686 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100687 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200688 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100689 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200690 for flag1, flag2 in zip(known_flags,
691 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100692 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200693
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200694 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200695 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100696 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200697
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200698 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200699 yield from self.generate_keys_for_usage_flags()
700 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100701
Gilles Peskine7de7c102021-04-29 22:28:07 +0200702 def key_for_type_and_alg(
703 self,
704 kt: crypto_knowledge.KeyType,
705 bits: int,
706 alg: Optional[crypto_knowledge.Algorithm] = None,
707 ) -> StorageTestData:
708 """Construct a test key of the given type.
709
710 If alg is not None, this key allows it.
711 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100712 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100713 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200714 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100715 if alg is not None:
716 alg1 = alg.expression
717 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200718 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100719 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200720 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100721 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200722 key = StorageTestData(version=self.version,
723 id=1, lifetime=0x00000001,
724 type=kt.expression, bits=bits,
725 usage=usage_flags, alg=alg1, alg2=alg2,
726 material=key_material,
727 description=description)
728 return key
729
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100730 def keys_for_type(
731 self,
732 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200733 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200734 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 """Generate test keys for the given key type."""
736 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100737 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200738 # Test a non-exercisable key, as well as exercisable keys for
739 # each compatible algorithm.
740 # To do: test reading a key from storage with an incompatible
741 # or unsupported algorithm.
742 yield self.key_for_type_and_alg(kt, bits)
743 compatible_algorithms = [alg for alg in all_algorithms
744 if kt.can_do(alg)]
745 for alg in compatible_algorithms:
746 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100747
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200748 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100749 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200750 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 all_algorithms = [crypto_knowledge.Algorithm(alg)
752 for alg in self.constructors.generate_expressions(
753 sorted(self.constructors.algorithms)
754 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200755 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200756 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100757
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200758 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200759 """Generate test keys for the encoding of the specified algorithm."""
760 # These test cases only validate the encoding of algorithms, not
761 # whether the key read from storage is suitable for an operation.
762 # `keys_for_types` generate read tests with an algorithm and a
763 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100764 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100765 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200766 key1 = StorageTestData(version=self.version,
767 id=1, lifetime=0x00000001,
768 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
769 usage=usage, alg=alg, alg2=0,
770 material=b'K',
771 description='alg: ' + descr)
772 yield key1
773 key2 = StorageTestData(version=self.version,
774 id=1, lifetime=0x00000001,
775 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
776 usage=usage, alg=0, alg2=alg,
777 material=b'L',
778 description='alg2: ' + descr)
779 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100780
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200781 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100782 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200783 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200784 for alg in self.constructors.generate_expressions(algorithms):
785 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100786
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200787 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200788 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200789 yield from self.all_keys_for_lifetimes()
790 yield from self.all_keys_for_usage_flags()
791 yield from self.all_keys_for_types()
792 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200793
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200794 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100795 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200796 # First build a list of all keys, then construct all the corresponding
797 # test cases. This allows all required information to be obtained in
798 # one go, which is a significant performance gain as the information
799 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200800 all_keys = list(self.generate_all_keys())
801 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200802 if key.location_value() != 0:
803 # Skip keys with a non-default location, because they
804 # require a driver and we currently have no mechanism to
805 # determine whether a driver is available.
806 continue
807 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100808
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200809class StorageFormatForward(StorageFormat):
810 """Storage format stability test cases for forward compatibility."""
811
812 def __init__(self, info: Information, version: int) -> None:
813 super().__init__(info, version, True)
814
815class StorageFormatV0(StorageFormat):
816 """Storage format stability test cases for version 0 compatibility."""
817
818 def __init__(self, info: Information) -> None:
819 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100820
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200821 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200822 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100823 yield from super().all_keys_for_usage_flags()
824 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200825
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200826 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200827 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200828 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200829 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200830 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200831 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200832 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200833 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200834 algorithm and key type combination.
835 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200836 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200837 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100838 usage_flags = ['PSA_KEY_USAGE_EXPORT']
839 material_usage_flags = usage_flags + [implyer_usage]
840 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200841 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200842 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100843 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
844 alg_expression = crypto_knowledge.short_expression(alg, 1)
845 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200846 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200847 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200848 key = StorageTestData(version=self.version,
849 id=1, lifetime=0x00000001,
850 type=key_type.expression, bits=bits,
851 usage=material_usage_flags,
852 expected_usage=expected_usage_flags,
853 without_implicit_usage=True,
854 alg=alg, alg2=alg2,
855 material=key_material,
856 description=description)
857 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200858
859 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200860 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200861 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800862 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200863 # must be filtered. Pair them with keywords created from its names.
864 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
865 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
866 keyword_translation = {
867 'ECDSA': 'ECC',
868 'ED[0-9]*.*' : 'EDWARDS'
869 }
870 exclusive_keywords = {
871 'EDWARDS': 'ECC'
872 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200873 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
874 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200875 alg_with_keys = {} #type: Dict[str, List[str]]
876 translation_table = str.maketrans('(', '_', ')')
877 for alg in algorithms:
878 # Generate keywords from the name of the algorithm
879 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
880 # Translate keywords for better matching with the key types
881 for keyword in alg_keywords.copy():
882 for pattern, replace in keyword_translation.items():
883 if re.match(pattern, keyword):
884 alg_keywords.remove(keyword)
885 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800886 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200887 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
888 continue
889
890 for key_type in key_types:
891 # Generate keywords from the of the key type
892 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
893
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800894 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200895 for keyword1, keyword2 in exclusive_keywords.items():
896 if keyword1 in key_type_keywords:
897 key_type_keywords.remove(keyword2)
898
899 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
900 not key_type_keywords.isdisjoint(alg_keywords):
901 if alg in alg_with_keys:
902 alg_with_keys[alg].append(key_type)
903 else:
904 alg_with_keys[alg] = [key_type]
905 return alg_with_keys
906
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200907 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 """Generate test keys for usage flag extensions."""
909 # Generate a key type and algorithm pair for each extendable usage
910 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800911 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200912 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200913
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200914 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
915 for alg in sorted(alg_with_keys):
916 for key_type in sorted(alg_with_keys[alg]):
917 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200918 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100919 if kt.is_public() and '_SIGN_' in usage:
920 # Can't sign with a public key
921 continue
922 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200923
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200924 def generate_all_keys(self) -> Iterator[StorageTestData]:
925 yield from super().generate_all_keys()
926 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200927
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200928class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100929 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100930 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200931 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100932 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200933 'test_suite_psa_crypto_generate_key.generated':
934 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100935 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100936 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200937 'test_suite_psa_crypto_op_fail.generated':
938 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100939 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200940 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100941 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200942 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100943 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
944
Werner Lewisfbb75e32022-08-24 11:30:03 +0100945 def __init__(self, options):
946 super().__init__(options)
947 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100948
Werner Lewisfbb75e32022-08-24 11:30:03 +0100949 def generate_target(self, name: str, *target_args) -> None:
950 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100951
952if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200953 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)