blob: c788ce6d6d3efa7a6b7a061fc040e1ab19f4782a [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
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136def test_case_for_key_type_not_supported(
137 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
151 tc.set_description('PSA {} {} {}-bit {} supported'
152 .format(verb, short_key_type, bits, adverb))
153 tc.set_dependencies(dependencies)
154 tc.set_function(verb + '_not_supported')
155 tc.set_arguments([key_type] + list(args))
156 return tc
157
158class NotSupported:
159 """Generate test cases for when something is not supported."""
160
161 def __init__(self, info: Information) -> None:
162 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100163
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100164 ALWAYS_SUPPORTED = frozenset([
165 'PSA_KEY_TYPE_DERIVE',
166 'PSA_KEY_TYPE_RAW_DATA',
167 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100168 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100169 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100170 kt: crypto_knowledge.KeyType,
171 param: Optional[int] = None,
172 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100173 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100174 """Return test cases exercising key creation when the given type is unsupported.
175
176 If param is present and not None, emit test cases conditioned on this
177 parameter not being supported. If it is absent or None, emit test cases
178 conditioned on the base type not being supported.
179 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100180 if kt.name in self.ALWAYS_SUPPORTED:
181 # Don't generate test cases for key types that are always supported.
182 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100183 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100184 import_dependencies = [('!' if param is None else '') +
185 psa_want_symbol(kt.name)]
186 if kt.params is not None:
187 import_dependencies += [('!' if param == i else '') +
188 psa_want_symbol(sym)
189 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 if kt.name.endswith('_PUBLIC_KEY'):
191 generate_dependencies = []
192 else:
193 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100194 for bits in kt.sizes_to_test():
Gilles Peskine3d778392021-02-17 15:11:05 +0100195 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100196 'import', kt.expression, bits,
197 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100198 test_case.hex_string(kt.key_material(bits)),
199 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100200 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100201 if not generate_dependencies and param is not None:
202 # If generation is impossible for this key type, rather than
203 # supported or not depending on implementation capabilities,
204 # only generate the test case once.
205 continue
Gilles Peskine3d778392021-02-17 15:11:05 +0100206 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100207 'generate', kt.expression, bits,
208 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100209 str(bits),
210 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100211 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200214 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
215 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
216
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200220 if key_type in self.ECC_KEY_TYPES:
221 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200225 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100231
232
Gilles Peskine897dff92021-03-10 15:03:44 +0100233class StorageKey(psa_storage.Key):
234 """Representation of a key for storage format testing."""
235
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200236 IMPLICIT_USAGE_FLAGS = {
237 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
238 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
239 } #type: Dict[str, str]
240 """Mapping of usage flags to the flags that they imply."""
241
242 def __init__(
243 self,
244 usage: str,
245 without_implicit_usage: Optional[bool] = False,
246 **kwargs
247 ) -> None:
248 """Prepare to generate a key.
249
250 * `usage` : The usage flags used for the key.
251 * `without_implicit_usage`: Flag to defide to apply the usage extension
252 """
gabor-mezei-arm2c9e54a2021-06-29 17:21:21 +0200253 super().__init__(usage=usage, **kwargs)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200254
255 if not without_implicit_usage:
256 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
257 if self.usage.value() & psa_storage.Expr(flag).value() and \
258 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
259 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
260
261class StorageTestData(StorageKey):
262 """Representation of test case data for storage format testing."""
263
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200264 def __init__(
265 self,
266 description: str,
267 expected_usage: Optional[str] = None,
268 **kwargs
269 ) -> None:
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200270 """Prepare to generate test data
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200271
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200272 * `description` : used for the the test case names
273 * `expected_usage`: the usage flags generated as the expected usage flags
274 in the test cases. CAn differ from the usage flags
275 stored in the keys because of the usage flags extension.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200276 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100277 super().__init__(**kwargs)
278 self.description = description #type: str
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200279 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200280
Gilles Peskine897dff92021-03-10 15:03:44 +0100281class StorageFormat:
282 """Storage format stability test cases."""
283
284 def __init__(self, info: Information, version: int, forward: bool) -> None:
285 """Prepare to generate test cases for storage format stability.
286
287 * `info`: information about the API. See the `Information` class.
288 * `version`: the storage format version to generate test cases for.
289 * `forward`: if true, generate forward compatibility test cases which
290 save a key and check that its representation is as intended. Otherwise
291 generate backward compatibility test cases which inject a key
292 representation and check that it can be read and used.
293 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200294 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
295 self.version = version #type: int
296 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100297
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200298 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100299 """Construct a storage format test case for the given key.
300
301 If ``forward`` is true, generate a forward compatibility test case:
302 create a key and validate that it has the expected representation.
303 Otherwise generate a backward compatibility test case: inject the
304 key representation into storage and validate that it can be read
305 correctly.
306 """
307 verb = 'save' if self.forward else 'read'
308 tc = test_case.TestCase()
309 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100310 dependencies = automatic_dependencies(
311 key.lifetime.string, key.type.string,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200312 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100313 )
314 dependencies = finish_family_dependencies(dependencies, key.bits)
315 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100316 tc.set_function('key_storage_' + verb)
317 if self.forward:
318 extra_arguments = []
319 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200320 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100321 # Some test keys have the RAW_DATA type and attributes that don't
322 # necessarily make sense. We do this to validate numerical
323 # encodings of the attributes.
324 # Raw data keys have no useful exercise anyway so there is no
325 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200326 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
327 flags.append('TEST_FLAG_EXERCISE')
328 if 'READ_ONLY' in key.lifetime.string:
329 flags.append('TEST_FLAG_READ_ONLY')
330 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100331 tc.set_arguments([key.lifetime.string,
332 key.type.string, str(key.bits),
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200333 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100334 '"' + key.material.hex() + '"',
335 '"' + key.hex() + '"',
336 *extra_arguments])
337 return tc
338
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200339 def key_for_lifetime(
340 self,
341 lifetime: str,
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200342 ) -> StorageTestData:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200343 """Construct a test key for the given lifetime."""
344 short = lifetime
345 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
346 r'', short)
347 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
348 description = 'lifetime: ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200349 key = StorageTestData(version=self.version,
350 id=1, lifetime=lifetime,
351 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
352 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
353 material=b'L',
354 description=description)
355 return key
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200356
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200357 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200358 """Generate test keys covering lifetimes."""
359 lifetimes = sorted(self.constructors.lifetimes)
360 expressions = self.constructors.generate_expressions(lifetimes)
361 for lifetime in expressions:
362 # Don't attempt to create or load a volatile key in storage
363 if 'VOLATILE' in lifetime:
364 continue
365 # Don't attempt to create a read-only key in storage,
366 # but do attempt to load one.
367 if 'READ_ONLY' in lifetime and self.forward:
368 continue
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200369 yield self.key_for_lifetime(lifetime)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200370
gabor-mezei-arm63857802021-06-29 15:39:56 +0200371 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100372 self,
373 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200374 short: Optional[str] = None,
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200375 test_implicit_usage: Optional[bool] = False
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200376 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100377 """Construct a test key for the given key usage."""
378 usage = ' | '.join(usage_flags) if usage_flags else '0'
379 if short is None:
380 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200381 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200382 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200383 key1 = StorageTestData(version=self.version,
384 id=1, lifetime=0x00000001,
385 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
386 expected_usage=usage,
387 usage=usage, alg=0, alg2=0,
388 material=b'K',
389 description=description)
390 yield key1
391
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200392 if test_implicit_usage:
393 description = 'usage without implication' + ': ' + short
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200394 key2 = StorageTestData(version=self.version,
395 id=1, lifetime=0x00000001,
396 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
397 without_implicit_usage=True,
398 usage=usage, alg=0, alg2=0,
399 material=b'K',
400 description=description)
401 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100402
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200403 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100404 """Generate test keys covering usage flags."""
405 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200406 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200407 for usage_flag in known_flags:
gabor-mezei-arm63857802021-06-29 15:39:56 +0200408 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200409 for flag1, flag2 in zip(known_flags,
410 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm63857802021-06-29 15:39:56 +0200411 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200412
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200413 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200414 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm63857802021-06-29 15:39:56 +0200415 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200416
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200417 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200418 yield from self.generate_keys_for_usage_flags()
419 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100420
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100421 def keys_for_type(
422 self,
423 key_type: str,
424 params: Optional[Iterable[str]] = None
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200425 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100426 """Generate test keys for the given key type.
427
428 For key types that depend on a parameter (e.g. elliptic curve family),
429 `param` is the parameter to pass to the constructor. Only a single
430 parameter is supported.
431 """
432 kt = crypto_knowledge.KeyType(key_type, params)
433 for bits in kt.sizes_to_test():
434 usage_flags = 'PSA_KEY_USAGE_EXPORT'
435 alg = 0
436 alg2 = 0
437 key_material = kt.key_material(bits)
438 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
439 r'',
440 kt.expression)
441 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200442 key = StorageTestData(version=self.version,
443 id=1, lifetime=0x00000001,
444 type=kt.expression, bits=bits,
445 usage=usage_flags, alg=alg, alg2=alg2,
446 material=key_material,
447 description=description)
448 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100449
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200450 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100451 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200452 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200453 for key_type in self.constructors.generate_expressions(key_types):
454 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100455
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200456 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100457 """Generate test keys for the specified algorithm."""
458 # For now, we don't have information on the compatibility of key
459 # types and algorithms. So we just test the encoding of algorithms,
460 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200461 descr = re.sub(r'PSA_ALG_', r'', alg)
462 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100463 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200464 key1 = StorageTestData(version=self.version,
465 id=1, lifetime=0x00000001,
466 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
467 usage=usage, alg=alg, alg2=0,
468 material=b'K',
469 description='alg: ' + descr)
470 yield key1
471 key2 = StorageTestData(version=self.version,
472 id=1, lifetime=0x00000001,
473 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
474 usage=usage, alg=0, alg2=alg,
475 material=b'L',
476 description='alg2: ' + descr)
477 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100478
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200479 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100480 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200481 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200482 for alg in self.constructors.generate_expressions(algorithms):
483 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100484
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200485 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200486 """Generate all keys for the test cases."""
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200487 yield from self.all_keys_for_lifetimes()
488 yield from self.all_keys_for_usage_flags()
489 yield from self.all_keys_for_types()
490 yield from self.all_keys_for_algorithms()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200491
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200492 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100493 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200494 # First build a list of all keys, then construct all the corresponding
495 # test cases. This allows all required information to be obtained in
496 # one go, which is a significant performance gain as the information
497 # includes numerical values obtained by compiling a C program.
Gilles Peskine45f2a402021-07-06 21:05:52 +0200498 all_keys = list(self.generate_all_keys())
499 for key in all_keys:
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200500 if key.location_value() != 0:
501 # Skip keys with a non-default location, because they
502 # require a driver and we currently have no mechanism to
503 # determine whether a driver is available.
504 continue
505 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100506
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200507class StorageFormatForward(StorageFormat):
508 """Storage format stability test cases for forward compatibility."""
509
510 def __init__(self, info: Information, version: int) -> None:
511 super().__init__(info, version, True)
512
513class StorageFormatV0(StorageFormat):
514 """Storage format stability test cases for version 0 compatibility."""
515
516 def __init__(self, info: Information) -> None:
517 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100518
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200519 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200520 """Generate test keys covering usage flags."""
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200521 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
522 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200523
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200524 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200525 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200526 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200527 alg: str,
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200528 key_type: crypto_knowledge.KeyType
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200529 ) -> StorageTestData:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200530 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200531 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200532 algorithm and key type combination.
533 """
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200534 bits = key_type.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200535 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200536 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200537 material_usage_flags = usage_flags + ' | ' + implyer_usage
538 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200539 alg2 = 0
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200540 key_material = key_type.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200541 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200542 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
543 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
544 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
545 r'',
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200546 key_type.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200547 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200548 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200549 key = StorageTestData(version=self.version,
550 id=1, lifetime=0x00000001,
551 type=key_type.expression, bits=bits,
552 usage=material_usage_flags,
553 expected_usage=expected_usage_flags,
554 without_implicit_usage=True,
555 alg=alg, alg2=alg2,
556 material=key_material,
557 description=description)
558 return key
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200559
560 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200561 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200562 """Match possible key types for sign algorithms."""
563 # To create a valid combinaton both the algorithms and key types
564 # must be filtered. Pair them with keywords created from its names.
565 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
566 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
567 keyword_translation = {
568 'ECDSA': 'ECC',
569 'ED[0-9]*.*' : 'EDWARDS'
570 }
571 exclusive_keywords = {
572 'EDWARDS': 'ECC'
573 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200574 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
575 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200576 alg_with_keys = {} #type: Dict[str, List[str]]
577 translation_table = str.maketrans('(', '_', ')')
578 for alg in algorithms:
579 # Generate keywords from the name of the algorithm
580 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
581 # Translate keywords for better matching with the key types
582 for keyword in alg_keywords.copy():
583 for pattern, replace in keyword_translation.items():
584 if re.match(pattern, keyword):
585 alg_keywords.remove(keyword)
586 alg_keywords.add(replace)
587 # Filter out incompatible algortihms
588 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
589 continue
590
591 for key_type in key_types:
592 # Generate keywords from the of the key type
593 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
594
595 # Remove ambigious keywords
596 for keyword1, keyword2 in exclusive_keywords.items():
597 if keyword1 in key_type_keywords:
598 key_type_keywords.remove(keyword2)
599
600 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
601 not key_type_keywords.isdisjoint(alg_keywords):
602 if alg in alg_with_keys:
603 alg_with_keys[alg].append(key_type)
604 else:
605 alg_with_keys[alg] = [key_type]
606 return alg_with_keys
607
gabor-mezei-arm2a499c02021-06-29 15:29:24 +0200608 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200609 """Generate test keys for usage flag extensions."""
610 # Generate a key type and algorithm pair for each extendable usage
611 # flag to generate a valid key for exercising. The key is generated
612 # without usage extension to check the extension compatiblity.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200613 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200614
gabor-mezei-arm340fbf32021-06-28 19:26:55 +0200615 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
616 for alg in sorted(alg_with_keys):
617 for key_type in sorted(alg_with_keys[alg]):
618 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200619 kt = crypto_knowledge.KeyType(key_type)
620 if kt.is_valid_for_signature(usage):
621 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200622
gabor-mezei-arm0c24edd2021-06-29 15:42:57 +0200623 def generate_all_keys(self) -> Iterator[StorageTestData]:
624 yield from super().generate_all_keys()
625 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200626
Gilles Peskineb94ea512021-03-10 02:12:08 +0100627class TestGenerator:
628 """Generate test data."""
629
630 def __init__(self, options) -> None:
631 self.test_suite_directory = self.get_option(options, 'directory',
632 'tests/suites')
633 self.info = Information()
634
635 @staticmethod
636 def get_option(options, name: str, default: T) -> T:
637 value = getattr(options, name, None)
638 return default if value is None else value
639
Gilles Peskine0298bda2021-03-10 02:34:37 +0100640 def filename_for(self, basename: str) -> str:
641 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200642 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100643
Gilles Peskineb94ea512021-03-10 02:12:08 +0100644 def write_test_data_file(self, basename: str,
645 test_cases: Iterable[test_case.TestCase]) -> None:
646 """Write the test cases to a .data file.
647
648 The output file is ``basename + '.data'`` in the test suite directory.
649 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100650 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100651 test_case.write_data_file(filename, test_cases)
652
Gilles Peskine0298bda2021-03-10 02:34:37 +0100653 TARGETS = {
654 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100655 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100656 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200657 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100658 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200659 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100660 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
661
662 def generate_target(self, name: str) -> None:
663 test_cases = self.TARGETS[name](self.info)
664 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100665
Gilles Peskine09940492021-01-26 22:16:30 +0100666def main(args):
667 """Command line entry point."""
668 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100669 parser.add_argument('--list', action='store_true',
670 help='List available targets and exit')
671 parser.add_argument('targets', nargs='*', metavar='TARGET',
672 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100673 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200674 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100675 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100676 if options.list:
677 for name in sorted(generator.TARGETS):
678 print(generator.filename_for(name))
679 return
680 if options.targets:
681 # Allow "-" as a special case so you can run
682 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
683 # ``$targets`` is empty or not.
684 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
685 for target in options.targets
686 if target != '-']
687 else:
688 options.targets = sorted(generator.TARGETS)
689 for target in options.targets:
690 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100691
692if __name__ == '__main__':
693 main(sys.argv[1:])