blob: ea00290362ddc304a802cd493e9e5f32d69a7660 [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',
Przemek Stekiel1068c222022-05-05 11:52:30 +0200168 'PSA_KEY_TYPE_HMAC'
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100169 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100170 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100171 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100172 kt: crypto_knowledge.KeyType,
173 param: Optional[int] = None,
174 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100175 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200176 """Return test cases exercising key creation when the given type is unsupported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100177
178 If param is present and not None, emit test cases conditioned on this
179 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200180 conditioned on the base type not being supported.
Gilles Peskineaf172842021-01-27 18:24:48 +0100181 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100182 if kt.name in self.ALWAYS_SUPPORTED:
183 # Don't generate test cases for key types that are always supported.
184 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100185 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100186 import_dependencies = [('!' if param is None else '') +
187 psa_want_symbol(kt.name)]
188 if kt.params is not None:
189 import_dependencies += [('!' if param == i else '') +
190 psa_want_symbol(sym)
191 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100192 if kt.name.endswith('_PUBLIC_KEY'):
193 generate_dependencies = []
194 else:
195 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100196 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200197 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100198 'import', kt.expression, bits,
199 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100200 test_case.hex_string(kt.key_material(bits)),
201 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100202 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100203 if not generate_dependencies and param is not None:
204 # If generation is impossible for this key type, rather than
205 # supported or not depending on implementation capabilities,
206 # only generate the test case once.
207 continue
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100208 # For public key we expect that key generation fails with
209 # INVALID_ARGUMENT. It is handled by KeyGenerate class.
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100210 if not kt.is_public():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200211 yield test_case_for_key_type_not_supported(
212 'generate', kt.expression, bits,
213 finish_family_dependencies(generate_dependencies, bits),
214 str(bits),
215 param_descr=param_descr,
216 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200219 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
220 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
221
Gilles Peskine3d778392021-02-17 15:11:05 +0100222 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100223 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100224 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200225 if key_type in self.ECC_KEY_TYPES:
226 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100227 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100228 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200230 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100231 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100232 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100233 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100234 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100235 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100236
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200237def test_case_for_key_generation(
238 key_type: str, bits: int,
239 dependencies: List[str],
240 *args: str,
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200241 result: str = ''
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200242) -> test_case.TestCase:
243 """Return one test case exercising a key generation.
244 """
245 hack_dependencies_not_implemented(dependencies)
246 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100247 short_key_type = crypto_knowledge.short_expression(key_type)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200248 tc.set_description('PSA {} {}-bit'
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200249 .format(short_key_type, bits))
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200250 tc.set_dependencies(dependencies)
251 tc.set_function('generate_key')
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100252 tc.set_arguments([key_type] + list(args) + [result])
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200253
254 return tc
255
256class KeyGenerate:
257 """Generate positive and negative (invalid argument) test cases for key generation."""
258
259 def __init__(self, info: Information) -> None:
260 self.constructors = info.constructors
261
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200262 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
263 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
264
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100265 @staticmethod
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200266 def test_cases_for_key_type_key_generation(
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200267 kt: crypto_knowledge.KeyType
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200268 ) -> Iterator[test_case.TestCase]:
269 """Return test cases exercising key generation.
270
271 All key types can be generated except for public keys. For public key
272 PSA_ERROR_INVALID_ARGUMENT status is expected.
273 """
274 result = 'PSA_SUCCESS'
275
276 import_dependencies = [psa_want_symbol(kt.name)]
277 if kt.params is not None:
278 import_dependencies += [psa_want_symbol(sym)
279 for i, sym in enumerate(kt.params)]
280 if kt.name.endswith('_PUBLIC_KEY'):
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100281 # The library checks whether the key type is a public key generically,
282 # before it reaches a point where it needs support for the specific key
283 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200284 generate_dependencies = []
285 result = 'PSA_ERROR_INVALID_ARGUMENT'
286 else:
287 generate_dependencies = import_dependencies
Przemyslaw Stekiel7bc26b82021-11-02 10:50:44 +0100288 if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
Przemyslaw Stekielba20fc92021-10-22 10:39:56 +0200289 generate_dependencies.append("MBEDTLS_GENPRIME")
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200290 for bits in kt.sizes_to_test():
291 yield test_case_for_key_generation(
292 kt.expression, bits,
293 finish_family_dependencies(generate_dependencies, bits),
294 str(bits),
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200295 result
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200296 )
297
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200298 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
299 """Generate test cases that exercise the generation of keys."""
300 for key_type in sorted(self.constructors.key_types):
301 if key_type in self.ECC_KEY_TYPES:
302 continue
303 kt = crypto_knowledge.KeyType(key_type)
304 yield from self.test_cases_for_key_type_key_generation(kt)
305 for curve_family in sorted(self.constructors.ecc_curves):
306 for constr in self.ECC_KEY_TYPES:
307 kt = crypto_knowledge.KeyType(constr, [curve_family])
Przemyslaw Stekiel437da192021-10-20 11:59:50 +0200308 yield from self.test_cases_for_key_type_key_generation(kt)
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200309
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200310class OpFail:
311 """Generate test cases for operations that must fail."""
312 #pylint: disable=too-few-public-methods
313
Gilles Peskinecba28a72022-03-15 17:26:33 +0100314 class Reason(enum.Enum):
315 NOT_SUPPORTED = 0
316 INVALID = 1
317 INCOMPATIBLE = 2
Gilles Peskinee6300952021-04-29 21:56:59 +0200318 PUBLIC = 3
Gilles Peskinecba28a72022-03-15 17:26:33 +0100319
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200320 def __init__(self, info: Information) -> None:
321 self.constructors = info.constructors
Gilles Peskinecba28a72022-03-15 17:26:33 +0100322 key_type_expressions = self.constructors.generate_expressions(
323 sorted(self.constructors.key_types)
324 )
325 self.key_types = [crypto_knowledge.KeyType(kt_expr)
326 for kt_expr in key_type_expressions]
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200327
Gilles Peskinecba28a72022-03-15 17:26:33 +0100328 def make_test_case(
329 self,
330 alg: crypto_knowledge.Algorithm,
331 category: crypto_knowledge.AlgorithmCategory,
332 reason: 'Reason',
333 kt: Optional[crypto_knowledge.KeyType] = None,
334 not_deps: FrozenSet[str] = frozenset(),
335 ) -> test_case.TestCase:
336 """Construct a failure test case for a one-key or keyless operation."""
337 #pylint: disable=too-many-arguments,too-many-locals
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200338 tc = test_case.TestCase()
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100339 pretty_alg = alg.short_expression()
Gilles Peskined79e3b92021-04-29 21:35:03 +0200340 if reason == self.Reason.NOT_SUPPORTED:
341 short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
342 for dep in not_deps]
343 pretty_reason = '!' + '&'.join(sorted(short_deps))
344 else:
345 pretty_reason = reason.name.lower()
Gilles Peskinecba28a72022-03-15 17:26:33 +0100346 if kt:
347 key_type = kt.expression
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100348 pretty_type = kt.short_expression()
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200349 else:
Gilles Peskinecba28a72022-03-15 17:26:33 +0100350 key_type = ''
351 pretty_type = ''
352 tc.set_description('PSA {} {}: {}{}'
353 .format(category.name.lower(),
354 pretty_alg,
355 pretty_reason,
356 ' with ' + pretty_type if pretty_type else ''))
357 dependencies = automatic_dependencies(alg.base_expression, key_type)
358 for i, dep in enumerate(dependencies):
359 if dep in not_deps:
360 dependencies[i] = '!' + dep
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200361 tc.set_dependencies(dependencies)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100362 tc.set_function(category.name.lower() + '_fail')
363 arguments = []
364 if kt:
365 key_material = kt.key_material(kt.sizes_to_test()[0])
366 arguments += [key_type, test_case.hex_string(key_material)]
367 arguments.append(alg.expression)
Gilles Peskinee6300952021-04-29 21:56:59 +0200368 if category.is_asymmetric():
369 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
Gilles Peskinecba28a72022-03-15 17:26:33 +0100370 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
371 'INVALID_ARGUMENT')
372 arguments.append('PSA_ERROR_' + error)
373 tc.set_arguments(arguments)
374 return tc
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200375
Gilles Peskinecba28a72022-03-15 17:26:33 +0100376 def no_key_test_cases(
377 self,
378 alg: crypto_knowledge.Algorithm,
379 category: crypto_knowledge.AlgorithmCategory,
380 ) -> Iterator[test_case.TestCase]:
381 """Generate failure test cases for keyless operations with the specified algorithm."""
Gilles Peskinea4013862021-04-29 20:54:40 +0200382 if alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100383 # Compatible operation, unsupported algorithm
384 for dep in automatic_dependencies(alg.base_expression):
385 yield self.make_test_case(alg, category,
386 self.Reason.NOT_SUPPORTED,
387 not_deps=frozenset([dep]))
388 else:
389 # Incompatible operation, supported algorithm
390 yield self.make_test_case(alg, category, self.Reason.INVALID)
391
392 def one_key_test_cases(
393 self,
394 alg: crypto_knowledge.Algorithm,
395 category: crypto_knowledge.AlgorithmCategory,
396 ) -> Iterator[test_case.TestCase]:
397 """Generate failure test cases for one-key operations with the specified algorithm."""
398 for kt in self.key_types:
399 key_is_compatible = kt.can_do(alg)
Gilles Peskinea4013862021-04-29 20:54:40 +0200400 if key_is_compatible and alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100401 # Compatible key and operation, unsupported algorithm
402 for dep in automatic_dependencies(alg.base_expression):
403 yield self.make_test_case(alg, category,
404 self.Reason.NOT_SUPPORTED,
405 kt=kt, not_deps=frozenset([dep]))
Gilles Peskinee6300952021-04-29 21:56:59 +0200406 # Public key for a private-key operation
407 if category.is_asymmetric() and kt.is_public():
408 yield self.make_test_case(alg, category,
409 self.Reason.PUBLIC,
410 kt=kt)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100411 elif key_is_compatible:
412 # Compatible key, incompatible operation, supported algorithm
413 yield self.make_test_case(alg, category,
414 self.Reason.INVALID,
415 kt=kt)
Gilles Peskinea4013862021-04-29 20:54:40 +0200416 elif alg.can_do(category):
Gilles Peskinecba28a72022-03-15 17:26:33 +0100417 # Incompatible key, compatible operation, supported algorithm
418 yield self.make_test_case(alg, category,
419 self.Reason.INCOMPATIBLE,
420 kt=kt)
421 else:
422 # Incompatible key and operation. Don't test cases where
423 # multiple things are wrong, to keep the number of test
424 # cases reasonable.
425 pass
426
427 def test_cases_for_algorithm(
428 self,
429 alg: crypto_knowledge.Algorithm,
430 ) -> Iterator[test_case.TestCase]:
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200431 """Generate operation failure test cases for the specified algorithm."""
Gilles Peskinecba28a72022-03-15 17:26:33 +0100432 for category in crypto_knowledge.AlgorithmCategory:
433 if category == crypto_knowledge.AlgorithmCategory.PAKE:
434 # PAKE operations are not implemented yet
435 pass
436 elif category.requires_key():
437 yield from self.one_key_test_cases(alg, category)
438 else:
439 yield from self.no_key_test_cases(alg, category)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200440
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200441 def all_test_cases(self) -> Iterator[test_case.TestCase]:
442 """Generate all test cases for operations that must fail."""
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200443 algorithms = sorted(self.constructors.algorithms)
Gilles Peskinecba28a72022-03-15 17:26:33 +0100444 for expr in self.constructors.generate_expressions(algorithms):
445 alg = crypto_knowledge.Algorithm(expr)
Gilles Peskine8b4a3812021-04-27 21:03:43 +0200446 yield from self.test_cases_for_algorithm(alg)
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200447
448
Gilles Peskine897dff92021-03-10 15:03:44 +0100449class StorageKey(psa_storage.Key):
450 """Representation of a key for storage format testing."""
451
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200452 IMPLICIT_USAGE_FLAGS = {
453 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
454 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
455 } #type: Dict[str, str]
456 """Mapping of usage flags to the flags that they imply."""
457
458 def __init__(
459 self,
Gilles Peskine564fae82022-03-17 22:32:59 +0100460 usage: Iterable[str],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200461 without_implicit_usage: Optional[bool] = False,
462 **kwargs
463 ) -> None:
464 """Prepare to generate a key.
465
466 * `usage` : The usage flags used for the key.
467 * `without_implicit_usage`: Flag to defide to apply the usage extension
468 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100469 usage_flags = set(usage)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200470 if not without_implicit_usage:
Gilles Peskine564fae82022-03-17 22:32:59 +0100471 for flag in sorted(usage_flags):
472 if flag in self.IMPLICIT_USAGE_FLAGS:
473 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
474 if usage_flags:
475 usage_expression = ' | '.join(sorted(usage_flags))
476 else:
477 usage_expression = '0'
478 super().__init__(usage=usage_expression, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200479
480class StorageTestData(StorageKey):
481 """Representation of test case data for storage format testing."""
482
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200483 def __init__(
484 self,
485 description: str,
Gilles Peskine564fae82022-03-17 22:32:59 +0100486 expected_usage: Optional[List[str]] = None,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200487 **kwargs
488 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200489 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200490
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200491 * `description` : used for the the test case names
492 * `expected_usage`: the usage flags generated as the expected usage flags
493 in the test cases. CAn differ from the usage flags
494 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200495 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100496 super().__init__(**kwargs)
497 self.description = description #type: str
Gilles Peskine564fae82022-03-17 22:32:59 +0100498 if expected_usage is None:
499 self.expected_usage = self.usage #type: psa_storage.Expr
500 elif expected_usage:
501 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
502 else:
503 self.expected_usage = psa_storage.Expr(0)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200504
Gilles Peskine897dff92021-03-10 15:03:44 +0100505class StorageFormat:
506 """Storage format stability test cases."""
507
508 def __init__(self, info: Information, version: int, forward: bool) -> None:
509 """Prepare to generate test cases for storage format stability.
510
511 * `info`: information about the API. See the `Information` class.
512 * `version`: the storage format version to generate test cases for.
513 * `forward`: if true, generate forward compatibility test cases which
514 save a key and check that its representation is as intended. Otherwise
515 generate backward compatibility test cases which inject a key
516 representation and check that it can be read and used.
517 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200518 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
519 self.version = version #type: int
520 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100521
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100522 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
Gilles Peskine61548d12022-03-19 15:36:09 +0100523 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100524 @classmethod
Gilles Peskine61548d12022-03-19 15:36:09 +0100525 def exercise_key_with_algorithm(
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100526 cls,
527 key_type: psa_storage.Expr, bits: int,
528 alg: psa_storage.Expr
529 ) -> bool:
Gilles Peskine61548d12022-03-19 15:36:09 +0100530 """Whether to the given key with the given algorithm.
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100531
532 Normally only the type and algorithm matter for compatibility, and
533 this is handled in crypto_knowledge.KeyType.can_do(). This function
534 exists to detect exceptional cases. Exceptional cases detected here
535 are not tested in OpFail and should therefore have manually written
536 test cases.
537 """
Gilles Peskine61548d12022-03-19 15:36:09 +0100538 # Some test keys have the RAW_DATA type and attributes that don't
539 # necessarily make sense. We do this to validate numerical
540 # encodings of the attributes.
541 # Raw data keys have no useful exercise anyway so there is no
542 # loss of test coverage.
543 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
544 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100545 # OAEP requires room for two hashes plus wrapping
546 m = cls.RSA_OAEP_RE.match(alg.string)
547 if m:
548 hash_alg = m.group(1)
549 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
550 key_length = (bits + 7) // 8
551 # Leave enough room for at least one byte of plaintext
552 return key_length > 2 * hash_length + 2
Gilles Peskine61548d12022-03-19 15:36:09 +0100553 # There's nothing wrong with ECC keys on Brainpool curves,
554 # but operations with them are very slow. So we only exercise them
555 # with a single algorithm, not with all possible hashes. We do
556 # exercise other curves with all algorithms so test coverage is
557 # perfectly adequate like this.
558 m = cls.BRAINPOOL_RE.match(key_type.string)
559 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
560 return False
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100561 return True
562
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200563 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100564 """Construct a storage format test case for the given key.
565
566 If ``forward`` is true, generate a forward compatibility test case:
567 create a key and validate that it has the expected representation.
568 Otherwise generate a backward compatibility test case: inject the
569 key representation into storage and validate that it can be read
570 correctly.
571 """
572 verb = 'save' if self.forward else 'read'
573 tc = test_case.TestCase()
Gilles Peskine16b25062022-03-18 00:02:15 +0100574 tc.set_description(verb + ' ' + key.description)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100575 dependencies = automatic_dependencies(
576 key.lifetime.string, key.type.string,
Gilles Peskine564fae82022-03-17 22:32:59 +0100577 key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100578 )
579 dependencies = finish_family_dependencies(dependencies, key.bits)
580 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100581 tc.set_function('key_storage_' + verb)
582 if self.forward:
583 extra_arguments = []
584 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200585 flags = []
Gilles Peskine61548d12022-03-19 15:36:09 +0100586 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200587 flags.append('TEST_FLAG_EXERCISE')
588 if 'READ_ONLY' in key.lifetime.string:
589 flags.append('TEST_FLAG_READ_ONLY')
590 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100591 tc.set_arguments([key.lifetime.string,
592 key.type.string, str(key.bits),
Gilles Peskine564fae82022-03-17 22:32:59 +0100593 key.expected_usage.string,
594 key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100595 '"' + key.material.hex() + '"',
596 '"' + key.hex() + '"',
597 *extra_arguments])
598 return tc
599
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200600 def key_for_lifetime(
601 self,
602 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200603 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200604 """Construct a test key for the given lifetime."""
605 short = lifetime
606 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
607 r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100608 short = crypto_knowledge.short_expression(short)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200609 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200610 key = StorageTestData(version=self.version,
611 id=1, lifetime=lifetime,
612 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100613 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200614 material=b'L',
615 description=description)
616 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200617
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200618 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200619 """Generate test keys covering lifetimes."""
620 lifetimes = sorted(self.constructors.lifetimes)
621 expressions = self.constructors.generate_expressions(lifetimes)
622 for lifetime in expressions:
623 # Don't attempt to create or load a volatile key in storage
624 if 'VOLATILE' in lifetime:
625 continue
626 # Don't attempt to create a read-only key in storage,
627 # but do attempt to load one.
628 if 'READ_ONLY' in lifetime and self.forward:
629 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200630 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200631
Gilles Peskinef7614272022-02-24 18:58:08 +0100632 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100633 self,
634 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200635 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100636 test_implicit_usage: Optional[bool] = True
637 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100638 """Construct a test key for the given key usage."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100639 extra_desc = ' without implication' if test_implicit_usage else ''
Gilles Peskine564fae82022-03-17 22:32:59 +0100640 description = 'usage' + extra_desc + ': '
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200641 key1 = StorageTestData(version=self.version,
642 id=1, lifetime=0x00000001,
643 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
Gilles Peskine564fae82022-03-17 22:32:59 +0100644 expected_usage=usage_flags,
Gilles Peskinef7614272022-02-24 18:58:08 +0100645 without_implicit_usage=not test_implicit_usage,
Gilles Peskine564fae82022-03-17 22:32:59 +0100646 usage=usage_flags, alg=0, alg2=0,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200647 material=b'K',
648 description=description)
Gilles Peskine564fae82022-03-17 22:32:59 +0100649 if short is None:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100650 usage_expr = key1.expected_usage.string
651 key1.description += crypto_knowledge.short_expression(usage_expr)
Gilles Peskine564fae82022-03-17 22:32:59 +0100652 else:
653 key1.description += short
Gilles Peskinef7614272022-02-24 18:58:08 +0100654 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100655
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200656 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100657 """Generate test keys covering usage flags."""
658 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100659 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200660 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100661 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200662 for flag1, flag2 in zip(known_flags,
663 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100664 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200665
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200666 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200667 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100668 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200669
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200670 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200671 yield from self.generate_keys_for_usage_flags()
672 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100673
Gilles Peskine7de7c102021-04-29 22:28:07 +0200674 def key_for_type_and_alg(
675 self,
676 kt: crypto_knowledge.KeyType,
677 bits: int,
678 alg: Optional[crypto_knowledge.Algorithm] = None,
679 ) -> StorageTestData:
680 """Construct a test key of the given type.
681
682 If alg is not None, this key allows it.
683 """
Gilles Peskine564fae82022-03-17 22:32:59 +0100684 usage_flags = ['PSA_KEY_USAGE_EXPORT']
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100685 alg1 = 0 #type: psa_storage.Exprable
Gilles Peskine7de7c102021-04-29 22:28:07 +0200686 alg2 = 0
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100687 if alg is not None:
688 alg1 = alg.expression
689 usage_flags += alg.usage_flags(public=kt.is_public())
Gilles Peskine7de7c102021-04-29 22:28:07 +0200690 key_material = kt.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100691 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200692 if alg is not None:
Gilles Peskine16b25062022-03-18 00:02:15 +0100693 description += ', ' + alg.short_expression(1)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200694 key = StorageTestData(version=self.version,
695 id=1, lifetime=0x00000001,
696 type=kt.expression, bits=bits,
697 usage=usage_flags, alg=alg1, alg2=alg2,
698 material=key_material,
699 description=description)
700 return key
701
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100702 def keys_for_type(
703 self,
704 key_type: str,
Gilles Peskine7de7c102021-04-29 22:28:07 +0200705 all_algorithms: List[crypto_knowledge.Algorithm],
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200706 ) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200707 """Generate test keys for the given key type."""
708 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100709 for bits in kt.sizes_to_test():
Gilles Peskine7de7c102021-04-29 22:28:07 +0200710 # Test a non-exercisable key, as well as exercisable keys for
711 # each compatible algorithm.
712 # To do: test reading a key from storage with an incompatible
713 # or unsupported algorithm.
714 yield self.key_for_type_and_alg(kt, bits)
715 compatible_algorithms = [alg for alg in all_algorithms
716 if kt.can_do(alg)]
717 for alg in compatible_algorithms:
718 yield self.key_for_type_and_alg(kt, bits, alg)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100719
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200720 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100721 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200722 key_types = sorted(self.constructors.key_types)
Gilles Peskine7de7c102021-04-29 22:28:07 +0200723 all_algorithms = [crypto_knowledge.Algorithm(alg)
724 for alg in self.constructors.generate_expressions(
725 sorted(self.constructors.algorithms)
726 )]
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200727 for key_type in self.constructors.generate_expressions(key_types):
Gilles Peskine7de7c102021-04-29 22:28:07 +0200728 yield from self.keys_for_type(key_type, all_algorithms)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100729
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200730 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskine7de7c102021-04-29 22:28:07 +0200731 """Generate test keys for the encoding of the specified algorithm."""
732 # These test cases only validate the encoding of algorithms, not
733 # whether the key read from storage is suitable for an operation.
734 # `keys_for_types` generate read tests with an algorithm and a
735 # compatible key.
Gilles Peskine16b25062022-03-18 00:02:15 +0100736 descr = crypto_knowledge.short_expression(alg, 1)
Gilles Peskine564fae82022-03-17 22:32:59 +0100737 usage = ['PSA_KEY_USAGE_EXPORT']
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200738 key1 = StorageTestData(version=self.version,
739 id=1, lifetime=0x00000001,
740 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
741 usage=usage, alg=alg, alg2=0,
742 material=b'K',
743 description='alg: ' + descr)
744 yield key1
745 key2 = StorageTestData(version=self.version,
746 id=1, lifetime=0x00000001,
747 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
748 usage=usage, alg=0, alg2=alg,
749 material=b'L',
750 description='alg2: ' + descr)
751 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100752
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200753 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100754 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200755 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200756 for alg in self.constructors.generate_expressions(algorithms):
757 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100758
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200759 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200760 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200761 yield from self.all_keys_for_lifetimes()
762 yield from self.all_keys_for_usage_flags()
763 yield from self.all_keys_for_types()
764 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200765
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200766 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100767 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200768 # First build a list of all keys, then construct all the corresponding
769 # test cases. This allows all required information to be obtained in
770 # one go, which is a significant performance gain as the information
771 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200772 all_keys = list(self.generate_all_keys())
773 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200774 if key.location_value() != 0:
775 # Skip keys with a non-default location, because they
776 # require a driver and we currently have no mechanism to
777 # determine whether a driver is available.
778 continue
779 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100780
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200781class StorageFormatForward(StorageFormat):
782 """Storage format stability test cases for forward compatibility."""
783
784 def __init__(self, info: Information, version: int) -> None:
785 super().__init__(info, version, True)
786
787class StorageFormatV0(StorageFormat):
788 """Storage format stability test cases for version 0 compatibility."""
789
790 def __init__(self, info: Information) -> None:
791 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100792
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200793 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200794 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100795 yield from super().all_keys_for_usage_flags()
796 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200797
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200798 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200799 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200800 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200801 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200802 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200803 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200804 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200805 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200806 algorithm and key type combination.
807 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200808 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200809 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
Gilles Peskine564fae82022-03-17 22:32:59 +0100810 usage_flags = ['PSA_KEY_USAGE_EXPORT']
811 material_usage_flags = usage_flags + [implyer_usage]
812 expected_usage_flags = material_usage_flags + [implicit_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200813 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200814 key_material = key_type.key_material(bits)
Gilles Peskine16b25062022-03-18 00:02:15 +0100815 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
816 alg_expression = crypto_knowledge.short_expression(alg, 1)
817 key_type_expression = key_type.short_expression(1)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200818 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200819 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200820 key = StorageTestData(version=self.version,
821 id=1, lifetime=0x00000001,
822 type=key_type.expression, bits=bits,
823 usage=material_usage_flags,
824 expected_usage=expected_usage_flags,
825 without_implicit_usage=True,
826 alg=alg, alg2=alg2,
827 material=key_material,
828 description=description)
829 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200830
831 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200832 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200833 """Match possible key types for sign algorithms."""
834 # To create a valid combinaton both the algorithms and key types
835 # must be filtered. Pair them with keywords created from its names.
836 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
837 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
838 keyword_translation = {
839 'ECDSA': 'ECC',
840 'ED[0-9]*.*' : 'EDWARDS'
841 }
842 exclusive_keywords = {
843 'EDWARDS': 'ECC'
844 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200845 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
846 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200847 alg_with_keys = {} #type: Dict[str, List[str]]
848 translation_table = str.maketrans('(', '_', ')')
849 for alg in algorithms:
850 # Generate keywords from the name of the algorithm
851 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
852 # Translate keywords for better matching with the key types
853 for keyword in alg_keywords.copy():
854 for pattern, replace in keyword_translation.items():
855 if re.match(pattern, keyword):
856 alg_keywords.remove(keyword)
857 alg_keywords.add(replace)
858 # Filter out incompatible algortihms
859 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
860 continue
861
862 for key_type in key_types:
863 # Generate keywords from the of the key type
864 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
865
866 # Remove ambigious keywords
867 for keyword1, keyword2 in exclusive_keywords.items():
868 if keyword1 in key_type_keywords:
869 key_type_keywords.remove(keyword2)
870
871 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
872 not key_type_keywords.isdisjoint(alg_keywords):
873 if alg in alg_with_keys:
874 alg_with_keys[alg].append(key_type)
875 else:
876 alg_with_keys[alg] = [key_type]
877 return alg_with_keys
878
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200879 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200880 """Generate test keys for usage flag extensions."""
881 # Generate a key type and algorithm pair for each extendable usage
882 # flag to generate a valid key for exercising. The key is generated
883 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200884 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200885
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200886 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
887 for alg in sorted(alg_with_keys):
888 for key_type in sorted(alg_with_keys[alg]):
889 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200890 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskinefa70ced2022-03-17 12:52:24 +0100891 if kt.is_public() and '_SIGN_' in usage:
892 # Can't sign with a public key
893 continue
894 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200895
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200896 def generate_all_keys(self) -> Iterator[StorageTestData]:
897 yield from super().generate_all_keys()
898 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200899
Gilles Peskineb94ea512021-03-10 02:12:08 +0100900class TestGenerator:
901 """Generate test data."""
902
903 def __init__(self, options) -> None:
904 self.test_suite_directory = self.get_option(options, 'directory',
905 'tests/suites')
906 self.info = Information()
907
908 @staticmethod
909 def get_option(options, name: str, default: T) -> T:
910 value = getattr(options, name, None)
911 return default if value is None else value
912
Gilles Peskine0298bda2021-03-10 02:34:37 +0100913 def filename_for(self, basename: str) -> str:
914 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200915 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100916
Gilles Peskineb94ea512021-03-10 02:12:08 +0100917 def write_test_data_file(self, basename: str,
918 test_cases: Iterable[test_case.TestCase]) -> None:
919 """Write the test cases to a .data file.
920
921 The output file is ``basename + '.data'`` in the test suite directory.
922 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100923 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100924 test_case.write_data_file(filename, test_cases)
925
Gilles Peskine92165362021-04-23 16:37:12 +0200926 # Note that targets whose name containns 'test_format' have their content
927 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100928 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200929 'test_suite_psa_crypto_generate_key.generated':
930 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100931 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100932 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200933 'test_suite_psa_crypto_op_fail.generated':
934 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100935 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200936 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100937 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200938 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100939 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
940
941 def generate_target(self, name: str) -> None:
942 test_cases = self.TARGETS[name](self.info)
943 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100944
Gilles Peskine09940492021-01-26 22:16:30 +0100945def main(args):
946 """Command line entry point."""
947 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100948 parser.add_argument('--list', action='store_true',
949 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100950 parser.add_argument('--list-for-cmake', action='store_true',
951 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200952 parser.add_argument('--directory', metavar='DIR',
953 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100954 parser.add_argument('targets', nargs='*', metavar='TARGET',
955 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100956 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200957 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100958 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100959 if options.list:
960 for name in sorted(generator.TARGETS):
961 print(generator.filename_for(name))
962 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100963 # List in a cmake list format (i.e. ';'-separated)
964 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100965 print(';'.join(generator.filename_for(name)
966 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100967 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100968 if options.targets:
969 # Allow "-" as a special case so you can run
970 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
971 # ``$targets`` is empty or not.
972 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
973 for target in options.targets
974 if target != '-']
975 else:
976 options.targets = sorted(generator.TARGETS)
977 for target in options.targets:
978 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100979
980if __name__ == '__main__':
981 main(sys.argv[1:])