blob: cad788461cfa6ebd4a026267bc434f432454a001 [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():
350 yield test_case_for_key_generation(
351 kt.expression, bits,
352 finish_family_dependencies(generate_dependencies, bits),
353 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200354 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200355 )
356
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200357 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
358 """Generate test cases that exercise the generation of keys."""
359 for key_type in sorted(self.constructors.key_types):
360 if key_type in self.ECC_KEY_TYPES:
361 continue
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200362 if key_type in self.DH_KEY_TYPES:
363 continue
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200364 kt = crypto_knowledge.KeyType(key_type)
365 yield from self.test_cases_for_key_type_key_generation(kt)
366 for curve_family in sorted(self.constructors.ecc_curves):
367 for constr in self.ECC_KEY_TYPES:
368 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200369 yield from self.test_cases_for_key_type_key_generation(kt)
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200370 for dh_family in sorted(self.constructors.dh_groups):
371 for constr in self.DH_KEY_TYPES:
372 kt = crypto_knowledge.KeyType(constr, [dh_family])
373 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200374
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200375class OpFail:
376 """Generate test cases for operations that must fail."""
377 #pylint: disable=too-few-public-methods
378
Gilles Peskinecba28a72022-03-15 17:26:33 +0100379 class Reason(enum.Enum):
380 NOT_SUPPORTED = 0
381 INVALID = 1
382 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200383 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100384
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200385 def __init__(self, info: Information) -> None:
386 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100387 key_type_expressions = self.constructors.generate_expressions(
388 sorted(self.constructors.key_types)
389 )
390 self.key_types = [crypto_knowledge.KeyType(kt_expr)
391 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200392
Gilles Peskinecba28a72022-03-15 17:26:33 +0100393 def make_test_case(
394 self,
395 alg: crypto_knowledge.Algorithm,
396 category: crypto_knowledge.AlgorithmCategory,
397 reason: 'Reason',
398 kt: Optional[crypto_knowledge.KeyType] = None,
399 not_deps: FrozenSet[str] = frozenset(),
400 ) -> test_case.TestCase:
401 """Construct a failure test case for a one-key or keyless operation."""
402 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200403 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100404 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200405 if reason == self.Reason.NOT_SUPPORTED:
406 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
407 for dep in not_deps]
408 pretty_reason = '!' + '&'.join(sorted(short_deps))
409 else:
410 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100411 if kt:
412 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100413 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200414 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100415 key_type = ''
416 pretty_type = ''
417 tc.set_description('PSA {} {}: {}{}'
418 .format(category.name.lower(),
419 pretty_alg,
420 pretty_reason,
421 ' with ' + pretty_type if pretty_type else ''))
422 dependencies = automatic_dependencies(alg.base_expression, key_type)
Valerio Setti27c501a2023-06-27 16:58:52 +0200423 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100424 for i, dep in enumerate(dependencies):
425 if dep in not_deps:
426 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200427 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100428 tc.set_function(category.name.lower() + '_fail')
David Horstmannf0c75792023-01-24 18:53:15 +0000429 arguments = [] # type: List[str]
Gilles Peskinecba28a72022-03-15 17:26:33 +0100430 if kt:
431 key_material = kt.key_material(kt.sizes_to_test()[0])
432 arguments += [key_type, test_case.hex_string(key_material)]
433 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200434 if category.is_asymmetric():
435 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100436 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
437 'INVALID_ARGUMENT')
438 arguments.append('PSA_ERROR_' + error)
439 tc.set_arguments(arguments)
440 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200441
Gilles Peskinecba28a72022-03-15 17:26:33 +0100442 def no_key_test_cases(
443 self,
444 alg: crypto_knowledge.Algorithm,
445 category: crypto_knowledge.AlgorithmCategory,
446 ) -> Iterator[test_case.TestCase]:
447 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200448 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100449 # Compatible operation, unsupported algorithm
450 for dep in automatic_dependencies(alg.base_expression):
451 yield self.make_test_case(alg, category,
452 self.Reason.NOT_SUPPORTED,
453 not_deps=frozenset([dep]))
454 else:
455 # Incompatible operation, supported algorithm
456 yield self.make_test_case(alg, category, self.Reason.INVALID)
457
458 def one_key_test_cases(
459 self,
460 alg: crypto_knowledge.Algorithm,
461 category: crypto_knowledge.AlgorithmCategory,
462 ) -> Iterator[test_case.TestCase]:
463 """Generate failure test cases for one-key operations with the specified algorithm."""
464 for kt in self.key_types:
465 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200466 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100467 # Compatible key and operation, unsupported algorithm
468 for dep in automatic_dependencies(alg.base_expression):
469 yield self.make_test_case(alg, category,
470 self.Reason.NOT_SUPPORTED,
471 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200472 # Public key for a private-key operation
473 if category.is_asymmetric() and kt.is_public():
474 yield self.make_test_case(alg, category,
475 self.Reason.PUBLIC,
476 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100477 elif key_is_compatible:
478 # Compatible key, incompatible operation, supported algorithm
479 yield self.make_test_case(alg, category,
480 self.Reason.INVALID,
481 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200482 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100483 # Incompatible key, compatible operation, supported algorithm
484 yield self.make_test_case(alg, category,
485 self.Reason.INCOMPATIBLE,
486 kt=kt)
487 else:
488 # Incompatible key and operation. Don't test cases where
489 # multiple things are wrong, to keep the number of test
490 # cases reasonable.
491 pass
492
493 def test_cases_for_algorithm(
494 self,
495 alg: crypto_knowledge.Algorithm,
496 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200497 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100498 for category in crypto_knowledge.AlgorithmCategory:
499 if category == crypto_knowledge.AlgorithmCategory.PAKE:
500 # PAKE operations are not implemented yet
501 pass
502 elif category.requires_key():
503 yield from self.one_key_test_cases(alg, category)
504 else:
505 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200506
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200507 def all_test_cases(self) -> Iterator[test_case.TestCase]:
508 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200509 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100510 for expr in self.constructors.generate_expressions(algorithms):
511 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200512 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200513
514
Gilles Peskine897dff92021-03-10 15:03:44 +0100515class StorageKey(psa_storage.Key):
516 """Representation of a key for storage format testing."""
517
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200518 IMPLICIT_USAGE_FLAGS = {
519 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
520 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
521 } #type: Dict[str, str]
522 """Mapping of usage flags to the flags that they imply."""
523
524 def __init__(
525 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100526 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200527 without_implicit_usage: Optional[bool] = False,
528 **kwargs
529 ) -> None:
530 """Prepare to generate a key.
531
532 * `usage` : The usage flags used for the key.
Tom Cosgrove1797b052022-12-04 17:19:59 +0000533 * `without_implicit_usage`: Flag to define to apply the usage extension
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200534 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100535 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200536 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100537 for flag in sorted(usage_flags):
538 if flag in self.IMPLICIT_USAGE_FLAGS:
539 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
540 if usage_flags:
541 usage_expression = ' | '.join(sorted(usage_flags))
542 else:
543 usage_expression = '0'
544 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200545
546class StorageTestData(StorageKey):
547 """Representation of test case data for storage format testing."""
548
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200549 def __init__(
550 self,
551 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100552 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200553 **kwargs
554 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200555 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200556
Tom Cosgrove1797b052022-12-04 17:19:59 +0000557 * `description` : used for the test case names
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200558 * `expected_usage`: the usage flags generated as the expected usage flags
559 in the test cases. CAn differ from the usage flags
560 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200561 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100562 super().__init__(**kwargs)
563 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100564 if expected_usage is None:
565 self.expected_usage = self.usage #type: psa_storage.Expr
566 elif expected_usage:
567 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
568 else:
569 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200570
Gilles Peskine897dff92021-03-10 15:03:44 +0100571class StorageFormat:
572 """Storage format stability test cases."""
573
574 def __init__(self, info: Information, version: int, forward: bool) -> None:
575 """Prepare to generate test cases for storage format stability.
576
577 * `info`: information about the API. See the `Information` class.
578 * `version`: the storage format version to generate test cases for.
579 * `forward`: if true, generate forward compatibility test cases which
580 save a key and check that its representation is as intended. Otherwise
581 generate backward compatibility test cases which inject a key
582 representation and check that it can be read and used.
583 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200584 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
585 self.version = version #type: int
586 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100587
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100588 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100589 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100590 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100591 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100592 cls,
593 key_type: psa_storage.Expr, bits: int,
594 alg: psa_storage.Expr
595 ) -> bool:
Gilles Peskinecafda872022-12-15 23:03:19 +0100596 """Whether to exercise the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100597
598 Normally only the type and algorithm matter for compatibility, and
599 this is handled in crypto_knowledge.KeyType.can_do(). This function
600 exists to detect exceptional cases. Exceptional cases detected here
601 are not tested in OpFail and should therefore have manually written
602 test cases.
603 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100604 # Some test keys have the RAW_DATA type and attributes that don't
605 # necessarily make sense. We do this to validate numerical
606 # encodings of the attributes.
607 # Raw data keys have no useful exercise anyway so there is no
608 # loss of test coverage.
609 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
610 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100611 # OAEP requires room for two hashes plus wrapping
612 m = cls.RSA_OAEP_RE.match(alg.string)
613 if m:
614 hash_alg = m.group(1)
615 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
616 key_length = (bits + 7) // 8
617 # Leave enough room for at least one byte of plaintext
618 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100619 # There's nothing wrong with ECC keys on Brainpool curves,
620 # but operations with them are very slow. So we only exercise them
621 # with a single algorithm, not with all possible hashes. We do
622 # exercise other curves with all algorithms so test coverage is
623 # perfectly adequate like this.
624 m = cls.BRAINPOOL_RE.match(key_type.string)
625 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
626 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100627 return True
628
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200629 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100630 """Construct a storage format test case for the given key.
631
632 If ``forward`` is true, generate a forward compatibility test case:
633 create a key and validate that it has the expected representation.
634 Otherwise generate a backward compatibility test case: inject the
635 key representation into storage and validate that it can be read
636 correctly.
637 """
638 verb = 'save' if self.forward else 'read'
639 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100640 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100641 dependencies = automatic_dependencies(
642 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100643 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100644 )
645 dependencies = finish_family_dependencies(dependencies, key.bits)
Yanray Wang5dd429c2023-05-10 09:58:46 +0800646 dependencies += generate_key_dependencies(key.description)
Valerio Setti27c501a2023-06-27 16:58:52 +0200647 dependencies = fix_key_pair_dependencies(dependencies, 'BASIC')
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100648 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100649 tc.set_function('key_storage_' + verb)
650 if self.forward:
651 extra_arguments = []
652 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200653 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100654 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200655 flags.append('TEST_FLAG_EXERCISE')
656 if 'READ_ONLY' in key.lifetime.string:
657 flags.append('TEST_FLAG_READ_ONLY')
658 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100659 tc.set_arguments([key.lifetime.string,
660 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100661 key.expected_usage.string,
662 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100663 '"' + key.material.hex() + '"',
664 '"' + key.hex() + '"',
665 *extra_arguments])
666 return tc
667
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200668 def key_for_lifetime(
669 self,
670 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200671 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200672 """Construct a test key for the given lifetime."""
673 short = lifetime
674 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
675 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100676 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200677 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200678 key = StorageTestData(version=self.version,
679 id=1, lifetime=lifetime,
680 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100681 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200682 material=b'L',
683 description=description)
684 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200685
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200686 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200687 """Generate test keys covering lifetimes."""
688 lifetimes = sorted(self.constructors.lifetimes)
689 expressions = self.constructors.generate_expressions(lifetimes)
690 for lifetime in expressions:
691 # Don't attempt to create or load a volatile key in storage
692 if 'VOLATILE' in lifetime:
693 continue
694 # Don't attempt to create a read-only key in storage,
695 # but do attempt to load one.
696 if 'READ_ONLY' in lifetime and self.forward:
697 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200698 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200699
Gilles Peskinef7614272022-02-24 18:58:08 +0100700 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100701 self,
702 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200703 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100704 test_implicit_usage: Optional[bool] = True
705 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100706 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100707 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100708 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200709 key1 = StorageTestData(version=self.version,
710 id=1, lifetime=0x00000001,
711 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100712 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100713 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100714 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200715 material=b'K',
716 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100717 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100718 usage_expr = key1.expected_usage.string
719 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100720 else:
721 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100722 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100723
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200724 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100725 """Generate test keys covering usage flags."""
726 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100727 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200728 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100729 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200730 for flag1, flag2 in zip(known_flags,
731 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100732 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200733
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200734 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200735 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100736 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200737
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200738 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200739 yield from self.generate_keys_for_usage_flags()
740 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100741
Gilles Peskine7de7c102021-04-29 22:28:07 +0200742 def key_for_type_and_alg(
743 self,
744 kt: crypto_knowledge.KeyType,
745 bits: int,
746 alg: Optional[crypto_knowledge.Algorithm] = None,
747 ) -> StorageTestData:
748 """Construct a test key of the given type.
749
750 If alg is not None, this key allows it.
751 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100752 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100753 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200754 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100755 if alg is not None:
756 alg1 = alg.expression
757 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200758 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100759 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200760 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100761 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200762 key = StorageTestData(version=self.version,
763 id=1, lifetime=0x00000001,
764 type=kt.expression, bits=bits,
765 usage=usage_flags, alg=alg1, alg2=alg2,
766 material=key_material,
767 description=description)
768 return key
769
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100770 def keys_for_type(
771 self,
772 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200773 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200774 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200775 """Generate test keys for the given key type."""
776 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100777 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200778 # Test a non-exercisable key, as well as exercisable keys for
779 # each compatible algorithm.
780 # To do: test reading a key from storage with an incompatible
781 # or unsupported algorithm.
782 yield self.key_for_type_and_alg(kt, bits)
783 compatible_algorithms = [alg for alg in all_algorithms
784 if kt.can_do(alg)]
785 for alg in compatible_algorithms:
786 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100787
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200788 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100789 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200790 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200791 all_algorithms = [crypto_knowledge.Algorithm(alg)
792 for alg in self.constructors.generate_expressions(
793 sorted(self.constructors.algorithms)
794 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200795 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200796 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100797
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200798 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200799 """Generate test keys for the encoding of the specified algorithm."""
800 # These test cases only validate the encoding of algorithms, not
801 # whether the key read from storage is suitable for an operation.
802 # `keys_for_types` generate read tests with an algorithm and a
803 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100804 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100805 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200806 key1 = StorageTestData(version=self.version,
807 id=1, lifetime=0x00000001,
808 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
809 usage=usage, alg=alg, alg2=0,
810 material=b'K',
811 description='alg: ' + descr)
812 yield key1
813 key2 = StorageTestData(version=self.version,
814 id=1, lifetime=0x00000001,
815 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
816 usage=usage, alg=0, alg2=alg,
817 material=b'L',
818 description='alg2: ' + descr)
819 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100820
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200821 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100822 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200823 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200824 for alg in self.constructors.generate_expressions(algorithms):
825 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100826
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200827 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200828 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200829 yield from self.all_keys_for_lifetimes()
830 yield from self.all_keys_for_usage_flags()
831 yield from self.all_keys_for_types()
832 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200833
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200834 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100835 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200836 # First build a list of all keys, then construct all the corresponding
837 # test cases. This allows all required information to be obtained in
838 # one go, which is a significant performance gain as the information
839 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200840 all_keys = list(self.generate_all_keys())
841 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200842 if key.location_value() != 0:
843 # Skip keys with a non-default location, because they
844 # require a driver and we currently have no mechanism to
845 # determine whether a driver is available.
846 continue
847 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100848
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200849class StorageFormatForward(StorageFormat):
850 """Storage format stability test cases for forward compatibility."""
851
852 def __init__(self, info: Information, version: int) -> None:
853 super().__init__(info, version, True)
854
855class StorageFormatV0(StorageFormat):
856 """Storage format stability test cases for version 0 compatibility."""
857
858 def __init__(self, info: Information) -> None:
859 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100860
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200861 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200862 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100863 yield from super().all_keys_for_usage_flags()
864 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200865
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200866 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200867 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200868 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200869 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200870 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200871 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200872 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200873 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 algorithm and key type combination.
875 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200876 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200877 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100878 usage_flags = ['PSA_KEY_USAGE_EXPORT']
879 material_usage_flags = usage_flags + [implyer_usage]
880 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200881 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200882 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100883 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
884 alg_expression = crypto_knowledge.short_expression(alg, 1)
885 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200886 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200887 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200888 key = StorageTestData(version=self.version,
889 id=1, lifetime=0x00000001,
890 type=key_type.expression, bits=bits,
891 usage=material_usage_flags,
892 expected_usage=expected_usage_flags,
893 without_implicit_usage=True,
894 alg=alg, alg2=alg2,
895 material=key_material,
896 description=description)
897 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200898
899 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200900 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200901 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800902 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200903 # must be filtered. Pair them with keywords created from its names.
904 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
905 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
906 keyword_translation = {
907 'ECDSA': 'ECC',
908 'ED[0-9]*.*' : 'EDWARDS'
909 }
910 exclusive_keywords = {
911 'EDWARDS': 'ECC'
912 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200913 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
914 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200915 alg_with_keys = {} #type: Dict[str, List[str]]
916 translation_table = str.maketrans('(', '_', ')')
917 for alg in algorithms:
918 # Generate keywords from the name of the algorithm
919 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
920 # Translate keywords for better matching with the key types
921 for keyword in alg_keywords.copy():
922 for pattern, replace in keyword_translation.items():
923 if re.match(pattern, keyword):
924 alg_keywords.remove(keyword)
925 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800926 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200927 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
928 continue
929
930 for key_type in key_types:
931 # Generate keywords from the of the key type
932 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
933
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800934 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200935 for keyword1, keyword2 in exclusive_keywords.items():
936 if keyword1 in key_type_keywords:
937 key_type_keywords.remove(keyword2)
938
939 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
940 not key_type_keywords.isdisjoint(alg_keywords):
941 if alg in alg_with_keys:
942 alg_with_keys[alg].append(key_type)
943 else:
944 alg_with_keys[alg] = [key_type]
945 return alg_with_keys
946
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200947 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200948 """Generate test keys for usage flag extensions."""
949 # Generate a key type and algorithm pair for each extendable usage
950 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800951 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200952 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200953
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200954 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
955 for alg in sorted(alg_with_keys):
956 for key_type in sorted(alg_with_keys[alg]):
957 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200958 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100959 if kt.is_public() and '_SIGN_' in usage:
960 # Can't sign with a public key
961 continue
962 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200963
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200964 def generate_all_keys(self) -> Iterator[StorageTestData]:
965 yield from super().generate_all_keys()
966 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200967
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200968class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100969 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100970 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200971 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100972 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200973 'test_suite_psa_crypto_generate_key.generated':
974 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100975 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine0e9e4422022-12-15 22:14:28 +0100976 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200977 'test_suite_psa_crypto_op_fail.generated':
978 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100979 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200980 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100981 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200982 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100983 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
984
Werner Lewisfbb75e32022-08-24 11:30:03 +0100985 def __init__(self, options):
986 super().__init__(options)
987 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100988
Werner Lewisfbb75e32022-08-24 11:30:03 +0100989 def generate_target(self, name: str, *target_args) -> None:
990 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100991
992if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200993 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)