blob: 993457872f0e20bc7c32c054fda26720f26de68a [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
Manuel Pégourié-Gonnardc154a042023-07-18 11:01:14 +0200114 not dep.lstrip('!').startswith('PSA_WANT'))
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 Setti3a962272023-07-27 11:01:33 +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():
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000343 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Waleed Elmelegyd7bdbbe2023-07-20 16:26:58 +0000344 size_dependency = "PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS <= " + str(bits)
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000345 test_dependencies = generate_dependencies + [size_dependency]
346 else:
347 test_dependencies = generate_dependencies
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200348 yield test_case_for_key_generation(
349 kt.expression, bits,
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000350 finish_family_dependencies(test_dependencies, bits),
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200351 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200352 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200353 )
354
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200355 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
356 """Generate test cases that exercise the generation of keys."""
357 for key_type in sorted(self.constructors.key_types):
358 if key_type in self.ECC_KEY_TYPES:
359 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200360 if key_type in self.DH_KEY_TYPES:
361 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200362 kt = crypto_knowledge.KeyType(key_type)
363 yield from self.test_cases_for_key_type_key_generation(kt)
364 for curve_family in sorted(self.constructors.ecc_curves):
365 for constr in self.ECC_KEY_TYPES:
366 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200367 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200368 for dh_family in sorted(self.constructors.dh_groups):
369 for constr in self.DH_KEY_TYPES:
370 kt = crypto_knowledge.KeyType(constr, [dh_family])
371 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200372
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200373class OpFail:
374 """Generate test cases for operations that must fail."""
375 #pylint: disable=too-few-public-methods
376
Gilles Peskinecba28a72022-03-15 17:26:33 +0100377 class Reason(enum.Enum):
378 NOT_SUPPORTED = 0
379 INVALID = 1
380 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200381 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100382
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200383 def __init__(self, info: Information) -> None:
384 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100385 key_type_expressions = self.constructors.generate_expressions(
386 sorted(self.constructors.key_types)
387 )
388 self.key_types = [crypto_knowledge.KeyType(kt_expr)
389 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200390
Gilles Peskinecba28a72022-03-15 17:26:33 +0100391 def make_test_case(
392 self,
393 alg: crypto_knowledge.Algorithm,
394 category: crypto_knowledge.AlgorithmCategory,
395 reason: 'Reason',
396 kt: Optional[crypto_knowledge.KeyType] = None,
397 not_deps: FrozenSet[str] = frozenset(),
398 ) -> test_case.TestCase:
399 """Construct a failure test case for a one-key or keyless operation."""
400 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200401 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100402 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200403 if reason == self.Reason.NOT_SUPPORTED:
404 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
405 for dep in not_deps]
406 pretty_reason = '!' + '&'.join(sorted(short_deps))
407 else:
408 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100409 if kt:
410 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100411 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200412 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100413 key_type = ''
414 pretty_type = ''
415 tc.set_description('PSA {} {}: {}{}'
416 .format(category.name.lower(),
417 pretty_alg,
418 pretty_reason,
419 ' with ' + pretty_type if pretty_type else ''))
420 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200421 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100422 for i, dep in enumerate(dependencies):
423 if dep in not_deps:
424 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200425 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100426 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000427 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100428 if kt:
429 key_material = kt.key_material(kt.sizes_to_test()[0])
430 arguments += [key_type, test_case.hex_string(key_material)]
431 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200432 if category.is_asymmetric():
433 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100434 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
435 'INVALID_ARGUMENT')
436 arguments.append('PSA_ERROR_' + error)
437 tc.set_arguments(arguments)
438 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200439
Gilles Peskinecba28a72022-03-15 17:26:33 +0100440 def no_key_test_cases(
441 self,
442 alg: crypto_knowledge.Algorithm,
443 category: crypto_knowledge.AlgorithmCategory,
444 ) -> Iterator[test_case.TestCase]:
445 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200446 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100447 # Compatible operation, unsupported algorithm
448 for dep in automatic_dependencies(alg.base_expression):
449 yield self.make_test_case(alg, category,
450 self.Reason.NOT_SUPPORTED,
451 not_deps=frozenset([dep]))
452 else:
453 # Incompatible operation, supported algorithm
454 yield self.make_test_case(alg, category, self.Reason.INVALID)
455
456 def one_key_test_cases(
457 self,
458 alg: crypto_knowledge.Algorithm,
459 category: crypto_knowledge.AlgorithmCategory,
460 ) -> Iterator[test_case.TestCase]:
461 """Generate failure test cases for one-key operations with the specified algorithm."""
462 for kt in self.key_types:
463 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200464 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100465 # Compatible key and operation, unsupported algorithm
466 for dep in automatic_dependencies(alg.base_expression):
467 yield self.make_test_case(alg, category,
468 self.Reason.NOT_SUPPORTED,
469 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200470 # Public key for a private-key operation
471 if category.is_asymmetric() and kt.is_public():
472 yield self.make_test_case(alg, category,
473 self.Reason.PUBLIC,
474 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100475 elif key_is_compatible:
476 # Compatible key, incompatible operation, supported algorithm
477 yield self.make_test_case(alg, category,
478 self.Reason.INVALID,
479 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200480 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100481 # Incompatible key, compatible operation, supported algorithm
482 yield self.make_test_case(alg, category,
483 self.Reason.INCOMPATIBLE,
484 kt=kt)
485 else:
486 # Incompatible key and operation. Don't test cases where
487 # multiple things are wrong, to keep the number of test
488 # cases reasonable.
489 pass
490
491 def test_cases_for_algorithm(
492 self,
493 alg: crypto_knowledge.Algorithm,
494 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200495 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100496 for category in crypto_knowledge.AlgorithmCategory:
497 if category == crypto_knowledge.AlgorithmCategory.PAKE:
498 # PAKE operations are not implemented yet
499 pass
500 elif category.requires_key():
501 yield from self.one_key_test_cases(alg, category)
502 else:
503 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200504
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200505 def all_test_cases(self) -> Iterator[test_case.TestCase]:
506 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200507 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100508 for expr in self.constructors.generate_expressions(algorithms):
509 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200510 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200511
512
Gilles Peskine897dff92021-03-10 15:03:44 +0100513class StorageKey(psa_storage.Key):
514 """Representation of a key for storage format testing."""
515
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200516 IMPLICIT_USAGE_FLAGS = {
517 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
518 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
519 } #type: Dict[str, str]
520 """Mapping of usage flags to the flags that they imply."""
521
522 def __init__(
523 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100524 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200525 without_implicit_usage: Optional[bool] = False,
526 **kwargs
527 ) -> None:
528 """Prepare to generate a key.
529
530 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000531 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200532 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100533 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200534 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100535 for flag in sorted(usage_flags):
536 if flag in self.IMPLICIT_USAGE_FLAGS:
537 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
538 if usage_flags:
539 usage_expression = ' | '.join(sorted(usage_flags))
540 else:
541 usage_expression = '0'
542 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200543
544class StorageTestData(StorageKey):
545 """Representation of test case data for storage format testing."""
546
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200547 def __init__(
548 self,
549 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100550 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200551 **kwargs
552 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200553 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200554
Tom Cosgrove1797b052022-12-04 17:19:59 +0000555 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200556 * `expected_usage`: the usage flags generated as the expected usage flags
557 in the test cases. CAn differ from the usage flags
558 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200559 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100560 super().__init__(**kwargs)
561 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100562 if expected_usage is None:
563 self.expected_usage = self.usage #type: psa_storage.Expr
564 elif expected_usage:
565 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
566 else:
567 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200568
Gilles Peskine897dff92021-03-10 15:03:44 +0100569class StorageFormat:
570 """Storage format stability test cases."""
571
572 def __init__(self, info: Information, version: int, forward: bool) -> None:
573 """Prepare to generate test cases for storage format stability.
574
575 * `info`: information about the API. See the `Information` class.
576 * `version`: the storage format version to generate test cases for.
577 * `forward`: if true, generate forward compatibility test cases which
578 save a key and check that its representation is as intended. Otherwise
579 generate backward compatibility test cases which inject a key
580 representation and check that it can be read and used.
581 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200582 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
583 self.version = version #type: int
584 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100585
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100586 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100587 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100588 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100589 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100590 cls,
591 key_type: psa_storage.Expr, bits: int,
592 alg: psa_storage.Expr
593 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100594 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100595
596 Normally only the type and algorithm matter for compatibility, and
597 this is handled in crypto_knowledge.KeyType.can_do(). This function
598 exists to detect exceptional cases. Exceptional cases detected here
599 are not tested in OpFail and should therefore have manually written
600 test cases.
601 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100602 # Some test keys have the RAW_DATA type and attributes that don't
603 # necessarily make sense. We do this to validate numerical
604 # encodings of the attributes.
605 # Raw data keys have no useful exercise anyway so there is no
606 # loss of test coverage.
607 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
608 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100609 # OAEP requires room for two hashes plus wrapping
610 m = cls.RSA_OAEP_RE.match(alg.string)
611 if m:
612 hash_alg = m.group(1)
613 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
614 key_length = (bits + 7) // 8
615 # Leave enough room for at least one byte of plaintext
616 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100617 # There's nothing wrong with ECC keys on Brainpool curves,
618 # but operations with them are very slow. So we only exercise them
619 # with a single algorithm, not with all possible hashes. We do
620 # exercise other curves with all algorithms so test coverage is
621 # perfectly adequate like this.
622 m = cls.BRAINPOOL_RE.match(key_type.string)
623 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
624 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100625 return True
626
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200627 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100628 """Construct a storage format test case for the given key.
629
630 If ``forward`` is true, generate a forward compatibility test case:
631 create a key and validate that it has the expected representation.
632 Otherwise generate a backward compatibility test case: inject the
633 key representation into storage and validate that it can be read
634 correctly.
635 """
636 verb = 'save' if self.forward else 'read'
637 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100638 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100639 dependencies = automatic_dependencies(
640 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100641 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100642 )
643 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800644 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200645 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100646 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100647 tc.set_function('key_storage_' + verb)
648 if self.forward:
649 extra_arguments = []
650 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200651 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100652 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200653 flags.append('TEST_FLAG_EXERCISE')
654 if 'READ_ONLY' in key.lifetime.string:
655 flags.append('TEST_FLAG_READ_ONLY')
656 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100657 tc.set_arguments([key.lifetime.string,
658 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100659 key.expected_usage.string,
660 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100661 '"' + key.material.hex() + '"',
662 '"' + key.hex() + '"',
663 *extra_arguments])
664 return tc
665
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200666 def key_for_lifetime(
667 self,
668 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200669 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200670 """Construct a test key for the given lifetime."""
671 short = lifetime
672 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
673 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100674 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200675 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200676 key = StorageTestData(version=self.version,
677 id=1, lifetime=lifetime,
678 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100679 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200680 material=b'L',
681 description=description)
682 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200683
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200684 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200685 """Generate test keys covering lifetimes."""
686 lifetimes = sorted(self.constructors.lifetimes)
687 expressions = self.constructors.generate_expressions(lifetimes)
688 for lifetime in expressions:
689 # Don't attempt to create or load a volatile key in storage
690 if 'VOLATILE' in lifetime:
691 continue
692 # Don't attempt to create a read-only key in storage,
693 # but do attempt to load one.
694 if 'READ_ONLY' in lifetime and self.forward:
695 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200696 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200697
Gilles Peskinef7614272022-02-24 18:58:08 +0100698 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100699 self,
700 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200701 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100702 test_implicit_usage: Optional[bool] = True
703 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100704 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100706 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200707 key1 = StorageTestData(version=self.version,
708 id=1, lifetime=0x00000001,
709 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100710 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100711 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100712 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200713 material=b'K',
714 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100715 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100716 usage_expr = key1.expected_usage.string
717 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100718 else:
719 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100720 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100721
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200722 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100723 """Generate test keys covering usage flags."""
724 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100725 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200726 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100727 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200728 for flag1, flag2 in zip(known_flags,
729 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100730 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200731
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200732 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200733 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100734 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200735
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200736 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200737 yield from self.generate_keys_for_usage_flags()
738 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100739
Gilles Peskine7de7c102021-04-29 22:28:07 +0200740 def key_for_type_and_alg(
741 self,
742 kt: crypto_knowledge.KeyType,
743 bits: int,
744 alg: Optional[crypto_knowledge.Algorithm] = None,
745 ) -> StorageTestData:
746 """Construct a test key of the given type.
747
748 If alg is not None, this key allows it.
749 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100750 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100751 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200752 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100753 if alg is not None:
754 alg1 = alg.expression
755 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200756 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100757 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200758 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100759 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200760 key = StorageTestData(version=self.version,
761 id=1, lifetime=0x00000001,
762 type=kt.expression, bits=bits,
763 usage=usage_flags, alg=alg1, alg2=alg2,
764 material=key_material,
765 description=description)
766 return key
767
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100768 def keys_for_type(
769 self,
770 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200771 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200772 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200773 """Generate test keys for the given key type."""
774 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100775 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200776 # Test a non-exercisable key, as well as exercisable keys for
777 # each compatible algorithm.
778 # To do: test reading a key from storage with an incompatible
779 # or unsupported algorithm.
780 yield self.key_for_type_and_alg(kt, bits)
781 compatible_algorithms = [alg for alg in all_algorithms
782 if kt.can_do(alg)]
783 for alg in compatible_algorithms:
784 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100785
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200786 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100787 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200788 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200789 all_algorithms = [crypto_knowledge.Algorithm(alg)
790 for alg in self.constructors.generate_expressions(
791 sorted(self.constructors.algorithms)
792 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200793 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200794 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100795
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200796 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200797 """Generate test keys for the encoding of the specified algorithm."""
798 # These test cases only validate the encoding of algorithms, not
799 # whether the key read from storage is suitable for an operation.
800 # `keys_for_types` generate read tests with an algorithm and a
801 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100802 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100803 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200804 key1 = StorageTestData(version=self.version,
805 id=1, lifetime=0x00000001,
806 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
807 usage=usage, alg=alg, alg2=0,
808 material=b'K',
809 description='alg: ' + descr)
810 yield key1
811 key2 = StorageTestData(version=self.version,
812 id=1, lifetime=0x00000001,
813 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
814 usage=usage, alg=0, alg2=alg,
815 material=b'L',
816 description='alg2: ' + descr)
817 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100818
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200819 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100820 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200821 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200822 for alg in self.constructors.generate_expressions(algorithms):
823 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100824
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200825 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200826 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200827 yield from self.all_keys_for_lifetimes()
828 yield from self.all_keys_for_usage_flags()
829 yield from self.all_keys_for_types()
830 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200831
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200832 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100833 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200834 # First build a list of all keys, then construct all the corresponding
835 # test cases. This allows all required information to be obtained in
836 # one go, which is a significant performance gain as the information
837 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200838 all_keys = list(self.generate_all_keys())
839 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200840 if key.location_value() != 0:
841 # Skip keys with a non-default location, because they
842 # require a driver and we currently have no mechanism to
843 # determine whether a driver is available.
844 continue
845 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100846
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200847class StorageFormatForward(StorageFormat):
848 """Storage format stability test cases for forward compatibility."""
849
850 def __init__(self, info: Information, version: int) -> None:
851 super().__init__(info, version, True)
852
853class StorageFormatV0(StorageFormat):
854 """Storage format stability test cases for version 0 compatibility."""
855
856 def __init__(self, info: Information) -> None:
857 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100858
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200859 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200860 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100861 yield from super().all_keys_for_usage_flags()
862 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200863
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200864 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200865 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200866 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200867 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200868 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200869 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200870 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200871 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200872 algorithm and key type combination.
873 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200874 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200875 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100876 usage_flags = ['PSA_KEY_USAGE_EXPORT']
877 material_usage_flags = usage_flags + [implyer_usage]
878 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200879 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200880 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100881 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
882 alg_expression = crypto_knowledge.short_expression(alg, 1)
883 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200884 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200885 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200886 key = StorageTestData(version=self.version,
887 id=1, lifetime=0x00000001,
888 type=key_type.expression, bits=bits,
889 usage=material_usage_flags,
890 expected_usage=expected_usage_flags,
891 without_implicit_usage=True,
892 alg=alg, alg2=alg2,
893 material=key_material,
894 description=description)
895 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200896
897 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200898 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200899 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800900 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200901 # must be filtered. Pair them with keywords created from its names.
902 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
903 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
904 keyword_translation = {
905 'ECDSA': 'ECC',
906 'ED[0-9]*.*' : 'EDWARDS'
907 }
908 exclusive_keywords = {
909 'EDWARDS': 'ECC'
910 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200911 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
912 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200913 alg_with_keys = {} #type: Dict[str, List[str]]
914 translation_table = str.maketrans('(', '_', ')')
915 for alg in algorithms:
916 # Generate keywords from the name of the algorithm
917 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
918 # Translate keywords for better matching with the key types
919 for keyword in alg_keywords.copy():
920 for pattern, replace in keyword_translation.items():
921 if re.match(pattern, keyword):
922 alg_keywords.remove(keyword)
923 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800924 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200925 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
926 continue
927
928 for key_type in key_types:
929 # Generate keywords from the of the key type
930 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
931
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800932 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200933 for keyword1, keyword2 in exclusive_keywords.items():
934 if keyword1 in key_type_keywords:
935 key_type_keywords.remove(keyword2)
936
937 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
938 not key_type_keywords.isdisjoint(alg_keywords):
939 if alg in alg_with_keys:
940 alg_with_keys[alg].append(key_type)
941 else:
942 alg_with_keys[alg] = [key_type]
943 return alg_with_keys
944
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200945 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200946 """Generate test keys for usage flag extensions."""
947 # Generate a key type and algorithm pair for each extendable usage
948 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800949 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200950 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200951
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200952 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
953 for alg in sorted(alg_with_keys):
954 for key_type in sorted(alg_with_keys[alg]):
955 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200956 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100957 if kt.is_public() and '_SIGN_' in usage:
958 # Can't sign with a public key
959 continue
960 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200961
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200962 def generate_all_keys(self) -> Iterator[StorageTestData]:
963 yield from super().generate_all_keys()
964 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200965
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200966class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100967 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100968 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200969 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100970 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200971 'test_suite_psa_crypto_generate_key.generated':
972 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100973 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100974 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200975 'test_suite_psa_crypto_op_fail.generated':
976 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100977 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200978 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100979 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200980 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100981 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
982
Werner Lewisfbb75e32022-08-24 11:30:03 +0100983 def __init__(self, options):
984 super().__init__(options)
985 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100986
Werner Lewisfbb75e32022-08-24 11:30:03 +0100987 def generate_target(self, name: str, *target_args) -> None:
988 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100989
990if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200991 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)