blob: d34663188bed6abc3c99d880e3531b3180ead2a8 [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:
Przemek Stekiel398c5032022-05-11 14:05:40 +020081 # HKDF_EXTRACT and HKDF_EXPAND algs depend on HKDF
82 if "HKDF_EXTRACT" in expr:
83 expr = expr.replace("HKDF_EXTRACT", "HKDF")
84 if "HKDF_EXPAND" in expr:
85 expr = expr.replace("HKDF_EXPAND", "HKDF")
Gilles Peskinef8223ab2021-03-10 15:07:16 +010086 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020087 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010088 return sorted(psa_want_symbol(name) for name in used)
89
Gilles Peskined169d602021-02-16 14:16:25 +010090# A temporary hack: at the time of writing, not all dependency symbols
91# are implemented yet. Skip test cases for which the dependency symbols are
92# not available. Once all dependency symbols are available, this hack must
93# be removed so that a bug in the dependency symbols proprely leads to a test
94# failure.
95def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
96 return frozenset(symbol
97 for line in open(filename)
98 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020099_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +0100100def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200101 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
102 if _implemented_dependencies is None:
103 _implemented_dependencies = \
104 read_implemented_dependencies('include/psa/crypto_config.h')
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200105 if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
Gilles Peskined169d602021-02-16 14:16:25 +0100106 for dep in dependencies):
107 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
108
Gilles Peskine14e428f2021-01-26 22:19:21 +0100109
Gilles Peskineb94ea512021-03-10 02:12:08 +0100110class Information:
111 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100112
Gilles Peskineb94ea512021-03-10 02:12:08 +0100113 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100114 self.constructors = self.read_psa_interface()
115
116 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100117 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200118 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100119 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200120 # Mbed TLS doesn't support finite-field DH yet and will not support
121 # finite-field DSA. Don't attempt to generate any related test case.
122 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
123 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100124 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
125 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100126
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200127 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100128 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200129 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100130 header_file_names = ['include/psa/crypto_values.h',
131 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200132 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100133 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200134 constructors.parse_header(header_file_name)
135 for test_cases in test_suites:
136 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100137 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200138 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100139 return constructors
140
Gilles Peskine14e428f2021-01-26 22:19:21 +0100141
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200142def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100143 verb: str, key_type: str, bits: int,
144 dependencies: List[str],
145 *args: str,
146 param_descr: str = ''
147) -> test_case.TestCase:
148 """Return one test case exercising a key creation method
149 for an unsupported key type or size.
150 """
151 hack_dependencies_not_implemented(dependencies)
152 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100153 short_key_type = crypto_knowledge.short_expression(key_type)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154 adverb = 'not' if dependencies else 'never'
155 if param_descr:
156 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200157 tc.set_description('PSA {} {} {}-bit {} supported'
158 .format(verb, short_key_type, bits, adverb))
159 tc.set_dependencies(dependencies)
160 tc.set_function(verb + '_not_supported')
161 tc.set_arguments([key_type] + list(args))
162 return tc
163
Gilles Peskineb94ea512021-03-10 02:12:08 +0100164class NotSupported:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200165 """Generate test cases for when something is not supported."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100166
167 def __init__(self, info: Information) -> None:
168 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100169
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100170 ALWAYS_SUPPORTED = frozenset([
171 'PSA_KEY_TYPE_DERIVE',
172 'PSA_KEY_TYPE_RAW_DATA',
173 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100174 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100175 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100176 kt: crypto_knowledge.KeyType,
177 param: Optional[int] = None,
178 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100179 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200180 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100181
182 If param is present and not None, emit test cases conditioned on this
183 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200184 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100185 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100186 if kt.name in self.ALWAYS_SUPPORTED:
187 # Don't generate test cases for key types that are always supported.
188 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100189 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100190 import_dependencies = [('!' if param is None else '') +
191 psa_want_symbol(kt.name)]
192 if kt.params is not None:
193 import_dependencies += [('!' if param == i else '') +
194 psa_want_symbol(sym)
195 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100196 if kt.name.endswith('_PUBLIC_KEY'):
197 generate_dependencies = []
198 else:
199 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100200 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200201 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100202 'import', kt.expression, bits,
203 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100204 test_case.hex_string(kt.key_material(bits)),
205 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100206 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100207 if not generate_dependencies and param is not None:
208 # If generation is impossible for this key type, rather than
209 # supported or not depending on implementation capabilities,
210 # only generate the test case once.
211 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100212 # For public key we expect that key generation fails with
213 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100214 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200215 yield test_case_for_key_type_not_supported(
216 'generate', kt.expression, bits,
217 finish_family_dependencies(generate_dependencies, bits),
218 str(bits),
219 param_descr=param_descr,
220 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200223 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
224 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
225
Gilles Peskine3d778392021-02-17 15:11:05 +0100226 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100227 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100228 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200229 if key_type in self.ECC_KEY_TYPES:
230 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100231 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100232 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100233 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200234 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100235 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100236 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100237 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100238 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100239 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100240
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200241def test_case_for_key_generation(
242 key_type: str, bits: int,
243 dependencies: List[str],
244 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200245 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200246) -> test_case.TestCase:
247 """Return one test case exercising a key generation.
248 """
249 hack_dependencies_not_implemented(dependencies)
250 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100251 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200252 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200253 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200254 tc.set_dependencies(dependencies)
255 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100256 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200257
258 return tc
259
260class KeyGenerate:
261 """Generate positive and negative (invalid argument) test cases for key generation."""
262
263 def __init__(self, info: Information) -> None:
264 self.constructors = info.constructors
265
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200266 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
267 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
268
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100269 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200270 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200271 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200272 ) -> Iterator[test_case.TestCase]:
273 """Return test cases exercising key generation.
274
275 All key types can be generated except for public keys. For public key
276 PSA_ERROR_INVALID_ARGUMENT status is expected.
277 """
278 result = 'PSA_SUCCESS'
279
280 import_dependencies = [psa_want_symbol(kt.name)]
281 if kt.params is not None:
282 import_dependencies += [psa_want_symbol(sym)
283 for i, sym in enumerate(kt.params)]
284 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100285 # The library checks whether the key type is a public key generically,
286 # before it reaches a point where it needs support for the specific key
287 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200288 generate_dependencies = []
289 result = 'PSA_ERROR_INVALID_ARGUMENT'
290 else:
291 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100292 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200293 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200294 for bits in kt.sizes_to_test():
295 yield test_case_for_key_generation(
296 kt.expression, bits,
297 finish_family_dependencies(generate_dependencies, bits),
298 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200299 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200300 )
301
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200302 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
303 """Generate test cases that exercise the generation of keys."""
304 for key_type in sorted(self.constructors.key_types):
305 if key_type in self.ECC_KEY_TYPES:
306 continue
307 kt = crypto_knowledge.KeyType(key_type)
308 yield from self.test_cases_for_key_type_key_generation(kt)
309 for curve_family in sorted(self.constructors.ecc_curves):
310 for constr in self.ECC_KEY_TYPES:
311 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200312 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200313
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200314class OpFail:
315 """Generate test cases for operations that must fail."""
316 #pylint: disable=too-few-public-methods
317
Gilles Peskinecba28a72022-03-15 17:26:33 +0100318 class Reason(enum.Enum):
319 NOT_SUPPORTED = 0
320 INVALID = 1
321 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200322 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100323
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200324 def __init__(self, info: Information) -> None:
325 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100326 key_type_expressions = self.constructors.generate_expressions(
327 sorted(self.constructors.key_types)
328 )
329 self.key_types = [crypto_knowledge.KeyType(kt_expr)
330 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200331
Gilles Peskinecba28a72022-03-15 17:26:33 +0100332 def make_test_case(
333 self,
334 alg: crypto_knowledge.Algorithm,
335 category: crypto_knowledge.AlgorithmCategory,
336 reason: 'Reason',
337 kt: Optional[crypto_knowledge.KeyType] = None,
338 not_deps: FrozenSet[str] = frozenset(),
339 ) -> test_case.TestCase:
340 """Construct a failure test case for a one-key or keyless operation."""
341 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200342 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100343 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200344 if reason == self.Reason.NOT_SUPPORTED:
345 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
346 for dep in not_deps]
347 pretty_reason = '!' + '&'.join(sorted(short_deps))
348 else:
349 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100350 if kt:
351 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100352 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200353 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100354 key_type = ''
355 pretty_type = ''
356 tc.set_description('PSA {} {}: {}{}'
357 .format(category.name.lower(),
358 pretty_alg,
359 pretty_reason,
360 ' with ' + pretty_type if pretty_type else ''))
361 dependencies = automatic_dependencies(alg.base_expression, key_type)
362 for i, dep in enumerate(dependencies):
363 if dep in not_deps:
364 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200365 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100366 tc.set_function(category.name.lower() + '_fail')
367 arguments = []
368 if kt:
369 key_material = kt.key_material(kt.sizes_to_test()[0])
370 arguments += [key_type, test_case.hex_string(key_material)]
371 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200372 if category.is_asymmetric():
373 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100374 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
375 'INVALID_ARGUMENT')
376 arguments.append('PSA_ERROR_' + error)
377 tc.set_arguments(arguments)
378 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200379
Gilles Peskinecba28a72022-03-15 17:26:33 +0100380 def no_key_test_cases(
381 self,
382 alg: crypto_knowledge.Algorithm,
383 category: crypto_knowledge.AlgorithmCategory,
384 ) -> Iterator[test_case.TestCase]:
385 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200386 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100387 # Compatible operation, unsupported algorithm
388 for dep in automatic_dependencies(alg.base_expression):
389 yield self.make_test_case(alg, category,
390 self.Reason.NOT_SUPPORTED,
391 not_deps=frozenset([dep]))
392 else:
393 # Incompatible operation, supported algorithm
394 yield self.make_test_case(alg, category, self.Reason.INVALID)
395
396 def one_key_test_cases(
397 self,
398 alg: crypto_knowledge.Algorithm,
399 category: crypto_knowledge.AlgorithmCategory,
400 ) -> Iterator[test_case.TestCase]:
401 """Generate failure test cases for one-key operations with the specified algorithm."""
402 for kt in self.key_types:
403 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200404 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100405 # Compatible key and operation, unsupported algorithm
406 for dep in automatic_dependencies(alg.base_expression):
407 yield self.make_test_case(alg, category,
408 self.Reason.NOT_SUPPORTED,
409 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200410 # Public key for a private-key operation
411 if category.is_asymmetric() and kt.is_public():
412 yield self.make_test_case(alg, category,
413 self.Reason.PUBLIC,
414 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100415 elif key_is_compatible:
416 # Compatible key, incompatible operation, supported algorithm
417 yield self.make_test_case(alg, category,
418 self.Reason.INVALID,
419 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200420 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100421 # Incompatible key, compatible operation, supported algorithm
422 yield self.make_test_case(alg, category,
423 self.Reason.INCOMPATIBLE,
424 kt=kt)
425 else:
426 # Incompatible key and operation. Don't test cases where
427 # multiple things are wrong, to keep the number of test
428 # cases reasonable.
429 pass
430
431 def test_cases_for_algorithm(
432 self,
433 alg: crypto_knowledge.Algorithm,
434 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200435 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100436 for category in crypto_knowledge.AlgorithmCategory:
437 if category == crypto_knowledge.AlgorithmCategory.PAKE:
438 # PAKE operations are not implemented yet
439 pass
440 elif category.requires_key():
441 yield from self.one_key_test_cases(alg, category)
442 else:
443 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200444
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200445 def all_test_cases(self) -> Iterator[test_case.TestCase]:
446 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200447 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100448 for expr in self.constructors.generate_expressions(algorithms):
449 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200450 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200451
452
Gilles Peskine897dff92021-03-10 15:03:44 +0100453class StorageKey(psa_storage.Key):
454 """Representation of a key for storage format testing."""
455
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200456 IMPLICIT_USAGE_FLAGS = {
457 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
458 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
459 } #type: Dict[str, str]
460 """Mapping of usage flags to the flags that they imply."""
461
462 def __init__(
463 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100464 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200465 without_implicit_usage: Optional[bool] = False,
466 **kwargs
467 ) -> None:
468 """Prepare to generate a key.
469
470 * `usage` : The usage flags used for the key.
471 * `without_implicit_usage`: Flag to defide to apply the usage extension
472 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100473 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200474 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100475 for flag in sorted(usage_flags):
476 if flag in self.IMPLICIT_USAGE_FLAGS:
477 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
478 if usage_flags:
479 usage_expression = ' | '.join(sorted(usage_flags))
480 else:
481 usage_expression = '0'
482 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200483
484class StorageTestData(StorageKey):
485 """Representation of test case data for storage format testing."""
486
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200487 def __init__(
488 self,
489 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100490 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200491 **kwargs
492 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200493 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200494
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200495 * `description` : used for the the test case names
496 * `expected_usage`: the usage flags generated as the expected usage flags
497 in the test cases. CAn differ from the usage flags
498 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200499 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100500 super().__init__(**kwargs)
501 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100502 if expected_usage is None:
503 self.expected_usage = self.usage #type: psa_storage.Expr
504 elif expected_usage:
505 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
506 else:
507 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200508
Gilles Peskine897dff92021-03-10 15:03:44 +0100509class StorageFormat:
510 """Storage format stability test cases."""
511
512 def __init__(self, info: Information, version: int, forward: bool) -> None:
513 """Prepare to generate test cases for storage format stability.
514
515 * `info`: information about the API. See the `Information` class.
516 * `version`: the storage format version to generate test cases for.
517 * `forward`: if true, generate forward compatibility test cases which
518 save a key and check that its representation is as intended. Otherwise
519 generate backward compatibility test cases which inject a key
520 representation and check that it can be read and used.
521 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200522 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
523 self.version = version #type: int
524 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100525
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100526 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100527 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100528 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100529 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100530 cls,
531 key_type: psa_storage.Expr, bits: int,
532 alg: psa_storage.Expr
533 ) -> bool:
Gilles Peskine61548d12022-03-19 15:36:09 +0100534 """Whether to the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100535
536 Normally only the type and algorithm matter for compatibility, and
537 this is handled in crypto_knowledge.KeyType.can_do(). This function
538 exists to detect exceptional cases. Exceptional cases detected here
539 are not tested in OpFail and should therefore have manually written
540 test cases.
541 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100542 # Some test keys have the RAW_DATA type and attributes that don't
543 # necessarily make sense. We do this to validate numerical
544 # encodings of the attributes.
545 # Raw data keys have no useful exercise anyway so there is no
546 # loss of test coverage.
547 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
548 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100549 # OAEP requires room for two hashes plus wrapping
550 m = cls.RSA_OAEP_RE.match(alg.string)
551 if m:
552 hash_alg = m.group(1)
553 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
554 key_length = (bits + 7) // 8
555 # Leave enough room for at least one byte of plaintext
556 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100557 # There's nothing wrong with ECC keys on Brainpool curves,
558 # but operations with them are very slow. So we only exercise them
559 # with a single algorithm, not with all possible hashes. We do
560 # exercise other curves with all algorithms so test coverage is
561 # perfectly adequate like this.
562 m = cls.BRAINPOOL_RE.match(key_type.string)
563 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
564 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100565 return True
566
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200567 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100568 """Construct a storage format test case for the given key.
569
570 If ``forward`` is true, generate a forward compatibility test case:
571 create a key and validate that it has the expected representation.
572 Otherwise generate a backward compatibility test case: inject the
573 key representation into storage and validate that it can be read
574 correctly.
575 """
576 verb = 'save' if self.forward else 'read'
577 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100578 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100579 dependencies = automatic_dependencies(
580 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100581 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100582 )
583 dependencies = finish_family_dependencies(dependencies, key.bits)
584 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100585 tc.set_function('key_storage_' + verb)
586 if self.forward:
587 extra_arguments = []
588 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200589 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100590 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200591 flags.append('TEST_FLAG_EXERCISE')
592 if 'READ_ONLY' in key.lifetime.string:
593 flags.append('TEST_FLAG_READ_ONLY')
594 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100595 tc.set_arguments([key.lifetime.string,
596 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100597 key.expected_usage.string,
598 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100599 '"' + key.material.hex() + '"',
600 '"' + key.hex() + '"',
601 *extra_arguments])
602 return tc
603
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200604 def key_for_lifetime(
605 self,
606 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200607 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200608 """Construct a test key for the given lifetime."""
609 short = lifetime
610 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
611 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100612 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200613 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200614 key = StorageTestData(version=self.version,
615 id=1, lifetime=lifetime,
616 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100617 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200618 material=b'L',
619 description=description)
620 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200621
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200622 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200623 """Generate test keys covering lifetimes."""
624 lifetimes = sorted(self.constructors.lifetimes)
625 expressions = self.constructors.generate_expressions(lifetimes)
626 for lifetime in expressions:
627 # Don't attempt to create or load a volatile key in storage
628 if 'VOLATILE' in lifetime:
629 continue
630 # Don't attempt to create a read-only key in storage,
631 # but do attempt to load one.
632 if 'READ_ONLY' in lifetime and self.forward:
633 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200634 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200635
Gilles Peskinef7614272022-02-24 18:58:08 +0100636 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100637 self,
638 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200639 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100640 test_implicit_usage: Optional[bool] = True
641 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100642 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100643 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100644 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200645 key1 = StorageTestData(version=self.version,
646 id=1, lifetime=0x00000001,
647 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100648 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100649 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100650 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200651 material=b'K',
652 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100653 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100654 usage_expr = key1.expected_usage.string
655 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100656 else:
657 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100658 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100659
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200660 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100661 """Generate test keys covering usage flags."""
662 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100663 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200664 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100665 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200666 for flag1, flag2 in zip(known_flags,
667 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100668 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200669
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200670 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200671 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100672 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200673
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200674 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200675 yield from self.generate_keys_for_usage_flags()
676 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100677
Gilles Peskine7de7c102021-04-29 22:28:07 +0200678 def key_for_type_and_alg(
679 self,
680 kt: crypto_knowledge.KeyType,
681 bits: int,
682 alg: Optional[crypto_knowledge.Algorithm] = None,
683 ) -> StorageTestData:
684 """Construct a test key of the given type.
685
686 If alg is not None, this key allows it.
687 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100688 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100689 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200690 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100691 if alg is not None:
692 alg1 = alg.expression
693 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200694 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100695 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200696 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100697 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200698 key = StorageTestData(version=self.version,
699 id=1, lifetime=0x00000001,
700 type=kt.expression, bits=bits,
701 usage=usage_flags, alg=alg1, alg2=alg2,
702 material=key_material,
703 description=description)
704 return key
705
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100706 def keys_for_type(
707 self,
708 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200709 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200710 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200711 """Generate test keys for the given key type."""
712 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100713 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200714 # Test a non-exercisable key, as well as exercisable keys for
715 # each compatible algorithm.
716 # To do: test reading a key from storage with an incompatible
717 # or unsupported algorithm.
718 yield self.key_for_type_and_alg(kt, bits)
719 compatible_algorithms = [alg for alg in all_algorithms
720 if kt.can_do(alg)]
721 for alg in compatible_algorithms:
722 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100723
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200724 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100725 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200726 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200727 all_algorithms = [crypto_knowledge.Algorithm(alg)
728 for alg in self.constructors.generate_expressions(
729 sorted(self.constructors.algorithms)
730 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200731 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200732 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100733
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200734 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200735 """Generate test keys for the encoding of the specified algorithm."""
736 # These test cases only validate the encoding of algorithms, not
737 # whether the key read from storage is suitable for an operation.
738 # `keys_for_types` generate read tests with an algorithm and a
739 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100740 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100741 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200742 key1 = StorageTestData(version=self.version,
743 id=1, lifetime=0x00000001,
744 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
745 usage=usage, alg=alg, alg2=0,
746 material=b'K',
747 description='alg: ' + descr)
748 yield key1
749 key2 = StorageTestData(version=self.version,
750 id=1, lifetime=0x00000001,
751 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
752 usage=usage, alg=0, alg2=alg,
753 material=b'L',
754 description='alg2: ' + descr)
755 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100756
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200757 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100758 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200759 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200760 for alg in self.constructors.generate_expressions(algorithms):
761 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100762
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200763 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200764 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200765 yield from self.all_keys_for_lifetimes()
766 yield from self.all_keys_for_usage_flags()
767 yield from self.all_keys_for_types()
768 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200769
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200770 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100771 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200772 # First build a list of all keys, then construct all the corresponding
773 # test cases. This allows all required information to be obtained in
774 # one go, which is a significant performance gain as the information
775 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200776 all_keys = list(self.generate_all_keys())
777 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200778 if key.location_value() != 0:
779 # Skip keys with a non-default location, because they
780 # require a driver and we currently have no mechanism to
781 # determine whether a driver is available.
782 continue
783 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100784
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200785class StorageFormatForward(StorageFormat):
786 """Storage format stability test cases for forward compatibility."""
787
788 def __init__(self, info: Information, version: int) -> None:
789 super().__init__(info, version, True)
790
791class StorageFormatV0(StorageFormat):
792 """Storage format stability test cases for version 0 compatibility."""
793
794 def __init__(self, info: Information) -> None:
795 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100796
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200797 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200798 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100799 yield from super().all_keys_for_usage_flags()
800 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200801
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200802 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200803 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200804 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200805 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200806 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200807 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200808 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200809 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200810 algorithm and key type combination.
811 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200812 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200813 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100814 usage_flags = ['PSA_KEY_USAGE_EXPORT']
815 material_usage_flags = usage_flags + [implyer_usage]
816 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200817 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200818 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100819 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
820 alg_expression = crypto_knowledge.short_expression(alg, 1)
821 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200822 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200823 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200824 key = StorageTestData(version=self.version,
825 id=1, lifetime=0x00000001,
826 type=key_type.expression, bits=bits,
827 usage=material_usage_flags,
828 expected_usage=expected_usage_flags,
829 without_implicit_usage=True,
830 alg=alg, alg2=alg2,
831 material=key_material,
832 description=description)
833 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200834
835 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200836 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200837 """Match possible key types for sign algorithms."""
838 # To create a valid combinaton both the algorithms and key types
839 # must be filtered. Pair them with keywords created from its names.
840 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
841 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
842 keyword_translation = {
843 'ECDSA': 'ECC',
844 'ED[0-9]*.*' : 'EDWARDS'
845 }
846 exclusive_keywords = {
847 'EDWARDS': 'ECC'
848 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200849 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
850 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200851 alg_with_keys = {} #type: Dict[str, List[str]]
852 translation_table = str.maketrans('(', '_', ')')
853 for alg in algorithms:
854 # Generate keywords from the name of the algorithm
855 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
856 # Translate keywords for better matching with the key types
857 for keyword in alg_keywords.copy():
858 for pattern, replace in keyword_translation.items():
859 if re.match(pattern, keyword):
860 alg_keywords.remove(keyword)
861 alg_keywords.add(replace)
862 # Filter out incompatible algortihms
863 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
864 continue
865
866 for key_type in key_types:
867 # Generate keywords from the of the key type
868 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
869
870 # Remove ambigious keywords
871 for keyword1, keyword2 in exclusive_keywords.items():
872 if keyword1 in key_type_keywords:
873 key_type_keywords.remove(keyword2)
874
875 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
876 not key_type_keywords.isdisjoint(alg_keywords):
877 if alg in alg_with_keys:
878 alg_with_keys[alg].append(key_type)
879 else:
880 alg_with_keys[alg] = [key_type]
881 return alg_with_keys
882
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200883 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200884 """Generate test keys for usage flag extensions."""
885 # Generate a key type and algorithm pair for each extendable usage
886 # flag to generate a valid key for exercising. The key is generated
887 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200888 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200889
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200890 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
891 for alg in sorted(alg_with_keys):
892 for key_type in sorted(alg_with_keys[alg]):
893 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200894 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100895 if kt.is_public() and '_SIGN_' in usage:
896 # Can't sign with a public key
897 continue
898 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200899
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200900 def generate_all_keys(self) -> Iterator[StorageTestData]:
901 yield from super().generate_all_keys()
902 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200903
Gilles Peskineb94ea512021-03-10 02:12:08 +0100904class TestGenerator:
905 """Generate test data."""
906
907 def __init__(self, options) -> None:
908 self.test_suite_directory = self.get_option(options, 'directory',
909 'tests/suites')
910 self.info = Information()
911
912 @staticmethod
913 def get_option(options, name: str, default: T) -> T:
914 value = getattr(options, name, None)
915 return default if value is None else value
916
Gilles Peskine0298bda2021-03-10 02:34:37 +0100917 def filename_for(self, basename: str) -> str:
918 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200919 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100920
Gilles Peskineb94ea512021-03-10 02:12:08 +0100921 def write_test_data_file(self, basename: str,
922 test_cases: Iterable[test_case.TestCase]) -> None:
923 """Write the test cases to a .data file.
924
925 The output file is ``basename + '.data'`` in the test suite directory.
926 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100927 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100928 test_case.write_data_file(filename, test_cases)
929
Gilles Peskine92165362021-04-23 16:37:12 +0200930 # Note that targets whose name containns 'test_format' have their content
931 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100932 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200933 'test_suite_psa_crypto_generate_key.generated':
934 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100935 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100936 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200937 'test_suite_psa_crypto_op_fail.generated':
938 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100939 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200940 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100941 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200942 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100943 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
944
945 def generate_target(self, name: str) -> None:
946 test_cases = self.TARGETS[name](self.info)
947 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100948
Gilles Peskine09940492021-01-26 22:16:30 +0100949def main(args):
950 """Command line entry point."""
951 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100952 parser.add_argument('--list', action='store_true',
953 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100954 parser.add_argument('--list-for-cmake', action='store_true',
955 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200956 parser.add_argument('--directory', metavar='DIR',
957 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100958 parser.add_argument('targets', nargs='*', metavar='TARGET',
959 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100960 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200961 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100962 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100963 if options.list:
964 for name in sorted(generator.TARGETS):
965 print(generator.filename_for(name))
966 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100967 # List in a cmake list format (i.e. ';'-separated)
968 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100969 print(';'.join(generator.filename_for(name)
970 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100971 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100972 if options.targets:
973 # Allow "-" as a special case so you can run
974 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
975 # ``$targets`` is empty or not.
976 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
977 for target in options.targets
978 if target != '-']
979 else:
980 options.targets = sorted(generator.TARGETS)
981 for target in options.targets:
982 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100983
984if __name__ == '__main__':
985 main(sys.argv[1:])