blob: 1cdd28f89a7c9146a9a5d5bc4cafdcfe3d8a41c6 [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 Stekield9d630c2021-10-08 12:26:21 +0200136def test_case_for_key_type_not_supported_invalid_arg(
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 Stekield9d630c2021-10-08 12:26:21 +0200151 if (verb == "generate") and ("PUBLIC" in short_key_type):
152 tc.set_description('PSA {} {} {}-bit invalid argument'
153 .format(verb, short_key_type, bits))
154 tc.set_function(verb + '_invalid_arg')
155 else:
156 tc.set_description('PSA {} {} {}-bit {} supported'
157 .format(verb, short_key_type, bits, adverb))
158 tc.set_function(verb + '_not_supported')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100159 tc.set_dependencies(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100160 tc.set_arguments([key_type] + list(args))
161 return tc
162
163class NotSupported:
164 """Generate test cases for when something is not supported."""
165
166 def __init__(self, info: Information) -> None:
167 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100168
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100169 ALWAYS_SUPPORTED = frozenset([
170 'PSA_KEY_TYPE_DERIVE',
171 'PSA_KEY_TYPE_RAW_DATA',
172 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100173 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100174 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100175 kt: crypto_knowledge.KeyType,
176 param: Optional[int] = None,
177 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100178 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100179 """Return test cases exercising key creation when the given type is unsupported.
180
181 If param is present and not None, emit test cases conditioned on this
182 parameter not being supported. If it is absent or None, emit test cases
183 conditioned on the base type not being supported.
184 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100185 if kt.name in self.ALWAYS_SUPPORTED:
186 # Don't generate test cases for key types that are always supported.
187 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100188 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100189 import_dependencies = [('!' if param is None else '') +
190 psa_want_symbol(kt.name)]
191 if kt.params is not None:
192 import_dependencies += [('!' if param == i else '') +
193 psa_want_symbol(sym)
194 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100195 if kt.name.endswith('_PUBLIC_KEY'):
196 generate_dependencies = []
197 else:
198 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100199 for bits in kt.sizes_to_test():
Przemyslaw Stekield9d630c2021-10-08 12:26:21 +0200200 yield test_case_for_key_type_not_supported_invalid_arg(
Gilles Peskine7f756872021-02-16 12:13:12 +0100201 'import', kt.expression, bits,
202 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100203 test_case.hex_string(kt.key_material(bits)),
204 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100205 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100206 if not generate_dependencies and param is not None:
207 # If generation is impossible for this key type, rather than
208 # supported or not depending on implementation capabilities,
209 # only generate the test case once.
210 continue
Przemyslaw Stekield9d630c2021-10-08 12:26:21 +0200211 yield test_case_for_key_type_not_supported_invalid_arg(
Gilles Peskine7f756872021-02-16 12:13:12 +0100212 'generate', kt.expression, bits,
213 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100214 str(bits),
215 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100216 )
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
237
Gilles Peskine897dff92021-03-10 15:03:44 +0100238class StorageKey(psa_storage.Key):
239 """Representation of a key for storage format testing."""
240
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200241 IMPLICIT_USAGE_FLAGS = {
242 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
243 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
244 } #type: Dict[str, str]
245 """Mapping of usage flags to the flags that they imply."""
246
247 def __init__(
248 self,
249 usage: str,
250 without_implicit_usage: Optional[bool] = False,
251 **kwargs
252 ) -> None:
253 """Prepare to generate a key.
254
255 * `usage` : The usage flags used for the key.
256 * `without_implicit_usage`: Flag to defide to apply the usage extension
257 """
gabor-mezei-arm2c9e54a2021-06-29 17:21:21 +0200258 super().__init__(usage=usage, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200259
260 if not without_implicit_usage:
261 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
262 if self.usage.value() & psa_storage.Expr(flag).value() and \
263 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
264 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
265
266class StorageTestData(StorageKey):
267 """Representation of test case data for storage format testing."""
268
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200269 def __init__(
270 self,
271 description: str,
272 expected_usage: Optional[str] = None,
273 **kwargs
274 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200275 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200276
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200277 * `description` : used for the the test case names
278 * `expected_usage`: the usage flags generated as the expected usage flags
279 in the test cases. CAn differ from the usage flags
280 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200281 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100282 super().__init__(**kwargs)
283 self.description = description #type: str
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200284 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200285
Gilles Peskine897dff92021-03-10 15:03:44 +0100286class StorageFormat:
287 """Storage format stability test cases."""
288
289 def __init__(self, info: Information, version: int, forward: bool) -> None:
290 """Prepare to generate test cases for storage format stability.
291
292 * `info`: information about the API. See the `Information` class.
293 * `version`: the storage format version to generate test cases for.
294 * `forward`: if true, generate forward compatibility test cases which
295 save a key and check that its representation is as intended. Otherwise
296 generate backward compatibility test cases which inject a key
297 representation and check that it can be read and used.
298 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200299 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
300 self.version = version #type: int
301 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100302
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200303 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100304 """Construct a storage format test case for the given key.
305
306 If ``forward`` is true, generate a forward compatibility test case:
307 create a key and validate that it has the expected representation.
308 Otherwise generate a backward compatibility test case: inject the
309 key representation into storage and validate that it can be read
310 correctly.
311 """
312 verb = 'save' if self.forward else 'read'
313 tc = test_case.TestCase()
314 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100315 dependencies = automatic_dependencies(
316 key.lifetime.string, key.type.string,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200317 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100318 )
319 dependencies = finish_family_dependencies(dependencies, key.bits)
320 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100321 tc.set_function('key_storage_' + verb)
322 if self.forward:
323 extra_arguments = []
324 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200325 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100326 # Some test keys have the RAW_DATA type and attributes that don't
327 # necessarily make sense. We do this to validate numerical
328 # encodings of the attributes.
329 # Raw data keys have no useful exercise anyway so there is no
330 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200331 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
332 flags.append('TEST_FLAG_EXERCISE')
333 if 'READ_ONLY' in key.lifetime.string:
334 flags.append('TEST_FLAG_READ_ONLY')
335 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100336 tc.set_arguments([key.lifetime.string,
337 key.type.string, str(key.bits),
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200338 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100339 '"' + key.material.hex() + '"',
340 '"' + key.hex() + '"',
341 *extra_arguments])
342 return tc
343
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200344 def key_for_lifetime(
345 self,
346 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200347 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200348 """Construct a test key for the given lifetime."""
349 short = lifetime
350 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
351 r'', short)
352 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
353 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200354 key = StorageTestData(version=self.version,
355 id=1, lifetime=lifetime,
356 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
357 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
358 material=b'L',
359 description=description)
360 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200361
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200362 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200363 """Generate test keys covering lifetimes."""
364 lifetimes = sorted(self.constructors.lifetimes)
365 expressions = self.constructors.generate_expressions(lifetimes)
366 for lifetime in expressions:
367 # Don't attempt to create or load a volatile key in storage
368 if 'VOLATILE' in lifetime:
369 continue
370 # Don't attempt to create a read-only key in storage,
371 # but do attempt to load one.
372 if 'READ_ONLY' in lifetime and self.forward:
373 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200374 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200375
gabor-mezei-arm63857802021-06-29 15:39:56 +0200376 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100377 self,
378 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200379 short: Optional[str] = None,
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200380 test_implicit_usage: Optional[bool] = False
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200381 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100382 """Construct a test key for the given key usage."""
383 usage = ' | '.join(usage_flags) if usage_flags else '0'
384 if short is None:
385 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200386 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200387 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200388 key1 = StorageTestData(version=self.version,
389 id=1, lifetime=0x00000001,
390 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
391 expected_usage=usage,
392 usage=usage, alg=0, alg2=0,
393 material=b'K',
394 description=description)
395 yield key1
396
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200397 if test_implicit_usage:
398 description = 'usage without implication' + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200399 key2 = StorageTestData(version=self.version,
400 id=1, lifetime=0x00000001,
401 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
402 without_implicit_usage=True,
403 usage=usage, alg=0, alg2=0,
404 material=b'K',
405 description=description)
406 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100407
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200408 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100409 """Generate test keys covering usage flags."""
410 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200411 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200412 for usage_flag in known_flags:
gabor-mezei-arm63857802021-06-29 15:39:56 +0200413 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200414 for flag1, flag2 in zip(known_flags,
415 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm63857802021-06-29 15:39:56 +0200416 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200417
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200418 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200419 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200420 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200421
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200422 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200423 yield from self.generate_keys_for_usage_flags()
424 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100425
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100426 def keys_for_type(
427 self,
428 key_type: str,
429 params: Optional[Iterable[str]] = None
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200430 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100431 """Generate test keys for the given key type.
432
433 For key types that depend on a parameter (e.g. elliptic curve family),
434 `param` is the parameter to pass to the constructor. Only a single
435 parameter is supported.
436 """
437 kt = crypto_knowledge.KeyType(key_type, params)
438 for bits in kt.sizes_to_test():
439 usage_flags = 'PSA_KEY_USAGE_EXPORT'
440 alg = 0
441 alg2 = 0
442 key_material = kt.key_material(bits)
443 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
444 r'',
445 kt.expression)
446 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200447 key = StorageTestData(version=self.version,
448 id=1, lifetime=0x00000001,
449 type=kt.expression, bits=bits,
450 usage=usage_flags, alg=alg, alg2=alg2,
451 material=key_material,
452 description=description)
453 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100454
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200455 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100456 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200457 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200458 for key_type in self.constructors.generate_expressions(key_types):
459 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100460
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200461 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100462 """Generate test keys for the specified algorithm."""
463 # For now, we don't have information on the compatibility of key
464 # types and algorithms. So we just test the encoding of algorithms,
465 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200466 descr = re.sub(r'PSA_ALG_', r'', alg)
467 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100468 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200469 key1 = StorageTestData(version=self.version,
470 id=1, lifetime=0x00000001,
471 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
472 usage=usage, alg=alg, alg2=0,
473 material=b'K',
474 description='alg: ' + descr)
475 yield key1
476 key2 = StorageTestData(version=self.version,
477 id=1, lifetime=0x00000001,
478 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
479 usage=usage, alg=0, alg2=alg,
480 material=b'L',
481 description='alg2: ' + descr)
482 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100483
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200484 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100485 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200486 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200487 for alg in self.constructors.generate_expressions(algorithms):
488 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100489
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200490 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200491 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200492 yield from self.all_keys_for_lifetimes()
493 yield from self.all_keys_for_usage_flags()
494 yield from self.all_keys_for_types()
495 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200496
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200497 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100498 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200499 # First build a list of all keys, then construct all the corresponding
500 # test cases. This allows all required information to be obtained in
501 # one go, which is a significant performance gain as the information
502 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200503 all_keys = list(self.generate_all_keys())
504 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200505 if key.location_value() != 0:
506 # Skip keys with a non-default location, because they
507 # require a driver and we currently have no mechanism to
508 # determine whether a driver is available.
509 continue
510 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100511
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200512class StorageFormatForward(StorageFormat):
513 """Storage format stability test cases for forward compatibility."""
514
515 def __init__(self, info: Information, version: int) -> None:
516 super().__init__(info, version, True)
517
518class StorageFormatV0(StorageFormat):
519 """Storage format stability test cases for version 0 compatibility."""
520
521 def __init__(self, info: Information) -> None:
522 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100523
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200524 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200525 """Generate test keys covering usage flags."""
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200526 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
527 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200528
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200529 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200530 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200531 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200532 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200533 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200534 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200535 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200536 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200537 algorithm and key type combination.
538 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200539 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200540 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200541 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200542 material_usage_flags = usage_flags + ' | ' + implyer_usage
543 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200544 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200545 key_material = key_type.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200546 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200547 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
548 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
549 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
550 r'',
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200551 key_type.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200552 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200553 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200554 key = StorageTestData(version=self.version,
555 id=1, lifetime=0x00000001,
556 type=key_type.expression, bits=bits,
557 usage=material_usage_flags,
558 expected_usage=expected_usage_flags,
559 without_implicit_usage=True,
560 alg=alg, alg2=alg2,
561 material=key_material,
562 description=description)
563 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200564
565 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200566 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200567 """Match possible key types for sign algorithms."""
568 # To create a valid combinaton both the algorithms and key types
569 # must be filtered. Pair them with keywords created from its names.
570 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
571 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
572 keyword_translation = {
573 'ECDSA': 'ECC',
574 'ED[0-9]*.*' : 'EDWARDS'
575 }
576 exclusive_keywords = {
577 'EDWARDS': 'ECC'
578 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200579 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
580 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200581 alg_with_keys = {} #type: Dict[str, List[str]]
582 translation_table = str.maketrans('(', '_', ')')
583 for alg in algorithms:
584 # Generate keywords from the name of the algorithm
585 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
586 # Translate keywords for better matching with the key types
587 for keyword in alg_keywords.copy():
588 for pattern, replace in keyword_translation.items():
589 if re.match(pattern, keyword):
590 alg_keywords.remove(keyword)
591 alg_keywords.add(replace)
592 # Filter out incompatible algortihms
593 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
594 continue
595
596 for key_type in key_types:
597 # Generate keywords from the of the key type
598 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
599
600 # Remove ambigious keywords
601 for keyword1, keyword2 in exclusive_keywords.items():
602 if keyword1 in key_type_keywords:
603 key_type_keywords.remove(keyword2)
604
605 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
606 not key_type_keywords.isdisjoint(alg_keywords):
607 if alg in alg_with_keys:
608 alg_with_keys[alg].append(key_type)
609 else:
610 alg_with_keys[alg] = [key_type]
611 return alg_with_keys
612
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200613 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200614 """Generate test keys for usage flag extensions."""
615 # Generate a key type and algorithm pair for each extendable usage
616 # flag to generate a valid key for exercising. The key is generated
617 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200618 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200619
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200620 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
621 for alg in sorted(alg_with_keys):
622 for key_type in sorted(alg_with_keys[alg]):
623 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200624 kt = crypto_knowledge.KeyType(key_type)
625 if kt.is_valid_for_signature(usage):
626 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200627
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200628 def generate_all_keys(self) -> Iterator[StorageTestData]:
629 yield from super().generate_all_keys()
630 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200631
Gilles Peskineb94ea512021-03-10 02:12:08 +0100632class TestGenerator:
633 """Generate test data."""
634
635 def __init__(self, options) -> None:
636 self.test_suite_directory = self.get_option(options, 'directory',
637 'tests/suites')
638 self.info = Information()
639
640 @staticmethod
641 def get_option(options, name: str, default: T) -> T:
642 value = getattr(options, name, None)
643 return default if value is None else value
644
Gilles Peskine0298bda2021-03-10 02:34:37 +0100645 def filename_for(self, basename: str) -> str:
646 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200647 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100648
Gilles Peskineb94ea512021-03-10 02:12:08 +0100649 def write_test_data_file(self, basename: str,
650 test_cases: Iterable[test_case.TestCase]) -> None:
651 """Write the test cases to a .data file.
652
653 The output file is ``basename + '.data'`` in the test suite directory.
654 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100655 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100656 test_case.write_data_file(filename, test_cases)
657
Gilles Peskine0298bda2021-03-10 02:34:37 +0100658 TARGETS = {
659 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100660 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100661 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200662 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100663 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200664 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100665 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
666
667 def generate_target(self, name: str) -> None:
668 test_cases = self.TARGETS[name](self.info)
669 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100670
Gilles Peskine09940492021-01-26 22:16:30 +0100671def main(args):
672 """Command line entry point."""
673 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100674 parser.add_argument('--list', action='store_true',
675 help='List available targets and exit')
676 parser.add_argument('targets', nargs='*', metavar='TARGET',
677 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100678 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200679 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100680 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100681 if options.list:
682 for name in sorted(generator.TARGETS):
683 print(generator.filename_for(name))
684 return
685 if options.targets:
686 # Allow "-" as a special case so you can run
687 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
688 # ``$targets`` is empty or not.
689 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
690 for target in options.targets
691 if target != '-']
692 else:
693 options.targets = sorted(generator.TARGETS)
694 for target in options.targets:
695 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100696
697if __name__ == '__main__':
698 main(sys.argv[1:])