blob: 6fd311e632bcac817ba236f437a263e3d21f48b1 [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
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100521 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
522 @classmethod
523 def valid_key_size_for_algorithm(
524 cls,
525 key_type: psa_storage.Expr, bits: int,
526 alg: psa_storage.Expr
527 ) -> bool:
528 """Whether the given key type and size are valid for the algorithm.
529
530 Normally only the type and algorithm matter for compatibility, and
531 this is handled in crypto_knowledge.KeyType.can_do(). This function
532 exists to detect exceptional cases. Exceptional cases detected here
533 are not tested in OpFail and should therefore have manually written
534 test cases.
535 """
536 #pylint: disable=unused-argument
537 # OAEP requires room for two hashes plus wrapping
538 m = cls.RSA_OAEP_RE.match(alg.string)
539 if m:
540 hash_alg = m.group(1)
541 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
542 key_length = (bits + 7) // 8
543 # Leave enough room for at least one byte of plaintext
544 return key_length > 2 * hash_length + 2
545 return True
546
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200547 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100548 """Construct a storage format test case for the given key.
549
550 If ``forward`` is true, generate a forward compatibility test case:
551 create a key and validate that it has the expected representation.
552 Otherwise generate a backward compatibility test case: inject the
553 key representation into storage and validate that it can be read
554 correctly.
555 """
556 verb = 'save' if self.forward else 'read'
557 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100558 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100559 dependencies = automatic_dependencies(
560 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100561 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100562 )
563 dependencies = finish_family_dependencies(dependencies, key.bits)
564 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100565 tc.set_function('key_storage_' + verb)
566 if self.forward:
567 extra_arguments = []
568 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200569 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100570 # Some test keys have the RAW_DATA type and attributes that don't
571 # necessarily make sense. We do this to validate numerical
572 # encodings of the attributes.
573 # Raw data keys have no useful exercise anyway so there is no
574 # loss of test coverage.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100575 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA' and \
576 self.valid_key_size_for_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200577 flags.append('TEST_FLAG_EXERCISE')
578 if 'READ_ONLY' in key.lifetime.string:
579 flags.append('TEST_FLAG_READ_ONLY')
580 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100581 tc.set_arguments([key.lifetime.string,
582 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100583 key.expected_usage.string,
584 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100585 '"' + key.material.hex() + '"',
586 '"' + key.hex() + '"',
587 *extra_arguments])
588 return tc
589
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200590 def key_for_lifetime(
591 self,
592 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200593 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200594 """Construct a test key for the given lifetime."""
595 short = lifetime
596 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
597 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100598 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200599 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200600 key = StorageTestData(version=self.version,
601 id=1, lifetime=lifetime,
602 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100603 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200604 material=b'L',
605 description=description)
606 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200607
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200608 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200609 """Generate test keys covering lifetimes."""
610 lifetimes = sorted(self.constructors.lifetimes)
611 expressions = self.constructors.generate_expressions(lifetimes)
612 for lifetime in expressions:
613 # Don't attempt to create or load a volatile key in storage
614 if 'VOLATILE' in lifetime:
615 continue
616 # Don't attempt to create a read-only key in storage,
617 # but do attempt to load one.
618 if 'READ_ONLY' in lifetime and self.forward:
619 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200620 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200621
Gilles Peskinef7614272022-02-24 18:58:08 +0100622 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100623 self,
624 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200625 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100626 test_implicit_usage: Optional[bool] = True
627 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100628 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100629 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100630 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200631 key1 = StorageTestData(version=self.version,
632 id=1, lifetime=0x00000001,
633 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100634 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100635 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100636 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200637 material=b'K',
638 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100639 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100640 usage_expr = key1.expected_usage.string
641 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100642 else:
643 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100644 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100645
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200646 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100647 """Generate test keys covering usage flags."""
648 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100649 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200650 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100651 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200652 for flag1, flag2 in zip(known_flags,
653 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100654 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200655
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200656 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200657 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100658 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200659
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200660 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200661 yield from self.generate_keys_for_usage_flags()
662 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100663
Gilles Peskine7de7c102021-04-29 22:28:07 +0200664 def key_for_type_and_alg(
665 self,
666 kt: crypto_knowledge.KeyType,
667 bits: int,
668 alg: Optional[crypto_knowledge.Algorithm] = None,
669 ) -> StorageTestData:
670 """Construct a test key of the given type.
671
672 If alg is not None, this key allows it.
673 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100674 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100675 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200676 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100677 if alg is not None:
678 alg1 = alg.expression
679 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200680 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100681 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200682 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100683 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200684 key = StorageTestData(version=self.version,
685 id=1, lifetime=0x00000001,
686 type=kt.expression, bits=bits,
687 usage=usage_flags, alg=alg1, alg2=alg2,
688 material=key_material,
689 description=description)
690 return key
691
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100692 def keys_for_type(
693 self,
694 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200695 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200696 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200697 """Generate test keys for the given key type."""
698 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100699 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200700 # Test a non-exercisable key, as well as exercisable keys for
701 # each compatible algorithm.
702 # To do: test reading a key from storage with an incompatible
703 # or unsupported algorithm.
704 yield self.key_for_type_and_alg(kt, bits)
705 compatible_algorithms = [alg for alg in all_algorithms
706 if kt.can_do(alg)]
707 for alg in compatible_algorithms:
708 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100709
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200710 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100711 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200712 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200713 all_algorithms = [crypto_knowledge.Algorithm(alg)
714 for alg in self.constructors.generate_expressions(
715 sorted(self.constructors.algorithms)
716 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200717 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200718 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100719
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200720 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200721 """Generate test keys for the encoding of the specified algorithm."""
722 # These test cases only validate the encoding of algorithms, not
723 # whether the key read from storage is suitable for an operation.
724 # `keys_for_types` generate read tests with an algorithm and a
725 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100726 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100727 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200728 key1 = StorageTestData(version=self.version,
729 id=1, lifetime=0x00000001,
730 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
731 usage=usage, alg=alg, alg2=0,
732 material=b'K',
733 description='alg: ' + descr)
734 yield key1
735 key2 = StorageTestData(version=self.version,
736 id=1, lifetime=0x00000001,
737 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
738 usage=usage, alg=0, alg2=alg,
739 material=b'L',
740 description='alg2: ' + descr)
741 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100742
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200743 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100744 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200745 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200746 for alg in self.constructors.generate_expressions(algorithms):
747 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100748
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200749 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200750 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200751 yield from self.all_keys_for_lifetimes()
752 yield from self.all_keys_for_usage_flags()
753 yield from self.all_keys_for_types()
754 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200755
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200756 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100757 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200758 # First build a list of all keys, then construct all the corresponding
759 # test cases. This allows all required information to be obtained in
760 # one go, which is a significant performance gain as the information
761 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200762 all_keys = list(self.generate_all_keys())
763 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200764 if key.location_value() != 0:
765 # Skip keys with a non-default location, because they
766 # require a driver and we currently have no mechanism to
767 # determine whether a driver is available.
768 continue
769 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100770
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200771class StorageFormatForward(StorageFormat):
772 """Storage format stability test cases for forward compatibility."""
773
774 def __init__(self, info: Information, version: int) -> None:
775 super().__init__(info, version, True)
776
777class StorageFormatV0(StorageFormat):
778 """Storage format stability test cases for version 0 compatibility."""
779
780 def __init__(self, info: Information) -> None:
781 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100782
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200783 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200784 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100785 yield from super().all_keys_for_usage_flags()
786 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200787
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200788 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200789 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200790 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200791 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200792 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200793 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200794 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200795 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200796 algorithm and key type combination.
797 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200798 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200799 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100800 usage_flags = ['PSA_KEY_USAGE_EXPORT']
801 material_usage_flags = usage_flags + [implyer_usage]
802 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200803 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200804 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100805 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
806 alg_expression = crypto_knowledge.short_expression(alg, 1)
807 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200808 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200809 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200810 key = StorageTestData(version=self.version,
811 id=1, lifetime=0x00000001,
812 type=key_type.expression, bits=bits,
813 usage=material_usage_flags,
814 expected_usage=expected_usage_flags,
815 without_implicit_usage=True,
816 alg=alg, alg2=alg2,
817 material=key_material,
818 description=description)
819 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200820
821 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200822 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200823 """Match possible key types for sign algorithms."""
824 # To create a valid combinaton both the algorithms and key types
825 # must be filtered. Pair them with keywords created from its names.
826 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
827 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
828 keyword_translation = {
829 'ECDSA': 'ECC',
830 'ED[0-9]*.*' : 'EDWARDS'
831 }
832 exclusive_keywords = {
833 'EDWARDS': 'ECC'
834 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200835 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
836 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200837 alg_with_keys = {} #type: Dict[str, List[str]]
838 translation_table = str.maketrans('(', '_', ')')
839 for alg in algorithms:
840 # Generate keywords from the name of the algorithm
841 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
842 # Translate keywords for better matching with the key types
843 for keyword in alg_keywords.copy():
844 for pattern, replace in keyword_translation.items():
845 if re.match(pattern, keyword):
846 alg_keywords.remove(keyword)
847 alg_keywords.add(replace)
848 # Filter out incompatible algortihms
849 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
850 continue
851
852 for key_type in key_types:
853 # Generate keywords from the of the key type
854 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
855
856 # Remove ambigious keywords
857 for keyword1, keyword2 in exclusive_keywords.items():
858 if keyword1 in key_type_keywords:
859 key_type_keywords.remove(keyword2)
860
861 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
862 not key_type_keywords.isdisjoint(alg_keywords):
863 if alg in alg_with_keys:
864 alg_with_keys[alg].append(key_type)
865 else:
866 alg_with_keys[alg] = [key_type]
867 return alg_with_keys
868
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200869 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200870 """Generate test keys for usage flag extensions."""
871 # Generate a key type and algorithm pair for each extendable usage
872 # flag to generate a valid key for exercising. The key is generated
873 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200874 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200875
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200876 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
877 for alg in sorted(alg_with_keys):
878 for key_type in sorted(alg_with_keys[alg]):
879 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200880 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100881 if kt.is_public() and '_SIGN_' in usage:
882 # Can't sign with a public key
883 continue
884 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200885
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200886 def generate_all_keys(self) -> Iterator[StorageTestData]:
887 yield from super().generate_all_keys()
888 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200889
Gilles Peskineb94ea512021-03-10 02:12:08 +0100890class TestGenerator:
891 """Generate test data."""
892
893 def __init__(self, options) -> None:
894 self.test_suite_directory = self.get_option(options, 'directory',
895 'tests/suites')
896 self.info = Information()
897
898 @staticmethod
899 def get_option(options, name: str, default: T) -> T:
900 value = getattr(options, name, None)
901 return default if value is None else value
902
Gilles Peskine0298bda2021-03-10 02:34:37 +0100903 def filename_for(self, basename: str) -> str:
904 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200905 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100906
Gilles Peskineb94ea512021-03-10 02:12:08 +0100907 def write_test_data_file(self, basename: str,
908 test_cases: Iterable[test_case.TestCase]) -> None:
909 """Write the test cases to a .data file.
910
911 The output file is ``basename + '.data'`` in the test suite directory.
912 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100913 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100914 test_case.write_data_file(filename, test_cases)
915
Gilles Peskine92165362021-04-23 16:37:12 +0200916 # Note that targets whose name containns 'test_format' have their content
917 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100918 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200919 'test_suite_psa_crypto_generate_key.generated':
920 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100921 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100922 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200923 'test_suite_psa_crypto_op_fail.generated':
924 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100925 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200926 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100927 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200928 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100929 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
930
931 def generate_target(self, name: str) -> None:
932 test_cases = self.TARGETS[name](self.info)
933 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100934
Gilles Peskine09940492021-01-26 22:16:30 +0100935def main(args):
936 """Command line entry point."""
937 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100938 parser.add_argument('--list', action='store_true',
939 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100940 parser.add_argument('--list-for-cmake', action='store_true',
941 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200942 parser.add_argument('--directory', metavar='DIR',
943 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100944 parser.add_argument('targets', nargs='*', metavar='TARGET',
945 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100946 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200947 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100948 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100949 if options.list:
950 for name in sorted(generator.TARGETS):
951 print(generator.filename_for(name))
952 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100953 # List in a cmake list format (i.e. ';'-separated)
954 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100955 print(';'.join(generator.filename_for(name)
956 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100957 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100958 if options.targets:
959 # Allow "-" as a special case so you can run
960 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
961 # ``$targets`` is empty or not.
962 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
963 for target in options.targets
964 if target != '-']
965 else:
966 options.targets = sorted(generator.TARGETS)
967 for target in options.targets:
968 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100969
970if __name__ == '__main__':
971 main(sys.argv[1:])