blob: 738136cfcc17a6e71311c67ee4f5196d139a1548 [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 Setti42796e22023-07-10 12:24:34 +0200124 if dep.endswith('KEY_PAIR'):
Valerio Setti0c42c432023-06-29 12:10:52 +0200125 if usage == "BASIC":
126 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
127 # config_psa.h).
Valerio Setti42796e22023-07-10 12:24:34 +0200128 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_BASIC', dep))
129 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_IMPORT', dep))
130 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_EXPORT', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200131 elif usage == "GENERATE":
Valerio Setti42796e22023-07-10 12:24:34 +0200132 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_GENERATE', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200133 else:
134 # No replacement to do in this case
135 ret_list.append(dep)
136 return ret_list
137
138def fix_key_pair_dependencies(dep_list: List[str], usage: str):
139 new_list = [new_deps
140 for dep in dep_list
141 for new_deps in tweak_key_pair_dependency(dep, usage)]
142
Valerio Setti24c64e82023-06-26 11:49:02 +0200143 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100144
Gilles Peskineb94ea512021-03-10 02:12:08 +0100145class Information:
146 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100147
Gilles Peskineb94ea512021-03-10 02:12:08 +0100148 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100149 self.constructors = self.read_psa_interface()
150
151 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100152 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200153 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100154 ) -> None:
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200155 # Mbed TLS does not support finite-field DSA.
156 # Don't attempt to generate any related test case.
Gilles Peskine09940492021-01-26 22:16:30 +0100157 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
158 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100159
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200160 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100161 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200162 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100163 header_file_names = ['include/psa/crypto_values.h',
164 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200165 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100166 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200167 constructors.parse_header(header_file_name)
168 for test_cases in test_suites:
169 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100170 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200171 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100172 return constructors
173
Gilles Peskine14e428f2021-01-26 22:19:21 +0100174
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200175def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100176 verb: str, key_type: str, bits: int,
177 dependencies: List[str],
178 *args: str,
179 param_descr: str = ''
180) -> test_case.TestCase:
181 """Return one test case exercising a key creation method
182 for an unsupported key type or size.
183 """
184 hack_dependencies_not_implemented(dependencies)
185 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100186 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100187 adverb = 'not' if dependencies else 'never'
188 if param_descr:
189 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200190 tc.set_description('PSA {} {} {}-bit {} supported'
191 .format(verb, short_key_type, bits, adverb))
192 tc.set_dependencies(dependencies)
193 tc.set_function(verb + '_not_supported')
194 tc.set_arguments([key_type] + list(args))
195 return tc
196
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100197class KeyTypeNotSupported:
198 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100199
200 def __init__(self, info: Information) -> None:
201 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100202
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100203 ALWAYS_SUPPORTED = frozenset([
204 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100205 'PSA_KEY_TYPE_PASSWORD',
206 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100207 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200208 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100209 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100210 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100211 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100212 kt: crypto_knowledge.KeyType,
213 param: Optional[int] = None,
214 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100215 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200216 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100217
218 If param is present and not None, emit test cases conditioned on this
219 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200220 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100221 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100222 if kt.name in self.ALWAYS_SUPPORTED:
223 # Don't generate test cases for key types that are always supported.
224 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100225 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 import_dependencies = [('!' if param is None else '') +
227 psa_want_symbol(kt.name)]
228 if kt.params is not None:
229 import_dependencies += [('!' if param == i else '') +
230 psa_want_symbol(sym)
231 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100232 if kt.name.endswith('_PUBLIC_KEY'):
233 generate_dependencies = []
234 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200235 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200236 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100237 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200238 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100239 'import', kt.expression, bits,
240 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100241 test_case.hex_string(kt.key_material(bits)),
242 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100243 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100244 if not generate_dependencies and param is not None:
245 # If generation is impossible for this key type, rather than
246 # supported or not depending on implementation capabilities,
247 # only generate the test case once.
248 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100249 # For public key we expect that key generation fails with
250 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100251 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200252 yield test_case_for_key_type_not_supported(
253 'generate', kt.expression, bits,
254 finish_family_dependencies(generate_dependencies, bits),
255 str(bits),
256 param_descr=param_descr,
257 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100258 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100259
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200260 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
261 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200262 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
263 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200264
Gilles Peskine3d778392021-02-17 15:11:05 +0100265 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100266 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100267 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200268 if key_type in self.ECC_KEY_TYPES:
269 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200270 if key_type in self.DH_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')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200281 for dh_family in sorted(self.constructors.dh_groups):
282 for constr in self.DH_KEY_TYPES:
283 kt = crypto_knowledge.KeyType(constr, [dh_family])
284 yield from self.test_cases_for_key_type_not_supported(
285 kt, param_descr='type')
286 yield from self.test_cases_for_key_type_not_supported(
287 kt, 0, param_descr='group')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100288
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200289def test_case_for_key_generation(
290 key_type: str, bits: int,
291 dependencies: List[str],
292 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200293 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200294) -> test_case.TestCase:
295 """Return one test case exercising a key generation.
296 """
297 hack_dependencies_not_implemented(dependencies)
298 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100299 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200300 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200301 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200302 tc.set_dependencies(dependencies)
303 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100304 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200305
306 return tc
307
308class KeyGenerate:
309 """Generate positive and negative (invalid argument) test cases for key generation."""
310
311 def __init__(self, info: Information) -> None:
312 self.constructors = info.constructors
313
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200314 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
315 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200316 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
317 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200318
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100319 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200320 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200321 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200322 ) -> Iterator[test_case.TestCase]:
323 """Return test cases exercising key generation.
324
325 All key types can be generated except for public keys. For public key
326 PSA_ERROR_INVALID_ARGUMENT status is expected.
327 """
328 result = 'PSA_SUCCESS'
329
330 import_dependencies = [psa_want_symbol(kt.name)]
331 if kt.params is not None:
332 import_dependencies += [psa_want_symbol(sym)
333 for i, sym in enumerate(kt.params)]
334 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100335 # The library checks whether the key type is a public key generically,
336 # before it reaches a point where it needs support for the specific key
337 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200338 generate_dependencies = []
339 result = 'PSA_ERROR_INVALID_ARGUMENT'
340 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200341 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200342 for bits in kt.sizes_to_test():
343 yield test_case_for_key_generation(
344 kt.expression, bits,
345 finish_family_dependencies(generate_dependencies, bits),
346 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200347 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200348 )
349
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200350 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
351 """Generate test cases that exercise the generation of keys."""
352 for key_type in sorted(self.constructors.key_types):
353 if key_type in self.ECC_KEY_TYPES:
354 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200355 if key_type in self.DH_KEY_TYPES:
356 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200357 kt = crypto_knowledge.KeyType(key_type)
358 yield from self.test_cases_for_key_type_key_generation(kt)
359 for curve_family in sorted(self.constructors.ecc_curves):
360 for constr in self.ECC_KEY_TYPES:
361 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200362 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200363 for dh_family in sorted(self.constructors.dh_groups):
364 for constr in self.DH_KEY_TYPES:
365 kt = crypto_knowledge.KeyType(constr, [dh_family])
366 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200367
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200368class OpFail:
369 """Generate test cases for operations that must fail."""
370 #pylint: disable=too-few-public-methods
371
Gilles Peskinecba28a72022-03-15 17:26:33 +0100372 class Reason(enum.Enum):
373 NOT_SUPPORTED = 0
374 INVALID = 1
375 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200376 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100377
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200378 def __init__(self, info: Information) -> None:
379 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100380 key_type_expressions = self.constructors.generate_expressions(
381 sorted(self.constructors.key_types)
382 )
383 self.key_types = [crypto_knowledge.KeyType(kt_expr)
384 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200385
Gilles Peskinecba28a72022-03-15 17:26:33 +0100386 def make_test_case(
387 self,
388 alg: crypto_knowledge.Algorithm,
389 category: crypto_knowledge.AlgorithmCategory,
390 reason: 'Reason',
391 kt: Optional[crypto_knowledge.KeyType] = None,
392 not_deps: FrozenSet[str] = frozenset(),
393 ) -> test_case.TestCase:
394 """Construct a failure test case for a one-key or keyless operation."""
395 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200396 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100397 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200398 if reason == self.Reason.NOT_SUPPORTED:
399 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
400 for dep in not_deps]
401 pretty_reason = '!' + '&'.join(sorted(short_deps))
402 else:
403 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100404 if kt:
405 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100406 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200407 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100408 key_type = ''
409 pretty_type = ''
410 tc.set_description('PSA {} {}: {}{}'
411 .format(category.name.lower(),
412 pretty_alg,
413 pretty_reason,
414 ' with ' + pretty_type if pretty_type else ''))
415 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200416 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100417 for i, dep in enumerate(dependencies):
418 if dep in not_deps:
419 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200420 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100421 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000422 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100423 if kt:
424 key_material = kt.key_material(kt.sizes_to_test()[0])
425 arguments += [key_type, test_case.hex_string(key_material)]
426 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200427 if category.is_asymmetric():
428 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100429 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
430 'INVALID_ARGUMENT')
431 arguments.append('PSA_ERROR_' + error)
432 tc.set_arguments(arguments)
433 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200434
Gilles Peskinecba28a72022-03-15 17:26:33 +0100435 def no_key_test_cases(
436 self,
437 alg: crypto_knowledge.Algorithm,
438 category: crypto_knowledge.AlgorithmCategory,
439 ) -> Iterator[test_case.TestCase]:
440 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200441 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100442 # Compatible operation, unsupported algorithm
443 for dep in automatic_dependencies(alg.base_expression):
444 yield self.make_test_case(alg, category,
445 self.Reason.NOT_SUPPORTED,
446 not_deps=frozenset([dep]))
447 else:
448 # Incompatible operation, supported algorithm
449 yield self.make_test_case(alg, category, self.Reason.INVALID)
450
451 def one_key_test_cases(
452 self,
453 alg: crypto_knowledge.Algorithm,
454 category: crypto_knowledge.AlgorithmCategory,
455 ) -> Iterator[test_case.TestCase]:
456 """Generate failure test cases for one-key operations with the specified algorithm."""
457 for kt in self.key_types:
458 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200459 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100460 # Compatible key and operation, unsupported algorithm
461 for dep in automatic_dependencies(alg.base_expression):
462 yield self.make_test_case(alg, category,
463 self.Reason.NOT_SUPPORTED,
464 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200465 # Public key for a private-key operation
466 if category.is_asymmetric() and kt.is_public():
467 yield self.make_test_case(alg, category,
468 self.Reason.PUBLIC,
469 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100470 elif key_is_compatible:
471 # Compatible key, incompatible operation, supported algorithm
472 yield self.make_test_case(alg, category,
473 self.Reason.INVALID,
474 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200475 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100476 # Incompatible key, compatible operation, supported algorithm
477 yield self.make_test_case(alg, category,
478 self.Reason.INCOMPATIBLE,
479 kt=kt)
480 else:
481 # Incompatible key and operation. Don't test cases where
482 # multiple things are wrong, to keep the number of test
483 # cases reasonable.
484 pass
485
486 def test_cases_for_algorithm(
487 self,
488 alg: crypto_knowledge.Algorithm,
489 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200490 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100491 for category in crypto_knowledge.AlgorithmCategory:
492 if category == crypto_knowledge.AlgorithmCategory.PAKE:
493 # PAKE operations are not implemented yet
494 pass
495 elif category.requires_key():
496 yield from self.one_key_test_cases(alg, category)
497 else:
498 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200499
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200500 def all_test_cases(self) -> Iterator[test_case.TestCase]:
501 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200502 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100503 for expr in self.constructors.generate_expressions(algorithms):
504 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200505 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200506
507
Gilles Peskine897dff92021-03-10 15:03:44 +0100508class StorageKey(psa_storage.Key):
509 """Representation of a key for storage format testing."""
510
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200511 IMPLICIT_USAGE_FLAGS = {
512 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
513 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
514 } #type: Dict[str, str]
515 """Mapping of usage flags to the flags that they imply."""
516
517 def __init__(
518 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100519 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200520 without_implicit_usage: Optional[bool] = False,
521 **kwargs
522 ) -> None:
523 """Prepare to generate a key.
524
525 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000526 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200527 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100528 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200529 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100530 for flag in sorted(usage_flags):
531 if flag in self.IMPLICIT_USAGE_FLAGS:
532 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
533 if usage_flags:
534 usage_expression = ' | '.join(sorted(usage_flags))
535 else:
536 usage_expression = '0'
537 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200538
539class StorageTestData(StorageKey):
540 """Representation of test case data for storage format testing."""
541
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200542 def __init__(
543 self,
544 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100545 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200546 **kwargs
547 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200548 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200549
Tom Cosgrove1797b052022-12-04 17:19:59 +0000550 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200551 * `expected_usage`: the usage flags generated as the expected usage flags
552 in the test cases. CAn differ from the usage flags
553 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200554 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100555 super().__init__(**kwargs)
556 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100557 if expected_usage is None:
558 self.expected_usage = self.usage #type: psa_storage.Expr
559 elif expected_usage:
560 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
561 else:
562 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200563
Gilles Peskine897dff92021-03-10 15:03:44 +0100564class StorageFormat:
565 """Storage format stability test cases."""
566
567 def __init__(self, info: Information, version: int, forward: bool) -> None:
568 """Prepare to generate test cases for storage format stability.
569
570 * `info`: information about the API. See the `Information` class.
571 * `version`: the storage format version to generate test cases for.
572 * `forward`: if true, generate forward compatibility test cases which
573 save a key and check that its representation is as intended. Otherwise
574 generate backward compatibility test cases which inject a key
575 representation and check that it can be read and used.
576 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200577 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
578 self.version = version #type: int
579 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100580
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100581 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100582 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100583 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100584 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100585 cls,
586 key_type: psa_storage.Expr, bits: int,
587 alg: psa_storage.Expr
588 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100589 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100590
591 Normally only the type and algorithm matter for compatibility, and
592 this is handled in crypto_knowledge.KeyType.can_do(). This function
593 exists to detect exceptional cases. Exceptional cases detected here
594 are not tested in OpFail and should therefore have manually written
595 test cases.
596 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100597 # Some test keys have the RAW_DATA type and attributes that don't
598 # necessarily make sense. We do this to validate numerical
599 # encodings of the attributes.
600 # Raw data keys have no useful exercise anyway so there is no
601 # loss of test coverage.
602 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
603 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100604 # OAEP requires room for two hashes plus wrapping
605 m = cls.RSA_OAEP_RE.match(alg.string)
606 if m:
607 hash_alg = m.group(1)
608 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
609 key_length = (bits + 7) // 8
610 # Leave enough room for at least one byte of plaintext
611 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100612 # There's nothing wrong with ECC keys on Brainpool curves,
613 # but operations with them are very slow. So we only exercise them
614 # with a single algorithm, not with all possible hashes. We do
615 # exercise other curves with all algorithms so test coverage is
616 # perfectly adequate like this.
617 m = cls.BRAINPOOL_RE.match(key_type.string)
618 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
619 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100620 return True
621
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200622 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100623 """Construct a storage format test case for the given key.
624
625 If ``forward`` is true, generate a forward compatibility test case:
626 create a key and validate that it has the expected representation.
627 Otherwise generate a backward compatibility test case: inject the
628 key representation into storage and validate that it can be read
629 correctly.
630 """
631 verb = 'save' if self.forward else 'read'
632 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100633 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100634 dependencies = automatic_dependencies(
635 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100636 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100637 )
638 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800639 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200640 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100641 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100642 tc.set_function('key_storage_' + verb)
643 if self.forward:
644 extra_arguments = []
645 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200646 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100647 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200648 flags.append('TEST_FLAG_EXERCISE')
649 if 'READ_ONLY' in key.lifetime.string:
650 flags.append('TEST_FLAG_READ_ONLY')
651 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100652 tc.set_arguments([key.lifetime.string,
653 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100654 key.expected_usage.string,
655 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100656 '"' + key.material.hex() + '"',
657 '"' + key.hex() + '"',
658 *extra_arguments])
659 return tc
660
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200661 def key_for_lifetime(
662 self,
663 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200664 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200665 """Construct a test key for the given lifetime."""
666 short = lifetime
667 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
668 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100669 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200670 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200671 key = StorageTestData(version=self.version,
672 id=1, lifetime=lifetime,
673 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100674 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200675 material=b'L',
676 description=description)
677 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200678
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200679 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200680 """Generate test keys covering lifetimes."""
681 lifetimes = sorted(self.constructors.lifetimes)
682 expressions = self.constructors.generate_expressions(lifetimes)
683 for lifetime in expressions:
684 # Don't attempt to create or load a volatile key in storage
685 if 'VOLATILE' in lifetime:
686 continue
687 # Don't attempt to create a read-only key in storage,
688 # but do attempt to load one.
689 if 'READ_ONLY' in lifetime and self.forward:
690 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200691 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200692
Gilles Peskinef7614272022-02-24 18:58:08 +0100693 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100694 self,
695 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200696 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100697 test_implicit_usage: Optional[bool] = True
698 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100699 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100700 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100701 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200702 key1 = StorageTestData(version=self.version,
703 id=1, lifetime=0x00000001,
704 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100705 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100706 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100707 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200708 material=b'K',
709 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100710 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100711 usage_expr = key1.expected_usage.string
712 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100713 else:
714 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100715 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100716
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200717 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100718 """Generate test keys covering usage flags."""
719 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100720 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200721 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100722 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200723 for flag1, flag2 in zip(known_flags,
724 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100725 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200726
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200727 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200728 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100729 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200730
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200731 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200732 yield from self.generate_keys_for_usage_flags()
733 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100734
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 def key_for_type_and_alg(
736 self,
737 kt: crypto_knowledge.KeyType,
738 bits: int,
739 alg: Optional[crypto_knowledge.Algorithm] = None,
740 ) -> StorageTestData:
741 """Construct a test key of the given type.
742
743 If alg is not None, this key allows it.
744 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100745 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100746 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200747 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100748 if alg is not None:
749 alg1 = alg.expression
750 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200751 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100752 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200753 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100754 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200755 key = StorageTestData(version=self.version,
756 id=1, lifetime=0x00000001,
757 type=kt.expression, bits=bits,
758 usage=usage_flags, alg=alg1, alg2=alg2,
759 material=key_material,
760 description=description)
761 return key
762
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100763 def keys_for_type(
764 self,
765 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200766 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200767 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200768 """Generate test keys for the given key type."""
769 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100770 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200771 # Test a non-exercisable key, as well as exercisable keys for
772 # each compatible algorithm.
773 # To do: test reading a key from storage with an incompatible
774 # or unsupported algorithm.
775 yield self.key_for_type_and_alg(kt, bits)
776 compatible_algorithms = [alg for alg in all_algorithms
777 if kt.can_do(alg)]
778 for alg in compatible_algorithms:
779 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100780
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200781 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100782 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200783 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200784 all_algorithms = [crypto_knowledge.Algorithm(alg)
785 for alg in self.constructors.generate_expressions(
786 sorted(self.constructors.algorithms)
787 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200788 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200789 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100790
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200791 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200792 """Generate test keys for the encoding of the specified algorithm."""
793 # These test cases only validate the encoding of algorithms, not
794 # whether the key read from storage is suitable for an operation.
795 # `keys_for_types` generate read tests with an algorithm and a
796 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100797 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100798 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200799 key1 = StorageTestData(version=self.version,
800 id=1, lifetime=0x00000001,
801 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
802 usage=usage, alg=alg, alg2=0,
803 material=b'K',
804 description='alg: ' + descr)
805 yield key1
806 key2 = StorageTestData(version=self.version,
807 id=1, lifetime=0x00000001,
808 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
809 usage=usage, alg=0, alg2=alg,
810 material=b'L',
811 description='alg2: ' + descr)
812 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100813
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200814 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100815 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200816 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200817 for alg in self.constructors.generate_expressions(algorithms):
818 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100819
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200820 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200821 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200822 yield from self.all_keys_for_lifetimes()
823 yield from self.all_keys_for_usage_flags()
824 yield from self.all_keys_for_types()
825 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200826
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200827 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100828 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200829 # First build a list of all keys, then construct all the corresponding
830 # test cases. This allows all required information to be obtained in
831 # one go, which is a significant performance gain as the information
832 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200833 all_keys = list(self.generate_all_keys())
834 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200835 if key.location_value() != 0:
836 # Skip keys with a non-default location, because they
837 # require a driver and we currently have no mechanism to
838 # determine whether a driver is available.
839 continue
840 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100841
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200842class StorageFormatForward(StorageFormat):
843 """Storage format stability test cases for forward compatibility."""
844
845 def __init__(self, info: Information, version: int) -> None:
846 super().__init__(info, version, True)
847
848class StorageFormatV0(StorageFormat):
849 """Storage format stability test cases for version 0 compatibility."""
850
851 def __init__(self, info: Information) -> None:
852 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100853
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200854 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200855 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100856 yield from super().all_keys_for_usage_flags()
857 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200858
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200859 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200860 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200861 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200862 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200863 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200864 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200865 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200866 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200867 algorithm and key type combination.
868 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200869 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200870 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100871 usage_flags = ['PSA_KEY_USAGE_EXPORT']
872 material_usage_flags = usage_flags + [implyer_usage]
873 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200874 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200875 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100876 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
877 alg_expression = crypto_knowledge.short_expression(alg, 1)
878 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200879 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200880 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200881 key = StorageTestData(version=self.version,
882 id=1, lifetime=0x00000001,
883 type=key_type.expression, bits=bits,
884 usage=material_usage_flags,
885 expected_usage=expected_usage_flags,
886 without_implicit_usage=True,
887 alg=alg, alg2=alg2,
888 material=key_material,
889 description=description)
890 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200891
892 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200893 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200894 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800895 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200896 # must be filtered. Pair them with keywords created from its names.
897 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
898 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
899 keyword_translation = {
900 'ECDSA': 'ECC',
901 'ED[0-9]*.*' : 'EDWARDS'
902 }
903 exclusive_keywords = {
904 'EDWARDS': 'ECC'
905 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200906 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
907 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 alg_with_keys = {} #type: Dict[str, List[str]]
909 translation_table = str.maketrans('(', '_', ')')
910 for alg in algorithms:
911 # Generate keywords from the name of the algorithm
912 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
913 # Translate keywords for better matching with the key types
914 for keyword in alg_keywords.copy():
915 for pattern, replace in keyword_translation.items():
916 if re.match(pattern, keyword):
917 alg_keywords.remove(keyword)
918 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800919 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200920 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
921 continue
922
923 for key_type in key_types:
924 # Generate keywords from the of the key type
925 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
926
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800927 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200928 for keyword1, keyword2 in exclusive_keywords.items():
929 if keyword1 in key_type_keywords:
930 key_type_keywords.remove(keyword2)
931
932 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
933 not key_type_keywords.isdisjoint(alg_keywords):
934 if alg in alg_with_keys:
935 alg_with_keys[alg].append(key_type)
936 else:
937 alg_with_keys[alg] = [key_type]
938 return alg_with_keys
939
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200940 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200941 """Generate test keys for usage flag extensions."""
942 # Generate a key type and algorithm pair for each extendable usage
943 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800944 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200945 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200946
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200947 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
948 for alg in sorted(alg_with_keys):
949 for key_type in sorted(alg_with_keys[alg]):
950 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200951 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100952 if kt.is_public() and '_SIGN_' in usage:
953 # Can't sign with a public key
954 continue
955 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200956
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200957 def generate_all_keys(self) -> Iterator[StorageTestData]:
958 yield from super().generate_all_keys()
959 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200960
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200961class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100962 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100963 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200964 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100965 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200966 'test_suite_psa_crypto_generate_key.generated':
967 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100968 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100969 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200970 'test_suite_psa_crypto_op_fail.generated':
971 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100972 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200973 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100974 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200975 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100976 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
977
Werner Lewisfbb75e32022-08-24 11:30:03 +0100978 def __init__(self, options):
979 super().__init__(options)
980 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100981
Werner Lewisfbb75e32022-08-24 11:30:03 +0100982 def generate_target(self, name: str, *target_args) -> None:
983 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100984
985if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200986 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)