blob: a77ece691384904afa793582b9672698b846a97a [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
Gilles Peskine0298bda2021-03-10 02:34:37 +01003
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Gilles Peskine09940492021-01-26 22:16:30 +01006"""
7
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0
10#
11# Licensed under the Apache License, Version 2.0 (the "License"); you may
12# not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22
Gilles Peskinecba28a72022-03-15 17:26:33 +010023import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import re
Gilles Peskine09940492021-01-26 22:16:30 +010025import sys
Werner Lewisfbb75e32022-08-24 11:30:03 +010026from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
Gilles Peskine09940492021-01-26 22:16:30 +010027
28import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010029from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010030from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010031from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010032from mbedtls_dev import test_case
Gilles Peskine64f2efd2022-09-16 21:41:47 +020033from mbedtls_dev import test_data_generation
Gilles Peskine09940492021-01-26 22:16:30 +010034
Gilles Peskine14e428f2021-01-26 22:19:21 +010035
Gilles Peskine7f756872021-02-16 12:13:12 +010036def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010037 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
38 if name.startswith('PSA_'):
39 return name[:4] + 'WANT_' + name[4:]
40 else:
41 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
42
Gilles Peskine7f756872021-02-16 12:13:12 +010043def finish_family_dependency(dep: str, bits: int) -> str:
44 """Finish dep if it's a family dependency symbol prefix.
45
46 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
47 qualified by the key size. If dep is such a symbol, finish it by adjusting
48 the prefix and appending the key size. Other symbols are left unchanged.
49 """
50 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
51
52def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
53 """Finish any family dependency symbol prefixes.
54
55 Apply `finish_family_dependency` to each element of `dependencies`.
56 """
57 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010058
Gilles Peskinec5d086f2021-04-20 23:23:45 +020059SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
60 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
61 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
62 'PSA_ALG_ANY_HASH', # only in policies
63 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
64 'PSA_ALG_KEY_AGREEMENT', # chaining
65 'PSA_ALG_TRUNCATED_MAC', # modifier
66])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010067def automatic_dependencies(*expressions: str) -> List[str]:
68 """Infer dependencies of a test case by looking for PSA_xxx symbols.
69
70 The arguments are strings which should be C expressions. Do not use
71 string literals or comments as this function is not smart enough to
72 skip them.
73 """
74 used = set()
75 for expr in expressions:
76 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020077 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010078 return sorted(psa_want_symbol(name) for name in used)
79
Yanray Wang3f417442023-04-21 14:29:16 +080080# Define set of regular expressions and dependencies to optionally append
81# extra dependencies for test case.
82AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
83AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
84
85DEPENDENCY_FROM_KEY = {
86 AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
87}#type: Dict[str, List[str]]
Yanray Wang5dd429c2023-05-10 09:58:46 +080088def generate_key_dependencies(description: str) -> List[str]:
Yanray Wang3f417442023-04-21 14:29:16 +080089 """Return additional dependencies based on pairs of REGEX and dependencies.
90 """
91 deps = []
92 for regex, dep in DEPENDENCY_FROM_KEY.items():
93 if re.search(regex, description):
94 deps += dep
95
96 return deps
97
Gilles Peskined169d602021-02-16 14:16:25 +010098# A temporary hack: at the time of writing, not all dependency symbols
99# are implemented yet. Skip test cases for which the dependency symbols are
100# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove1797b052022-12-04 17:19:59 +0000101# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +0100102# failure.
103def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
104 return frozenset(symbol
105 for line in open(filename)
106 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200107_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +0100108def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200109 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
110 if _implemented_dependencies is None:
111 _implemented_dependencies = \
112 read_implemented_dependencies('include/psa/crypto_config.h')
Valerio Setti323ad1c2023-05-26 17:47:55 +0200113 if not all((dep.lstrip('!') in _implemented_dependencies or
Valerio Setti5ca80e72023-06-20 19:27:02 +0200114 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100115 for dep in dependencies):
116 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
117
Valerio Setti0c42c432023-06-29 12:10:52 +0200118def tweak_key_pair_dependency(dep: str, usage: str):
Valerio Settieabfef32023-06-30 11:09:43 +0200119 """
120 This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
121 symbols according to the required usage.
122 """
Valerio Setti0c42c432023-06-29 12:10:52 +0200123 ret_list = list()
Valerio Setti24c64e82023-06-26 11:49:02 +0200124 # Note: this LEGACY replacement for RSA is temporary and it's going to be
125 # aligned with ECC one in #7772.
Valerio Setti0c42c432023-06-29 12:10:52 +0200126 if dep.endswith('RSA_KEY_PAIR'):
127 ret_list.append(re.sub(r'RSA_KEY_PAIR\Z', r'RSA_KEY_PAIR_LEGACY', dep))
128 elif dep.endswith('ECC_KEY_PAIR'):
129 if usage == "BASIC":
130 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
131 # config_psa.h).
132 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_BASIC', dep))
133 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_IMPORT', dep))
134 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_EXPORT', dep))
135 elif usage == "GENERATE":
136 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_GENERATE', dep))
137 else:
138 # No replacement to do in this case
139 ret_list.append(dep)
140 return ret_list
141
142def fix_key_pair_dependencies(dep_list: List[str], usage: str):
143 new_list = [new_deps
144 for dep in dep_list
145 for new_deps in tweak_key_pair_dependency(dep, usage)]
146
Valerio Setti24c64e82023-06-26 11:49:02 +0200147 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100148
Gilles Peskineb94ea512021-03-10 02:12:08 +0100149class Information:
150 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100151
Gilles Peskineb94ea512021-03-10 02:12:08 +0100152 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100153 self.constructors = self.read_psa_interface()
154
155 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100156 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200157 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100158 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200159 # Mbed TLS doesn't support finite-field DH yet and will not support
160 # finite-field DSA. Don't attempt to generate any related test case.
161 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
162 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100163 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
164 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100165
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200166 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100167 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200168 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100169 header_file_names = ['include/psa/crypto_values.h',
170 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200171 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100172 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200173 constructors.parse_header(header_file_name)
174 for test_cases in test_suites:
175 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100176 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200177 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100178 return constructors
179
Gilles Peskine14e428f2021-01-26 22:19:21 +0100180
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200181def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100182 verb: str, key_type: str, bits: int,
183 dependencies: List[str],
184 *args: str,
185 param_descr: str = ''
186) -> test_case.TestCase:
187 """Return one test case exercising a key creation method
188 for an unsupported key type or size.
189 """
190 hack_dependencies_not_implemented(dependencies)
191 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100192 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100193 adverb = 'not' if dependencies else 'never'
194 if param_descr:
195 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200196 tc.set_description('PSA {} {} {}-bit {} supported'
197 .format(verb, short_key_type, bits, adverb))
198 tc.set_dependencies(dependencies)
199 tc.set_function(verb + '_not_supported')
200 tc.set_arguments([key_type] + list(args))
201 return tc
202
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100203class KeyTypeNotSupported:
204 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100205
206 def __init__(self, info: Information) -> None:
207 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100208
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100209 ALWAYS_SUPPORTED = frozenset([
210 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100211 'PSA_KEY_TYPE_PASSWORD',
212 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100213 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200214 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100215 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100216 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100217 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100218 kt: crypto_knowledge.KeyType,
219 param: Optional[int] = None,
220 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100221 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200222 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100223
224 If param is present and not None, emit test cases conditioned on this
225 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200226 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100228 if kt.name in self.ALWAYS_SUPPORTED:
229 # Don't generate test cases for key types that are always supported.
230 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100231 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100232 import_dependencies = [('!' if param is None else '') +
233 psa_want_symbol(kt.name)]
234 if kt.params is not None:
235 import_dependencies += [('!' if param == i else '') +
236 psa_want_symbol(sym)
237 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100238 if kt.name.endswith('_PUBLIC_KEY'):
239 generate_dependencies = []
240 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200241 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200242 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100243 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200244 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100245 'import', kt.expression, bits,
246 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100247 test_case.hex_string(kt.key_material(bits)),
248 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100249 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100250 if not generate_dependencies and param is not None:
251 # If generation is impossible for this key type, rather than
252 # supported or not depending on implementation capabilities,
253 # only generate the test case once.
254 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100255 # For public key we expect that key generation fails with
256 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100257 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200258 yield test_case_for_key_type_not_supported(
259 'generate', kt.expression, bits,
260 finish_family_dependencies(generate_dependencies, bits),
261 str(bits),
262 param_descr=param_descr,
263 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100264 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100265
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200266 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
267 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
268
Gilles Peskine3d778392021-02-17 15:11:05 +0100269 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100270 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100271 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200272 if key_type in self.ECC_KEY_TYPES:
273 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100274 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100275 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100276 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200277 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100278 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100279 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100280 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100281 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100282 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100283
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200284def test_case_for_key_generation(
285 key_type: str, bits: int,
286 dependencies: List[str],
287 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200288 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200289) -> test_case.TestCase:
290 """Return one test case exercising a key generation.
291 """
292 hack_dependencies_not_implemented(dependencies)
293 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100294 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200295 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200296 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200297 tc.set_dependencies(dependencies)
298 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100299 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200300
301 return tc
302
303class KeyGenerate:
304 """Generate positive and negative (invalid argument) test cases for key generation."""
305
306 def __init__(self, info: Information) -> None:
307 self.constructors = info.constructors
308
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200309 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
310 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
311
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100312 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200314 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200315 ) -> Iterator[test_case.TestCase]:
316 """Return test cases exercising key generation.
317
318 All key types can be generated except for public keys. For public key
319 PSA_ERROR_INVALID_ARGUMENT status is expected.
320 """
321 result = 'PSA_SUCCESS'
322
323 import_dependencies = [psa_want_symbol(kt.name)]
324 if kt.params is not None:
325 import_dependencies += [psa_want_symbol(sym)
326 for i, sym in enumerate(kt.params)]
327 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100328 # The library checks whether the key type is a public key generically,
329 # before it reaches a point where it needs support for the specific key
330 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200331 generate_dependencies = []
332 result = 'PSA_ERROR_INVALID_ARGUMENT'
333 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200334 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200335 for bits in kt.sizes_to_test():
336 yield test_case_for_key_generation(
337 kt.expression, bits,
338 finish_family_dependencies(generate_dependencies, bits),
339 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200340 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200341 )
342
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200343 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
344 """Generate test cases that exercise the generation of keys."""
345 for key_type in sorted(self.constructors.key_types):
346 if key_type in self.ECC_KEY_TYPES:
347 continue
348 kt = crypto_knowledge.KeyType(key_type)
349 yield from self.test_cases_for_key_type_key_generation(kt)
350 for curve_family in sorted(self.constructors.ecc_curves):
351 for constr in self.ECC_KEY_TYPES:
352 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200353 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200354
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200355class OpFail:
356 """Generate test cases for operations that must fail."""
357 #pylint: disable=too-few-public-methods
358
Gilles Peskinecba28a72022-03-15 17:26:33 +0100359 class Reason(enum.Enum):
360 NOT_SUPPORTED = 0
361 INVALID = 1
362 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200363 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100364
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200365 def __init__(self, info: Information) -> None:
366 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100367 key_type_expressions = self.constructors.generate_expressions(
368 sorted(self.constructors.key_types)
369 )
370 self.key_types = [crypto_knowledge.KeyType(kt_expr)
371 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200372
Gilles Peskinecba28a72022-03-15 17:26:33 +0100373 def make_test_case(
374 self,
375 alg: crypto_knowledge.Algorithm,
376 category: crypto_knowledge.AlgorithmCategory,
377 reason: 'Reason',
378 kt: Optional[crypto_knowledge.KeyType] = None,
379 not_deps: FrozenSet[str] = frozenset(),
380 ) -> test_case.TestCase:
381 """Construct a failure test case for a one-key or keyless operation."""
382 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200383 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100384 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200385 if reason == self.Reason.NOT_SUPPORTED:
386 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
387 for dep in not_deps]
388 pretty_reason = '!' + '&'.join(sorted(short_deps))
389 else:
390 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100391 if kt:
392 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100393 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200394 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100395 key_type = ''
396 pretty_type = ''
397 tc.set_description('PSA {} {}: {}{}'
398 .format(category.name.lower(),
399 pretty_alg,
400 pretty_reason,
401 ' with ' + pretty_type if pretty_type else ''))
402 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200403 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100404 for i, dep in enumerate(dependencies):
405 if dep in not_deps:
406 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200407 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100408 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000409 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100410 if kt:
411 key_material = kt.key_material(kt.sizes_to_test()[0])
412 arguments += [key_type, test_case.hex_string(key_material)]
413 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200414 if category.is_asymmetric():
415 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100416 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
417 'INVALID_ARGUMENT')
418 arguments.append('PSA_ERROR_' + error)
419 tc.set_arguments(arguments)
420 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200421
Gilles Peskinecba28a72022-03-15 17:26:33 +0100422 def no_key_test_cases(
423 self,
424 alg: crypto_knowledge.Algorithm,
425 category: crypto_knowledge.AlgorithmCategory,
426 ) -> Iterator[test_case.TestCase]:
427 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200428 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100429 # Compatible operation, unsupported algorithm
430 for dep in automatic_dependencies(alg.base_expression):
431 yield self.make_test_case(alg, category,
432 self.Reason.NOT_SUPPORTED,
433 not_deps=frozenset([dep]))
434 else:
435 # Incompatible operation, supported algorithm
436 yield self.make_test_case(alg, category, self.Reason.INVALID)
437
438 def one_key_test_cases(
439 self,
440 alg: crypto_knowledge.Algorithm,
441 category: crypto_knowledge.AlgorithmCategory,
442 ) -> Iterator[test_case.TestCase]:
443 """Generate failure test cases for one-key operations with the specified algorithm."""
444 for kt in self.key_types:
445 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200446 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100447 # Compatible key and operation, unsupported algorithm
448 for dep in automatic_dependencies(alg.base_expression):
449 yield self.make_test_case(alg, category,
450 self.Reason.NOT_SUPPORTED,
451 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200452 # Public key for a private-key operation
453 if category.is_asymmetric() and kt.is_public():
454 yield self.make_test_case(alg, category,
455 self.Reason.PUBLIC,
456 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100457 elif key_is_compatible:
458 # Compatible key, incompatible operation, supported algorithm
459 yield self.make_test_case(alg, category,
460 self.Reason.INVALID,
461 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200462 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100463 # Incompatible key, compatible operation, supported algorithm
464 yield self.make_test_case(alg, category,
465 self.Reason.INCOMPATIBLE,
466 kt=kt)
467 else:
468 # Incompatible key and operation. Don't test cases where
469 # multiple things are wrong, to keep the number of test
470 # cases reasonable.
471 pass
472
473 def test_cases_for_algorithm(
474 self,
475 alg: crypto_knowledge.Algorithm,
476 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200477 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100478 for category in crypto_knowledge.AlgorithmCategory:
479 if category == crypto_knowledge.AlgorithmCategory.PAKE:
480 # PAKE operations are not implemented yet
481 pass
482 elif category.requires_key():
483 yield from self.one_key_test_cases(alg, category)
484 else:
485 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200486
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200487 def all_test_cases(self) -> Iterator[test_case.TestCase]:
488 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200489 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100490 for expr in self.constructors.generate_expressions(algorithms):
491 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200492 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200493
494
Gilles Peskine897dff92021-03-10 15:03:44 +0100495class StorageKey(psa_storage.Key):
496 """Representation of a key for storage format testing."""
497
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200498 IMPLICIT_USAGE_FLAGS = {
499 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
500 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
501 } #type: Dict[str, str]
502 """Mapping of usage flags to the flags that they imply."""
503
504 def __init__(
505 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100506 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200507 without_implicit_usage: Optional[bool] = False,
508 **kwargs
509 ) -> None:
510 """Prepare to generate a key.
511
512 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000513 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200514 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100515 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200516 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100517 for flag in sorted(usage_flags):
518 if flag in self.IMPLICIT_USAGE_FLAGS:
519 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
520 if usage_flags:
521 usage_expression = ' | '.join(sorted(usage_flags))
522 else:
523 usage_expression = '0'
524 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200525
526class StorageTestData(StorageKey):
527 """Representation of test case data for storage format testing."""
528
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200529 def __init__(
530 self,
531 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100532 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200533 **kwargs
534 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200535 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200536
Tom Cosgrove1797b052022-12-04 17:19:59 +0000537 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200538 * `expected_usage`: the usage flags generated as the expected usage flags
539 in the test cases. CAn differ from the usage flags
540 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200541 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100542 super().__init__(**kwargs)
543 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100544 if expected_usage is None:
545 self.expected_usage = self.usage #type: psa_storage.Expr
546 elif expected_usage:
547 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
548 else:
549 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200550
Gilles Peskine897dff92021-03-10 15:03:44 +0100551class StorageFormat:
552 """Storage format stability test cases."""
553
554 def __init__(self, info: Information, version: int, forward: bool) -> None:
555 """Prepare to generate test cases for storage format stability.
556
557 * `info`: information about the API. See the `Information` class.
558 * `version`: the storage format version to generate test cases for.
559 * `forward`: if true, generate forward compatibility test cases which
560 save a key and check that its representation is as intended. Otherwise
561 generate backward compatibility test cases which inject a key
562 representation and check that it can be read and used.
563 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200564 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
565 self.version = version #type: int
566 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100567
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100568 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100569 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100570 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100571 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100572 cls,
573 key_type: psa_storage.Expr, bits: int,
574 alg: psa_storage.Expr
575 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100576 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100577
578 Normally only the type and algorithm matter for compatibility, and
579 this is handled in crypto_knowledge.KeyType.can_do(). This function
580 exists to detect exceptional cases. Exceptional cases detected here
581 are not tested in OpFail and should therefore have manually written
582 test cases.
583 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100584 # Some test keys have the RAW_DATA type and attributes that don't
585 # necessarily make sense. We do this to validate numerical
586 # encodings of the attributes.
587 # Raw data keys have no useful exercise anyway so there is no
588 # loss of test coverage.
589 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
590 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100591 # OAEP requires room for two hashes plus wrapping
592 m = cls.RSA_OAEP_RE.match(alg.string)
593 if m:
594 hash_alg = m.group(1)
595 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
596 key_length = (bits + 7) // 8
597 # Leave enough room for at least one byte of plaintext
598 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100599 # There's nothing wrong with ECC keys on Brainpool curves,
600 # but operations with them are very slow. So we only exercise them
601 # with a single algorithm, not with all possible hashes. We do
602 # exercise other curves with all algorithms so test coverage is
603 # perfectly adequate like this.
604 m = cls.BRAINPOOL_RE.match(key_type.string)
605 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
606 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100607 return True
608
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200609 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100610 """Construct a storage format test case for the given key.
611
612 If ``forward`` is true, generate a forward compatibility test case:
613 create a key and validate that it has the expected representation.
614 Otherwise generate a backward compatibility test case: inject the
615 key representation into storage and validate that it can be read
616 correctly.
617 """
618 verb = 'save' if self.forward else 'read'
619 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100620 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100621 dependencies = automatic_dependencies(
622 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100623 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100624 )
625 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800626 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200627 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100628 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100629 tc.set_function('key_storage_' + verb)
630 if self.forward:
631 extra_arguments = []
632 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200633 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100634 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200635 flags.append('TEST_FLAG_EXERCISE')
636 if 'READ_ONLY' in key.lifetime.string:
637 flags.append('TEST_FLAG_READ_ONLY')
638 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100639 tc.set_arguments([key.lifetime.string,
640 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100641 key.expected_usage.string,
642 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100643 '"' + key.material.hex() + '"',
644 '"' + key.hex() + '"',
645 *extra_arguments])
646 return tc
647
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200648 def key_for_lifetime(
649 self,
650 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200652 """Construct a test key for the given lifetime."""
653 short = lifetime
654 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
655 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100656 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200657 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200658 key = StorageTestData(version=self.version,
659 id=1, lifetime=lifetime,
660 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100661 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200662 material=b'L',
663 description=description)
664 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200665
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200666 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200667 """Generate test keys covering lifetimes."""
668 lifetimes = sorted(self.constructors.lifetimes)
669 expressions = self.constructors.generate_expressions(lifetimes)
670 for lifetime in expressions:
671 # Don't attempt to create or load a volatile key in storage
672 if 'VOLATILE' in lifetime:
673 continue
674 # Don't attempt to create a read-only key in storage,
675 # but do attempt to load one.
676 if 'READ_ONLY' in lifetime and self.forward:
677 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200678 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200679
Gilles Peskinef7614272022-02-24 18:58:08 +0100680 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100681 self,
682 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200683 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100684 test_implicit_usage: Optional[bool] = True
685 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100686 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100687 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100688 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200689 key1 = StorageTestData(version=self.version,
690 id=1, lifetime=0x00000001,
691 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100692 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100693 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100694 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200695 material=b'K',
696 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100697 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100698 usage_expr = key1.expected_usage.string
699 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100700 else:
701 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100702 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100703
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200704 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100705 """Generate test keys covering usage flags."""
706 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100707 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200708 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100709 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200710 for flag1, flag2 in zip(known_flags,
711 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100712 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200713
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200714 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200715 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100716 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200717
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200718 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200719 yield from self.generate_keys_for_usage_flags()
720 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100721
Gilles Peskine7de7c102021-04-29 22:28:07 +0200722 def key_for_type_and_alg(
723 self,
724 kt: crypto_knowledge.KeyType,
725 bits: int,
726 alg: Optional[crypto_knowledge.Algorithm] = None,
727 ) -> StorageTestData:
728 """Construct a test key of the given type.
729
730 If alg is not None, this key allows it.
731 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100732 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100733 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200734 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100735 if alg is not None:
736 alg1 = alg.expression
737 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200738 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100739 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200740 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100741 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200742 key = StorageTestData(version=self.version,
743 id=1, lifetime=0x00000001,
744 type=kt.expression, bits=bits,
745 usage=usage_flags, alg=alg1, alg2=alg2,
746 material=key_material,
747 description=description)
748 return key
749
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100750 def keys_for_type(
751 self,
752 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200753 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200754 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200755 """Generate test keys for the given key type."""
756 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100757 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200758 # Test a non-exercisable key, as well as exercisable keys for
759 # each compatible algorithm.
760 # To do: test reading a key from storage with an incompatible
761 # or unsupported algorithm.
762 yield self.key_for_type_and_alg(kt, bits)
763 compatible_algorithms = [alg for alg in all_algorithms
764 if kt.can_do(alg)]
765 for alg in compatible_algorithms:
766 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100767
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200768 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100769 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200770 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200771 all_algorithms = [crypto_knowledge.Algorithm(alg)
772 for alg in self.constructors.generate_expressions(
773 sorted(self.constructors.algorithms)
774 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200775 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200776 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100777
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200778 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200779 """Generate test keys for the encoding of the specified algorithm."""
780 # These test cases only validate the encoding of algorithms, not
781 # whether the key read from storage is suitable for an operation.
782 # `keys_for_types` generate read tests with an algorithm and a
783 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100784 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100785 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200786 key1 = StorageTestData(version=self.version,
787 id=1, lifetime=0x00000001,
788 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
789 usage=usage, alg=alg, alg2=0,
790 material=b'K',
791 description='alg: ' + descr)
792 yield key1
793 key2 = StorageTestData(version=self.version,
794 id=1, lifetime=0x00000001,
795 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
796 usage=usage, alg=0, alg2=alg,
797 material=b'L',
798 description='alg2: ' + descr)
799 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100800
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200801 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100802 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200803 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200804 for alg in self.constructors.generate_expressions(algorithms):
805 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100806
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200807 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200808 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200809 yield from self.all_keys_for_lifetimes()
810 yield from self.all_keys_for_usage_flags()
811 yield from self.all_keys_for_types()
812 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200813
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200814 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100815 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200816 # First build a list of all keys, then construct all the corresponding
817 # test cases. This allows all required information to be obtained in
818 # one go, which is a significant performance gain as the information
819 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200820 all_keys = list(self.generate_all_keys())
821 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200822 if key.location_value() != 0:
823 # Skip keys with a non-default location, because they
824 # require a driver and we currently have no mechanism to
825 # determine whether a driver is available.
826 continue
827 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100828
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200829class StorageFormatForward(StorageFormat):
830 """Storage format stability test cases for forward compatibility."""
831
832 def __init__(self, info: Information, version: int) -> None:
833 super().__init__(info, version, True)
834
835class StorageFormatV0(StorageFormat):
836 """Storage format stability test cases for version 0 compatibility."""
837
838 def __init__(self, info: Information) -> None:
839 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100840
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200841 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200842 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100843 yield from super().all_keys_for_usage_flags()
844 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200845
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200846 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200847 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200848 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200849 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200850 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200851 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200852 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200853 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200854 algorithm and key type combination.
855 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200856 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200857 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100858 usage_flags = ['PSA_KEY_USAGE_EXPORT']
859 material_usage_flags = usage_flags + [implyer_usage]
860 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200861 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200862 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100863 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
864 alg_expression = crypto_knowledge.short_expression(alg, 1)
865 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200866 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200867 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200868 key = StorageTestData(version=self.version,
869 id=1, lifetime=0x00000001,
870 type=key_type.expression, bits=bits,
871 usage=material_usage_flags,
872 expected_usage=expected_usage_flags,
873 without_implicit_usage=True,
874 alg=alg, alg2=alg2,
875 material=key_material,
876 description=description)
877 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200878
879 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200880 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200881 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800882 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200883 # must be filtered. Pair them with keywords created from its names.
884 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
885 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
886 keyword_translation = {
887 'ECDSA': 'ECC',
888 'ED[0-9]*.*' : 'EDWARDS'
889 }
890 exclusive_keywords = {
891 'EDWARDS': 'ECC'
892 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200893 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
894 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200895 alg_with_keys = {} #type: Dict[str, List[str]]
896 translation_table = str.maketrans('(', '_', ')')
897 for alg in algorithms:
898 # Generate keywords from the name of the algorithm
899 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
900 # Translate keywords for better matching with the key types
901 for keyword in alg_keywords.copy():
902 for pattern, replace in keyword_translation.items():
903 if re.match(pattern, keyword):
904 alg_keywords.remove(keyword)
905 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800906 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200907 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
908 continue
909
910 for key_type in key_types:
911 # Generate keywords from the of the key type
912 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
913
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800914 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200915 for keyword1, keyword2 in exclusive_keywords.items():
916 if keyword1 in key_type_keywords:
917 key_type_keywords.remove(keyword2)
918
919 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
920 not key_type_keywords.isdisjoint(alg_keywords):
921 if alg in alg_with_keys:
922 alg_with_keys[alg].append(key_type)
923 else:
924 alg_with_keys[alg] = [key_type]
925 return alg_with_keys
926
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200927 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200928 """Generate test keys for usage flag extensions."""
929 # Generate a key type and algorithm pair for each extendable usage
930 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800931 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200932 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200933
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200934 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
935 for alg in sorted(alg_with_keys):
936 for key_type in sorted(alg_with_keys[alg]):
937 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200938 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100939 if kt.is_public() and '_SIGN_' in usage:
940 # Can't sign with a public key
941 continue
942 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200943
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200944 def generate_all_keys(self) -> Iterator[StorageTestData]:
945 yield from super().generate_all_keys()
946 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200947
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200948class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100949 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100950 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200951 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100952 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200953 'test_suite_psa_crypto_generate_key.generated':
954 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100955 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100956 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200957 'test_suite_psa_crypto_op_fail.generated':
958 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100959 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200960 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100961 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200962 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100963 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
964
Werner Lewisfbb75e32022-08-24 11:30:03 +0100965 def __init__(self, options):
966 super().__init__(options)
967 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100968
Werner Lewisfbb75e32022-08-24 11:30:03 +0100969 def generate_target(self, name: str, *target_args) -> None:
970 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100971
972if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200973 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)