blob: 2e9783a15148181eeabc2a57d2baf5430166ba53 [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()
148 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
149 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.
Przemyslaw Stekiel8d468e42021-10-18 14:58:20 +0200210 if not kt.name.endswith('_PUBLIC_KEY'):
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()
247 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
248 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 Peskinecba28a72022-03-15 17:26:33 +0100339 pretty_alg = re.sub(r'PSA_ALG_', r'', alg.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
348 pretty_type = re.sub(r'PSA_KEY_TYPE_', r'', key_type)
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,
460 usage: str,
461 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 """
gabor-mezei-arm2c9e54a2021-06-29 17:21:21 +0200469 super().__init__(usage=usage, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200470
471 if not without_implicit_usage:
472 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
473 if self.usage.value() & psa_storage.Expr(flag).value() and \
474 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
475 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
476
477class StorageTestData(StorageKey):
478 """Representation of test case data for storage format testing."""
479
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200480 def __init__(
481 self,
482 description: str,
483 expected_usage: Optional[str] = None,
484 **kwargs
485 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200486 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200487
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200488 * `description` : used for the the test case names
489 * `expected_usage`: the usage flags generated as the expected usage flags
490 in the test cases. CAn differ from the usage flags
491 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200492 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100493 super().__init__(**kwargs)
494 self.description = description #type: str
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200495 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200496
Gilles Peskine897dff92021-03-10 15:03:44 +0100497class StorageFormat:
498 """Storage format stability test cases."""
499
500 def __init__(self, info: Information, version: int, forward: bool) -> None:
501 """Prepare to generate test cases for storage format stability.
502
503 * `info`: information about the API. See the `Information` class.
504 * `version`: the storage format version to generate test cases for.
505 * `forward`: if true, generate forward compatibility test cases which
506 save a key and check that its representation is as intended. Otherwise
507 generate backward compatibility test cases which inject a key
508 representation and check that it can be read and used.
509 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200510 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
511 self.version = version #type: int
512 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100513
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200514 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100515 """Construct a storage format test case for the given key.
516
517 If ``forward`` is true, generate a forward compatibility test case:
518 create a key and validate that it has the expected representation.
519 Otherwise generate a backward compatibility test case: inject the
520 key representation into storage and validate that it can be read
521 correctly.
522 """
523 verb = 'save' if self.forward else 'read'
524 tc = test_case.TestCase()
525 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100526 dependencies = automatic_dependencies(
527 key.lifetime.string, key.type.string,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200528 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100529 )
530 dependencies = finish_family_dependencies(dependencies, key.bits)
531 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100532 tc.set_function('key_storage_' + verb)
533 if self.forward:
534 extra_arguments = []
535 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200536 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100537 # Some test keys have the RAW_DATA type and attributes that don't
538 # necessarily make sense. We do this to validate numerical
539 # encodings of the attributes.
540 # Raw data keys have no useful exercise anyway so there is no
541 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200542 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
543 flags.append('TEST_FLAG_EXERCISE')
544 if 'READ_ONLY' in key.lifetime.string:
545 flags.append('TEST_FLAG_READ_ONLY')
546 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100547 tc.set_arguments([key.lifetime.string,
548 key.type.string, str(key.bits),
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200549 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100550 '"' + key.material.hex() + '"',
551 '"' + key.hex() + '"',
552 *extra_arguments])
553 return tc
554
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200555 def key_for_lifetime(
556 self,
557 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200558 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200559 """Construct a test key for the given lifetime."""
560 short = lifetime
561 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
562 r'', short)
563 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
564 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200565 key = StorageTestData(version=self.version,
566 id=1, lifetime=lifetime,
567 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
568 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
569 material=b'L',
570 description=description)
571 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200572
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200573 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200574 """Generate test keys covering lifetimes."""
575 lifetimes = sorted(self.constructors.lifetimes)
576 expressions = self.constructors.generate_expressions(lifetimes)
577 for lifetime in expressions:
578 # Don't attempt to create or load a volatile key in storage
579 if 'VOLATILE' in lifetime:
580 continue
581 # Don't attempt to create a read-only key in storage,
582 # but do attempt to load one.
583 if 'READ_ONLY' in lifetime and self.forward:
584 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200585 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200586
Gilles Peskinef7614272022-02-24 18:58:08 +0100587 def key_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100588 self,
589 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200590 short: Optional[str] = None,
Gilles Peskinef7614272022-02-24 18:58:08 +0100591 test_implicit_usage: Optional[bool] = True
592 ) -> StorageTestData:
Gilles Peskine897dff92021-03-10 15:03:44 +0100593 """Construct a test key for the given key usage."""
594 usage = ' | '.join(usage_flags) if usage_flags else '0'
595 if short is None:
596 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
Gilles Peskinef7614272022-02-24 18:58:08 +0100597 extra_desc = ' without implication' if test_implicit_usage else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200598 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200599 key1 = StorageTestData(version=self.version,
600 id=1, lifetime=0x00000001,
601 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
602 expected_usage=usage,
Gilles Peskinef7614272022-02-24 18:58:08 +0100603 without_implicit_usage=not test_implicit_usage,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200604 usage=usage, alg=0, alg2=0,
605 material=b'K',
606 description=description)
Gilles Peskinef7614272022-02-24 18:58:08 +0100607 return key1
Gilles Peskine897dff92021-03-10 15:03:44 +0100608
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200609 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100610 """Generate test keys covering usage flags."""
611 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100612 yield self.key_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200613 for usage_flag in known_flags:
Gilles Peskinef7614272022-02-24 18:58:08 +0100614 yield self.key_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200615 for flag1, flag2 in zip(known_flags,
616 known_flags[1:] + [known_flags[0]]):
Gilles Peskinef7614272022-02-24 18:58:08 +0100617 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200618
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200619 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200620 known_flags = sorted(self.constructors.key_usage_flags)
Gilles Peskinef7614272022-02-24 18:58:08 +0100621 yield self.key_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200622
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200623 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200624 yield from self.generate_keys_for_usage_flags()
625 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100626
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100627 def keys_for_type(
628 self,
629 key_type: str,
630 params: Optional[Iterable[str]] = None
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200631 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100632 """Generate test keys for the given key type.
633
634 For key types that depend on a parameter (e.g. elliptic curve family),
635 `param` is the parameter to pass to the constructor. Only a single
636 parameter is supported.
637 """
638 kt = crypto_knowledge.KeyType(key_type, params)
639 for bits in kt.sizes_to_test():
640 usage_flags = 'PSA_KEY_USAGE_EXPORT'
641 alg = 0
642 alg2 = 0
643 key_material = kt.key_material(bits)
644 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
645 r'',
646 kt.expression)
647 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200648 key = StorageTestData(version=self.version,
649 id=1, lifetime=0x00000001,
650 type=kt.expression, bits=bits,
651 usage=usage_flags, alg=alg, alg2=alg2,
652 material=key_material,
653 description=description)
654 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100655
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200656 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100657 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200658 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200659 for key_type in self.constructors.generate_expressions(key_types):
660 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100661
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200662 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100663 """Generate test keys for the specified algorithm."""
664 # For now, we don't have information on the compatibility of key
665 # types and algorithms. So we just test the encoding of algorithms,
666 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200667 descr = re.sub(r'PSA_ALG_', r'', alg)
668 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100669 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200670 key1 = StorageTestData(version=self.version,
671 id=1, lifetime=0x00000001,
672 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
673 usage=usage, alg=alg, alg2=0,
674 material=b'K',
675 description='alg: ' + descr)
676 yield key1
677 key2 = StorageTestData(version=self.version,
678 id=1, lifetime=0x00000001,
679 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
680 usage=usage, alg=0, alg2=alg,
681 material=b'L',
682 description='alg2: ' + descr)
683 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100684
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200685 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100686 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200687 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200688 for alg in self.constructors.generate_expressions(algorithms):
689 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100690
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200691 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200692 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200693 yield from self.all_keys_for_lifetimes()
694 yield from self.all_keys_for_usage_flags()
695 yield from self.all_keys_for_types()
696 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200697
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200698 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100699 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200700 # First build a list of all keys, then construct all the corresponding
701 # test cases. This allows all required information to be obtained in
702 # one go, which is a significant performance gain as the information
703 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200704 all_keys = list(self.generate_all_keys())
705 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200706 if key.location_value() != 0:
707 # Skip keys with a non-default location, because they
708 # require a driver and we currently have no mechanism to
709 # determine whether a driver is available.
710 continue
711 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100712
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200713class StorageFormatForward(StorageFormat):
714 """Storage format stability test cases for forward compatibility."""
715
716 def __init__(self, info: Information, version: int) -> None:
717 super().__init__(info, version, True)
718
719class StorageFormatV0(StorageFormat):
720 """Storage format stability test cases for version 0 compatibility."""
721
722 def __init__(self, info: Information) -> None:
723 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100724
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200725 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200726 """Generate test keys covering usage flags."""
Gilles Peskinef7614272022-02-24 18:58:08 +0100727 yield from super().all_keys_for_usage_flags()
728 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200729
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200730 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200731 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200732 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200733 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200734 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200735 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200736 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200737 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200738 algorithm and key type combination.
739 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200740 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200741 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200742 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200743 material_usage_flags = usage_flags + ' | ' + implyer_usage
744 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200745 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200746 key_material = key_type.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200747 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200748 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
749 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
750 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
751 r'',
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200752 key_type.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200753 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200754 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200755 key = StorageTestData(version=self.version,
756 id=1, lifetime=0x00000001,
757 type=key_type.expression, bits=bits,
758 usage=material_usage_flags,
759 expected_usage=expected_usage_flags,
760 without_implicit_usage=True,
761 alg=alg, alg2=alg2,
762 material=key_material,
763 description=description)
764 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200765
766 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200767 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200768 """Match possible key types for sign algorithms."""
769 # To create a valid combinaton both the algorithms and key types
770 # must be filtered. Pair them with keywords created from its names.
771 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
772 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
773 keyword_translation = {
774 'ECDSA': 'ECC',
775 'ED[0-9]*.*' : 'EDWARDS'
776 }
777 exclusive_keywords = {
778 'EDWARDS': 'ECC'
779 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200780 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
781 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200782 alg_with_keys = {} #type: Dict[str, List[str]]
783 translation_table = str.maketrans('(', '_', ')')
784 for alg in algorithms:
785 # Generate keywords from the name of the algorithm
786 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
787 # Translate keywords for better matching with the key types
788 for keyword in alg_keywords.copy():
789 for pattern, replace in keyword_translation.items():
790 if re.match(pattern, keyword):
791 alg_keywords.remove(keyword)
792 alg_keywords.add(replace)
793 # Filter out incompatible algortihms
794 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
795 continue
796
797 for key_type in key_types:
798 # Generate keywords from the of the key type
799 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
800
801 # Remove ambigious keywords
802 for keyword1, keyword2 in exclusive_keywords.items():
803 if keyword1 in key_type_keywords:
804 key_type_keywords.remove(keyword2)
805
806 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
807 not key_type_keywords.isdisjoint(alg_keywords):
808 if alg in alg_with_keys:
809 alg_with_keys[alg].append(key_type)
810 else:
811 alg_with_keys[alg] = [key_type]
812 return alg_with_keys
813
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200814 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200815 """Generate test keys for usage flag extensions."""
816 # Generate a key type and algorithm pair for each extendable usage
817 # flag to generate a valid key for exercising. The key is generated
818 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200819 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200820
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200821 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
822 for alg in sorted(alg_with_keys):
823 for key_type in sorted(alg_with_keys[alg]):
824 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200825 kt = crypto_knowledge.KeyType(key_type)
826 if kt.is_valid_for_signature(usage):
827 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200828
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200829 def generate_all_keys(self) -> Iterator[StorageTestData]:
830 yield from super().generate_all_keys()
831 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200832
Gilles Peskineb94ea512021-03-10 02:12:08 +0100833class TestGenerator:
834 """Generate test data."""
835
836 def __init__(self, options) -> None:
837 self.test_suite_directory = self.get_option(options, 'directory',
838 'tests/suites')
839 self.info = Information()
840
841 @staticmethod
842 def get_option(options, name: str, default: T) -> T:
843 value = getattr(options, name, None)
844 return default if value is None else value
845
Gilles Peskine0298bda2021-03-10 02:34:37 +0100846 def filename_for(self, basename: str) -> str:
847 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200848 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100849
Gilles Peskineb94ea512021-03-10 02:12:08 +0100850 def write_test_data_file(self, basename: str,
851 test_cases: Iterable[test_case.TestCase]) -> None:
852 """Write the test cases to a .data file.
853
854 The output file is ``basename + '.data'`` in the test suite directory.
855 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100856 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100857 test_case.write_data_file(filename, test_cases)
858
Gilles Peskine92165362021-04-23 16:37:12 +0200859 # Note that targets whose name containns 'test_format' have their content
860 # validated by `abi_check.py`.
Gilles Peskine0298bda2021-03-10 02:34:37 +0100861 TARGETS = {
Przemyslaw Stekiel1b0978b2021-10-15 15:21:51 +0200862 'test_suite_psa_crypto_generate_key.generated':
863 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100864 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100865 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskinec7e1ea02021-04-27 20:40:10 +0200866 'test_suite_psa_crypto_op_fail.generated':
867 lambda info: OpFail(info).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100868 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200869 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100870 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200871 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100872 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
873
874 def generate_target(self, name: str) -> None:
875 test_cases = self.TARGETS[name](self.info)
876 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100877
Gilles Peskine09940492021-01-26 22:16:30 +0100878def main(args):
879 """Command line entry point."""
880 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100881 parser.add_argument('--list', action='store_true',
882 help='List available targets and exit')
David Horstmanne12e7f42021-10-15 19:10:15 +0100883 parser.add_argument('--list-for-cmake', action='store_true',
884 help='Print \';\'-separated list of available targets and exit')
Manuel Pégourié-Gonnarda9cb8942021-05-14 11:37:09 +0200885 parser.add_argument('--directory', metavar='DIR',
886 help='Output directory (default: tests/suites)')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100887 parser.add_argument('targets', nargs='*', metavar='TARGET',
888 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100889 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200890 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100891 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100892 if options.list:
893 for name in sorted(generator.TARGETS):
894 print(generator.filename_for(name))
895 return
David Horstmanne12e7f42021-10-15 19:10:15 +0100896 # List in a cmake list format (i.e. ';'-separated)
897 if options.list_for_cmake:
David Horstmann65d8c692021-10-21 16:09:51 +0100898 print(';'.join(generator.filename_for(name)
899 for name in sorted(generator.TARGETS)), end='')
David Horstmanne12e7f42021-10-15 19:10:15 +0100900 return
Gilles Peskine0298bda2021-03-10 02:34:37 +0100901 if options.targets:
902 # Allow "-" as a special case so you can run
903 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
904 # ``$targets`` is empty or not.
905 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
906 for target in options.targets
907 if target != '-']
908 else:
909 options.targets = sorted(generator.TARGETS)
910 for target in options.targets:
911 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100912
913if __name__ == '__main__':
914 main(sys.argv[1:])