blob: 4c8143ff09ecda48ba64fede1c2a16b2c181b248 [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 Peskine14e428f2021-01-26 22:19:21 +010024import os
Bence Szépkúti9e84ec72021-05-07 11:49:17 +020025import posixpath
Gilles Peskine14e428f2021-01-26 22:19:21 +010026import re
Gilles Peskine09940492021-01-26 22:16:30 +010027import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010028from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010029
30import scripts_path # pylint: disable=unused-import
Gilles Peskinec86f20a2021-04-22 00:20:47 +020031from mbedtls_dev import build_tree
Gilles Peskine14e428f2021-01-26 22:19:21 +010032from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010033from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010034from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010035from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010036
37T = TypeVar('T') #pylint: disable=invalid-name
38
Gilles Peskine14e428f2021-01-26 22:19:21 +010039
Gilles Peskine7f756872021-02-16 12:13:12 +010040def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010041 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
42 if name.startswith('PSA_'):
43 return name[:4] + 'WANT_' + name[4:]
44 else:
45 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
46
Gilles Peskine7f756872021-02-16 12:13:12 +010047def finish_family_dependency(dep: str, bits: int) -> str:
48 """Finish dep if it's a family dependency symbol prefix.
49
50 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
51 qualified by the key size. If dep is such a symbol, finish it by adjusting
52 the prefix and appending the key size. Other symbols are left unchanged.
53 """
54 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
55
56def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
57 """Finish any family dependency symbol prefixes.
58
59 Apply `finish_family_dependency` to each element of `dependencies`.
60 """
61 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010062
Gilles Peskinec5d086f2021-04-20 23:23:45 +020063SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
64 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
65 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
66 'PSA_ALG_ANY_HASH', # only in policies
67 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
68 'PSA_ALG_KEY_AGREEMENT', # chaining
69 'PSA_ALG_TRUNCATED_MAC', # modifier
70])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010071def automatic_dependencies(*expressions: str) -> List[str]:
72 """Infer dependencies of a test case by looking for PSA_xxx symbols.
73
74 The arguments are strings which should be C expressions. Do not use
75 string literals or comments as this function is not smart enough to
76 skip them.
77 """
78 used = set()
79 for expr in expressions:
80 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020081 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010082 return sorted(psa_want_symbol(name) for name in used)
83
Gilles Peskined169d602021-02-16 14:16:25 +010084# A temporary hack: at the time of writing, not all dependency symbols
85# are implemented yet. Skip test cases for which the dependency symbols are
86# not available. Once all dependency symbols are available, this hack must
87# be removed so that a bug in the dependency symbols proprely leads to a test
88# failure.
89def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
90 return frozenset(symbol
91 for line in open(filename)
92 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020093_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010094def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +020095 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
96 if _implemented_dependencies is None:
97 _implemented_dependencies = \
98 read_implemented_dependencies('include/psa/crypto_config.h')
99 if not all(dep.lstrip('!') in _implemented_dependencies
Gilles Peskined169d602021-02-16 14:16:25 +0100100 for dep in dependencies):
101 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
102
Gilles Peskine14e428f2021-01-26 22:19:21 +0100103
Gilles Peskineb94ea512021-03-10 02:12:08 +0100104class Information:
105 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100106
Gilles Peskineb94ea512021-03-10 02:12:08 +0100107 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100108 self.constructors = self.read_psa_interface()
109
110 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100111 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200112 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100113 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200114 # Mbed TLS doesn't support finite-field DH yet and will not support
115 # finite-field DSA. Don't attempt to generate any related test case.
116 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
117 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100118 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
119 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100120
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200121 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100122 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200123 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100124 header_file_names = ['include/psa/crypto_values.h',
125 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200126 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100127 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200128 constructors.parse_header(header_file_name)
129 for test_cases in test_suites:
130 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100131 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200132 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100133 return constructors
134
Gilles Peskine14e428f2021-01-26 22:19:21 +0100135
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200136def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100137 verb: str, key_type: str, bits: int,
138 dependencies: List[str],
139 *args: str,
140 param_descr: str = ''
141) -> test_case.TestCase:
142 """Return one test case exercising a key creation method
143 for an unsupported key type or size.
144 """
145 hack_dependencies_not_implemented(dependencies)
146 tc = test_case.TestCase()
147 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
148 adverb = 'not' if dependencies else 'never'
149 if param_descr:
150 adverb = param_descr + ' ' + adverb
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200151 tc.set_description('PSA {} {} {}-bit {} supported'
152 .format(verb, short_key_type, bits, adverb))
153 tc.set_dependencies(dependencies)
154 tc.set_function(verb + '_not_supported')
155 tc.set_arguments([key_type] + list(args))
156 return tc
157
158def test_case_for_key_type_invalid_argument(
159 verb: str, key_type: str, bits: int,
160 dependencies: List[str],
161 *args: str,
162 param_descr: str = ''
163) -> test_case.TestCase:
164 """Return one test case exercising a key creation method
165 for an invalid argument when key is public.
166 """
167 hack_dependencies_not_implemented(dependencies)
168 tc = test_case.TestCase()
169 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
170 adverb = 'not' if dependencies else 'never'
171 if param_descr:
172 adverb = param_descr + ' ' + adverb
173 tc.set_description('PSA {} {} {}-bit invalid argument'
174 .format(verb, short_key_type, bits))
175 tc.set_function(verb + '_invalid_argument')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100176 tc.set_dependencies(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100177 tc.set_arguments([key_type] + list(args))
178 return tc
179
180class NotSupported:
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200181 """Generate test cases for when something is not supported or argument is inavlid."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100182
183 def __init__(self, info: Information) -> None:
184 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100185
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100186 ALWAYS_SUPPORTED = frozenset([
187 'PSA_KEY_TYPE_DERIVE',
188 'PSA_KEY_TYPE_RAW_DATA',
189 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100191 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100192 kt: crypto_knowledge.KeyType,
193 param: Optional[int] = None,
194 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100195 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200196 """Return test cases exercising key creation when the given type is unsupported
197 or argument is invalid.
Gilles Peskineaf172842021-01-27 18:24:48 +0100198
199 If param is present and not None, emit test cases conditioned on this
200 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200201 conditioned on the base type not being supported. If key is public emit test
202 case for invalid argument.
Gilles Peskineaf172842021-01-27 18:24:48 +0100203 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100204 if kt.name in self.ALWAYS_SUPPORTED:
205 # Don't generate test cases for key types that are always supported.
206 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100207 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100208 import_dependencies = [('!' if param is None else '') +
209 psa_want_symbol(kt.name)]
210 if kt.params is not None:
211 import_dependencies += [('!' if param == i else '') +
212 psa_want_symbol(sym)
213 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100214 if kt.name.endswith('_PUBLIC_KEY'):
215 generate_dependencies = []
216 else:
217 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 for bits in kt.sizes_to_test():
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200219 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100220 'import', kt.expression, bits,
221 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100222 test_case.hex_string(kt.key_material(bits)),
223 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100224 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 if not generate_dependencies and param is not None:
226 # If generation is impossible for this key type, rather than
227 # supported or not depending on implementation capabilities,
228 # only generate the test case once.
229 continue
Przemyslaw Stekielb576c7b2021-10-11 10:15:25 +0200230 if kt.name.endswith('_PUBLIC_KEY'):
231 yield test_case_for_key_type_invalid_argument(
232 'generate', kt.expression, bits,
233 finish_family_dependencies(generate_dependencies, bits),
234 str(bits),
235 param_descr=param_descr,
236 )
237 else:
238 yield test_case_for_key_type_not_supported(
239 'generate', kt.expression, bits,
240 finish_family_dependencies(generate_dependencies, bits),
241 str(bits),
242 param_descr=param_descr,
243 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100244 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100245
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200246 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
247 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
248
Gilles Peskine3d778392021-02-17 15:11:05 +0100249 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100250 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100251 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200252 if key_type in self.ECC_KEY_TYPES:
253 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100254 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100255 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100256 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200257 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100258 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100259 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100260 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100261 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100262 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100263
Gilles Peskine897dff92021-03-10 15:03:44 +0100264class StorageKey(psa_storage.Key):
265 """Representation of a key for storage format testing."""
266
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200267 IMPLICIT_USAGE_FLAGS = {
268 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
269 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
270 } #type: Dict[str, str]
271 """Mapping of usage flags to the flags that they imply."""
272
273 def __init__(
274 self,
275 usage: str,
276 without_implicit_usage: Optional[bool] = False,
277 **kwargs
278 ) -> None:
279 """Prepare to generate a key.
280
281 * `usage` : The usage flags used for the key.
282 * `without_implicit_usage`: Flag to defide to apply the usage extension
283 """
gabor-mezei-arm2c9e54a2021-06-29 17:21:21 +0200284 super().__init__(usage=usage, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200285
286 if not without_implicit_usage:
287 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
288 if self.usage.value() & psa_storage.Expr(flag).value() and \
289 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
290 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
291
292class StorageTestData(StorageKey):
293 """Representation of test case data for storage format testing."""
294
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200295 def __init__(
296 self,
297 description: str,
298 expected_usage: Optional[str] = None,
299 **kwargs
300 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200301 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200302
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200303 * `description` : used for the the test case names
304 * `expected_usage`: the usage flags generated as the expected usage flags
305 in the test cases. CAn differ from the usage flags
306 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200307 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100308 super().__init__(**kwargs)
309 self.description = description #type: str
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200310 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200311
Gilles Peskine897dff92021-03-10 15:03:44 +0100312class StorageFormat:
313 """Storage format stability test cases."""
314
315 def __init__(self, info: Information, version: int, forward: bool) -> None:
316 """Prepare to generate test cases for storage format stability.
317
318 * `info`: information about the API. See the `Information` class.
319 * `version`: the storage format version to generate test cases for.
320 * `forward`: if true, generate forward compatibility test cases which
321 save a key and check that its representation is as intended. Otherwise
322 generate backward compatibility test cases which inject a key
323 representation and check that it can be read and used.
324 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200325 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
326 self.version = version #type: int
327 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100328
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200329 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100330 """Construct a storage format test case for the given key.
331
332 If ``forward`` is true, generate a forward compatibility test case:
333 create a key and validate that it has the expected representation.
334 Otherwise generate a backward compatibility test case: inject the
335 key representation into storage and validate that it can be read
336 correctly.
337 """
338 verb = 'save' if self.forward else 'read'
339 tc = test_case.TestCase()
340 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100341 dependencies = automatic_dependencies(
342 key.lifetime.string, key.type.string,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200343 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100344 )
345 dependencies = finish_family_dependencies(dependencies, key.bits)
346 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100347 tc.set_function('key_storage_' + verb)
348 if self.forward:
349 extra_arguments = []
350 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200351 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100352 # Some test keys have the RAW_DATA type and attributes that don't
353 # necessarily make sense. We do this to validate numerical
354 # encodings of the attributes.
355 # Raw data keys have no useful exercise anyway so there is no
356 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200357 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
358 flags.append('TEST_FLAG_EXERCISE')
359 if 'READ_ONLY' in key.lifetime.string:
360 flags.append('TEST_FLAG_READ_ONLY')
361 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100362 tc.set_arguments([key.lifetime.string,
363 key.type.string, str(key.bits),
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200364 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100365 '"' + key.material.hex() + '"',
366 '"' + key.hex() + '"',
367 *extra_arguments])
368 return tc
369
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200370 def key_for_lifetime(
371 self,
372 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200373 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200374 """Construct a test key for the given lifetime."""
375 short = lifetime
376 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
377 r'', short)
378 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
379 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200380 key = StorageTestData(version=self.version,
381 id=1, lifetime=lifetime,
382 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
383 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
384 material=b'L',
385 description=description)
386 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200387
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200388 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200389 """Generate test keys covering lifetimes."""
390 lifetimes = sorted(self.constructors.lifetimes)
391 expressions = self.constructors.generate_expressions(lifetimes)
392 for lifetime in expressions:
393 # Don't attempt to create or load a volatile key in storage
394 if 'VOLATILE' in lifetime:
395 continue
396 # Don't attempt to create a read-only key in storage,
397 # but do attempt to load one.
398 if 'READ_ONLY' in lifetime and self.forward:
399 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200400 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200401
gabor-mezei-arm63857802021-06-29 15:39:56 +0200402 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100403 self,
404 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200405 short: Optional[str] = None,
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200406 test_implicit_usage: Optional[bool] = False
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200407 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100408 """Construct a test key for the given key usage."""
409 usage = ' | '.join(usage_flags) if usage_flags else '0'
410 if short is None:
411 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200412 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200413 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200414 key1 = StorageTestData(version=self.version,
415 id=1, lifetime=0x00000001,
416 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
417 expected_usage=usage,
418 usage=usage, alg=0, alg2=0,
419 material=b'K',
420 description=description)
421 yield key1
422
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200423 if test_implicit_usage:
424 description = 'usage without implication' + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200425 key2 = StorageTestData(version=self.version,
426 id=1, lifetime=0x00000001,
427 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
428 without_implicit_usage=True,
429 usage=usage, alg=0, alg2=0,
430 material=b'K',
431 description=description)
432 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100433
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200434 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100435 """Generate test keys covering usage flags."""
436 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200437 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200438 for usage_flag in known_flags:
gabor-mezei-arm63857802021-06-29 15:39:56 +0200439 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200440 for flag1, flag2 in zip(known_flags,
441 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm63857802021-06-29 15:39:56 +0200442 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200443
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200444 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200445 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200446 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200447
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200448 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200449 yield from self.generate_keys_for_usage_flags()
450 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100451
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100452 def keys_for_type(
453 self,
454 key_type: str,
455 params: Optional[Iterable[str]] = None
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200456 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100457 """Generate test keys for the given key type.
458
459 For key types that depend on a parameter (e.g. elliptic curve family),
460 `param` is the parameter to pass to the constructor. Only a single
461 parameter is supported.
462 """
463 kt = crypto_knowledge.KeyType(key_type, params)
464 for bits in kt.sizes_to_test():
465 usage_flags = 'PSA_KEY_USAGE_EXPORT'
466 alg = 0
467 alg2 = 0
468 key_material = kt.key_material(bits)
469 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
470 r'',
471 kt.expression)
472 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200473 key = StorageTestData(version=self.version,
474 id=1, lifetime=0x00000001,
475 type=kt.expression, bits=bits,
476 usage=usage_flags, alg=alg, alg2=alg2,
477 material=key_material,
478 description=description)
479 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100480
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200481 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100482 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200483 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200484 for key_type in self.constructors.generate_expressions(key_types):
485 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100486
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200487 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100488 """Generate test keys for the specified algorithm."""
489 # For now, we don't have information on the compatibility of key
490 # types and algorithms. So we just test the encoding of algorithms,
491 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200492 descr = re.sub(r'PSA_ALG_', r'', alg)
493 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100494 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200495 key1 = StorageTestData(version=self.version,
496 id=1, lifetime=0x00000001,
497 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
498 usage=usage, alg=alg, alg2=0,
499 material=b'K',
500 description='alg: ' + descr)
501 yield key1
502 key2 = StorageTestData(version=self.version,
503 id=1, lifetime=0x00000001,
504 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
505 usage=usage, alg=0, alg2=alg,
506 material=b'L',
507 description='alg2: ' + descr)
508 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100509
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200510 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100511 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200512 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200513 for alg in self.constructors.generate_expressions(algorithms):
514 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100515
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200516 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200517 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200518 yield from self.all_keys_for_lifetimes()
519 yield from self.all_keys_for_usage_flags()
520 yield from self.all_keys_for_types()
521 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200522
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200523 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100524 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200525 # First build a list of all keys, then construct all the corresponding
526 # test cases. This allows all required information to be obtained in
527 # one go, which is a significant performance gain as the information
528 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200529 all_keys = list(self.generate_all_keys())
530 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200531 if key.location_value() != 0:
532 # Skip keys with a non-default location, because they
533 # require a driver and we currently have no mechanism to
534 # determine whether a driver is available.
535 continue
536 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100537
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200538class StorageFormatForward(StorageFormat):
539 """Storage format stability test cases for forward compatibility."""
540
541 def __init__(self, info: Information, version: int) -> None:
542 super().__init__(info, version, True)
543
544class StorageFormatV0(StorageFormat):
545 """Storage format stability test cases for version 0 compatibility."""
546
547 def __init__(self, info: Information) -> None:
548 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100549
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200550 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200551 """Generate test keys covering usage flags."""
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200552 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
553 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200554
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200555 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200556 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200557 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200558 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200559 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200560 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200561 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200562 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200563 algorithm and key type combination.
564 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200565 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200566 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200567 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200568 material_usage_flags = usage_flags + ' | ' + implyer_usage
569 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200570 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200571 key_material = key_type.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200572 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200573 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
574 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
575 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
576 r'',
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200577 key_type.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200578 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200579 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200580 key = StorageTestData(version=self.version,
581 id=1, lifetime=0x00000001,
582 type=key_type.expression, bits=bits,
583 usage=material_usage_flags,
584 expected_usage=expected_usage_flags,
585 without_implicit_usage=True,
586 alg=alg, alg2=alg2,
587 material=key_material,
588 description=description)
589 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200590
591 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200592 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200593 """Match possible key types for sign algorithms."""
594 # To create a valid combinaton both the algorithms and key types
595 # must be filtered. Pair them with keywords created from its names.
596 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
597 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
598 keyword_translation = {
599 'ECDSA': 'ECC',
600 'ED[0-9]*.*' : 'EDWARDS'
601 }
602 exclusive_keywords = {
603 'EDWARDS': 'ECC'
604 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200605 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
606 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200607 alg_with_keys = {} #type: Dict[str, List[str]]
608 translation_table = str.maketrans('(', '_', ')')
609 for alg in algorithms:
610 # Generate keywords from the name of the algorithm
611 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
612 # Translate keywords for better matching with the key types
613 for keyword in alg_keywords.copy():
614 for pattern, replace in keyword_translation.items():
615 if re.match(pattern, keyword):
616 alg_keywords.remove(keyword)
617 alg_keywords.add(replace)
618 # Filter out incompatible algortihms
619 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
620 continue
621
622 for key_type in key_types:
623 # Generate keywords from the of the key type
624 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
625
626 # Remove ambigious keywords
627 for keyword1, keyword2 in exclusive_keywords.items():
628 if keyword1 in key_type_keywords:
629 key_type_keywords.remove(keyword2)
630
631 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
632 not key_type_keywords.isdisjoint(alg_keywords):
633 if alg in alg_with_keys:
634 alg_with_keys[alg].append(key_type)
635 else:
636 alg_with_keys[alg] = [key_type]
637 return alg_with_keys
638
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200639 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200640 """Generate test keys for usage flag extensions."""
641 # Generate a key type and algorithm pair for each extendable usage
642 # flag to generate a valid key for exercising. The key is generated
643 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200644 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200645
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200646 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
647 for alg in sorted(alg_with_keys):
648 for key_type in sorted(alg_with_keys[alg]):
649 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200650 kt = crypto_knowledge.KeyType(key_type)
651 if kt.is_valid_for_signature(usage):
652 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200653
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200654 def generate_all_keys(self) -> Iterator[StorageTestData]:
655 yield from super().generate_all_keys()
656 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200657
Gilles Peskineb94ea512021-03-10 02:12:08 +0100658class TestGenerator:
659 """Generate test data."""
660
661 def __init__(self, options) -> None:
662 self.test_suite_directory = self.get_option(options, 'directory',
663 'tests/suites')
664 self.info = Information()
665
666 @staticmethod
667 def get_option(options, name: str, default: T) -> T:
668 value = getattr(options, name, None)
669 return default if value is None else value
670
Gilles Peskine0298bda2021-03-10 02:34:37 +0100671 def filename_for(self, basename: str) -> str:
672 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200673 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100674
Gilles Peskineb94ea512021-03-10 02:12:08 +0100675 def write_test_data_file(self, basename: str,
676 test_cases: Iterable[test_case.TestCase]) -> None:
677 """Write the test cases to a .data file.
678
679 The output file is ``basename + '.data'`` in the test suite directory.
680 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100681 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100682 test_case.write_data_file(filename, test_cases)
683
Gilles Peskine0298bda2021-03-10 02:34:37 +0100684 TARGETS = {
685 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100686 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100687 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200688 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100689 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200690 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100691 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
692
693 def generate_target(self, name: str) -> None:
694 test_cases = self.TARGETS[name](self.info)
695 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100696
Gilles Peskine09940492021-01-26 22:16:30 +0100697def main(args):
698 """Command line entry point."""
699 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100700 parser.add_argument('--list', action='store_true',
701 help='List available targets and exit')
702 parser.add_argument('targets', nargs='*', metavar='TARGET',
703 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100704 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200705 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100706 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100707 if options.list:
708 for name in sorted(generator.TARGETS):
709 print(generator.filename_for(name))
710 return
711 if options.targets:
712 # Allow "-" as a special case so you can run
713 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
714 # ``$targets`` is empty or not.
715 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
716 for target in options.targets
717 if target != '-']
718 else:
719 options.targets = sorted(generator.TARGETS)
720 for target in options.targets:
721 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100722
723if __name__ == '__main__':
724 main(sys.argv[1:])