blob: 2a701470555527b45995f11ec4d92dc9c9be4f53 [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
23import argparse
Gilles Peskinecba28a72022-03-15 17:26:33 +010024import enum
Gilles Peskine14e428f2021-01-26 22:19:21 +010025import os
Bence Szépkúti9e84ec72021-05-07 11:49:17 +020026import posixpath
Gilles Peskine14e428f2021-01-26 22:19:21 +010027import re
Gilles Peskine09940492021-01-26 22:16:30 +010028import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010029from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010030
31import scripts_path # pylint: disable=unused-import
Gilles Peskinec86f20a2021-04-22 00:20:47 +020032from mbedtls_dev import build_tree
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010034from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010035from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010036from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010037
38T = TypeVar('T') #pylint: disable=invalid-name
39
Gilles Peskine14e428f2021-01-26 22:19:21 +010040
Gilles Peskine7f756872021-02-16 12:13:12 +010041def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010042 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
43 if name.startswith('PSA_'):
44 return name[:4] + 'WANT_' + name[4:]
45 else:
46 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
47
Gilles Peskine7f756872021-02-16 12:13:12 +010048def finish_family_dependency(dep: str, bits: int) -> str:
49 """Finish dep if it's a family dependency symbol prefix.
50
51 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
52 qualified by the key size. If dep is such a symbol, finish it by adjusting
53 the prefix and appending the key size. Other symbols are left unchanged.
54 """
55 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
56
57def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
58 """Finish any family dependency symbol prefixes.
59
60 Apply `finish_family_dependency` to each element of `dependencies`.
61 """
62 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010063
Gilles Peskinec5d086f2021-04-20 23:23:45 +020064SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
65 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
66 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
67 'PSA_ALG_ANY_HASH', # only in policies
68 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
69 'PSA_ALG_KEY_AGREEMENT', # chaining
70 'PSA_ALG_TRUNCATED_MAC', # modifier
71])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010072def automatic_dependencies(*expressions: str) -> List[str]:
73 """Infer dependencies of a test case by looking for PSA_xxx symbols.
74
75 The arguments are strings which should be C expressions. Do not use
76 string literals or comments as this function is not smart enough to
77 skip them.
78 """
79 used = set()
80 for expr in expressions:
81 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020082 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010083 return sorted(psa_want_symbol(name) for name in used)
84
Gilles Peskined169d602021-02-16 14:16:25 +010085# A temporary hack: at the time of writing, not all dependency symbols
86# are implemented yet. Skip test cases for which the dependency symbols are
87# not available. Once all dependency symbols are available, this hack must
88# be removed so that a bug in the dependency symbols proprely leads to a test
89# failure.
90def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
91 return frozenset(symbol
92 for line in open(filename)
93 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020094_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010095def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +020096 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
97 if _implemented_dependencies is None:
98 _implemented_dependencies = \
99 read_implemented_dependencies('include/psa/crypto_config.h')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200100 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100101 for dep in dependencies):
102 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
103
Gilles Peskine14e428f2021-01-26 22:19:21 +0100104
Gilles Peskineb94ea512021-03-10 02:12:08 +0100105class Information:
106 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100107
Gilles Peskineb94ea512021-03-10 02:12:08 +0100108 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100109 self.constructors = self.read_psa_interface()
110
111 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100112 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200113 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100114 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200115 # Mbed TLS doesn't support finite-field DH yet and will not support
116 # finite-field DSA. Don't attempt to generate any related test case.
117 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
118 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100119 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
120 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100121
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200122 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100123 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200124 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100125 header_file_names = ['include/psa/crypto_values.h',
126 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200127 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100128 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200129 constructors.parse_header(header_file_name)
130 for test_cases in test_suites:
131 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100132 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200133 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100134 return constructors
135
Gilles Peskine14e428f2021-01-26 22:19:21 +0100136
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200137def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100138 verb: str, key_type: str, bits: int,
139 dependencies: List[str],
140 *args: str,
141 param_descr: str = ''
142) -> test_case.TestCase:
143 """Return one test case exercising a key creation method
144 for an unsupported key type or size.
145 """
146 hack_dependencies_not_implemented(dependencies)
147 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100148 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100149 adverb = 'not' if dependencies else 'never'
150 if param_descr:
151 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200152 tc.set_description('PSA {} {} {}-bit {} supported'
153 .format(verb, short_key_type, bits, adverb))
154 tc.set_dependencies(dependencies)
155 tc.set_function(verb + '_not_supported')
156 tc.set_arguments([key_type] + list(args))
157 return tc
158
Gilles Peskineb94ea512021-03-10 02:12:08 +0100159class NotSupported:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200160 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100161
162 def __init__(self, info: Information) -> None:
163 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100164
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100165 ALWAYS_SUPPORTED = frozenset([
166 'PSA_KEY_TYPE_DERIVE',
167 'PSA_KEY_TYPE_RAW_DATA',
168 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100169 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100170 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100171 kt: crypto_knowledge.KeyType,
172 param: Optional[int] = None,
173 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100174 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200175 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100176
177 If param is present and not None, emit test cases conditioned on this
178 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200179 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100180 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100181 if kt.name in self.ALWAYS_SUPPORTED:
182 # Don't generate test cases for key types that are always supported.
183 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100184 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100185 import_dependencies = [('!' if param is None else '') +
186 psa_want_symbol(kt.name)]
187 if kt.params is not None:
188 import_dependencies += [('!' if param == i else '') +
189 psa_want_symbol(sym)
190 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100191 if kt.name.endswith('_PUBLIC_KEY'):
192 generate_dependencies = []
193 else:
194 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100195 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200196 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100197 'import', kt.expression, bits,
198 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100199 test_case.hex_string(kt.key_material(bits)),
200 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100201 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100202 if not generate_dependencies and param is not None:
203 # If generation is impossible for this key type, rather than
204 # supported or not depending on implementation capabilities,
205 # only generate the test case once.
206 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100207 # For public key we expect that key generation fails with
208 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100209 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200210 yield test_case_for_key_type_not_supported(
211 'generate', kt.expression, bits,
212 finish_family_dependencies(generate_dependencies, bits),
213 str(bits),
214 param_descr=param_descr,
215 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100216 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200218 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
219 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
220
Gilles Peskine3d778392021-02-17 15:11:05 +0100221 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100223 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200224 if key_type in self.ECC_KEY_TYPES:
225 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100226 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200229 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100231 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100232 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100233 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100234 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100235
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200236def test_case_for_key_generation(
237 key_type: str, bits: int,
238 dependencies: List[str],
239 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200240 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200241) -> test_case.TestCase:
242 """Return one test case exercising a key generation.
243 """
244 hack_dependencies_not_implemented(dependencies)
245 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100246 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200247 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200248 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200249 tc.set_dependencies(dependencies)
250 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100251 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200252
253 return tc
254
255class KeyGenerate:
256 """Generate positive and negative (invalid argument) test cases for key generation."""
257
258 def __init__(self, info: Information) -> None:
259 self.constructors = info.constructors
260
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200261 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
262 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
263
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100264 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200265 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200266 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200267 ) -> Iterator[test_case.TestCase]:
268 """Return test cases exercising key generation.
269
270 All key types can be generated except for public keys. For public key
271 PSA_ERROR_INVALID_ARGUMENT status is expected.
272 """
273 result = 'PSA_SUCCESS'
274
275 import_dependencies = [psa_want_symbol(kt.name)]
276 if kt.params is not None:
277 import_dependencies += [psa_want_symbol(sym)
278 for i, sym in enumerate(kt.params)]
279 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100280 # The library checks whether the key type is a public key generically,
281 # before it reaches a point where it needs support for the specific key
282 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200283 generate_dependencies = []
284 result = 'PSA_ERROR_INVALID_ARGUMENT'
285 else:
286 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100287 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200288 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200289 for bits in kt.sizes_to_test():
290 yield test_case_for_key_generation(
291 kt.expression, bits,
292 finish_family_dependencies(generate_dependencies, bits),
293 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200294 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200295 )
296
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200297 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
298 """Generate test cases that exercise the generation of keys."""
299 for key_type in sorted(self.constructors.key_types):
300 if key_type in self.ECC_KEY_TYPES:
301 continue
302 kt = crypto_knowledge.KeyType(key_type)
303 yield from self.test_cases_for_key_type_key_generation(kt)
304 for curve_family in sorted(self.constructors.ecc_curves):
305 for constr in self.ECC_KEY_TYPES:
306 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200307 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200308
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200309class OpFail:
310 """Generate test cases for operations that must fail."""
311 #pylint: disable=too-few-public-methods
312
Gilles Peskinecba28a72022-03-15 17:26:33 +0100313 class Reason(enum.Enum):
314 NOT_SUPPORTED = 0
315 INVALID = 1
316 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200317 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100318
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200319 def __init__(self, info: Information) -> None:
320 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100321 key_type_expressions = self.constructors.generate_expressions(
322 sorted(self.constructors.key_types)
323 )
324 self.key_types = [crypto_knowledge.KeyType(kt_expr)
325 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200326
Gilles Peskinecba28a72022-03-15 17:26:33 +0100327 def make_test_case(
328 self,
329 alg: crypto_knowledge.Algorithm,
330 category: crypto_knowledge.AlgorithmCategory,
331 reason: 'Reason',
332 kt: Optional[crypto_knowledge.KeyType] = None,
333 not_deps: FrozenSet[str] = frozenset(),
334 ) -> test_case.TestCase:
335 """Construct a failure test case for a one-key or keyless operation."""
336 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200337 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100338 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200339 if reason == self.Reason.NOT_SUPPORTED:
340 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
341 for dep in not_deps]
342 pretty_reason = '!' + '&'.join(sorted(short_deps))
343 else:
344 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100345 if kt:
346 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100347 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200348 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100349 key_type = ''
350 pretty_type = ''
351 tc.set_description('PSA {} {}: {}{}'
352 .format(category.name.lower(),
353 pretty_alg,
354 pretty_reason,
355 ' with ' + pretty_type if pretty_type else ''))
356 dependencies = automatic_dependencies(alg.base_expression, key_type)
357 for i, dep in enumerate(dependencies):
358 if dep in not_deps:
359 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200360 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100361 tc.set_function(category.name.lower() + '_fail')
362 arguments = []
363 if kt:
364 key_material = kt.key_material(kt.sizes_to_test()[0])
365 arguments += [key_type, test_case.hex_string(key_material)]
366 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200367 if category.is_asymmetric():
368 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100369 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
370 'INVALID_ARGUMENT')
371 arguments.append('PSA_ERROR_' + error)
372 tc.set_arguments(arguments)
373 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200374
Gilles Peskinecba28a72022-03-15 17:26:33 +0100375 def no_key_test_cases(
376 self,
377 alg: crypto_knowledge.Algorithm,
378 category: crypto_knowledge.AlgorithmCategory,
379 ) -> Iterator[test_case.TestCase]:
380 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200381 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100382 # Compatible operation, unsupported algorithm
383 for dep in automatic_dependencies(alg.base_expression):
384 yield self.make_test_case(alg, category,
385 self.Reason.NOT_SUPPORTED,
386 not_deps=frozenset([dep]))
387 else:
388 # Incompatible operation, supported algorithm
389 yield self.make_test_case(alg, category, self.Reason.INVALID)
390
391 def one_key_test_cases(
392 self,
393 alg: crypto_knowledge.Algorithm,
394 category: crypto_knowledge.AlgorithmCategory,
395 ) -> Iterator[test_case.TestCase]:
396 """Generate failure test cases for one-key operations with the specified algorithm."""
397 for kt in self.key_types:
398 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200399 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100400 # Compatible key and operation, unsupported algorithm
401 for dep in automatic_dependencies(alg.base_expression):
402 yield self.make_test_case(alg, category,
403 self.Reason.NOT_SUPPORTED,
404 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200405 # Public key for a private-key operation
406 if category.is_asymmetric() and kt.is_public():
407 yield self.make_test_case(alg, category,
408 self.Reason.PUBLIC,
409 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100410 elif key_is_compatible:
411 # Compatible key, incompatible operation, supported algorithm
412 yield self.make_test_case(alg, category,
413 self.Reason.INVALID,
414 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200415 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100416 # Incompatible key, compatible operation, supported algorithm
417 yield self.make_test_case(alg, category,
418 self.Reason.INCOMPATIBLE,
419 kt=kt)
420 else:
421 # Incompatible key and operation. Don't test cases where
422 # multiple things are wrong, to keep the number of test
423 # cases reasonable.
424 pass
425
426 def test_cases_for_algorithm(
427 self,
428 alg: crypto_knowledge.Algorithm,
429 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200430 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100431 for category in crypto_knowledge.AlgorithmCategory:
432 if category == crypto_knowledge.AlgorithmCategory.PAKE:
433 # PAKE operations are not implemented yet
434 pass
435 elif category.requires_key():
436 yield from self.one_key_test_cases(alg, category)
437 else:
438 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200439
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200440 def all_test_cases(self) -> Iterator[test_case.TestCase]:
441 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200442 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100443 for expr in self.constructors.generate_expressions(algorithms):
444 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200445 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200446
447
Gilles Peskine897dff92021-03-10 15:03:44 +0100448class StorageKey(psa_storage.Key):
449 """Representation of a key for storage format testing."""
450
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200451 IMPLICIT_USAGE_FLAGS = {
452 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
453 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
454 } #type: Dict[str, str]
455 """Mapping of usage flags to the flags that they imply."""
456
457 def __init__(
458 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100459 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200460 without_implicit_usage: Optional[bool] = False,
461 **kwargs
462 ) -> None:
463 """Prepare to generate a key.
464
465 * `usage` : The usage flags used for the key.
466 * `without_implicit_usage`: Flag to defide to apply the usage extension
467 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100468 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200469 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100470 for flag in sorted(usage_flags):
471 if flag in self.IMPLICIT_USAGE_FLAGS:
472 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
473 if usage_flags:
474 usage_expression = ' | '.join(sorted(usage_flags))
475 else:
476 usage_expression = '0'
477 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200478
479class StorageTestData(StorageKey):
480 """Representation of test case data for storage format testing."""
481
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200482 def __init__(
483 self,
484 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100485 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200486 **kwargs
487 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200488 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200489
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200490 * `description` : used for the the test case names
491 * `expected_usage`: the usage flags generated as the expected usage flags
492 in the test cases. CAn differ from the usage flags
493 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200494 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100495 super().__init__(**kwargs)
496 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100497 if expected_usage is None:
498 self.expected_usage = self.usage #type: psa_storage.Expr
499 elif expected_usage:
500 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
501 else:
502 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200503
Gilles Peskine897dff92021-03-10 15:03:44 +0100504class StorageFormat:
505 """Storage format stability test cases."""
506
507 def __init__(self, info: Information, version: int, forward: bool) -> None:
508 """Prepare to generate test cases for storage format stability.
509
510 * `info`: information about the API. See the `Information` class.
511 * `version`: the storage format version to generate test cases for.
512 * `forward`: if true, generate forward compatibility test cases which
513 save a key and check that its representation is as intended. Otherwise
514 generate backward compatibility test cases which inject a key
515 representation and check that it can be read and used.
516 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200517 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
518 self.version = version #type: int
519 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100520
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200521 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100522 """Construct a storage format test case for the given key.
523
524 If ``forward`` is true, generate a forward compatibility test case:
525 create a key and validate that it has the expected representation.
526 Otherwise generate a backward compatibility test case: inject the
527 key representation into storage and validate that it can be read
528 correctly.
529 """
530 verb = 'save' if self.forward else 'read'
531 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100532 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100533 dependencies = automatic_dependencies(
534 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100535 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100536 )
537 dependencies = finish_family_dependencies(dependencies, key.bits)
538 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100539 tc.set_function('key_storage_' + verb)
540 if self.forward:
541 extra_arguments = []
542 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200543 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100544 # Some test keys have the RAW_DATA type and attributes that don't
545 # necessarily make sense. We do this to validate numerical
546 # encodings of the attributes.
547 # Raw data keys have no useful exercise anyway so there is no
548 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200549 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
550 flags.append('TEST_FLAG_EXERCISE')
551 if 'READ_ONLY' in key.lifetime.string:
552 flags.append('TEST_FLAG_READ_ONLY')
553 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100554 tc.set_arguments([key.lifetime.string,
555 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100556 key.expected_usage.string,
557 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100558 '"' + key.material.hex() + '"',
559 '"' + key.hex() + '"',
560 *extra_arguments])
561 return tc
562
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200563 def key_for_lifetime(
564 self,
565 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200566 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200567 """Construct a test key for the given lifetime."""
568 short = lifetime
569 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
570 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100571 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200572 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200573 key = StorageTestData(version=self.version,
574 id=1, lifetime=lifetime,
575 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100576 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200577 material=b'L',
578 description=description)
579 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200580
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200581 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200582 """Generate test keys covering lifetimes."""
583 lifetimes = sorted(self.constructors.lifetimes)
584 expressions = self.constructors.generate_expressions(lifetimes)
585 for lifetime in expressions:
586 # Don't attempt to create or load a volatile key in storage
587 if 'VOLATILE' in lifetime:
588 continue
589 # Don't attempt to create a read-only key in storage,
590 # but do attempt to load one.
591 if 'READ_ONLY' in lifetime and self.forward:
592 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200593 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200594
Gilles Peskinef7614272022-02-24 18:58:08 +0100595 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100596 self,
597 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200598 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100599 test_implicit_usage: Optional[bool] = True
600 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100601 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100602 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100603 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200604 key1 = StorageTestData(version=self.version,
605 id=1, lifetime=0x00000001,
606 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100607 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100608 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100609 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200610 material=b'K',
611 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100612 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100613 usage_expr = key1.expected_usage.string
614 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100615 else:
616 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100617 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100618
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200619 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100620 """Generate test keys covering usage flags."""
621 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100622 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200623 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100624 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200625 for flag1, flag2 in zip(known_flags,
626 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100627 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200628
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200629 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200630 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100631 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200632
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200633 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200634 yield from self.generate_keys_for_usage_flags()
635 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100636
Gilles Peskine7de7c102021-04-29 22:28:07 +0200637 def key_for_type_and_alg(
638 self,
639 kt: crypto_knowledge.KeyType,
640 bits: int,
641 alg: Optional[crypto_knowledge.Algorithm] = None,
642 ) -> StorageTestData:
643 """Construct a test key of the given type.
644
645 If alg is not None, this key allows it.
646 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100647 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskine7de7c102021-04-29 22:28:07 +0200648 alg1 = 0 if alg is None else alg.expression #type: psa_storage.Exprable
649 alg2 = 0
650 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100651 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200652 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100653 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200654 key = StorageTestData(version=self.version,
655 id=1, lifetime=0x00000001,
656 type=kt.expression, bits=bits,
657 usage=usage_flags, alg=alg1, alg2=alg2,
658 material=key_material,
659 description=description)
660 return key
661
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100662 def keys_for_type(
663 self,
664 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200665 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200666 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200667 """Generate test keys for the given key type."""
668 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100669 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200670 # Test a non-exercisable key, as well as exercisable keys for
671 # each compatible algorithm.
672 # To do: test reading a key from storage with an incompatible
673 # or unsupported algorithm.
674 yield self.key_for_type_and_alg(kt, bits)
675 compatible_algorithms = [alg for alg in all_algorithms
676 if kt.can_do(alg)]
677 for alg in compatible_algorithms:
678 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100679
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200680 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100681 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200682 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200683 all_algorithms = [crypto_knowledge.Algorithm(alg)
684 for alg in self.constructors.generate_expressions(
685 sorted(self.constructors.algorithms)
686 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200687 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200688 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100689
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200690 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200691 """Generate test keys for the encoding of the specified algorithm."""
692 # These test cases only validate the encoding of algorithms, not
693 # whether the key read from storage is suitable for an operation.
694 # `keys_for_types` generate read tests with an algorithm and a
695 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100696 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100697 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200698 key1 = StorageTestData(version=self.version,
699 id=1, lifetime=0x00000001,
700 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
701 usage=usage, alg=alg, alg2=0,
702 material=b'K',
703 description='alg: ' + descr)
704 yield key1
705 key2 = StorageTestData(version=self.version,
706 id=1, lifetime=0x00000001,
707 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
708 usage=usage, alg=0, alg2=alg,
709 material=b'L',
710 description='alg2: ' + descr)
711 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100712
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200713 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100714 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200715 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200716 for alg in self.constructors.generate_expressions(algorithms):
717 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100718
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200719 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200720 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200721 yield from self.all_keys_for_lifetimes()
722 yield from self.all_keys_for_usage_flags()
723 yield from self.all_keys_for_types()
724 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200725
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200726 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100727 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200728 # First build a list of all keys, then construct all the corresponding
729 # test cases. This allows all required information to be obtained in
730 # one go, which is a significant performance gain as the information
731 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200732 all_keys = list(self.generate_all_keys())
733 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200734 if key.location_value() != 0:
735 # Skip keys with a non-default location, because they
736 # require a driver and we currently have no mechanism to
737 # determine whether a driver is available.
738 continue
739 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100740
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200741class StorageFormatForward(StorageFormat):
742 """Storage format stability test cases for forward compatibility."""
743
744 def __init__(self, info: Information, version: int) -> None:
745 super().__init__(info, version, True)
746
747class StorageFormatV0(StorageFormat):
748 """Storage format stability test cases for version 0 compatibility."""
749
750 def __init__(self, info: Information) -> None:
751 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100752
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200753 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200754 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100755 yield from super().all_keys_for_usage_flags()
756 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200757
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200758 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200759 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200760 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200761 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200762 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200763 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200764 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200765 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200766 algorithm and key type combination.
767 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200768 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200769 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100770 usage_flags = ['PSA_KEY_USAGE_EXPORT']
771 material_usage_flags = usage_flags + [implyer_usage]
772 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200773 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200774 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100775 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
776 alg_expression = crypto_knowledge.short_expression(alg, 1)
777 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200778 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200779 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200780 key = StorageTestData(version=self.version,
781 id=1, lifetime=0x00000001,
782 type=key_type.expression, bits=bits,
783 usage=material_usage_flags,
784 expected_usage=expected_usage_flags,
785 without_implicit_usage=True,
786 alg=alg, alg2=alg2,
787 material=key_material,
788 description=description)
789 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200790
791 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200792 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200793 """Match possible key types for sign algorithms."""
794 # To create a valid combinaton both the algorithms and key types
795 # must be filtered. Pair them with keywords created from its names.
796 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
797 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
798 keyword_translation = {
799 'ECDSA': 'ECC',
800 'ED[0-9]*.*' : 'EDWARDS'
801 }
802 exclusive_keywords = {
803 'EDWARDS': 'ECC'
804 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200805 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
806 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200807 alg_with_keys = {} #type: Dict[str, List[str]]
808 translation_table = str.maketrans('(', '_', ')')
809 for alg in algorithms:
810 # Generate keywords from the name of the algorithm
811 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
812 # Translate keywords for better matching with the key types
813 for keyword in alg_keywords.copy():
814 for pattern, replace in keyword_translation.items():
815 if re.match(pattern, keyword):
816 alg_keywords.remove(keyword)
817 alg_keywords.add(replace)
818 # Filter out incompatible algortihms
819 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
820 continue
821
822 for key_type in key_types:
823 # Generate keywords from the of the key type
824 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
825
826 # Remove ambigious keywords
827 for keyword1, keyword2 in exclusive_keywords.items():
828 if keyword1 in key_type_keywords:
829 key_type_keywords.remove(keyword2)
830
831 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
832 not key_type_keywords.isdisjoint(alg_keywords):
833 if alg in alg_with_keys:
834 alg_with_keys[alg].append(key_type)
835 else:
836 alg_with_keys[alg] = [key_type]
837 return alg_with_keys
838
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200839 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200840 """Generate test keys for usage flag extensions."""
841 # Generate a key type and algorithm pair for each extendable usage
842 # flag to generate a valid key for exercising. The key is generated
843 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200844 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200845
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200846 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
847 for alg in sorted(alg_with_keys):
848 for key_type in sorted(alg_with_keys[alg]):
849 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200850 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100851 if kt.is_public() and '_SIGN_' in usage:
852 # Can't sign with a public key
853 continue
854 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200855
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200856 def generate_all_keys(self) -> Iterator[StorageTestData]:
857 yield from super().generate_all_keys()
858 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200859
Gilles Peskineb94ea512021-03-10 02:12:08 +0100860class TestGenerator:
861 """Generate test data."""
862
863 def __init__(self, options) -> None:
864 self.test_suite_directory = self.get_option(options, 'directory',
865 'tests/suites')
866 self.info = Information()
867
868 @staticmethod
869 def get_option(options, name: str, default: T) -> T:
870 value = getattr(options, name, None)
871 return default if value is None else value
872
Gilles Peskine0298bda2021-03-10 02:34:37 +0100873 def filename_for(self, basename: str) -> str:
874 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200875 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100876
Gilles Peskineb94ea512021-03-10 02:12:08 +0100877 def write_test_data_file(self, basename: str,
878 test_cases: Iterable[test_case.TestCase]) -> None:
879 """Write the test cases to a .data file.
880
881 The output file is ``basename + '.data'`` in the test suite directory.
882 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100883 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100884 test_case.write_data_file(filename, test_cases)
885
Gilles Peskine92165362021-04-23 16:37:12 +0200886 # Note that targets whose name containns 'test_format' have their content
887 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100888 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200889 'test_suite_psa_crypto_generate_key.generated':
890 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100891 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100892 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200893 'test_suite_psa_crypto_op_fail.generated':
894 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100895 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200896 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100897 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200898 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100899 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
900
901 def generate_target(self, name: str) -> None:
902 test_cases = self.TARGETS[name](self.info)
903 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100904
Gilles Peskine09940492021-01-26 22:16:30 +0100905def main(args):
906 """Command line entry point."""
907 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100908 parser.add_argument('--list', action='store_true',
909 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100910 parser.add_argument('--list-for-cmake', action='store_true',
911 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200912 parser.add_argument('--directory', metavar='DIR',
913 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100914 parser.add_argument('targets', nargs='*', metavar='TARGET',
915 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100916 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200917 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100918 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100919 if options.list:
920 for name in sorted(generator.TARGETS):
921 print(generator.filename_for(name))
922 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100923 # List in a cmake list format (i.e. ';'-separated)
924 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100925 print(';'.join(generator.filename_for(name)
926 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100927 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100928 if options.targets:
929 # Allow "-" as a special case so you can run
930 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
931 # ``$targets`` is empty or not.
932 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
933 for target in options.targets
934 if target != '-']
935 else:
936 options.targets = sorted(generator.TARGETS)
937 for target in options.targets:
938 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100939
940if __name__ == '__main__':
941 main(sys.argv[1:])