blob: edaec92b3cacc72efd9f349619a43c14b202480e [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()
Manuel Pégourié-Gonnardc154a042023-07-18 11:01:14 +0200124 # Note: this LEGACY replacement DH is temporary and it's going
125 # to be aligned with ECC one in #7773.
126 if dep.endswith('DH_KEY_PAIR'):
127 legacy = dep
128 legacy = re.sub(r'KEY_PAIR\Z', r'KEY_PAIR_LEGACY', legacy)
129 legacy = re.sub(r'PSA_WANT', r'MBEDTLS_PSA_WANT', legacy)
130 ret_list.append(legacy)
131 elif dep.endswith('KEY_PAIR'):
Valerio Setti0c42c432023-06-29 12:10:52 +0200132 if usage == "BASIC":
133 # BASIC automatically includes IMPORT and EXPORT for test purposes (see
134 # config_psa.h).
Valerio Setti42796e22023-07-10 12:24:34 +0200135 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_BASIC', dep))
136 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_IMPORT', dep))
137 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_EXPORT', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200138 elif usage == "GENERATE":
Valerio Setti42796e22023-07-10 12:24:34 +0200139 ret_list.append(re.sub(r'KEY_PAIR', r'KEY_PAIR_GENERATE', dep))
Valerio Setti0c42c432023-06-29 12:10:52 +0200140 else:
141 # No replacement to do in this case
142 ret_list.append(dep)
143 return ret_list
144
145def fix_key_pair_dependencies(dep_list: List[str], usage: str):
146 new_list = [new_deps
147 for dep in dep_list
148 for new_deps in tweak_key_pair_dependency(dep, usage)]
149
Valerio Setti24c64e82023-06-26 11:49:02 +0200150 return new_list
Gilles Peskine14e428f2021-01-26 22:19:21 +0100151
Gilles Peskineb94ea512021-03-10 02:12:08 +0100152class Information:
153 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100154
Gilles Peskineb94ea512021-03-10 02:12:08 +0100155 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100156 self.constructors = self.read_psa_interface()
157
158 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100159 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200160 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100161 ) -> None:
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200162 # Mbed TLS does not support finite-field DSA.
163 # Don't attempt to generate any related test case.
Gilles Peskine09940492021-01-26 22:16:30 +0100164 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
165 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100166
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200167 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100168 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200169 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100170 header_file_names = ['include/psa/crypto_values.h',
171 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200172 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100173 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200174 constructors.parse_header(header_file_name)
175 for test_cases in test_suites:
176 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100177 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200178 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100179 return constructors
180
Gilles Peskine14e428f2021-01-26 22:19:21 +0100181
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200182def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100183 verb: str, key_type: str, bits: int,
184 dependencies: List[str],
185 *args: str,
186 param_descr: str = ''
187) -> test_case.TestCase:
188 """Return one test case exercising a key creation method
189 for an unsupported key type or size.
190 """
191 hack_dependencies_not_implemented(dependencies)
192 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100193 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100194 adverb = 'not' if dependencies else 'never'
195 if param_descr:
196 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200197 tc.set_description('PSA {} {} {}-bit {} supported'
198 .format(verb, short_key_type, bits, adverb))
199 tc.set_dependencies(dependencies)
200 tc.set_function(verb + '_not_supported')
201 tc.set_arguments([key_type] + list(args))
202 return tc
203
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100204class KeyTypeNotSupported:
205 """Generate test cases for when a key type is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100206
207 def __init__(self, info: Information) -> None:
208 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100209
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100210 ALWAYS_SUPPORTED = frozenset([
211 'PSA_KEY_TYPE_DERIVE',
Gilles Peskinebba26302022-12-15 23:25:17 +0100212 'PSA_KEY_TYPE_PASSWORD',
213 'PSA_KEY_TYPE_PASSWORD_HASH',
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100214 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200215 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100216 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100218 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100219 kt: crypto_knowledge.KeyType,
220 param: Optional[int] = None,
221 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100222 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200223 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100224
225 If param is present and not None, emit test cases conditioned on this
226 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200227 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100229 if kt.name in self.ALWAYS_SUPPORTED:
230 # Don't generate test cases for key types that are always supported.
231 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100232 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100233 import_dependencies = [('!' if param is None else '') +
234 psa_want_symbol(kt.name)]
235 if kt.params is not None:
236 import_dependencies += [('!' if param == i else '') +
237 psa_want_symbol(sym)
238 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100239 if kt.name.endswith('_PUBLIC_KEY'):
240 generate_dependencies = []
241 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200242 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Valerio Setti27c501a2023-06-27 16:58:52 +0200243 import_dependencies = fix_key_pair_dependencies(import_dependencies, 'BASIC')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100244 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200245 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100246 'import', kt.expression, bits,
247 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100248 test_case.hex_string(kt.key_material(bits)),
249 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100250 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100251 if not generate_dependencies and param is not None:
252 # If generation is impossible for this key type, rather than
253 # supported or not depending on implementation capabilities,
254 # only generate the test case once.
255 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100256 # For public key we expect that key generation fails with
257 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100258 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200259 yield test_case_for_key_type_not_supported(
260 'generate', kt.expression, bits,
261 finish_family_dependencies(generate_dependencies, bits),
262 str(bits),
263 param_descr=param_descr,
264 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100265 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100266
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200267 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
268 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200269 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
270 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200271
Gilles Peskine3d778392021-02-17 15:11:05 +0100272 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100273 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100274 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200275 if key_type in self.ECC_KEY_TYPES:
276 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200277 if key_type in self.DH_KEY_TYPES:
278 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100279 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100280 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100281 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200282 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100283 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100284 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100285 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100286 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100287 kt, 0, param_descr='curve')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200288 for dh_family in sorted(self.constructors.dh_groups):
289 for constr in self.DH_KEY_TYPES:
290 kt = crypto_knowledge.KeyType(constr, [dh_family])
291 yield from self.test_cases_for_key_type_not_supported(
292 kt, param_descr='type')
293 yield from self.test_cases_for_key_type_not_supported(
294 kt, 0, param_descr='group')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100295
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296def test_case_for_key_generation(
297 key_type: str, bits: int,
298 dependencies: List[str],
299 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200300 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200301) -> test_case.TestCase:
302 """Return one test case exercising a key generation.
303 """
304 hack_dependencies_not_implemented(dependencies)
305 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100306 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200307 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200308 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200309 tc.set_dependencies(dependencies)
310 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100311 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200312
313 return tc
314
315class KeyGenerate:
316 """Generate positive and negative (invalid argument) test cases for key generation."""
317
318 def __init__(self, info: Information) -> None:
319 self.constructors = info.constructors
320
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200321 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
322 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200323 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
324 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200325
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100326 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200327 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200328 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200329 ) -> Iterator[test_case.TestCase]:
330 """Return test cases exercising key generation.
331
332 All key types can be generated except for public keys. For public key
333 PSA_ERROR_INVALID_ARGUMENT status is expected.
334 """
335 result = 'PSA_SUCCESS'
336
337 import_dependencies = [psa_want_symbol(kt.name)]
338 if kt.params is not None:
339 import_dependencies += [psa_want_symbol(sym)
340 for i, sym in enumerate(kt.params)]
341 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100342 # The library checks whether the key type is a public key generically,
343 # before it reaches a point where it needs support for the specific key
344 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200345 generate_dependencies = []
346 result = 'PSA_ERROR_INVALID_ARGUMENT'
347 else:
Valerio Setti24c64e82023-06-26 11:49:02 +0200348 generate_dependencies = fix_key_pair_dependencies(import_dependencies, 'GENERATE')
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200349 for bits in kt.sizes_to_test():
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000350 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
351 size_dependency = "PSA_VENDOR_RSA_MIN_KEY_BITS <= " + str(bits)
352 test_dependencies = generate_dependencies + [size_dependency]
353 else:
354 test_dependencies = generate_dependencies
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200355 yield test_case_for_key_generation(
356 kt.expression, bits,
Waleed Elmelegy3d158f02023-07-07 11:48:03 +0000357 finish_family_dependencies(test_dependencies, bits),
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200358 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200359 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200360 )
361
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200362 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
363 """Generate test cases that exercise the generation of keys."""
364 for key_type in sorted(self.constructors.key_types):
365 if key_type in self.ECC_KEY_TYPES:
366 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200367 if key_type in self.DH_KEY_TYPES:
368 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200369 kt = crypto_knowledge.KeyType(key_type)
370 yield from self.test_cases_for_key_type_key_generation(kt)
371 for curve_family in sorted(self.constructors.ecc_curves):
372 for constr in self.ECC_KEY_TYPES:
373 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200374 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200375 for dh_family in sorted(self.constructors.dh_groups):
376 for constr in self.DH_KEY_TYPES:
377 kt = crypto_knowledge.KeyType(constr, [dh_family])
378 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200379
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200380class OpFail:
381 """Generate test cases for operations that must fail."""
382 #pylint: disable=too-few-public-methods
383
Gilles Peskinecba28a72022-03-15 17:26:33 +0100384 class Reason(enum.Enum):
385 NOT_SUPPORTED = 0
386 INVALID = 1
387 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200388 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100389
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200390 def __init__(self, info: Information) -> None:
391 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100392 key_type_expressions = self.constructors.generate_expressions(
393 sorted(self.constructors.key_types)
394 )
395 self.key_types = [crypto_knowledge.KeyType(kt_expr)
396 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200397
Gilles Peskinecba28a72022-03-15 17:26:33 +0100398 def make_test_case(
399 self,
400 alg: crypto_knowledge.Algorithm,
401 category: crypto_knowledge.AlgorithmCategory,
402 reason: 'Reason',
403 kt: Optional[crypto_knowledge.KeyType] = None,
404 not_deps: FrozenSet[str] = frozenset(),
405 ) -> test_case.TestCase:
406 """Construct a failure test case for a one-key or keyless operation."""
407 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200408 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100409 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200410 if reason == self.Reason.NOT_SUPPORTED:
411 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
412 for dep in not_deps]
413 pretty_reason = '!' + '&'.join(sorted(short_deps))
414 else:
415 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100416 if kt:
417 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100418 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200419 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100420 key_type = ''
421 pretty_type = ''
422 tc.set_description('PSA {} {}: {}{}'
423 .format(category.name.lower(),
424 pretty_alg,
425 pretty_reason,
426 ' with ' + pretty_type if pretty_type else ''))
427 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200428 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100429 for i, dep in enumerate(dependencies):
430 if dep in not_deps:
431 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200432 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100433 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000434 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100435 if kt:
436 key_material = kt.key_material(kt.sizes_to_test()[0])
437 arguments += [key_type, test_case.hex_string(key_material)]
438 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200439 if category.is_asymmetric():
440 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100441 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
442 'INVALID_ARGUMENT')
443 arguments.append('PSA_ERROR_' + error)
444 tc.set_arguments(arguments)
445 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200446
Gilles Peskinecba28a72022-03-15 17:26:33 +0100447 def no_key_test_cases(
448 self,
449 alg: crypto_knowledge.Algorithm,
450 category: crypto_knowledge.AlgorithmCategory,
451 ) -> Iterator[test_case.TestCase]:
452 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200453 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100454 # Compatible operation, unsupported algorithm
455 for dep in automatic_dependencies(alg.base_expression):
456 yield self.make_test_case(alg, category,
457 self.Reason.NOT_SUPPORTED,
458 not_deps=frozenset([dep]))
459 else:
460 # Incompatible operation, supported algorithm
461 yield self.make_test_case(alg, category, self.Reason.INVALID)
462
463 def one_key_test_cases(
464 self,
465 alg: crypto_knowledge.Algorithm,
466 category: crypto_knowledge.AlgorithmCategory,
467 ) -> Iterator[test_case.TestCase]:
468 """Generate failure test cases for one-key operations with the specified algorithm."""
469 for kt in self.key_types:
470 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200471 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100472 # Compatible key and operation, unsupported algorithm
473 for dep in automatic_dependencies(alg.base_expression):
474 yield self.make_test_case(alg, category,
475 self.Reason.NOT_SUPPORTED,
476 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200477 # Public key for a private-key operation
478 if category.is_asymmetric() and kt.is_public():
479 yield self.make_test_case(alg, category,
480 self.Reason.PUBLIC,
481 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100482 elif key_is_compatible:
483 # Compatible key, incompatible operation, supported algorithm
484 yield self.make_test_case(alg, category,
485 self.Reason.INVALID,
486 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200487 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100488 # Incompatible key, compatible operation, supported algorithm
489 yield self.make_test_case(alg, category,
490 self.Reason.INCOMPATIBLE,
491 kt=kt)
492 else:
493 # Incompatible key and operation. Don't test cases where
494 # multiple things are wrong, to keep the number of test
495 # cases reasonable.
496 pass
497
498 def test_cases_for_algorithm(
499 self,
500 alg: crypto_knowledge.Algorithm,
501 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200502 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100503 for category in crypto_knowledge.AlgorithmCategory:
504 if category == crypto_knowledge.AlgorithmCategory.PAKE:
505 # PAKE operations are not implemented yet
506 pass
507 elif category.requires_key():
508 yield from self.one_key_test_cases(alg, category)
509 else:
510 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200511
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200512 def all_test_cases(self) -> Iterator[test_case.TestCase]:
513 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200514 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100515 for expr in self.constructors.generate_expressions(algorithms):
516 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200517 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200518
519
Gilles Peskine897dff92021-03-10 15:03:44 +0100520class StorageKey(psa_storage.Key):
521 """Representation of a key for storage format testing."""
522
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200523 IMPLICIT_USAGE_FLAGS = {
524 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
525 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
526 } #type: Dict[str, str]
527 """Mapping of usage flags to the flags that they imply."""
528
529 def __init__(
530 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100531 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200532 without_implicit_usage: Optional[bool] = False,
533 **kwargs
534 ) -> None:
535 """Prepare to generate a key.
536
537 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000538 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200539 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100540 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200541 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100542 for flag in sorted(usage_flags):
543 if flag in self.IMPLICIT_USAGE_FLAGS:
544 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
545 if usage_flags:
546 usage_expression = ' | '.join(sorted(usage_flags))
547 else:
548 usage_expression = '0'
549 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200550
551class StorageTestData(StorageKey):
552 """Representation of test case data for storage format testing."""
553
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200554 def __init__(
555 self,
556 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100557 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200558 **kwargs
559 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200560 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200561
Tom Cosgrove1797b052022-12-04 17:19:59 +0000562 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200563 * `expected_usage`: the usage flags generated as the expected usage flags
564 in the test cases. CAn differ from the usage flags
565 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200566 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100567 super().__init__(**kwargs)
568 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100569 if expected_usage is None:
570 self.expected_usage = self.usage #type: psa_storage.Expr
571 elif expected_usage:
572 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
573 else:
574 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200575
Gilles Peskine897dff92021-03-10 15:03:44 +0100576class StorageFormat:
577 """Storage format stability test cases."""
578
579 def __init__(self, info: Information, version: int, forward: bool) -> None:
580 """Prepare to generate test cases for storage format stability.
581
582 * `info`: information about the API. See the `Information` class.
583 * `version`: the storage format version to generate test cases for.
584 * `forward`: if true, generate forward compatibility test cases which
585 save a key and check that its representation is as intended. Otherwise
586 generate backward compatibility test cases which inject a key
587 representation and check that it can be read and used.
588 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200589 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
590 self.version = version #type: int
591 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100592
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100593 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100594 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100595 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100596 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100597 cls,
598 key_type: psa_storage.Expr, bits: int,
599 alg: psa_storage.Expr
600 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100601 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100602
603 Normally only the type and algorithm matter for compatibility, and
604 this is handled in crypto_knowledge.KeyType.can_do(). This function
605 exists to detect exceptional cases. Exceptional cases detected here
606 are not tested in OpFail and should therefore have manually written
607 test cases.
608 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100609 # Some test keys have the RAW_DATA type and attributes that don't
610 # necessarily make sense. We do this to validate numerical
611 # encodings of the attributes.
612 # Raw data keys have no useful exercise anyway so there is no
613 # loss of test coverage.
614 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
615 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100616 # OAEP requires room for two hashes plus wrapping
617 m = cls.RSA_OAEP_RE.match(alg.string)
618 if m:
619 hash_alg = m.group(1)
620 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
621 key_length = (bits + 7) // 8
622 # Leave enough room for at least one byte of plaintext
623 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100624 # There's nothing wrong with ECC keys on Brainpool curves,
625 # but operations with them are very slow. So we only exercise them
626 # with a single algorithm, not with all possible hashes. We do
627 # exercise other curves with all algorithms so test coverage is
628 # perfectly adequate like this.
629 m = cls.BRAINPOOL_RE.match(key_type.string)
630 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
631 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100632 return True
633
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200634 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100635 """Construct a storage format test case for the given key.
636
637 If ``forward`` is true, generate a forward compatibility test case:
638 create a key and validate that it has the expected representation.
639 Otherwise generate a backward compatibility test case: inject the
640 key representation into storage and validate that it can be read
641 correctly.
642 """
643 verb = 'save' if self.forward else 'read'
644 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100645 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100646 dependencies = automatic_dependencies(
647 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100648 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100649 )
650 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800651 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200652 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100653 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100654 tc.set_function('key_storage_' + verb)
655 if self.forward:
656 extra_arguments = []
657 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200658 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100659 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200660 flags.append('TEST_FLAG_EXERCISE')
661 if 'READ_ONLY' in key.lifetime.string:
662 flags.append('TEST_FLAG_READ_ONLY')
663 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100664 tc.set_arguments([key.lifetime.string,
665 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100666 key.expected_usage.string,
667 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100668 '"' + key.material.hex() + '"',
669 '"' + key.hex() + '"',
670 *extra_arguments])
671 return tc
672
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200673 def key_for_lifetime(
674 self,
675 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200676 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200677 """Construct a test key for the given lifetime."""
678 short = lifetime
679 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
680 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100681 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200682 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200683 key = StorageTestData(version=self.version,
684 id=1, lifetime=lifetime,
685 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100686 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200687 material=b'L',
688 description=description)
689 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200690
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200691 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200692 """Generate test keys covering lifetimes."""
693 lifetimes = sorted(self.constructors.lifetimes)
694 expressions = self.constructors.generate_expressions(lifetimes)
695 for lifetime in expressions:
696 # Don't attempt to create or load a volatile key in storage
697 if 'VOLATILE' in lifetime:
698 continue
699 # Don't attempt to create a read-only key in storage,
700 # but do attempt to load one.
701 if 'READ_ONLY' in lifetime and self.forward:
702 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200703 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200704
Gilles Peskinef7614272022-02-24 18:58:08 +0100705 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100706 self,
707 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200708 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100709 test_implicit_usage: Optional[bool] = True
710 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100711 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100712 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100713 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200714 key1 = StorageTestData(version=self.version,
715 id=1, lifetime=0x00000001,
716 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100717 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100718 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100719 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200720 material=b'K',
721 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100722 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100723 usage_expr = key1.expected_usage.string
724 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100725 else:
726 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100727 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100728
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200729 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100730 """Generate test keys covering usage flags."""
731 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100732 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200733 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100734 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200735 for flag1, flag2 in zip(known_flags,
736 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100737 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200738
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200739 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200740 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100741 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200742
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200743 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200744 yield from self.generate_keys_for_usage_flags()
745 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100746
Gilles Peskine7de7c102021-04-29 22:28:07 +0200747 def key_for_type_and_alg(
748 self,
749 kt: crypto_knowledge.KeyType,
750 bits: int,
751 alg: Optional[crypto_knowledge.Algorithm] = None,
752 ) -> StorageTestData:
753 """Construct a test key of the given type.
754
755 If alg is not None, this key allows it.
756 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100757 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100758 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200759 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100760 if alg is not None:
761 alg1 = alg.expression
762 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200763 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100764 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200765 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100766 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200767 key = StorageTestData(version=self.version,
768 id=1, lifetime=0x00000001,
769 type=kt.expression, bits=bits,
770 usage=usage_flags, alg=alg1, alg2=alg2,
771 material=key_material,
772 description=description)
773 return key
774
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100775 def keys_for_type(
776 self,
777 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200778 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200779 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200780 """Generate test keys for the given key type."""
781 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100782 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200783 # Test a non-exercisable key, as well as exercisable keys for
784 # each compatible algorithm.
785 # To do: test reading a key from storage with an incompatible
786 # or unsupported algorithm.
787 yield self.key_for_type_and_alg(kt, bits)
788 compatible_algorithms = [alg for alg in all_algorithms
789 if kt.can_do(alg)]
790 for alg in compatible_algorithms:
791 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100792
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200793 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100794 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200795 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200796 all_algorithms = [crypto_knowledge.Algorithm(alg)
797 for alg in self.constructors.generate_expressions(
798 sorted(self.constructors.algorithms)
799 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200800 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200801 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100802
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200803 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200804 """Generate test keys for the encoding of the specified algorithm."""
805 # These test cases only validate the encoding of algorithms, not
806 # whether the key read from storage is suitable for an operation.
807 # `keys_for_types` generate read tests with an algorithm and a
808 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100809 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100810 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200811 key1 = StorageTestData(version=self.version,
812 id=1, lifetime=0x00000001,
813 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
814 usage=usage, alg=alg, alg2=0,
815 material=b'K',
816 description='alg: ' + descr)
817 yield key1
818 key2 = StorageTestData(version=self.version,
819 id=1, lifetime=0x00000001,
820 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
821 usage=usage, alg=0, alg2=alg,
822 material=b'L',
823 description='alg2: ' + descr)
824 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100825
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200826 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100827 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200828 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200829 for alg in self.constructors.generate_expressions(algorithms):
830 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100831
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200832 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200833 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200834 yield from self.all_keys_for_lifetimes()
835 yield from self.all_keys_for_usage_flags()
836 yield from self.all_keys_for_types()
837 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200838
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200839 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100840 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200841 # First build a list of all keys, then construct all the corresponding
842 # test cases. This allows all required information to be obtained in
843 # one go, which is a significant performance gain as the information
844 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200845 all_keys = list(self.generate_all_keys())
846 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200847 if key.location_value() != 0:
848 # Skip keys with a non-default location, because they
849 # require a driver and we currently have no mechanism to
850 # determine whether a driver is available.
851 continue
852 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100853
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200854class StorageFormatForward(StorageFormat):
855 """Storage format stability test cases for forward compatibility."""
856
857 def __init__(self, info: Information, version: int) -> None:
858 super().__init__(info, version, True)
859
860class StorageFormatV0(StorageFormat):
861 """Storage format stability test cases for version 0 compatibility."""
862
863 def __init__(self, info: Information) -> None:
864 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100865
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200866 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200867 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100868 yield from super().all_keys_for_usage_flags()
869 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200870
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200871 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200872 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200873 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200875 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200876 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200877 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200878 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200879 algorithm and key type combination.
880 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200881 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200882 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100883 usage_flags = ['PSA_KEY_USAGE_EXPORT']
884 material_usage_flags = usage_flags + [implyer_usage]
885 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200886 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200887 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100888 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
889 alg_expression = crypto_knowledge.short_expression(alg, 1)
890 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200891 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200892 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200893 key = StorageTestData(version=self.version,
894 id=1, lifetime=0x00000001,
895 type=key_type.expression, bits=bits,
896 usage=material_usage_flags,
897 expected_usage=expected_usage_flags,
898 without_implicit_usage=True,
899 alg=alg, alg2=alg2,
900 material=key_material,
901 description=description)
902 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200903
904 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200905 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200906 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800907 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200908 # must be filtered. Pair them with keywords created from its names.
909 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
910 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
911 keyword_translation = {
912 'ECDSA': 'ECC',
913 'ED[0-9]*.*' : 'EDWARDS'
914 }
915 exclusive_keywords = {
916 'EDWARDS': 'ECC'
917 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200918 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
919 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200920 alg_with_keys = {} #type: Dict[str, List[str]]
921 translation_table = str.maketrans('(', '_', ')')
922 for alg in algorithms:
923 # Generate keywords from the name of the algorithm
924 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
925 # Translate keywords for better matching with the key types
926 for keyword in alg_keywords.copy():
927 for pattern, replace in keyword_translation.items():
928 if re.match(pattern, keyword):
929 alg_keywords.remove(keyword)
930 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800931 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200932 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
933 continue
934
935 for key_type in key_types:
936 # Generate keywords from the of the key type
937 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
938
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800939 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200940 for keyword1, keyword2 in exclusive_keywords.items():
941 if keyword1 in key_type_keywords:
942 key_type_keywords.remove(keyword2)
943
944 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
945 not key_type_keywords.isdisjoint(alg_keywords):
946 if alg in alg_with_keys:
947 alg_with_keys[alg].append(key_type)
948 else:
949 alg_with_keys[alg] = [key_type]
950 return alg_with_keys
951
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200952 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200953 """Generate test keys for usage flag extensions."""
954 # Generate a key type and algorithm pair for each extendable usage
955 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800956 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200957 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200958
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200959 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
960 for alg in sorted(alg_with_keys):
961 for key_type in sorted(alg_with_keys[alg]):
962 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200963 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100964 if kt.is_public() and '_SIGN_' in usage:
965 # Can't sign with a public key
966 continue
967 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200968
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200969 def generate_all_keys(self) -> Iterator[StorageTestData]:
970 yield from super().generate_all_keys()
971 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200972
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200973class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100974 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100975 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200976 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100977 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200978 'test_suite_psa_crypto_generate_key.generated':
979 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100980 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100981 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200982 'test_suite_psa_crypto_op_fail.generated':
983 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100984 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200985 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100986 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200987 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100988 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
989
Werner Lewisfbb75e32022-08-24 11:30:03 +0100990 def __init__(self, options):
991 super().__init__(options)
992 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100993
Werner Lewisfbb75e32022-08-24 11:30:03 +0100994 def generate_target(self, name: str, *target_args) -> None:
995 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100996
997if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200998 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)