blob: 64c43b2e10b60eea5c53c6589338b84485dd2b48 [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
25import re
Gilles Peskine09940492021-01-26 22:16:30 +010026import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010027from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010028
29import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010030from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010031from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010032from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010033from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010034
35T = TypeVar('T') #pylint: disable=invalid-name
36
Gilles Peskine14e428f2021-01-26 22:19:21 +010037
Gilles Peskine7f756872021-02-16 12:13:12 +010038def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010039 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
40 if name.startswith('PSA_'):
41 return name[:4] + 'WANT_' + name[4:]
42 else:
43 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
44
Gilles Peskine7f756872021-02-16 12:13:12 +010045def finish_family_dependency(dep: str, bits: int) -> str:
46 """Finish dep if it's a family dependency symbol prefix.
47
48 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
49 qualified by the key size. If dep is such a symbol, finish it by adjusting
50 the prefix and appending the key size. Other symbols are left unchanged.
51 """
52 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
53
54def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
55 """Finish any family dependency symbol prefixes.
56
57 Apply `finish_family_dependency` to each element of `dependencies`.
58 """
59 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010060
Gilles Peskine8a55b432021-04-20 23:23:45 +020061SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
62 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
63 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
64 'PSA_ALG_ANY_HASH', # only in policies
65 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
66 'PSA_ALG_KEY_AGREEMENT', # chaining
67 'PSA_ALG_TRUNCATED_MAC', # modifier
68])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010069def automatic_dependencies(*expressions: str) -> List[str]:
70 """Infer dependencies of a test case by looking for PSA_xxx symbols.
71
72 The arguments are strings which should be C expressions. Do not use
73 string literals or comments as this function is not smart enough to
74 skip them.
75 """
76 used = set()
77 for expr in expressions:
78 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskine8a55b432021-04-20 23:23:45 +020079 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010080 return sorted(psa_want_symbol(name) for name in used)
81
Gilles Peskined169d602021-02-16 14:16:25 +010082# A temporary hack: at the time of writing, not all dependency symbols
83# are implemented yet. Skip test cases for which the dependency symbols are
84# not available. Once all dependency symbols are available, this hack must
85# be removed so that a bug in the dependency symbols proprely leads to a test
86# failure.
87def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
88 return frozenset(symbol
89 for line in open(filename)
90 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
91IMPLEMENTED_DEPENDENCIES = read_implemented_dependencies('include/psa/crypto_config.h')
92def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
93 if not all(dep.lstrip('!') in IMPLEMENTED_DEPENDENCIES
94 for dep in dependencies):
95 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
96
Gilles Peskine14e428f2021-01-26 22:19:21 +010097
Gilles Peskineb94ea512021-03-10 02:12:08 +010098class Information:
99 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100100
Gilles Peskineb94ea512021-03-10 02:12:08 +0100101 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100102 self.constructors = self.read_psa_interface()
103
104 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100105 def remove_unwanted_macros(
Gilles Peskineb93f8542021-04-19 13:50:25 +0200106 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100107 ) -> None:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200108 # Mbed TLS doesn't support finite-field DH yet and will not support
109 # finite-field DSA. Don't attempt to generate any related test case.
110 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
111 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100112 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
113 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100114
Gilles Peskineb93f8542021-04-19 13:50:25 +0200115 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100116 """Return the list of known key types, algorithms, etc."""
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200117 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100118 header_file_names = ['include/psa/crypto_values.h',
119 'include/psa/crypto_extra.h']
Gilles Peskineb93f8542021-04-19 13:50:25 +0200120 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100121 for header_file_name in header_file_names:
Gilles Peskineb93f8542021-04-19 13:50:25 +0200122 constructors.parse_header(header_file_name)
123 for test_cases in test_suites:
124 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100125 self.remove_unwanted_macros(constructors)
Gilles Peskined6d2d6a2021-03-30 21:46:35 +0200126 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100127 return constructors
128
Gilles Peskine14e428f2021-01-26 22:19:21 +0100129
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200130def test_case_for_key_type_not_supported(
Gilles Peskineb94ea512021-03-10 02:12:08 +0100131 verb: str, key_type: str, bits: int,
132 dependencies: List[str],
133 *args: str,
134 param_descr: str = ''
135) -> test_case.TestCase:
136 """Return one test case exercising a key creation method
137 for an unsupported key type or size.
138 """
139 hack_dependencies_not_implemented(dependencies)
140 tc = test_case.TestCase()
141 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
142 adverb = 'not' if dependencies else 'never'
143 if param_descr:
144 adverb = param_descr + ' ' + adverb
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200145 tc.set_description('PSA {} {} {}-bit {} supported'
146 .format(verb, short_key_type, bits, adverb))
147 tc.set_dependencies(dependencies)
148 tc.set_function(verb + '_not_supported')
149 tc.set_arguments([key_type] + list(args))
150 return tc
151
152def test_case_for_key_type_invalid_argument(
153 verb: str, key_type: str, bits: int,
154 dependencies: List[str],
155 *args: str,
156 param_descr: str = ''
157) -> test_case.TestCase:
158 """Return one test case exercising a key creation method
159 for an invalid argument when key is public.
160 """
161 hack_dependencies_not_implemented(dependencies)
162 tc = test_case.TestCase()
163 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
164 adverb = 'not' if dependencies else 'never'
165 if param_descr:
166 adverb = param_descr + ' ' + adverb
167 tc.set_description('PSA {} {} {}-bit invalid argument'
168 .format(verb, short_key_type, bits))
169 tc.set_function(verb + '_invalid_argument')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100170 tc.set_dependencies(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100171 tc.set_arguments([key_type] + list(args))
172 return tc
173
174class NotSupported:
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200175 """Generate test cases for when something is not supported or argument is inavlid."""
Gilles Peskineb94ea512021-03-10 02:12:08 +0100176
177 def __init__(self, info: Information) -> None:
178 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100179
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100180 ALWAYS_SUPPORTED = frozenset([
181 'PSA_KEY_TYPE_DERIVE',
182 'PSA_KEY_TYPE_RAW_DATA',
183 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100184 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100185 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100186 kt: crypto_knowledge.KeyType,
187 param: Optional[int] = None,
188 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100189 ) -> Iterator[test_case.TestCase]:
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200190 """Return test cases exercising key creation when the given type is unsupported
191 or argument is invalid.
Gilles Peskineaf172842021-01-27 18:24:48 +0100192
193 If param is present and not None, emit test cases conditioned on this
194 parameter not being supported. If it is absent or None, emit test cases
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200195 conditioned on the base type not being supported. If key is public emit test
196 case for invalid argument.
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100198 if kt.name in self.ALWAYS_SUPPORTED:
199 # Don't generate test cases for key types that are always supported.
200 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100201 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100202 import_dependencies = [('!' if param is None else '') +
203 psa_want_symbol(kt.name)]
204 if kt.params is not None:
205 import_dependencies += [('!' if param == i else '') +
206 psa_want_symbol(sym)
207 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100208 if kt.name.endswith('_PUBLIC_KEY'):
209 generate_dependencies = []
210 else:
211 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 for bits in kt.sizes_to_test():
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200213 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100214 'import', kt.expression, bits,
215 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100216 test_case.hex_string(kt.key_material(bits)),
217 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100218 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100219 if not generate_dependencies and param is not None:
220 # If generation is impossible for this key type, rather than
221 # supported or not depending on implementation capabilities,
222 # only generate the test case once.
223 continue
Przemyslaw Stekield6ead7c2021-10-11 10:15:25 +0200224 if kt.name.endswith('_PUBLIC_KEY'):
225 yield test_case_for_key_type_invalid_argument(
226 'generate', kt.expression, bits,
227 finish_family_dependencies(generate_dependencies, bits),
228 str(bits),
229 param_descr=param_descr,
230 )
231 else:
232 yield test_case_for_key_type_not_supported(
233 'generate', kt.expression, bits,
234 finish_family_dependencies(generate_dependencies, bits),
235 str(bits),
236 param_descr=param_descr,
237 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100238 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100239
Gilles Peskineb93f8542021-04-19 13:50:25 +0200240 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
241 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
242
Gilles Peskine3d778392021-02-17 15:11:05 +0100243 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100244 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100245 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200246 if key_type in self.ECC_KEY_TYPES:
247 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100248 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100249 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100250 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200251 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100252 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100253 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100254 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100255 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100256 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100257
Gilles Peskine897dff92021-03-10 15:03:44 +0100258class StorageKey(psa_storage.Key):
259 """Representation of a key for storage format testing."""
260
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200261 IMPLICIT_USAGE_FLAGS = {
262 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
263 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
264 } #type: Dict[str, str]
265 """Mapping of usage flags to the flags that they imply."""
266
267 def __init__(
268 self,
269 usage: str,
270 without_implicit_usage: Optional[bool] = False,
271 **kwargs
272 ) -> None:
273 """Prepare to generate a key.
274
275 * `usage` : The usage flags used for the key.
276 * `without_implicit_usage`: Flag to defide to apply the usage extension
277 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200278 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200279
280 if not without_implicit_usage:
281 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
282 if self.usage.value() & psa_storage.Expr(flag).value() and \
283 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
284 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
285
286class StorageTestData(StorageKey):
287 """Representation of test case data for storage format testing."""
288
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200289 def __init__(
290 self,
291 description: str,
292 expected_usage: Optional[str] = None,
293 **kwargs
294 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200295 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200296
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200297 * `description` : used for the the test case names
298 * `expected_usage`: the usage flags generated as the expected usage flags
299 in the test cases. CAn differ from the usage flags
300 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200301 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100302 super().__init__(**kwargs)
303 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200304 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200305
Gilles Peskine897dff92021-03-10 15:03:44 +0100306class StorageFormat:
307 """Storage format stability test cases."""
308
309 def __init__(self, info: Information, version: int, forward: bool) -> None:
310 """Prepare to generate test cases for storage format stability.
311
312 * `info`: information about the API. See the `Information` class.
313 * `version`: the storage format version to generate test cases for.
314 * `forward`: if true, generate forward compatibility test cases which
315 save a key and check that its representation is as intended. Otherwise
316 generate backward compatibility test cases which inject a key
317 representation and check that it can be read and used.
318 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200319 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
320 self.version = version #type: int
321 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100322
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200323 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100324 """Construct a storage format test case for the given key.
325
326 If ``forward`` is true, generate a forward compatibility test case:
327 create a key and validate that it has the expected representation.
328 Otherwise generate a backward compatibility test case: inject the
329 key representation into storage and validate that it can be read
330 correctly.
331 """
332 verb = 'save' if self.forward else 'read'
333 tc = test_case.TestCase()
334 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100335 dependencies = automatic_dependencies(
336 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200337 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100338 )
339 dependencies = finish_family_dependencies(dependencies, key.bits)
340 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100341 tc.set_function('key_storage_' + verb)
342 if self.forward:
343 extra_arguments = []
344 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200345 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100346 # Some test keys have the RAW_DATA type and attributes that don't
347 # necessarily make sense. We do this to validate numerical
348 # encodings of the attributes.
349 # Raw data keys have no useful exercise anyway so there is no
350 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200351 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
352 flags.append('TEST_FLAG_EXERCISE')
353 if 'READ_ONLY' in key.lifetime.string:
354 flags.append('TEST_FLAG_READ_ONLY')
355 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100356 tc.set_arguments([key.lifetime.string,
357 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200358 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100359 '"' + key.material.hex() + '"',
360 '"' + key.hex() + '"',
361 *extra_arguments])
362 return tc
363
Gilles Peskineefb584d2021-04-21 22:05:34 +0200364 def key_for_lifetime(
365 self,
366 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200367 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200368 """Construct a test key for the given lifetime."""
369 short = lifetime
370 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
371 r'', short)
372 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
373 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200374 key = StorageTestData(version=self.version,
375 id=1, lifetime=lifetime,
376 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
377 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
378 material=b'L',
379 description=description)
380 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200381
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200382 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200383 """Generate test keys covering lifetimes."""
384 lifetimes = sorted(self.constructors.lifetimes)
385 expressions = self.constructors.generate_expressions(lifetimes)
386 for lifetime in expressions:
387 # Don't attempt to create or load a volatile key in storage
388 if 'VOLATILE' in lifetime:
389 continue
390 # Don't attempt to create a read-only key in storage,
391 # but do attempt to load one.
392 if 'READ_ONLY' in lifetime and self.forward:
393 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200394 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200395
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200396 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100397 self,
398 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200399 short: Optional[str] = None,
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200400 test_implicit_usage: Optional[bool] = False
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200401 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100402 """Construct a test key for the given key usage."""
403 usage = ' | '.join(usage_flags) if usage_flags else '0'
404 if short is None:
405 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200406 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200407 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200408 key1 = StorageTestData(version=self.version,
409 id=1, lifetime=0x00000001,
410 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
411 expected_usage=usage,
412 usage=usage, alg=0, alg2=0,
413 material=b'K',
414 description=description)
415 yield key1
416
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200417 if test_implicit_usage:
418 description = 'usage without implication' + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200419 key2 = StorageTestData(version=self.version,
420 id=1, lifetime=0x00000001,
421 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
422 without_implicit_usage=True,
423 usage=usage, alg=0, alg2=0,
424 material=b'K',
425 description=description)
426 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100427
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200428 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100429 """Generate test keys covering usage flags."""
430 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200431 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200432 for usage_flag in known_flags:
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200433 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200434 for flag1, flag2 in zip(known_flags,
435 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200436 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200437
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200438 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200439 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200440 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200441
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200442 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200443 yield from self.generate_keys_for_usage_flags()
444 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100445
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100446 def keys_for_type(
447 self,
448 key_type: str,
449 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200450 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100451 """Generate test keys for the given key type.
452
453 For key types that depend on a parameter (e.g. elliptic curve family),
454 `param` is the parameter to pass to the constructor. Only a single
455 parameter is supported.
456 """
457 kt = crypto_knowledge.KeyType(key_type, params)
458 for bits in kt.sizes_to_test():
459 usage_flags = 'PSA_KEY_USAGE_EXPORT'
460 alg = 0
461 alg2 = 0
462 key_material = kt.key_material(bits)
463 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
464 r'',
465 kt.expression)
466 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200467 key = StorageTestData(version=self.version,
468 id=1, lifetime=0x00000001,
469 type=kt.expression, bits=bits,
470 usage=usage_flags, alg=alg, alg2=alg2,
471 material=key_material,
472 description=description)
473 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100474
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200475 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100476 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200477 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200478 for key_type in self.constructors.generate_expressions(key_types):
479 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100480
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200481 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100482 """Generate test keys for the specified algorithm."""
483 # For now, we don't have information on the compatibility of key
484 # types and algorithms. So we just test the encoding of algorithms,
485 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200486 descr = re.sub(r'PSA_ALG_', r'', alg)
487 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100488 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200489 key1 = StorageTestData(version=self.version,
490 id=1, lifetime=0x00000001,
491 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
492 usage=usage, alg=alg, alg2=0,
493 material=b'K',
494 description='alg: ' + descr)
495 yield key1
496 key2 = StorageTestData(version=self.version,
497 id=1, lifetime=0x00000001,
498 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
499 usage=usage, alg=0, alg2=alg,
500 material=b'L',
501 description='alg2: ' + descr)
502 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100503
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200504 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100505 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200506 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200507 for alg in self.constructors.generate_expressions(algorithms):
508 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100509
gabor-mezei-armea840de2021-06-29 15:42:57 +0200510 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200511 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200512 yield from self.all_keys_for_lifetimes()
513 yield from self.all_keys_for_usage_flags()
514 yield from self.all_keys_for_types()
515 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200516
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200517 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100518 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200519 # First build a list of all keys, then construct all the corresponding
520 # test cases. This allows all required information to be obtained in
521 # one go, which is a significant performance gain as the information
522 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200523 all_keys = list(self.generate_all_keys())
524 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200525 if key.location_value() != 0:
526 # Skip keys with a non-default location, because they
527 # require a driver and we currently have no mechanism to
528 # determine whether a driver is available.
529 continue
530 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100531
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200532class StorageFormatForward(StorageFormat):
533 """Storage format stability test cases for forward compatibility."""
534
535 def __init__(self, info: Information, version: int) -> None:
536 super().__init__(info, version, True)
537
538class StorageFormatV0(StorageFormat):
539 """Storage format stability test cases for version 0 compatibility."""
540
541 def __init__(self, info: Information) -> None:
542 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100543
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200544 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200545 """Generate test keys covering usage flags."""
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200546 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
547 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200548
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200549 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200550 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200551 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200552 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200553 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200554 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200555 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200556 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200557 algorithm and key type combination.
558 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200559 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200560 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200561 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200562 material_usage_flags = usage_flags + ' | ' + implyer_usage
563 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200564 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200565 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200566 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200567 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
568 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
569 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
570 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200571 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200572 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200573 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200574 key = StorageTestData(version=self.version,
575 id=1, lifetime=0x00000001,
576 type=key_type.expression, bits=bits,
577 usage=material_usage_flags,
578 expected_usage=expected_usage_flags,
579 without_implicit_usage=True,
580 alg=alg, alg2=alg2,
581 material=key_material,
582 description=description)
583 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200584
585 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200586 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200587 """Match possible key types for sign algorithms."""
588 # To create a valid combinaton both the algorithms and key types
589 # must be filtered. Pair them with keywords created from its names.
590 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
591 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
592 keyword_translation = {
593 'ECDSA': 'ECC',
594 'ED[0-9]*.*' : 'EDWARDS'
595 }
596 exclusive_keywords = {
597 'EDWARDS': 'ECC'
598 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200599 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
600 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200601 alg_with_keys = {} #type: Dict[str, List[str]]
602 translation_table = str.maketrans('(', '_', ')')
603 for alg in algorithms:
604 # Generate keywords from the name of the algorithm
605 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
606 # Translate keywords for better matching with the key types
607 for keyword in alg_keywords.copy():
608 for pattern, replace in keyword_translation.items():
609 if re.match(pattern, keyword):
610 alg_keywords.remove(keyword)
611 alg_keywords.add(replace)
612 # Filter out incompatible algortihms
613 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
614 continue
615
616 for key_type in key_types:
617 # Generate keywords from the of the key type
618 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
619
620 # Remove ambigious keywords
621 for keyword1, keyword2 in exclusive_keywords.items():
622 if keyword1 in key_type_keywords:
623 key_type_keywords.remove(keyword2)
624
625 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
626 not key_type_keywords.isdisjoint(alg_keywords):
627 if alg in alg_with_keys:
628 alg_with_keys[alg].append(key_type)
629 else:
630 alg_with_keys[alg] = [key_type]
631 return alg_with_keys
632
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200633 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200634 """Generate test keys for usage flag extensions."""
635 # Generate a key type and algorithm pair for each extendable usage
636 # flag to generate a valid key for exercising. The key is generated
637 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200638 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200639
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200640 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
641 for alg in sorted(alg_with_keys):
642 for key_type in sorted(alg_with_keys[alg]):
643 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200644 kt = crypto_knowledge.KeyType(key_type)
645 if kt.is_valid_for_signature(usage):
646 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200647
gabor-mezei-armea840de2021-06-29 15:42:57 +0200648 def generate_all_keys(self) -> Iterator[StorageTestData]:
649 yield from super().generate_all_keys()
650 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200651
Gilles Peskineb94ea512021-03-10 02:12:08 +0100652class TestGenerator:
653 """Generate test data."""
654
655 def __init__(self, options) -> None:
656 self.test_suite_directory = self.get_option(options, 'directory',
657 'tests/suites')
658 self.info = Information()
659
660 @staticmethod
661 def get_option(options, name: str, default: T) -> T:
662 value = getattr(options, name, None)
663 return default if value is None else value
664
Gilles Peskine0298bda2021-03-10 02:34:37 +0100665 def filename_for(self, basename: str) -> str:
666 """The location of the data file with the specified base name."""
667 return os.path.join(self.test_suite_directory, basename + '.data')
668
Gilles Peskineb94ea512021-03-10 02:12:08 +0100669 def write_test_data_file(self, basename: str,
670 test_cases: Iterable[test_case.TestCase]) -> None:
671 """Write the test cases to a .data file.
672
673 The output file is ``basename + '.data'`` in the test suite directory.
674 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100675 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100676 test_case.write_data_file(filename, test_cases)
677
Gilles Peskine0298bda2021-03-10 02:34:37 +0100678 TARGETS = {
679 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100680 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100681 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200682 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100683 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200684 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100685 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
686
687 def generate_target(self, name: str) -> None:
688 test_cases = self.TARGETS[name](self.info)
689 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100690
Gilles Peskine09940492021-01-26 22:16:30 +0100691def main(args):
692 """Command line entry point."""
693 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100694 parser.add_argument('--list', action='store_true',
695 help='List available targets and exit')
696 parser.add_argument('targets', nargs='*', metavar='TARGET',
697 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100698 options = parser.parse_args(args)
699 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100700 if options.list:
701 for name in sorted(generator.TARGETS):
702 print(generator.filename_for(name))
703 return
704 if options.targets:
705 # Allow "-" as a special case so you can run
706 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
707 # ``$targets`` is empty or not.
708 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
709 for target in options.targets
710 if target != '-']
711 else:
712 options.targets = sorted(generator.TARGETS)
713 for target in options.targets:
714 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100715
716if __name__ == '__main__':
717 main(sys.argv[1:])