blob: 52f8edaec1d5374c01f8193c9145eab3847160a0 [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."""
Valerio Setti64f790f2023-05-26 13:50:20 +020038 # PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR symbols are deprecated and they should
39 # be replaced soon with newer PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR_yyy in
40 # library's code and tests. Until this happen though, they have been
41 # renamed to temporary internal symbols
42 # MBEDTLS_PSA_WANT_KEY_TYPE_[RSA/ECC]_KEY_PAIR_LEGACY so this is what must
43 # be used in tests' dependencies.
44 if name.endswith('RSA_KEY_PAIR') or name.endswith('ECC_KEY_PAIR'):
45 return 'MBEDTLS_' + name[:4] + 'WANT_' + name[4:] + '_LEGACY'
Gilles Peskineaf172842021-01-27 18:24:48 +010046 if name.startswith('PSA_'):
47 return name[:4] + 'WANT_' + name[4:]
48 else:
49 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
50
Gilles Peskine7f756872021-02-16 12:13:12 +010051def finish_family_dependency(dep: str, bits: int) -> str:
52 """Finish dep if it's a family dependency symbol prefix.
53
54 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
55 qualified by the key size. If dep is such a symbol, finish it by adjusting
56 the prefix and appending the key size. Other symbols are left unchanged.
57 """
58 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
59
60def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
61 """Finish any family dependency symbol prefixes.
62
63 Apply `finish_family_dependency` to each element of `dependencies`.
64 """
65 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010066
Gilles Peskinec5d086f2021-04-20 23:23:45 +020067SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
68 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
69 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
70 'PSA_ALG_ANY_HASH', # only in policies
71 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
72 'PSA_ALG_KEY_AGREEMENT', # chaining
73 'PSA_ALG_TRUNCATED_MAC', # modifier
74])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010075def automatic_dependencies(*expressions: str) -> List[str]:
76 """Infer dependencies of a test case by looking for PSA_xxx symbols.
77
78 The arguments are strings which should be C expressions. Do not use
79 string literals or comments as this function is not smart enough to
80 skip them.
81 """
82 used = set()
83 for expr in expressions:
84 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020085 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010086 return sorted(psa_want_symbol(name) for name in used)
87
Yanray Wang3f417442023-04-21 14:29:16 +080088# Define set of regular expressions and dependencies to optionally append
89# extra dependencies for test case.
90AES_128BIT_ONLY_DEP_REGEX = r'AES\s(192|256)'
91AES_128BIT_ONLY_DEP = ["!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH"]
92
93DEPENDENCY_FROM_KEY = {
94 AES_128BIT_ONLY_DEP_REGEX: AES_128BIT_ONLY_DEP
95}#type: Dict[str, List[str]]
Yanray Wang5dd429c2023-05-10 09:58:46 +080096def generate_key_dependencies(description: str) -> List[str]:
Yanray Wang3f417442023-04-21 14:29:16 +080097 """Return additional dependencies based on pairs of REGEX and dependencies.
98 """
99 deps = []
100 for regex, dep in DEPENDENCY_FROM_KEY.items():
101 if re.search(regex, description):
102 deps += dep
103
104 return deps
105
Gilles Peskined169d602021-02-16 14:16:25 +0100106# A temporary hack: at the time of writing, not all dependency symbols
107# are implemented yet. Skip test cases for which the dependency symbols are
108# not available. Once all dependency symbols are available, this hack must
Tom Cosgrove1797b052022-12-04 17:19:59 +0000109# be removed so that a bug in the dependency symbols properly leads to a test
Gilles Peskined169d602021-02-16 14:16:25 +0100110# failure.
111def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
112 return frozenset(symbol
113 for line in open(filename)
114 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200115_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Valerio Setti323ad1c2023-05-26 17:47:55 +0200116# This is a temporary fix for the KEY_PAIR_LEGACY symbols since they are not
117# defined in "crypto_config.h". This fix can be removed as soon as these _LEGACY
118# symbols will be removed from the code.
119_LEGACY_KEY_PAIR = ['MBEDTLS_PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_LEGACY',
120 'MBEDTLS_PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_LEGACY']
Gilles Peskined169d602021-02-16 14:16:25 +0100121def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200122 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
123 if _implemented_dependencies is None:
124 _implemented_dependencies = \
125 read_implemented_dependencies('include/psa/crypto_config.h')
Valerio Setti323ad1c2023-05-26 17:47:55 +0200126 if not all((dep.lstrip('!') in _implemented_dependencies or
127 'PSA_WANT' not in dep or
128 dep.lstrip('!') in _LEGACY_KEY_PAIR)
Gilles Peskined169d602021-02-16 14:16:25 +0100129 for dep in dependencies):
130 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
131
Gilles Peskine14e428f2021-01-26 22:19:21 +0100132
Gilles Peskineb94ea512021-03-10 02:12:08 +0100133class Information:
134 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100135
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100137 self.constructors = self.read_psa_interface()
138
139 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100140 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200141 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100142 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200143 # Mbed TLS doesn't support finite-field DH yet and will not support
144 # finite-field DSA. Don't attempt to generate any related test case.
145 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
146 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100147 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
148 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100149
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200150 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100151 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200152 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100153 header_file_names = ['include/psa/crypto_values.h',
154 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200155 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100156 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200157 constructors.parse_header(header_file_name)
158 for test_cases in test_suites:
159 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100160 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200161 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100162 return constructors
163
Gilles Peskine14e428f2021-01-26 22:19:21 +0100164
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200165def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100166 verb: str, key_type: str, bits: int,
167 dependencies: List[str],
168 *args: str,
169 param_descr: str = ''
170) -> test_case.TestCase:
171 """Return one test case exercising a key creation method
172 for an unsupported key type or size.
173 """
174 hack_dependencies_not_implemented(dependencies)
175 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100176 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100177 adverb = 'not' if dependencies else 'never'
178 if param_descr:
179 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200180 tc.set_description('PSA {} {} {}-bit {} supported'
181 .format(verb, short_key_type, bits, adverb))
182 tc.set_dependencies(dependencies)
183 tc.set_function(verb + '_not_supported')
184 tc.set_arguments([key_type] + list(args))
185 return tc
186
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100187class KeyTypeNotSupported:
188 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100189
190 def __init__(self, info: Information) -> None:
191 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100192
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100193 ALWAYS_SUPPORTED = frozenset([
194 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100195 'PSA_KEY_TYPE_PASSWORD',
196 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100197 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200198 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100199 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100200 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100201 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100202 kt: crypto_knowledge.KeyType,
203 param: Optional[int] = None,
204 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100205 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200206 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100207
208 If param is present and not None, emit test cases conditioned on this
209 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200210 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100211 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100212 if kt.name in self.ALWAYS_SUPPORTED:
213 # Don't generate test cases for key types that are always supported.
214 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100215 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100216 import_dependencies = [('!' if param is None else '') +
217 psa_want_symbol(kt.name)]
218 if kt.params is not None:
219 import_dependencies += [('!' if param == i else '') +
220 psa_want_symbol(sym)
221 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 if kt.name.endswith('_PUBLIC_KEY'):
223 generate_dependencies = []
224 else:
225 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100226 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200227 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100228 'import', kt.expression, bits,
229 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 test_case.hex_string(kt.key_material(bits)),
231 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100232 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100233 if not generate_dependencies and param is not None:
234 # If generation is impossible for this key type, rather than
235 # supported or not depending on implementation capabilities,
236 # only generate the test case once.
237 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100238 # For public key we expect that key generation fails with
239 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100240 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200241 yield test_case_for_key_type_not_supported(
242 'generate', kt.expression, bits,
243 finish_family_dependencies(generate_dependencies, bits),
244 str(bits),
245 param_descr=param_descr,
246 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100247 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100248
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200249 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
250 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
251
Gilles Peskine3d778392021-02-17 15:11:05 +0100252 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100253 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100254 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200255 if key_type in self.ECC_KEY_TYPES:
256 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100257 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100258 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100259 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200260 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100261 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100262 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100263 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100264 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100265 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100266
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200267def test_case_for_key_generation(
268 key_type: str, bits: int,
269 dependencies: List[str],
270 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200271 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200272) -> test_case.TestCase:
273 """Return one test case exercising a key generation.
274 """
275 hack_dependencies_not_implemented(dependencies)
276 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100277 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200278 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200279 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200280 tc.set_dependencies(dependencies)
281 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100282 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200283
284 return tc
285
286class KeyGenerate:
287 """Generate positive and negative (invalid argument) test cases for key generation."""
288
289 def __init__(self, info: Information) -> None:
290 self.constructors = info.constructors
291
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200292 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
293 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
294
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100295 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200297 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200298 ) -> Iterator[test_case.TestCase]:
299 """Return test cases exercising key generation.
300
301 All key types can be generated except for public keys. For public key
302 PSA_ERROR_INVALID_ARGUMENT status is expected.
303 """
304 result = 'PSA_SUCCESS'
305
306 import_dependencies = [psa_want_symbol(kt.name)]
307 if kt.params is not None:
308 import_dependencies += [psa_want_symbol(sym)
309 for i, sym in enumerate(kt.params)]
310 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100311 # The library checks whether the key type is a public key generically,
312 # before it reaches a point where it needs support for the specific key
313 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200314 generate_dependencies = []
315 result = 'PSA_ERROR_INVALID_ARGUMENT'
316 else:
317 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100318 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200319 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200320 for bits in kt.sizes_to_test():
321 yield test_case_for_key_generation(
322 kt.expression, bits,
323 finish_family_dependencies(generate_dependencies, bits),
324 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200325 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200326 )
327
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200328 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
329 """Generate test cases that exercise the generation of keys."""
330 for key_type in sorted(self.constructors.key_types):
331 if key_type in self.ECC_KEY_TYPES:
332 continue
333 kt = crypto_knowledge.KeyType(key_type)
334 yield from self.test_cases_for_key_type_key_generation(kt)
335 for curve_family in sorted(self.constructors.ecc_curves):
336 for constr in self.ECC_KEY_TYPES:
337 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200338 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200339
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200340class OpFail:
341 """Generate test cases for operations that must fail."""
342 #pylint: disable=too-few-public-methods
343
Gilles Peskinecba28a72022-03-15 17:26:33 +0100344 class Reason(enum.Enum):
345 NOT_SUPPORTED = 0
346 INVALID = 1
347 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200348 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100349
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200350 def __init__(self, info: Information) -> None:
351 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100352 key_type_expressions = self.constructors.generate_expressions(
353 sorted(self.constructors.key_types)
354 )
355 self.key_types = [crypto_knowledge.KeyType(kt_expr)
356 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200357
Gilles Peskinecba28a72022-03-15 17:26:33 +0100358 def make_test_case(
359 self,
360 alg: crypto_knowledge.Algorithm,
361 category: crypto_knowledge.AlgorithmCategory,
362 reason: 'Reason',
363 kt: Optional[crypto_knowledge.KeyType] = None,
364 not_deps: FrozenSet[str] = frozenset(),
365 ) -> test_case.TestCase:
366 """Construct a failure test case for a one-key or keyless operation."""
367 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200368 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100369 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200370 if reason == self.Reason.NOT_SUPPORTED:
371 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
372 for dep in not_deps]
373 pretty_reason = '!' + '&'.join(sorted(short_deps))
374 else:
375 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100376 if kt:
377 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100378 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200379 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100380 key_type = ''
381 pretty_type = ''
382 tc.set_description('PSA {} {}: {}{}'
383 .format(category.name.lower(),
384 pretty_alg,
385 pretty_reason,
386 ' with ' + pretty_type if pretty_type else ''))
387 dependencies = automatic_dependencies(alg.base_expression, key_type)
388 for i, dep in enumerate(dependencies):
389 if dep in not_deps:
390 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200391 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100392 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000393 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100394 if kt:
395 key_material = kt.key_material(kt.sizes_to_test()[0])
396 arguments += [key_type, test_case.hex_string(key_material)]
397 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200398 if category.is_asymmetric():
399 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100400 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
401 'INVALID_ARGUMENT')
402 arguments.append('PSA_ERROR_' + error)
403 tc.set_arguments(arguments)
404 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200405
Gilles Peskinecba28a72022-03-15 17:26:33 +0100406 def no_key_test_cases(
407 self,
408 alg: crypto_knowledge.Algorithm,
409 category: crypto_knowledge.AlgorithmCategory,
410 ) -> Iterator[test_case.TestCase]:
411 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200412 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100413 # Compatible operation, unsupported algorithm
414 for dep in automatic_dependencies(alg.base_expression):
415 yield self.make_test_case(alg, category,
416 self.Reason.NOT_SUPPORTED,
417 not_deps=frozenset([dep]))
418 else:
419 # Incompatible operation, supported algorithm
420 yield self.make_test_case(alg, category, self.Reason.INVALID)
421
422 def one_key_test_cases(
423 self,
424 alg: crypto_knowledge.Algorithm,
425 category: crypto_knowledge.AlgorithmCategory,
426 ) -> Iterator[test_case.TestCase]:
427 """Generate failure test cases for one-key operations with the specified algorithm."""
428 for kt in self.key_types:
429 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200430 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100431 # Compatible key and operation, unsupported algorithm
432 for dep in automatic_dependencies(alg.base_expression):
433 yield self.make_test_case(alg, category,
434 self.Reason.NOT_SUPPORTED,
435 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200436 # Public key for a private-key operation
437 if category.is_asymmetric() and kt.is_public():
438 yield self.make_test_case(alg, category,
439 self.Reason.PUBLIC,
440 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100441 elif key_is_compatible:
442 # Compatible key, incompatible operation, supported algorithm
443 yield self.make_test_case(alg, category,
444 self.Reason.INVALID,
445 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200446 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100447 # Incompatible key, compatible operation, supported algorithm
448 yield self.make_test_case(alg, category,
449 self.Reason.INCOMPATIBLE,
450 kt=kt)
451 else:
452 # Incompatible key and operation. Don't test cases where
453 # multiple things are wrong, to keep the number of test
454 # cases reasonable.
455 pass
456
457 def test_cases_for_algorithm(
458 self,
459 alg: crypto_knowledge.Algorithm,
460 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200461 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100462 for category in crypto_knowledge.AlgorithmCategory:
463 if category == crypto_knowledge.AlgorithmCategory.PAKE:
464 # PAKE operations are not implemented yet
465 pass
466 elif category.requires_key():
467 yield from self.one_key_test_cases(alg, category)
468 else:
469 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200470
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200471 def all_test_cases(self) -> Iterator[test_case.TestCase]:
472 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200473 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100474 for expr in self.constructors.generate_expressions(algorithms):
475 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200476 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200477
478
Gilles Peskine897dff92021-03-10 15:03:44 +0100479class StorageKey(psa_storage.Key):
480 """Representation of a key for storage format testing."""
481
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200482 IMPLICIT_USAGE_FLAGS = {
483 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
484 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
485 } #type: Dict[str, str]
486 """Mapping of usage flags to the flags that they imply."""
487
488 def __init__(
489 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100490 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200491 without_implicit_usage: Optional[bool] = False,
492 **kwargs
493 ) -> None:
494 """Prepare to generate a key.
495
496 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000497 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200498 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100499 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200500 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100501 for flag in sorted(usage_flags):
502 if flag in self.IMPLICIT_USAGE_FLAGS:
503 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
504 if usage_flags:
505 usage_expression = ' | '.join(sorted(usage_flags))
506 else:
507 usage_expression = '0'
508 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200509
510class StorageTestData(StorageKey):
511 """Representation of test case data for storage format testing."""
512
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200513 def __init__(
514 self,
515 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100516 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200517 **kwargs
518 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200519 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200520
Tom Cosgrove1797b052022-12-04 17:19:59 +0000521 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200522 * `expected_usage`: the usage flags generated as the expected usage flags
523 in the test cases. CAn differ from the usage flags
524 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200525 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100526 super().__init__(**kwargs)
527 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100528 if expected_usage is None:
529 self.expected_usage = self.usage #type: psa_storage.Expr
530 elif expected_usage:
531 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
532 else:
533 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200534
Gilles Peskine897dff92021-03-10 15:03:44 +0100535class StorageFormat:
536 """Storage format stability test cases."""
537
538 def __init__(self, info: Information, version: int, forward: bool) -> None:
539 """Prepare to generate test cases for storage format stability.
540
541 * `info`: information about the API. See the `Information` class.
542 * `version`: the storage format version to generate test cases for.
543 * `forward`: if true, generate forward compatibility test cases which
544 save a key and check that its representation is as intended. Otherwise
545 generate backward compatibility test cases which inject a key
546 representation and check that it can be read and used.
547 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200548 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
549 self.version = version #type: int
550 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100551
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100552 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100553 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100554 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100555 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100556 cls,
557 key_type: psa_storage.Expr, bits: int,
558 alg: psa_storage.Expr
559 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100560 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100561
562 Normally only the type and algorithm matter for compatibility, and
563 this is handled in crypto_knowledge.KeyType.can_do(). This function
564 exists to detect exceptional cases. Exceptional cases detected here
565 are not tested in OpFail and should therefore have manually written
566 test cases.
567 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100568 # Some test keys have the RAW_DATA type and attributes that don't
569 # necessarily make sense. We do this to validate numerical
570 # encodings of the attributes.
571 # Raw data keys have no useful exercise anyway so there is no
572 # loss of test coverage.
573 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
574 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100575 # OAEP requires room for two hashes plus wrapping
576 m = cls.RSA_OAEP_RE.match(alg.string)
577 if m:
578 hash_alg = m.group(1)
579 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
580 key_length = (bits + 7) // 8
581 # Leave enough room for at least one byte of plaintext
582 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100583 # There's nothing wrong with ECC keys on Brainpool curves,
584 # but operations with them are very slow. So we only exercise them
585 # with a single algorithm, not with all possible hashes. We do
586 # exercise other curves with all algorithms so test coverage is
587 # perfectly adequate like this.
588 m = cls.BRAINPOOL_RE.match(key_type.string)
589 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
590 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100591 return True
592
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200593 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100594 """Construct a storage format test case for the given key.
595
596 If ``forward`` is true, generate a forward compatibility test case:
597 create a key and validate that it has the expected representation.
598 Otherwise generate a backward compatibility test case: inject the
599 key representation into storage and validate that it can be read
600 correctly.
601 """
602 verb = 'save' if self.forward else 'read'
603 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100604 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100605 dependencies = automatic_dependencies(
606 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100607 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100608 )
609 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800610 dependencies += generate_key_dependencies(key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100611 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100612 tc.set_function('key_storage_' + verb)
613 if self.forward:
614 extra_arguments = []
615 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200616 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100617 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200618 flags.append('TEST_FLAG_EXERCISE')
619 if 'READ_ONLY' in key.lifetime.string:
620 flags.append('TEST_FLAG_READ_ONLY')
621 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100622 tc.set_arguments([key.lifetime.string,
623 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100624 key.expected_usage.string,
625 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100626 '"' + key.material.hex() + '"',
627 '"' + key.hex() + '"',
628 *extra_arguments])
629 return tc
630
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200631 def key_for_lifetime(
632 self,
633 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200634 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200635 """Construct a test key for the given lifetime."""
636 short = lifetime
637 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
638 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100639 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200640 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200641 key = StorageTestData(version=self.version,
642 id=1, lifetime=lifetime,
643 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100644 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200645 material=b'L',
646 description=description)
647 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200648
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200649 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200650 """Generate test keys covering lifetimes."""
651 lifetimes = sorted(self.constructors.lifetimes)
652 expressions = self.constructors.generate_expressions(lifetimes)
653 for lifetime in expressions:
654 # Don't attempt to create or load a volatile key in storage
655 if 'VOLATILE' in lifetime:
656 continue
657 # Don't attempt to create a read-only key in storage,
658 # but do attempt to load one.
659 if 'READ_ONLY' in lifetime and self.forward:
660 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200661 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200662
Gilles Peskinef7614272022-02-24 18:58:08 +0100663 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100664 self,
665 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200666 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100667 test_implicit_usage: Optional[bool] = True
668 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100669 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100670 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100671 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200672 key1 = StorageTestData(version=self.version,
673 id=1, lifetime=0x00000001,
674 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100675 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100676 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100677 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200678 material=b'K',
679 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100680 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100681 usage_expr = key1.expected_usage.string
682 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100683 else:
684 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100685 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100686
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200687 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100688 """Generate test keys covering usage flags."""
689 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100690 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200691 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100692 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200693 for flag1, flag2 in zip(known_flags,
694 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100695 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200696
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200697 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200698 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100699 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200700
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200701 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200702 yield from self.generate_keys_for_usage_flags()
703 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100704
Gilles Peskine7de7c102021-04-29 22:28:07 +0200705 def key_for_type_and_alg(
706 self,
707 kt: crypto_knowledge.KeyType,
708 bits: int,
709 alg: Optional[crypto_knowledge.Algorithm] = None,
710 ) -> StorageTestData:
711 """Construct a test key of the given type.
712
713 If alg is not None, this key allows it.
714 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100715 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100716 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200717 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100718 if alg is not None:
719 alg1 = alg.expression
720 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100722 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200723 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100724 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200725 key = StorageTestData(version=self.version,
726 id=1, lifetime=0x00000001,
727 type=kt.expression, bits=bits,
728 usage=usage_flags, alg=alg1, alg2=alg2,
729 material=key_material,
730 description=description)
731 return key
732
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100733 def keys_for_type(
734 self,
735 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200736 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200737 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200738 """Generate test keys for the given key type."""
739 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100740 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200741 # Test a non-exercisable key, as well as exercisable keys for
742 # each compatible algorithm.
743 # To do: test reading a key from storage with an incompatible
744 # or unsupported algorithm.
745 yield self.key_for_type_and_alg(kt, bits)
746 compatible_algorithms = [alg for alg in all_algorithms
747 if kt.can_do(alg)]
748 for alg in compatible_algorithms:
749 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100750
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200751 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100752 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200753 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200754 all_algorithms = [crypto_knowledge.Algorithm(alg)
755 for alg in self.constructors.generate_expressions(
756 sorted(self.constructors.algorithms)
757 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200758 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200759 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100760
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200761 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200762 """Generate test keys for the encoding of the specified algorithm."""
763 # These test cases only validate the encoding of algorithms, not
764 # whether the key read from storage is suitable for an operation.
765 # `keys_for_types` generate read tests with an algorithm and a
766 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100767 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100768 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200769 key1 = StorageTestData(version=self.version,
770 id=1, lifetime=0x00000001,
771 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
772 usage=usage, alg=alg, alg2=0,
773 material=b'K',
774 description='alg: ' + descr)
775 yield key1
776 key2 = StorageTestData(version=self.version,
777 id=1, lifetime=0x00000001,
778 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
779 usage=usage, alg=0, alg2=alg,
780 material=b'L',
781 description='alg2: ' + descr)
782 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100783
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200784 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100785 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200786 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200787 for alg in self.constructors.generate_expressions(algorithms):
788 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100789
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200790 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200791 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200792 yield from self.all_keys_for_lifetimes()
793 yield from self.all_keys_for_usage_flags()
794 yield from self.all_keys_for_types()
795 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200796
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200797 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100798 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200799 # First build a list of all keys, then construct all the corresponding
800 # test cases. This allows all required information to be obtained in
801 # one go, which is a significant performance gain as the information
802 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200803 all_keys = list(self.generate_all_keys())
804 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200805 if key.location_value() != 0:
806 # Skip keys with a non-default location, because they
807 # require a driver and we currently have no mechanism to
808 # determine whether a driver is available.
809 continue
810 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100811
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200812class StorageFormatForward(StorageFormat):
813 """Storage format stability test cases for forward compatibility."""
814
815 def __init__(self, info: Information, version: int) -> None:
816 super().__init__(info, version, True)
817
818class StorageFormatV0(StorageFormat):
819 """Storage format stability test cases for version 0 compatibility."""
820
821 def __init__(self, info: Information) -> None:
822 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100823
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200824 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200825 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100826 yield from super().all_keys_for_usage_flags()
827 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200828
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200829 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200830 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200831 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200832 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200833 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200834 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200835 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200836 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200837 algorithm and key type combination.
838 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200839 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200840 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100841 usage_flags = ['PSA_KEY_USAGE_EXPORT']
842 material_usage_flags = usage_flags + [implyer_usage]
843 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200844 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200845 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100846 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
847 alg_expression = crypto_knowledge.short_expression(alg, 1)
848 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200849 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200850 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200851 key = StorageTestData(version=self.version,
852 id=1, lifetime=0x00000001,
853 type=key_type.expression, bits=bits,
854 usage=material_usage_flags,
855 expected_usage=expected_usage_flags,
856 without_implicit_usage=True,
857 alg=alg, alg2=alg2,
858 material=key_material,
859 description=description)
860 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200861
862 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200863 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200864 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800865 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200866 # must be filtered. Pair them with keywords created from its names.
867 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
868 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
869 keyword_translation = {
870 'ECDSA': 'ECC',
871 'ED[0-9]*.*' : 'EDWARDS'
872 }
873 exclusive_keywords = {
874 'EDWARDS': 'ECC'
875 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200876 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
877 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200878 alg_with_keys = {} #type: Dict[str, List[str]]
879 translation_table = str.maketrans('(', '_', ')')
880 for alg in algorithms:
881 # Generate keywords from the name of the algorithm
882 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
883 # Translate keywords for better matching with the key types
884 for keyword in alg_keywords.copy():
885 for pattern, replace in keyword_translation.items():
886 if re.match(pattern, keyword):
887 alg_keywords.remove(keyword)
888 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800889 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200890 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
891 continue
892
893 for key_type in key_types:
894 # Generate keywords from the of the key type
895 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
896
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800897 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200898 for keyword1, keyword2 in exclusive_keywords.items():
899 if keyword1 in key_type_keywords:
900 key_type_keywords.remove(keyword2)
901
902 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
903 not key_type_keywords.isdisjoint(alg_keywords):
904 if alg in alg_with_keys:
905 alg_with_keys[alg].append(key_type)
906 else:
907 alg_with_keys[alg] = [key_type]
908 return alg_with_keys
909
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200910 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200911 """Generate test keys for usage flag extensions."""
912 # Generate a key type and algorithm pair for each extendable usage
913 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800914 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200915 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200916
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200917 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
918 for alg in sorted(alg_with_keys):
919 for key_type in sorted(alg_with_keys[alg]):
920 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200921 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100922 if kt.is_public() and '_SIGN_' in usage:
923 # Can't sign with a public key
924 continue
925 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200926
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200927 def generate_all_keys(self) -> Iterator[StorageTestData]:
928 yield from super().generate_all_keys()
929 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200930
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200931class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100932 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100933 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200934 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100935 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200936 'test_suite_psa_crypto_generate_key.generated':
937 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100938 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100939 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200940 'test_suite_psa_crypto_op_fail.generated':
941 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100942 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200943 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100944 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200945 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100946 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
947
Werner Lewisfbb75e32022-08-24 11:30:03 +0100948 def __init__(self, options):
949 super().__init__(options)
950 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100951
Werner Lewisfbb75e32022-08-24 11:30:03 +0100952 def generate_target(self, name: str, *target_args) -> None:
953 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100954
955if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200956 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)