blob: ffb03b33099d76ce58114d93e67b64226ea66985 [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 Stekiel330e4602021-10-08 12:26:21 +0200130def test_case_for_key_type_not_supported_invalid_arg(
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 Stekiel330e4602021-10-08 12:26:21 +0200145 if (verb == "generate") and ("PUBLIC" in short_key_type):
146 tc.set_description('PSA {} {} {}-bit invalid argument'
147 .format(verb, short_key_type, bits))
148 tc.set_function(verb + '_invalid_arg')
149 else:
150 tc.set_description('PSA {} {} {}-bit {} supported'
151 .format(verb, short_key_type, bits, adverb))
152 tc.set_function(verb + '_not_supported')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100153 tc.set_dependencies(dependencies)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100154 tc.set_arguments([key_type] + list(args))
155 return tc
156
157class NotSupported:
158 """Generate test cases for when something is not supported."""
159
160 def __init__(self, info: Information) -> None:
161 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100162
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100163 ALWAYS_SUPPORTED = frozenset([
164 'PSA_KEY_TYPE_DERIVE',
165 'PSA_KEY_TYPE_RAW_DATA',
166 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100167 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100168 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100169 kt: crypto_knowledge.KeyType,
170 param: Optional[int] = None,
171 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100172 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100173 """Return test cases exercising key creation when the given type is unsupported.
174
175 If param is present and not None, emit test cases conditioned on this
176 parameter not being supported. If it is absent or None, emit test cases
177 conditioned on the base type not being supported.
178 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100179 if kt.name in self.ALWAYS_SUPPORTED:
180 # Don't generate test cases for key types that are always supported.
181 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100182 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100183 import_dependencies = [('!' if param is None else '') +
184 psa_want_symbol(kt.name)]
185 if kt.params is not None:
186 import_dependencies += [('!' if param == i else '') +
187 psa_want_symbol(sym)
188 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100189 if kt.name.endswith('_PUBLIC_KEY'):
190 generate_dependencies = []
191 else:
192 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100193 for bits in kt.sizes_to_test():
Przemyslaw Stekiel330e4602021-10-08 12:26:21 +0200194 yield test_case_for_key_type_not_supported_invalid_arg(
Gilles Peskine7f756872021-02-16 12:13:12 +0100195 'import', kt.expression, bits,
196 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100197 test_case.hex_string(kt.key_material(bits)),
198 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100199 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100200 if not generate_dependencies and param is not None:
201 # If generation is impossible for this key type, rather than
202 # supported or not depending on implementation capabilities,
203 # only generate the test case once.
204 continue
Przemyslaw Stekiel330e4602021-10-08 12:26:21 +0200205 yield test_case_for_key_type_not_supported_invalid_arg(
Gilles Peskine7f756872021-02-16 12:13:12 +0100206 'generate', kt.expression, bits,
207 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100208 str(bits),
209 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100210 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100211 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212
Gilles Peskineb93f8542021-04-19 13:50:25 +0200213 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
214 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
215
Gilles Peskine3d778392021-02-17 15:11:05 +0100216 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100217 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200219 if key_type in self.ECC_KEY_TYPES:
220 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100221 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100222 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100223 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200224 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100225 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100226 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100227 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100228 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100229 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100230
231
Gilles Peskine897dff92021-03-10 15:03:44 +0100232class StorageKey(psa_storage.Key):
233 """Representation of a key for storage format testing."""
234
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200235 IMPLICIT_USAGE_FLAGS = {
236 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
237 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
238 } #type: Dict[str, str]
239 """Mapping of usage flags to the flags that they imply."""
240
241 def __init__(
242 self,
243 usage: str,
244 without_implicit_usage: Optional[bool] = False,
245 **kwargs
246 ) -> None:
247 """Prepare to generate a key.
248
249 * `usage` : The usage flags used for the key.
250 * `without_implicit_usage`: Flag to defide to apply the usage extension
251 """
gabor-mezei-arm3ea27322021-06-29 17:21:21 +0200252 super().__init__(usage=usage, **kwargs)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200253
254 if not without_implicit_usage:
255 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
256 if self.usage.value() & psa_storage.Expr(flag).value() and \
257 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
258 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
259
260class StorageTestData(StorageKey):
261 """Representation of test case data for storage format testing."""
262
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200263 def __init__(
264 self,
265 description: str,
266 expected_usage: Optional[str] = None,
267 **kwargs
268 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200269 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200270
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200271 * `description` : used for the the test case names
272 * `expected_usage`: the usage flags generated as the expected usage flags
273 in the test cases. CAn differ from the usage flags
274 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200275 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100276 super().__init__(**kwargs)
277 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200278 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200279
Gilles Peskine897dff92021-03-10 15:03:44 +0100280class StorageFormat:
281 """Storage format stability test cases."""
282
283 def __init__(self, info: Information, version: int, forward: bool) -> None:
284 """Prepare to generate test cases for storage format stability.
285
286 * `info`: information about the API. See the `Information` class.
287 * `version`: the storage format version to generate test cases for.
288 * `forward`: if true, generate forward compatibility test cases which
289 save a key and check that its representation is as intended. Otherwise
290 generate backward compatibility test cases which inject a key
291 representation and check that it can be read and used.
292 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200293 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
294 self.version = version #type: int
295 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100296
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200297 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100298 """Construct a storage format test case for the given key.
299
300 If ``forward`` is true, generate a forward compatibility test case:
301 create a key and validate that it has the expected representation.
302 Otherwise generate a backward compatibility test case: inject the
303 key representation into storage and validate that it can be read
304 correctly.
305 """
306 verb = 'save' if self.forward else 'read'
307 tc = test_case.TestCase()
308 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100309 dependencies = automatic_dependencies(
310 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200311 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100312 )
313 dependencies = finish_family_dependencies(dependencies, key.bits)
314 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100315 tc.set_function('key_storage_' + verb)
316 if self.forward:
317 extra_arguments = []
318 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200319 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100320 # Some test keys have the RAW_DATA type and attributes that don't
321 # necessarily make sense. We do this to validate numerical
322 # encodings of the attributes.
323 # Raw data keys have no useful exercise anyway so there is no
324 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200325 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
326 flags.append('TEST_FLAG_EXERCISE')
327 if 'READ_ONLY' in key.lifetime.string:
328 flags.append('TEST_FLAG_READ_ONLY')
329 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100330 tc.set_arguments([key.lifetime.string,
331 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200332 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100333 '"' + key.material.hex() + '"',
334 '"' + key.hex() + '"',
335 *extra_arguments])
336 return tc
337
Gilles Peskineefb584d2021-04-21 22:05:34 +0200338 def key_for_lifetime(
339 self,
340 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200341 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200342 """Construct a test key for the given lifetime."""
343 short = lifetime
344 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
345 r'', short)
346 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
347 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200348 key = StorageTestData(version=self.version,
349 id=1, lifetime=lifetime,
350 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
351 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
352 material=b'L',
353 description=description)
354 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200355
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200356 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200357 """Generate test keys covering lifetimes."""
358 lifetimes = sorted(self.constructors.lifetimes)
359 expressions = self.constructors.generate_expressions(lifetimes)
360 for lifetime in expressions:
361 # Don't attempt to create or load a volatile key in storage
362 if 'VOLATILE' in lifetime:
363 continue
364 # Don't attempt to create a read-only key in storage,
365 # but do attempt to load one.
366 if 'READ_ONLY' in lifetime and self.forward:
367 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200368 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200369
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200370 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100371 self,
372 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200373 short: Optional[str] = None,
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200374 test_implicit_usage: Optional[bool] = False
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200375 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100376 """Construct a test key for the given key usage."""
377 usage = ' | '.join(usage_flags) if usage_flags else '0'
378 if short is None:
379 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200380 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200381 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200382 key1 = StorageTestData(version=self.version,
383 id=1, lifetime=0x00000001,
384 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
385 expected_usage=usage,
386 usage=usage, alg=0, alg2=0,
387 material=b'K',
388 description=description)
389 yield key1
390
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200391 if test_implicit_usage:
392 description = 'usage without implication' + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200393 key2 = StorageTestData(version=self.version,
394 id=1, lifetime=0x00000001,
395 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
396 without_implicit_usage=True,
397 usage=usage, alg=0, alg2=0,
398 material=b'K',
399 description=description)
400 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100401
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200402 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100403 """Generate test keys covering usage flags."""
404 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200405 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200406 for usage_flag in known_flags:
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200407 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200408 for flag1, flag2 in zip(known_flags,
409 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200410 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200411
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200412 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200413 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200414 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200415
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200416 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200417 yield from self.generate_keys_for_usage_flags()
418 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100419
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100420 def keys_for_type(
421 self,
422 key_type: str,
423 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200424 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100425 """Generate test keys for the given key type.
426
427 For key types that depend on a parameter (e.g. elliptic curve family),
428 `param` is the parameter to pass to the constructor. Only a single
429 parameter is supported.
430 """
431 kt = crypto_knowledge.KeyType(key_type, params)
432 for bits in kt.sizes_to_test():
433 usage_flags = 'PSA_KEY_USAGE_EXPORT'
434 alg = 0
435 alg2 = 0
436 key_material = kt.key_material(bits)
437 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
438 r'',
439 kt.expression)
440 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200441 key = StorageTestData(version=self.version,
442 id=1, lifetime=0x00000001,
443 type=kt.expression, bits=bits,
444 usage=usage_flags, alg=alg, alg2=alg2,
445 material=key_material,
446 description=description)
447 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100448
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200449 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100450 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200451 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200452 for key_type in self.constructors.generate_expressions(key_types):
453 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100454
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200455 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100456 """Generate test keys for the specified algorithm."""
457 # For now, we don't have information on the compatibility of key
458 # types and algorithms. So we just test the encoding of algorithms,
459 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200460 descr = re.sub(r'PSA_ALG_', r'', alg)
461 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100462 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200463 key1 = StorageTestData(version=self.version,
464 id=1, lifetime=0x00000001,
465 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
466 usage=usage, alg=alg, alg2=0,
467 material=b'K',
468 description='alg: ' + descr)
469 yield key1
470 key2 = StorageTestData(version=self.version,
471 id=1, lifetime=0x00000001,
472 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
473 usage=usage, alg=0, alg2=alg,
474 material=b'L',
475 description='alg2: ' + descr)
476 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100477
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200478 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100479 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200480 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200481 for alg in self.constructors.generate_expressions(algorithms):
482 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100483
gabor-mezei-armea840de2021-06-29 15:42:57 +0200484 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200485 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200486 yield from self.all_keys_for_lifetimes()
487 yield from self.all_keys_for_usage_flags()
488 yield from self.all_keys_for_types()
489 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200490
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200491 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100492 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200493 # First build a list of all keys, then construct all the corresponding
494 # test cases. This allows all required information to be obtained in
495 # one go, which is a significant performance gain as the information
496 # includes numerical values obtained by compiling a C program.
Gilles Peskine3008c582021-07-06 21:05:52 +0200497 all_keys = list(self.generate_all_keys())
498 for key in all_keys:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200499 if key.location_value() != 0:
500 # Skip keys with a non-default location, because they
501 # require a driver and we currently have no mechanism to
502 # determine whether a driver is available.
503 continue
504 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100505
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200506class StorageFormatForward(StorageFormat):
507 """Storage format stability test cases for forward compatibility."""
508
509 def __init__(self, info: Information, version: int) -> None:
510 super().__init__(info, version, True)
511
512class StorageFormatV0(StorageFormat):
513 """Storage format stability test cases for version 0 compatibility."""
514
515 def __init__(self, info: Information) -> None:
516 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100517
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200518 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200519 """Generate test keys covering usage flags."""
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200520 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
521 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200522
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200523 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200524 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200525 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200526 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200527 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200528 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200529 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200530 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200531 algorithm and key type combination.
532 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200533 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200534 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200535 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200536 material_usage_flags = usage_flags + ' | ' + implyer_usage
537 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200538 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200539 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200540 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200541 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
542 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
543 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
544 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200545 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200546 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200547 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200548 key = StorageTestData(version=self.version,
549 id=1, lifetime=0x00000001,
550 type=key_type.expression, bits=bits,
551 usage=material_usage_flags,
552 expected_usage=expected_usage_flags,
553 without_implicit_usage=True,
554 alg=alg, alg2=alg2,
555 material=key_material,
556 description=description)
557 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200558
559 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200560 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200561 """Match possible key types for sign algorithms."""
562 # To create a valid combinaton both the algorithms and key types
563 # must be filtered. Pair them with keywords created from its names.
564 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
565 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
566 keyword_translation = {
567 'ECDSA': 'ECC',
568 'ED[0-9]*.*' : 'EDWARDS'
569 }
570 exclusive_keywords = {
571 'EDWARDS': 'ECC'
572 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200573 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
574 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200575 alg_with_keys = {} #type: Dict[str, List[str]]
576 translation_table = str.maketrans('(', '_', ')')
577 for alg in algorithms:
578 # Generate keywords from the name of the algorithm
579 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
580 # Translate keywords for better matching with the key types
581 for keyword in alg_keywords.copy():
582 for pattern, replace in keyword_translation.items():
583 if re.match(pattern, keyword):
584 alg_keywords.remove(keyword)
585 alg_keywords.add(replace)
586 # Filter out incompatible algortihms
587 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
588 continue
589
590 for key_type in key_types:
591 # Generate keywords from the of the key type
592 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
593
594 # Remove ambigious keywords
595 for keyword1, keyword2 in exclusive_keywords.items():
596 if keyword1 in key_type_keywords:
597 key_type_keywords.remove(keyword2)
598
599 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
600 not key_type_keywords.isdisjoint(alg_keywords):
601 if alg in alg_with_keys:
602 alg_with_keys[alg].append(key_type)
603 else:
604 alg_with_keys[alg] = [key_type]
605 return alg_with_keys
606
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200607 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200608 """Generate test keys for usage flag extensions."""
609 # Generate a key type and algorithm pair for each extendable usage
610 # flag to generate a valid key for exercising. The key is generated
611 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200612 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200613
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200614 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
615 for alg in sorted(alg_with_keys):
616 for key_type in sorted(alg_with_keys[alg]):
617 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200618 kt = crypto_knowledge.KeyType(key_type)
619 if kt.is_valid_for_signature(usage):
620 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200621
gabor-mezei-armea840de2021-06-29 15:42:57 +0200622 def generate_all_keys(self) -> Iterator[StorageTestData]:
623 yield from super().generate_all_keys()
624 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200625
Gilles Peskineb94ea512021-03-10 02:12:08 +0100626class TestGenerator:
627 """Generate test data."""
628
629 def __init__(self, options) -> None:
630 self.test_suite_directory = self.get_option(options, 'directory',
631 'tests/suites')
632 self.info = Information()
633
634 @staticmethod
635 def get_option(options, name: str, default: T) -> T:
636 value = getattr(options, name, None)
637 return default if value is None else value
638
Gilles Peskine0298bda2021-03-10 02:34:37 +0100639 def filename_for(self, basename: str) -> str:
640 """The location of the data file with the specified base name."""
641 return os.path.join(self.test_suite_directory, basename + '.data')
642
Gilles Peskineb94ea512021-03-10 02:12:08 +0100643 def write_test_data_file(self, basename: str,
644 test_cases: Iterable[test_case.TestCase]) -> None:
645 """Write the test cases to a .data file.
646
647 The output file is ``basename + '.data'`` in the test suite directory.
648 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100649 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100650 test_case.write_data_file(filename, test_cases)
651
Gilles Peskine0298bda2021-03-10 02:34:37 +0100652 TARGETS = {
653 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100654 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100655 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200656 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100657 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200658 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100659 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
660
661 def generate_target(self, name: str) -> None:
662 test_cases = self.TARGETS[name](self.info)
663 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100664
Gilles Peskine09940492021-01-26 22:16:30 +0100665def main(args):
666 """Command line entry point."""
667 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100668 parser.add_argument('--list', action='store_true',
669 help='List available targets and exit')
670 parser.add_argument('targets', nargs='*', metavar='TARGET',
671 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100672 options = parser.parse_args(args)
673 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100674 if options.list:
675 for name in sorted(generator.TARGETS):
676 print(generator.filename_for(name))
677 return
678 if options.targets:
679 # Allow "-" as a special case so you can run
680 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
681 # ``$targets`` is empty or not.
682 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
683 for target in options.targets
684 if target != '-']
685 else:
686 options.targets = sorted(generator.TARGETS)
687 for target in options.targets:
688 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100689
690if __name__ == '__main__':
691 main(sys.argv[1:])