blob: 2f0900757b15a4993bbbeb28c6802e1448ed76e4 [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
Gilles Peskined169d602021-02-16 14:16:25 +010080# A temporary hack: at the time of writing, not all dependency symbols
81# are implemented yet. Skip test cases for which the dependency symbols are
82# not available. Once all dependency symbols are available, this hack must
83# be removed so that a bug in the dependency symbols proprely leads to a test
84# failure.
85def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
86 return frozenset(symbol
87 for line in open(filename)
88 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020089_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010090def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +020091 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
92 if _implemented_dependencies is None:
93 _implemented_dependencies = \
94 read_implemented_dependencies('include/psa/crypto_config.h')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +020095 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +010096 for dep in dependencies):
97 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
98
Gilles Peskine14e428f2021-01-26 22:19:21 +010099
Gilles Peskineb94ea512021-03-10 02:12:08 +0100100class Information:
101 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100102
Gilles Peskineb94ea512021-03-10 02:12:08 +0100103 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100104 self.constructors = self.read_psa_interface()
105
106 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100107 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200108 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100109 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200110 # Mbed TLS doesn't support finite-field DH yet and will not support
111 # finite-field DSA. Don't attempt to generate any related test case.
112 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
113 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100114 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
115 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100116
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200117 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100118 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200119 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100120 header_file_names = ['include/psa/crypto_values.h',
121 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200122 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100123 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200124 constructors.parse_header(header_file_name)
125 for test_cases in test_suites:
126 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100127 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200128 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100129 return constructors
130
Gilles Peskine14e428f2021-01-26 22:19:21 +0100131
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200132def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100133 verb: str, key_type: str, bits: int,
134 dependencies: List[str],
135 *args: str,
136 param_descr: str = ''
137) -> test_case.TestCase:
138 """Return one test case exercising a key creation method
139 for an unsupported key type or size.
140 """
141 hack_dependencies_not_implemented(dependencies)
142 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100143 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100144 adverb = 'not' if dependencies else 'never'
145 if param_descr:
146 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200147 tc.set_description('PSA {} {} {}-bit {} supported'
148 .format(verb, short_key_type, bits, adverb))
149 tc.set_dependencies(dependencies)
150 tc.set_function(verb + '_not_supported')
151 tc.set_arguments([key_type] + list(args))
152 return tc
153
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154class NotSupported:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200155 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100156
157 def __init__(self, info: Information) -> None:
158 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100159
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100160 ALWAYS_SUPPORTED = frozenset([
161 'PSA_KEY_TYPE_DERIVE',
162 'PSA_KEY_TYPE_RAW_DATA',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200163 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100164 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100165 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100166 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100167 kt: crypto_knowledge.KeyType,
168 param: Optional[int] = None,
169 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100170 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200171 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100172
173 If param is present and not None, emit test cases conditioned on this
174 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200175 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100176 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100177 if kt.name in self.ALWAYS_SUPPORTED:
178 # Don't generate test cases for key types that are always supported.
179 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100180 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100181 import_dependencies = [('!' if param is None else '') +
182 psa_want_symbol(kt.name)]
183 if kt.params is not None:
184 import_dependencies += [('!' if param == i else '') +
185 psa_want_symbol(sym)
186 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100187 if kt.name.endswith('_PUBLIC_KEY'):
188 generate_dependencies = []
189 else:
190 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100191 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200192 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100193 'import', kt.expression, bits,
194 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100195 test_case.hex_string(kt.key_material(bits)),
196 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100197 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100198 if not generate_dependencies and param is not None:
199 # If generation is impossible for this key type, rather than
200 # supported or not depending on implementation capabilities,
201 # only generate the test case once.
202 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100203 # For public key we expect that key generation fails with
204 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100205 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200206 yield test_case_for_key_type_not_supported(
207 'generate', kt.expression, bits,
208 finish_family_dependencies(generate_dependencies, bits),
209 str(bits),
210 param_descr=param_descr,
211 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200214 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
215 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
216
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200220 if key_type in self.ECC_KEY_TYPES:
221 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200225 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100231
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200232def test_case_for_key_generation(
233 key_type: str, bits: int,
234 dependencies: List[str],
235 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200236 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200237) -> test_case.TestCase:
238 """Return one test case exercising a key generation.
239 """
240 hack_dependencies_not_implemented(dependencies)
241 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100242 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200243 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200244 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200245 tc.set_dependencies(dependencies)
246 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100247 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200248
249 return tc
250
251class KeyGenerate:
252 """Generate positive and negative (invalid argument) test cases for key generation."""
253
254 def __init__(self, info: Information) -> None:
255 self.constructors = info.constructors
256
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200257 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
258 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
259
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100260 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200261 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200262 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200263 ) -> Iterator[test_case.TestCase]:
264 """Return test cases exercising key generation.
265
266 All key types can be generated except for public keys. For public key
267 PSA_ERROR_INVALID_ARGUMENT status is expected.
268 """
269 result = 'PSA_SUCCESS'
270
271 import_dependencies = [psa_want_symbol(kt.name)]
272 if kt.params is not None:
273 import_dependencies += [psa_want_symbol(sym)
274 for i, sym in enumerate(kt.params)]
275 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100276 # The library checks whether the key type is a public key generically,
277 # before it reaches a point where it needs support for the specific key
278 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200279 generate_dependencies = []
280 result = 'PSA_ERROR_INVALID_ARGUMENT'
281 else:
282 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100283 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200284 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200285 for bits in kt.sizes_to_test():
286 yield test_case_for_key_generation(
287 kt.expression, bits,
288 finish_family_dependencies(generate_dependencies, bits),
289 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200290 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200291 )
292
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200293 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
294 """Generate test cases that exercise the generation of keys."""
295 for key_type in sorted(self.constructors.key_types):
296 if key_type in self.ECC_KEY_TYPES:
297 continue
298 kt = crypto_knowledge.KeyType(key_type)
299 yield from self.test_cases_for_key_type_key_generation(kt)
300 for curve_family in sorted(self.constructors.ecc_curves):
301 for constr in self.ECC_KEY_TYPES:
302 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200303 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200304
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200305class OpFail:
306 """Generate test cases for operations that must fail."""
307 #pylint: disable=too-few-public-methods
308
Gilles Peskinecba28a72022-03-15 17:26:33 +0100309 class Reason(enum.Enum):
310 NOT_SUPPORTED = 0
311 INVALID = 1
312 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200313 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100314
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200315 def __init__(self, info: Information) -> None:
316 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100317 key_type_expressions = self.constructors.generate_expressions(
318 sorted(self.constructors.key_types)
319 )
320 self.key_types = [crypto_knowledge.KeyType(kt_expr)
321 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200322
Gilles Peskinecba28a72022-03-15 17:26:33 +0100323 def make_test_case(
324 self,
325 alg: crypto_knowledge.Algorithm,
326 category: crypto_knowledge.AlgorithmCategory,
327 reason: 'Reason',
328 kt: Optional[crypto_knowledge.KeyType] = None,
329 not_deps: FrozenSet[str] = frozenset(),
330 ) -> test_case.TestCase:
331 """Construct a failure test case for a one-key or keyless operation."""
332 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200333 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100334 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200335 if reason == self.Reason.NOT_SUPPORTED:
336 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
337 for dep in not_deps]
338 pretty_reason = '!' + '&'.join(sorted(short_deps))
339 else:
340 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100341 if kt:
342 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100343 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200344 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100345 key_type = ''
346 pretty_type = ''
347 tc.set_description('PSA {} {}: {}{}'
348 .format(category.name.lower(),
349 pretty_alg,
350 pretty_reason,
351 ' with ' + pretty_type if pretty_type else ''))
352 dependencies = automatic_dependencies(alg.base_expression, key_type)
353 for i, dep in enumerate(dependencies):
354 if dep in not_deps:
355 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200356 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100357 tc.set_function(category.name.lower() + '_fail')
358 arguments = []
359 if kt:
360 key_material = kt.key_material(kt.sizes_to_test()[0])
361 arguments += [key_type, test_case.hex_string(key_material)]
362 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200363 if category.is_asymmetric():
364 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100365 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
366 'INVALID_ARGUMENT')
367 arguments.append('PSA_ERROR_' + error)
368 tc.set_arguments(arguments)
369 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200370
Gilles Peskinecba28a72022-03-15 17:26:33 +0100371 def no_key_test_cases(
372 self,
373 alg: crypto_knowledge.Algorithm,
374 category: crypto_knowledge.AlgorithmCategory,
375 ) -> Iterator[test_case.TestCase]:
376 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200377 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100378 # Compatible operation, unsupported algorithm
379 for dep in automatic_dependencies(alg.base_expression):
380 yield self.make_test_case(alg, category,
381 self.Reason.NOT_SUPPORTED,
382 not_deps=frozenset([dep]))
383 else:
384 # Incompatible operation, supported algorithm
385 yield self.make_test_case(alg, category, self.Reason.INVALID)
386
387 def one_key_test_cases(
388 self,
389 alg: crypto_knowledge.Algorithm,
390 category: crypto_knowledge.AlgorithmCategory,
391 ) -> Iterator[test_case.TestCase]:
392 """Generate failure test cases for one-key operations with the specified algorithm."""
393 for kt in self.key_types:
394 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200395 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100396 # Compatible key and operation, unsupported algorithm
397 for dep in automatic_dependencies(alg.base_expression):
398 yield self.make_test_case(alg, category,
399 self.Reason.NOT_SUPPORTED,
400 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200401 # Public key for a private-key operation
402 if category.is_asymmetric() and kt.is_public():
403 yield self.make_test_case(alg, category,
404 self.Reason.PUBLIC,
405 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100406 elif key_is_compatible:
407 # Compatible key, incompatible operation, supported algorithm
408 yield self.make_test_case(alg, category,
409 self.Reason.INVALID,
410 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200411 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100412 # Incompatible key, compatible operation, supported algorithm
413 yield self.make_test_case(alg, category,
414 self.Reason.INCOMPATIBLE,
415 kt=kt)
416 else:
417 # Incompatible key and operation. Don't test cases where
418 # multiple things are wrong, to keep the number of test
419 # cases reasonable.
420 pass
421
422 def test_cases_for_algorithm(
423 self,
424 alg: crypto_knowledge.Algorithm,
425 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200426 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100427 for category in crypto_knowledge.AlgorithmCategory:
428 if category == crypto_knowledge.AlgorithmCategory.PAKE:
429 # PAKE operations are not implemented yet
430 pass
431 elif category.requires_key():
432 yield from self.one_key_test_cases(alg, category)
433 else:
434 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200435
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200436 def all_test_cases(self) -> Iterator[test_case.TestCase]:
437 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200438 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100439 for expr in self.constructors.generate_expressions(algorithms):
440 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200441 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200442
443
Gilles Peskine897dff92021-03-10 15:03:44 +0100444class StorageKey(psa_storage.Key):
445 """Representation of a key for storage format testing."""
446
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200447 IMPLICIT_USAGE_FLAGS = {
448 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
449 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
450 } #type: Dict[str, str]
451 """Mapping of usage flags to the flags that they imply."""
452
453 def __init__(
454 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100455 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200456 without_implicit_usage: Optional[bool] = False,
457 **kwargs
458 ) -> None:
459 """Prepare to generate a key.
460
461 * `usage` : The usage flags used for the key.
462 * `without_implicit_usage`: Flag to defide to apply the usage extension
463 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100464 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200465 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100466 for flag in sorted(usage_flags):
467 if flag in self.IMPLICIT_USAGE_FLAGS:
468 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
469 if usage_flags:
470 usage_expression = ' | '.join(sorted(usage_flags))
471 else:
472 usage_expression = '0'
473 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200474
475class StorageTestData(StorageKey):
476 """Representation of test case data for storage format testing."""
477
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200478 def __init__(
479 self,
480 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100481 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200482 **kwargs
483 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200484 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200485
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200486 * `description` : used for the the test case names
487 * `expected_usage`: the usage flags generated as the expected usage flags
488 in the test cases. CAn differ from the usage flags
489 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200490 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100491 super().__init__(**kwargs)
492 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100493 if expected_usage is None:
494 self.expected_usage = self.usage #type: psa_storage.Expr
495 elif expected_usage:
496 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
497 else:
498 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200499
Gilles Peskine897dff92021-03-10 15:03:44 +0100500class StorageFormat:
501 """Storage format stability test cases."""
502
503 def __init__(self, info: Information, version: int, forward: bool) -> None:
504 """Prepare to generate test cases for storage format stability.
505
506 * `info`: information about the API. See the `Information` class.
507 * `version`: the storage format version to generate test cases for.
508 * `forward`: if true, generate forward compatibility test cases which
509 save a key and check that its representation is as intended. Otherwise
510 generate backward compatibility test cases which inject a key
511 representation and check that it can be read and used.
512 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200513 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
514 self.version = version #type: int
515 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100516
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100517 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100518 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100519 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100520 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100521 cls,
522 key_type: psa_storage.Expr, bits: int,
523 alg: psa_storage.Expr
524 ) -> bool:
Gilles Peskine61548d12022-03-19 15:36:09 +0100525 """Whether to the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100526
527 Normally only the type and algorithm matter for compatibility, and
528 this is handled in crypto_knowledge.KeyType.can_do(). This function
529 exists to detect exceptional cases. Exceptional cases detected here
530 are not tested in OpFail and should therefore have manually written
531 test cases.
532 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100533 # Some test keys have the RAW_DATA type and attributes that don't
534 # necessarily make sense. We do this to validate numerical
535 # encodings of the attributes.
536 # Raw data keys have no useful exercise anyway so there is no
537 # loss of test coverage.
538 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
539 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100540 # OAEP requires room for two hashes plus wrapping
541 m = cls.RSA_OAEP_RE.match(alg.string)
542 if m:
543 hash_alg = m.group(1)
544 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
545 key_length = (bits + 7) // 8
546 # Leave enough room for at least one byte of plaintext
547 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100548 # There's nothing wrong with ECC keys on Brainpool curves,
549 # but operations with them are very slow. So we only exercise them
550 # with a single algorithm, not with all possible hashes. We do
551 # exercise other curves with all algorithms so test coverage is
552 # perfectly adequate like this.
553 m = cls.BRAINPOOL_RE.match(key_type.string)
554 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
555 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100556 return True
557
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200558 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100559 """Construct a storage format test case for the given key.
560
561 If ``forward`` is true, generate a forward compatibility test case:
562 create a key and validate that it has the expected representation.
563 Otherwise generate a backward compatibility test case: inject the
564 key representation into storage and validate that it can be read
565 correctly.
566 """
567 verb = 'save' if self.forward else 'read'
568 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100569 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100570 dependencies = automatic_dependencies(
571 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100572 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100573 )
574 dependencies = finish_family_dependencies(dependencies, key.bits)
575 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100576 tc.set_function('key_storage_' + verb)
577 if self.forward:
578 extra_arguments = []
579 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200580 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100581 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200582 flags.append('TEST_FLAG_EXERCISE')
583 if 'READ_ONLY' in key.lifetime.string:
584 flags.append('TEST_FLAG_READ_ONLY')
585 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100586 tc.set_arguments([key.lifetime.string,
587 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100588 key.expected_usage.string,
589 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100590 '"' + key.material.hex() + '"',
591 '"' + key.hex() + '"',
592 *extra_arguments])
593 return tc
594
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200595 def key_for_lifetime(
596 self,
597 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200598 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200599 """Construct a test key for the given lifetime."""
600 short = lifetime
601 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
602 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100603 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200604 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200605 key = StorageTestData(version=self.version,
606 id=1, lifetime=lifetime,
607 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100608 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200609 material=b'L',
610 description=description)
611 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200612
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200613 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200614 """Generate test keys covering lifetimes."""
615 lifetimes = sorted(self.constructors.lifetimes)
616 expressions = self.constructors.generate_expressions(lifetimes)
617 for lifetime in expressions:
618 # Don't attempt to create or load a volatile key in storage
619 if 'VOLATILE' in lifetime:
620 continue
621 # Don't attempt to create a read-only key in storage,
622 # but do attempt to load one.
623 if 'READ_ONLY' in lifetime and self.forward:
624 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200625 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200626
Gilles Peskinef7614272022-02-24 18:58:08 +0100627 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100628 self,
629 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200630 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100631 test_implicit_usage: Optional[bool] = True
632 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100633 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100634 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100635 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200636 key1 = StorageTestData(version=self.version,
637 id=1, lifetime=0x00000001,
638 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100639 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100640 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100641 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200642 material=b'K',
643 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100644 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100645 usage_expr = key1.expected_usage.string
646 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100647 else:
648 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100649 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100650
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100652 """Generate test keys covering usage flags."""
653 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100654 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200655 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100656 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200657 for flag1, flag2 in zip(known_flags,
658 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100659 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200660
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200661 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200662 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100663 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200664
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200665 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200666 yield from self.generate_keys_for_usage_flags()
667 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100668
Gilles Peskine7de7c102021-04-29 22:28:07 +0200669 def key_for_type_and_alg(
670 self,
671 kt: crypto_knowledge.KeyType,
672 bits: int,
673 alg: Optional[crypto_knowledge.Algorithm] = None,
674 ) -> StorageTestData:
675 """Construct a test key of the given type.
676
677 If alg is not None, this key allows it.
678 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100679 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100680 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200681 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100682 if alg is not None:
683 alg1 = alg.expression
684 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200685 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100686 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200687 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100688 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200689 key = StorageTestData(version=self.version,
690 id=1, lifetime=0x00000001,
691 type=kt.expression, bits=bits,
692 usage=usage_flags, alg=alg1, alg2=alg2,
693 material=key_material,
694 description=description)
695 return key
696
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100697 def keys_for_type(
698 self,
699 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200700 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200701 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200702 """Generate test keys for the given key type."""
703 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100704 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200705 # Test a non-exercisable key, as well as exercisable keys for
706 # each compatible algorithm.
707 # To do: test reading a key from storage with an incompatible
708 # or unsupported algorithm.
709 yield self.key_for_type_and_alg(kt, bits)
710 compatible_algorithms = [alg for alg in all_algorithms
711 if kt.can_do(alg)]
712 for alg in compatible_algorithms:
713 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100714
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200715 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100716 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200717 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200718 all_algorithms = [crypto_knowledge.Algorithm(alg)
719 for alg in self.constructors.generate_expressions(
720 sorted(self.constructors.algorithms)
721 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200722 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200723 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100724
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200725 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200726 """Generate test keys for the encoding of the specified algorithm."""
727 # These test cases only validate the encoding of algorithms, not
728 # whether the key read from storage is suitable for an operation.
729 # `keys_for_types` generate read tests with an algorithm and a
730 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100731 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100732 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200733 key1 = StorageTestData(version=self.version,
734 id=1, lifetime=0x00000001,
735 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
736 usage=usage, alg=alg, alg2=0,
737 material=b'K',
738 description='alg: ' + descr)
739 yield key1
740 key2 = StorageTestData(version=self.version,
741 id=1, lifetime=0x00000001,
742 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
743 usage=usage, alg=0, alg2=alg,
744 material=b'L',
745 description='alg2: ' + descr)
746 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100747
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200748 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100749 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200750 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200751 for alg in self.constructors.generate_expressions(algorithms):
752 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100753
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200754 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200755 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200756 yield from self.all_keys_for_lifetimes()
757 yield from self.all_keys_for_usage_flags()
758 yield from self.all_keys_for_types()
759 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200760
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200761 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100762 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200763 # First build a list of all keys, then construct all the corresponding
764 # test cases. This allows all required information to be obtained in
765 # one go, which is a significant performance gain as the information
766 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200767 all_keys = list(self.generate_all_keys())
768 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200769 if key.location_value() != 0:
770 # Skip keys with a non-default location, because they
771 # require a driver and we currently have no mechanism to
772 # determine whether a driver is available.
773 continue
774 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100775
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200776class StorageFormatForward(StorageFormat):
777 """Storage format stability test cases for forward compatibility."""
778
779 def __init__(self, info: Information, version: int) -> None:
780 super().__init__(info, version, True)
781
782class StorageFormatV0(StorageFormat):
783 """Storage format stability test cases for version 0 compatibility."""
784
785 def __init__(self, info: Information) -> None:
786 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100787
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200788 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200789 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100790 yield from super().all_keys_for_usage_flags()
791 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200792
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200793 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200794 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200795 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200796 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200797 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200798 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200799 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200800 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200801 algorithm and key type combination.
802 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200803 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200804 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100805 usage_flags = ['PSA_KEY_USAGE_EXPORT']
806 material_usage_flags = usage_flags + [implyer_usage]
807 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200808 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200809 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100810 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
811 alg_expression = crypto_knowledge.short_expression(alg, 1)
812 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200813 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200814 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200815 key = StorageTestData(version=self.version,
816 id=1, lifetime=0x00000001,
817 type=key_type.expression, bits=bits,
818 usage=material_usage_flags,
819 expected_usage=expected_usage_flags,
820 without_implicit_usage=True,
821 alg=alg, alg2=alg2,
822 material=key_material,
823 description=description)
824 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200825
826 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200827 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200828 """Match possible key types for sign algorithms."""
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800829 # To create a valid combination both the algorithms and key types
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200830 # must be filtered. Pair them with keywords created from its names.
831 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
832 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
833 keyword_translation = {
834 'ECDSA': 'ECC',
835 'ED[0-9]*.*' : 'EDWARDS'
836 }
837 exclusive_keywords = {
838 'EDWARDS': 'ECC'
839 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200840 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
841 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200842 alg_with_keys = {} #type: Dict[str, List[str]]
843 translation_table = str.maketrans('(', '_', ')')
844 for alg in algorithms:
845 # Generate keywords from the name of the algorithm
846 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
847 # Translate keywords for better matching with the key types
848 for keyword in alg_keywords.copy():
849 for pattern, replace in keyword_translation.items():
850 if re.match(pattern, keyword):
851 alg_keywords.remove(keyword)
852 alg_keywords.add(replace)
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800853 # Filter out incompatible algorithms
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200854 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
855 continue
856
857 for key_type in key_types:
858 # Generate keywords from the of the key type
859 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
860
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800861 # Remove ambiguous keywords
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200862 for keyword1, keyword2 in exclusive_keywords.items():
863 if keyword1 in key_type_keywords:
864 key_type_keywords.remove(keyword2)
865
866 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
867 not key_type_keywords.isdisjoint(alg_keywords):
868 if alg in alg_with_keys:
869 alg_with_keys[alg].append(key_type)
870 else:
871 alg_with_keys[alg] = [key_type]
872 return alg_with_keys
873
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200874 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200875 """Generate test keys for usage flag extensions."""
876 # Generate a key type and algorithm pair for each extendable usage
877 # flag to generate a valid key for exercising. The key is generated
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800878 # without usage extension to check the extension compatibility.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200879 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200880
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200881 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
882 for alg in sorted(alg_with_keys):
883 for key_type in sorted(alg_with_keys[alg]):
884 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200885 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100886 if kt.is_public() and '_SIGN_' in usage:
887 # Can't sign with a public key
888 continue
889 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200890
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200891 def generate_all_keys(self) -> Iterator[StorageTestData]:
892 yield from super().generate_all_keys()
893 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200894
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200895class PSATestGenerator(test_data_generation.TestGenerator):
Werner Lewisfbb75e32022-08-24 11:30:03 +0100896 """Test generator subclass including PSA targets and info."""
Dave Rodgman3009a972022-04-22 14:52:41 +0100897 # Note that targets whose names contain 'test_format' have their content
Gilles Peskine92165362021-04-23 16:37:12 +0200898 # validated by `abi_check.py`.
Werner Lewisa4668a62022-09-02 11:56:34 +0100899 targets = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200900 'test_suite_psa_crypto_generate_key.generated':
901 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100902 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100903 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200904 'test_suite_psa_crypto_op_fail.generated':
905 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100906 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200907 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100908 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200909 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100910 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
911
Werner Lewisfbb75e32022-08-24 11:30:03 +0100912 def __init__(self, options):
913 super().__init__(options)
914 self.info = Information()
Gilles Peskine14e428f2021-01-26 22:19:21 +0100915
Werner Lewisfbb75e32022-08-24 11:30:03 +0100916 def generate_target(self, name: str, *target_args) -> None:
917 super().generate_target(name, self.info)
Gilles Peskine09940492021-01-26 22:16:30 +0100918
919if __name__ == '__main__':
Gilles Peskine64f2efd2022-09-16 21:41:47 +0200920 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)