blob: 851fe661a0e36758f609814674df4c4c452693ac [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 +0200118# This helper function add the proper suffix to PSA_WANT_KEY_TYPE_xxx_KEY_PAIR
119# symbols according to the required usage.
120def tweak_key_pair_dependency(dep: str, usage: str):
121 ret_list = list()
Valerio Setti24c64e82023-06-26 11:49:02 +0200122 # Note: this LEGACY replacement for RSA is temporary and it's going to be
123 # aligned with ECC one in #7772.
Valerio Setti0c42c432023-06-29 12:10:52 +0200124 if dep.endswith('RSA_KEY_PAIR'):
125 ret_list.append(re.sub(r'RSA_KEY_PAIR\Z', r'RSA_KEY_PAIR_LEGACY', dep))
126 elif dep.endswith('ECC_KEY_PAIR'):
127 if usage == "BASIC":
128 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
129 # config_psa.h).
130 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_BASIC', dep))
131 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_IMPORT', dep))
132 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_EXPORT', dep))
133 elif usage == "GENERATE":
134 ret_list.append(re.sub(r'ECC_KEY_PAIR', r'ECC_KEY_PAIR_GENERATE', dep))
135 else:
136 # No replacement to do in this case
137 ret_list.append(dep)
138 return ret_list
139
140def fix_key_pair_dependencies(dep_list: List[str], usage: str):
141 new_list = [new_deps
142 for dep in dep_list
143 for new_deps in tweak_key_pair_dependency(dep, usage)]
144
Valerio Setti24c64e82023-06-26 11:49:02 +0200145 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100146
Gilles Peskineb94ea512021-03-10 02:12:08 +0100147class Information:
148 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100149
Gilles Peskineb94ea512021-03-10 02:12:08 +0100150 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100151 self.constructors = self.read_psa_interface()
152
153 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100154 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200155 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100156 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200157 # Mbed TLS doesn't support finite-field DH yet and will not support
158 # finite-field DSA. Don't attempt to generate any related test case.
159 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
160 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100161 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
162 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100163
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200164 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100165 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200166 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100167 header_file_names = ['include/psa/crypto_values.h',
168 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200169 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100170 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200171 constructors.parse_header(header_file_name)
172 for test_cases in test_suites:
173 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100174 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200175 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100176 return constructors
177
Gilles Peskine14e428f2021-01-26 22:19:21 +0100178
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200179def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100180 verb: str, key_type: str, bits: int,
181 dependencies: List[str],
182 *args: str,
183 param_descr: str = ''
184) -> test_case.TestCase:
185 """Return one test case exercising a key creation method
186 for an unsupported key type or size.
187 """
188 hack_dependencies_not_implemented(dependencies)
189 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100190 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100191 adverb = 'not' if dependencies else 'never'
192 if param_descr:
193 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200194 tc.set_description('PSA {} {} {}-bit {} supported'
195 .format(verb, short_key_type, bits, adverb))
196 tc.set_dependencies(dependencies)
197 tc.set_function(verb + '_not_supported')
198 tc.set_arguments([key_type] + list(args))
199 return tc
200
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100201class KeyTypeNotSupported:
202 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100203
204 def __init__(self, info: Information) -> None:
205 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100206
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100207 ALWAYS_SUPPORTED = frozenset([
208 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100209 'PSA_KEY_TYPE_PASSWORD',
210 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100211 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200212 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100213 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100215 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100216 kt: crypto_knowledge.KeyType,
217 param: Optional[int] = None,
218 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100219 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200220 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100221
222 If param is present and not None, emit test cases conditioned on this
223 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200224 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100226 if kt.name in self.ALWAYS_SUPPORTED:
227 # Don't generate test cases for key types that are always supported.
228 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 import_dependencies = [('!' if param is None else '') +
231 psa_want_symbol(kt.name)]
232 if kt.params is not None:
233 import_dependencies += [('!' if param == i else '') +
234 psa_want_symbol(sym)
235 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100236 if kt.name.endswith('_PUBLIC_KEY'):
237 generate_dependencies = []
238 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200239 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200240 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100241 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200242 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100243 'import', kt.expression, bits,
244 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100245 test_case.hex_string(kt.key_material(bits)),
246 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100247 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100248 if not generate_dependencies and param is not None:
249 # If generation is impossible for this key type, rather than
250 # supported or not depending on implementation capabilities,
251 # only generate the test case once.
252 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100253 # For public key we expect that key generation fails with
254 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100255 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200256 yield test_case_for_key_type_not_supported(
257 'generate', kt.expression, bits,
258 finish_family_dependencies(generate_dependencies, bits),
259 str(bits),
260 param_descr=param_descr,
261 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100262 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100263
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200264 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
265 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
266
Gilles Peskine3d778392021-02-17 15:11:05 +0100267 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100268 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100269 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200270 if key_type in self.ECC_KEY_TYPES:
271 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100272 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100273 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100274 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200275 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100276 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100277 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100278 kt, param_descr='type')
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, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100281
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200282def test_case_for_key_generation(
283 key_type: str, bits: int,
284 dependencies: List[str],
285 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200286 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200287) -> test_case.TestCase:
288 """Return one test case exercising a key generation.
289 """
290 hack_dependencies_not_implemented(dependencies)
291 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100292 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200293 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200294 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200295 tc.set_dependencies(dependencies)
296 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100297 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200298
299 return tc
300
301class KeyGenerate:
302 """Generate positive and negative (invalid argument) test cases for key generation."""
303
304 def __init__(self, info: Information) -> None:
305 self.constructors = info.constructors
306
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200307 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
308 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
309
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100310 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200311 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200312 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313 ) -> Iterator[test_case.TestCase]:
314 """Return test cases exercising key generation.
315
316 All key types can be generated except for public keys. For public key
317 PSA_ERROR_INVALID_ARGUMENT status is expected.
318 """
319 result = 'PSA_SUCCESS'
320
321 import_dependencies = [psa_want_symbol(kt.name)]
322 if kt.params is not None:
323 import_dependencies += [psa_want_symbol(sym)
324 for i, sym in enumerate(kt.params)]
325 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100326 # The library checks whether the key type is a public key generically,
327 # before it reaches a point where it needs support for the specific key
328 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200329 generate_dependencies = []
330 result = 'PSA_ERROR_INVALID_ARGUMENT'
331 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200332 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200333 for bits in kt.sizes_to_test():
334 yield test_case_for_key_generation(
335 kt.expression, bits,
336 finish_family_dependencies(generate_dependencies, bits),
337 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200338 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200339 )
340
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200341 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
342 """Generate test cases that exercise the generation of keys."""
343 for key_type in sorted(self.constructors.key_types):
344 if key_type in self.ECC_KEY_TYPES:
345 continue
346 kt = crypto_knowledge.KeyType(key_type)
347 yield from self.test_cases_for_key_type_key_generation(kt)
348 for curve_family in sorted(self.constructors.ecc_curves):
349 for constr in self.ECC_KEY_TYPES:
350 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200351 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200352
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200353class OpFail:
354 """Generate test cases for operations that must fail."""
355 #pylint: disable=too-few-public-methods
356
Gilles Peskinecba28a72022-03-15 17:26:33 +0100357 class Reason(enum.Enum):
358 NOT_SUPPORTED = 0
359 INVALID = 1
360 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200361 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100362
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200363 def __init__(self, info: Information) -> None:
364 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100365 key_type_expressions = self.constructors.generate_expressions(
366 sorted(self.constructors.key_types)
367 )
368 self.key_types = [crypto_knowledge.KeyType(kt_expr)
369 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200370
Gilles Peskinecba28a72022-03-15 17:26:33 +0100371 def make_test_case(
372 self,
373 alg: crypto_knowledge.Algorithm,
374 category: crypto_knowledge.AlgorithmCategory,
375 reason: 'Reason',
376 kt: Optional[crypto_knowledge.KeyType] = None,
377 not_deps: FrozenSet[str] = frozenset(),
378 ) -> test_case.TestCase:
379 """Construct a failure test case for a one-key or keyless operation."""
380 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200381 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100382 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200383 if reason == self.Reason.NOT_SUPPORTED:
384 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
385 for dep in not_deps]
386 pretty_reason = '!' + '&'.join(sorted(short_deps))
387 else:
388 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100389 if kt:
390 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100391 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200392 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100393 key_type = ''
394 pretty_type = ''
395 tc.set_description('PSA {} {}: {}{}'
396 .format(category.name.lower(),
397 pretty_alg,
398 pretty_reason,
399 ' with ' + pretty_type if pretty_type else ''))
400 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200401 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100402 for i, dep in enumerate(dependencies):
403 if dep in not_deps:
404 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200405 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100406 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000407 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100408 if kt:
409 key_material = kt.key_material(kt.sizes_to_test()[0])
410 arguments += [key_type, test_case.hex_string(key_material)]
411 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200412 if category.is_asymmetric():
413 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100414 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
415 'INVALID_ARGUMENT')
416 arguments.append('PSA_ERROR_' + error)
417 tc.set_arguments(arguments)
418 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200419
Gilles Peskinecba28a72022-03-15 17:26:33 +0100420 def no_key_test_cases(
421 self,
422 alg: crypto_knowledge.Algorithm,
423 category: crypto_knowledge.AlgorithmCategory,
424 ) -> Iterator[test_case.TestCase]:
425 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200426 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100427 # Compatible 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 not_deps=frozenset([dep]))
432 else:
433 # Incompatible operation, supported algorithm
434 yield self.make_test_case(alg, category, self.Reason.INVALID)
435
436 def one_key_test_cases(
437 self,
438 alg: crypto_knowledge.Algorithm,
439 category: crypto_knowledge.AlgorithmCategory,
440 ) -> Iterator[test_case.TestCase]:
441 """Generate failure test cases for one-key operations with the specified algorithm."""
442 for kt in self.key_types:
443 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200444 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100445 # Compatible key and operation, unsupported algorithm
446 for dep in automatic_dependencies(alg.base_expression):
447 yield self.make_test_case(alg, category,
448 self.Reason.NOT_SUPPORTED,
449 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200450 # Public key for a private-key operation
451 if category.is_asymmetric() and kt.is_public():
452 yield self.make_test_case(alg, category,
453 self.Reason.PUBLIC,
454 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100455 elif key_is_compatible:
456 # Compatible key, incompatible operation, supported algorithm
457 yield self.make_test_case(alg, category,
458 self.Reason.INVALID,
459 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200460 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100461 # Incompatible key, compatible operation, supported algorithm
462 yield self.make_test_case(alg, category,
463 self.Reason.INCOMPATIBLE,
464 kt=kt)
465 else:
466 # Incompatible key and operation. Don't test cases where
467 # multiple things are wrong, to keep the number of test
468 # cases reasonable.
469 pass
470
471 def test_cases_for_algorithm(
472 self,
473 alg: crypto_knowledge.Algorithm,
474 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200475 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100476 for category in crypto_knowledge.AlgorithmCategory:
477 if category == crypto_knowledge.AlgorithmCategory.PAKE:
478 # PAKE operations are not implemented yet
479 pass
480 elif category.requires_key():
481 yield from self.one_key_test_cases(alg, category)
482 else:
483 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200484
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200485 def all_test_cases(self) -> Iterator[test_case.TestCase]:
486 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200487 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100488 for expr in self.constructors.generate_expressions(algorithms):
489 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200490 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200491
492
Gilles Peskine897dff92021-03-10 15:03:44 +0100493class StorageKey(psa_storage.Key):
494 """Representation of a key for storage format testing."""
495
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200496 IMPLICIT_USAGE_FLAGS = {
497 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
498 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
499 } #type: Dict[str, str]
500 """Mapping of usage flags to the flags that they imply."""
501
502 def __init__(
503 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100504 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200505 without_implicit_usage: Optional[bool] = False,
506 **kwargs
507 ) -> None:
508 """Prepare to generate a key.
509
510 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000511 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200512 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100513 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200514 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100515 for flag in sorted(usage_flags):
516 if flag in self.IMPLICIT_USAGE_FLAGS:
517 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
518 if usage_flags:
519 usage_expression = ' | '.join(sorted(usage_flags))
520 else:
521 usage_expression = '0'
522 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200523
524class StorageTestData(StorageKey):
525 """Representation of test case data for storage format testing."""
526
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200527 def __init__(
528 self,
529 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100530 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200531 **kwargs
532 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200533 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200534
Tom Cosgrove1797b052022-12-04 17:19:59 +0000535 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200536 * `expected_usage`: the usage flags generated as the expected usage flags
537 in the test cases. CAn differ from the usage flags
538 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200539 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100540 super().__init__(**kwargs)
541 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100542 if expected_usage is None:
543 self.expected_usage = self.usage #type: psa_storage.Expr
544 elif expected_usage:
545 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
546 else:
547 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200548
Gilles Peskine897dff92021-03-10 15:03:44 +0100549class StorageFormat:
550 """Storage format stability test cases."""
551
552 def __init__(self, info: Information, version: int, forward: bool) -> None:
553 """Prepare to generate test cases for storage format stability.
554
555 * `info`: information about the API. See the `Information` class.
556 * `version`: the storage format version to generate test cases for.
557 * `forward`: if true, generate forward compatibility test cases which
558 save a key and check that its representation is as intended. Otherwise
559 generate backward compatibility test cases which inject a key
560 representation and check that it can be read and used.
561 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200562 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
563 self.version = version #type: int
564 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100565
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100566 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100567 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100568 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100569 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100570 cls,
571 key_type: psa_storage.Expr, bits: int,
572 alg: psa_storage.Expr
573 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100574 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100575
576 Normally only the type and algorithm matter for compatibility, and
577 this is handled in crypto_knowledge.KeyType.can_do(). This function
578 exists to detect exceptional cases. Exceptional cases detected here
579 are not tested in OpFail and should therefore have manually written
580 test cases.
581 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100582 # Some test keys have the RAW_DATA type and attributes that don't
583 # necessarily make sense. We do this to validate numerical
584 # encodings of the attributes.
585 # Raw data keys have no useful exercise anyway so there is no
586 # loss of test coverage.
587 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
588 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100589 # OAEP requires room for two hashes plus wrapping
590 m = cls.RSA_OAEP_RE.match(alg.string)
591 if m:
592 hash_alg = m.group(1)
593 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
594 key_length = (bits + 7) // 8
595 # Leave enough room for at least one byte of plaintext
596 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100597 # There's nothing wrong with ECC keys on Brainpool curves,
598 # but operations with them are very slow. So we only exercise them
599 # with a single algorithm, not with all possible hashes. We do
600 # exercise other curves with all algorithms so test coverage is
601 # perfectly adequate like this.
602 m = cls.BRAINPOOL_RE.match(key_type.string)
603 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
604 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100605 return True
606
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200607 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100608 """Construct a storage format test case for the given key.
609
610 If ``forward`` is true, generate a forward compatibility test case:
611 create a key and validate that it has the expected representation.
612 Otherwise generate a backward compatibility test case: inject the
613 key representation into storage and validate that it can be read
614 correctly.
615 """
616 verb = 'save' if self.forward else 'read'
617 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100618 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100619 dependencies = automatic_dependencies(
620 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100621 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100622 )
623 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800624 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200625 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100626 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100627 tc.set_function('key_storage_' + verb)
628 if self.forward:
629 extra_arguments = []
630 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200631 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100632 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200633 flags.append('TEST_FLAG_EXERCISE')
634 if 'READ_ONLY' in key.lifetime.string:
635 flags.append('TEST_FLAG_READ_ONLY')
636 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100637 tc.set_arguments([key.lifetime.string,
638 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100639 key.expected_usage.string,
640 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100641 '"' + key.material.hex() + '"',
642 '"' + key.hex() + '"',
643 *extra_arguments])
644 return tc
645
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200646 def key_for_lifetime(
647 self,
648 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200649 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200650 """Construct a test key for the given lifetime."""
651 short = lifetime
652 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
653 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100654 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200655 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200656 key = StorageTestData(version=self.version,
657 id=1, lifetime=lifetime,
658 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100659 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200660 material=b'L',
661 description=description)
662 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200663
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200664 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200665 """Generate test keys covering lifetimes."""
666 lifetimes = sorted(self.constructors.lifetimes)
667 expressions = self.constructors.generate_expressions(lifetimes)
668 for lifetime in expressions:
669 # Don't attempt to create or load a volatile key in storage
670 if 'VOLATILE' in lifetime:
671 continue
672 # Don't attempt to create a read-only key in storage,
673 # but do attempt to load one.
674 if 'READ_ONLY' in lifetime and self.forward:
675 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200676 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200677
Gilles Peskinef7614272022-02-24 18:58:08 +0100678 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100679 self,
680 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200681 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100682 test_implicit_usage: Optional[bool] = True
683 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100684 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100685 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100686 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200687 key1 = StorageTestData(version=self.version,
688 id=1, lifetime=0x00000001,
689 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100690 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100691 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100692 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200693 material=b'K',
694 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100695 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100696 usage_expr = key1.expected_usage.string
697 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100698 else:
699 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100700 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100701
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200702 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100703 """Generate test keys covering usage flags."""
704 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200706 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100707 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200708 for flag1, flag2 in zip(known_flags,
709 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100710 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200711
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200712 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200713 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100714 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200715
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200716 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200717 yield from self.generate_keys_for_usage_flags()
718 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100719
Gilles Peskine7de7c102021-04-29 22:28:07 +0200720 def key_for_type_and_alg(
721 self,
722 kt: crypto_knowledge.KeyType,
723 bits: int,
724 alg: Optional[crypto_knowledge.Algorithm] = None,
725 ) -> StorageTestData:
726 """Construct a test key of the given type.
727
728 If alg is not None, this key allows it.
729 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100730 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100731 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200732 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100733 if alg is not None:
734 alg1 = alg.expression
735 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200736 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100737 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200738 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100739 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200740 key = StorageTestData(version=self.version,
741 id=1, lifetime=0x00000001,
742 type=kt.expression, bits=bits,
743 usage=usage_flags, alg=alg1, alg2=alg2,
744 material=key_material,
745 description=description)
746 return key
747
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100748 def keys_for_type(
749 self,
750 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200752 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200753 """Generate test keys for the given key type."""
754 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100755 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200756 # Test a non-exercisable key, as well as exercisable keys for
757 # each compatible algorithm.
758 # To do: test reading a key from storage with an incompatible
759 # or unsupported algorithm.
760 yield self.key_for_type_and_alg(kt, bits)
761 compatible_algorithms = [alg for alg in all_algorithms
762 if kt.can_do(alg)]
763 for alg in compatible_algorithms:
764 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100765
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200766 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100767 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200768 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200769 all_algorithms = [crypto_knowledge.Algorithm(alg)
770 for alg in self.constructors.generate_expressions(
771 sorted(self.constructors.algorithms)
772 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200773 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200774 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100775
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200776 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200777 """Generate test keys for the encoding of the specified algorithm."""
778 # These test cases only validate the encoding of algorithms, not
779 # whether the key read from storage is suitable for an operation.
780 # `keys_for_types` generate read tests with an algorithm and a
781 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100782 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100783 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200784 key1 = StorageTestData(version=self.version,
785 id=1, lifetime=0x00000001,
786 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
787 usage=usage, alg=alg, alg2=0,
788 material=b'K',
789 description='alg: ' + descr)
790 yield key1
791 key2 = StorageTestData(version=self.version,
792 id=1, lifetime=0x00000001,
793 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
794 usage=usage, alg=0, alg2=alg,
795 material=b'L',
796 description='alg2: ' + descr)
797 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100798
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200799 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100800 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200801 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200802 for alg in self.constructors.generate_expressions(algorithms):
803 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100804
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200805 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200806 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200807 yield from self.all_keys_for_lifetimes()
808 yield from self.all_keys_for_usage_flags()
809 yield from self.all_keys_for_types()
810 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200811
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200812 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100813 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200814 # First build a list of all keys, then construct all the corresponding
815 # test cases. This allows all required information to be obtained in
816 # one go, which is a significant performance gain as the information
817 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200818 all_keys = list(self.generate_all_keys())
819 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200820 if key.location_value() != 0:
821 # Skip keys with a non-default location, because they
822 # require a driver and we currently have no mechanism to
823 # determine whether a driver is available.
824 continue
825 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100826
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200827class StorageFormatForward(StorageFormat):
828 """Storage format stability test cases for forward compatibility."""
829
830 def __init__(self, info: Information, version: int) -> None:
831 super().__init__(info, version, True)
832
833class StorageFormatV0(StorageFormat):
834 """Storage format stability test cases for version 0 compatibility."""
835
836 def __init__(self, info: Information) -> None:
837 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100838
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200839 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200840 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100841 yield from super().all_keys_for_usage_flags()
842 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200843
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200844 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200845 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200846 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200847 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200848 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200849 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200850 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200851 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200852 algorithm and key type combination.
853 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200854 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200855 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100856 usage_flags = ['PSA_KEY_USAGE_EXPORT']
857 material_usage_flags = usage_flags + [implyer_usage]
858 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200859 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200860 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100861 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
862 alg_expression = crypto_knowledge.short_expression(alg, 1)
863 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200864 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200865 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200866 key = StorageTestData(version=self.version,
867 id=1, lifetime=0x00000001,
868 type=key_type.expression, bits=bits,
869 usage=material_usage_flags,
870 expected_usage=expected_usage_flags,
871 without_implicit_usage=True,
872 alg=alg, alg2=alg2,
873 material=key_material,
874 description=description)
875 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200876
877 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200878 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200879 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800880 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200881 # must be filtered. Pair them with keywords created from its names.
882 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
883 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
884 keyword_translation = {
885 'ECDSA': 'ECC',
886 'ED[0-9]*.*' : 'EDWARDS'
887 }
888 exclusive_keywords = {
889 'EDWARDS': 'ECC'
890 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200891 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
892 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200893 alg_with_keys = {} #type: Dict[str, List[str]]
894 translation_table = str.maketrans('(', '_', ')')
895 for alg in algorithms:
896 # Generate keywords from the name of the algorithm
897 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
898 # Translate keywords for better matching with the key types
899 for keyword in alg_keywords.copy():
900 for pattern, replace in keyword_translation.items():
901 if re.match(pattern, keyword):
902 alg_keywords.remove(keyword)
903 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800904 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200905 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
906 continue
907
908 for key_type in key_types:
909 # Generate keywords from the of the key type
910 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
911
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800912 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200913 for keyword1, keyword2 in exclusive_keywords.items():
914 if keyword1 in key_type_keywords:
915 key_type_keywords.remove(keyword2)
916
917 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
918 not key_type_keywords.isdisjoint(alg_keywords):
919 if alg in alg_with_keys:
920 alg_with_keys[alg].append(key_type)
921 else:
922 alg_with_keys[alg] = [key_type]
923 return alg_with_keys
924
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200925 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200926 """Generate test keys for usage flag extensions."""
927 # Generate a key type and algorithm pair for each extendable usage
928 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800929 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200930 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200931
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200932 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
933 for alg in sorted(alg_with_keys):
934 for key_type in sorted(alg_with_keys[alg]):
935 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200936 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100937 if kt.is_public() and '_SIGN_' in usage:
938 # Can't sign with a public key
939 continue
940 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200941
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200942 def generate_all_keys(self) -> Iterator[StorageTestData]:
943 yield from super().generate_all_keys()
944 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200945
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200946class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100947 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100948 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200949 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100950 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200951 'test_suite_psa_crypto_generate_key.generated':
952 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100953 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100954 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200955 'test_suite_psa_crypto_op_fail.generated':
956 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100957 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200958 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100959 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200960 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100961 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
962
Werner Lewisfbb75e32022-08-24 11:30:03 +0100963 def __init__(self, options):
964 super().__init__(options)
965 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100966
Werner Lewisfbb75e32022-08-24 11:30:03 +0100967 def generate_target(self, name: str, *target_args) -> None:
968 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100969
970if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200971 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)