blob: 2be9dfc54bb3d6bf89a629d0d19ff8e13f171ab4 [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
Gilles Peskineb94ea512021-03-10 02:12:08 +0100130def test_case_for_key_type_not_supported(
131 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
145 tc.set_description('PSA {} {} {}-bit {} supported'
146 .format(verb, short_key_type, bits, adverb))
147 tc.set_dependencies(dependencies)
148 tc.set_function(verb + '_not_supported')
149 tc.set_arguments([key_type] + list(args))
150 return tc
151
152class NotSupported:
153 """Generate test cases for when something is not supported."""
154
155 def __init__(self, info: Information) -> None:
156 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100157
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100158 ALWAYS_SUPPORTED = frozenset([
159 'PSA_KEY_TYPE_DERIVE',
160 'PSA_KEY_TYPE_RAW_DATA',
161 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100162 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100163 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100164 kt: crypto_knowledge.KeyType,
165 param: Optional[int] = None,
166 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100167 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100168 """Return test cases exercising key creation when the given type is unsupported.
169
170 If param is present and not None, emit test cases conditioned on this
171 parameter not being supported. If it is absent or None, emit test cases
172 conditioned on the base type not being supported.
173 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100174 if kt.name in self.ALWAYS_SUPPORTED:
175 # Don't generate test cases for key types that are always supported.
176 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100177 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100178 import_dependencies = [('!' if param is None else '') +
179 psa_want_symbol(kt.name)]
180 if kt.params is not None:
181 import_dependencies += [('!' if param == i else '') +
182 psa_want_symbol(sym)
183 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100184 if kt.name.endswith('_PUBLIC_KEY'):
185 generate_dependencies = []
186 else:
187 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100188 for bits in kt.sizes_to_test():
Gilles Peskine3d778392021-02-17 15:11:05 +0100189 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100190 'import', kt.expression, bits,
191 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100192 test_case.hex_string(kt.key_material(bits)),
193 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100194 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100195 if not generate_dependencies and param is not None:
196 # If generation is impossible for this key type, rather than
197 # supported or not depending on implementation capabilities,
198 # only generate the test case once.
199 continue
Gilles Peskine3d778392021-02-17 15:11:05 +0100200 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100201 'generate', kt.expression, bits,
202 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100203 str(bits),
204 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100205 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100206 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100207
Gilles Peskineb93f8542021-04-19 13:50:25 +0200208 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
209 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
210
Gilles Peskine3d778392021-02-17 15:11:05 +0100211 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213 for key_type in sorted(self.constructors.key_types):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200214 if key_type in self.ECC_KEY_TYPES:
215 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100216 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100218 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskineb93f8542021-04-19 13:50:25 +0200219 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100220 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100221 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100222 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100225
226
Gilles Peskine897dff92021-03-10 15:03:44 +0100227class StorageKey(psa_storage.Key):
228 """Representation of a key for storage format testing."""
229
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200230 IMPLICIT_USAGE_FLAGS = {
231 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
232 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
233 } #type: Dict[str, str]
234 """Mapping of usage flags to the flags that they imply."""
235
236 def __init__(
237 self,
238 usage: str,
239 without_implicit_usage: Optional[bool] = False,
240 **kwargs
241 ) -> None:
242 """Prepare to generate a key.
243
244 * `usage` : The usage flags used for the key.
245 * `without_implicit_usage`: Flag to defide to apply the usage extension
246 """
247 super().__init__(usage=usage,**kwargs)
248
249 if not without_implicit_usage:
250 for flag, implicit in self.IMPLICIT_USAGE_FLAGS.items():
251 if self.usage.value() & psa_storage.Expr(flag).value() and \
252 self.usage.value() & psa_storage.Expr(implicit).value() == 0:
253 self.usage = psa_storage.Expr(self.usage.string + ' | ' + implicit)
254
255class StorageTestData(StorageKey):
256 """Representation of test case data for storage format testing."""
257
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200258 def __init__(
259 self,
260 description: str,
261 expected_usage: Optional[str] = None,
262 **kwargs
263 ) -> None:
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200264 """Prepare to generate test data
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200265
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200266 * `description` : used for the the test case names
267 * `expected_usage`: the usage flags generated as the expected usage flags
268 in the test cases. CAn differ from the usage flags
269 stored in the keys because of the usage flags extension.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200270 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100271 super().__init__(**kwargs)
272 self.description = description #type: str
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200273 self.expected_usage = expected_usage if expected_usage else self.usage.string #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200274
Gilles Peskine897dff92021-03-10 15:03:44 +0100275class StorageFormat:
276 """Storage format stability test cases."""
277
278 def __init__(self, info: Information, version: int, forward: bool) -> None:
279 """Prepare to generate test cases for storage format stability.
280
281 * `info`: information about the API. See the `Information` class.
282 * `version`: the storage format version to generate test cases for.
283 * `forward`: if true, generate forward compatibility test cases which
284 save a key and check that its representation is as intended. Otherwise
285 generate backward compatibility test cases which inject a key
286 representation and check that it can be read and used.
287 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200288 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
289 self.version = version #type: int
290 self.forward = forward #type: bool
Gilles Peskine897dff92021-03-10 15:03:44 +0100291
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200292 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
Gilles Peskine897dff92021-03-10 15:03:44 +0100293 """Construct a storage format test case for the given key.
294
295 If ``forward`` is true, generate a forward compatibility test case:
296 create a key and validate that it has the expected representation.
297 Otherwise generate a backward compatibility test case: inject the
298 key representation into storage and validate that it can be read
299 correctly.
300 """
301 verb = 'save' if self.forward else 'read'
302 tc = test_case.TestCase()
303 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100304 dependencies = automatic_dependencies(
305 key.lifetime.string, key.type.string,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200306 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100307 )
308 dependencies = finish_family_dependencies(dependencies, key.bits)
309 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100310 tc.set_function('key_storage_' + verb)
311 if self.forward:
312 extra_arguments = []
313 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200314 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100315 # Some test keys have the RAW_DATA type and attributes that don't
316 # necessarily make sense. We do this to validate numerical
317 # encodings of the attributes.
318 # Raw data keys have no useful exercise anyway so there is no
319 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200320 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
321 flags.append('TEST_FLAG_EXERCISE')
322 if 'READ_ONLY' in key.lifetime.string:
323 flags.append('TEST_FLAG_READ_ONLY')
324 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100325 tc.set_arguments([key.lifetime.string,
326 key.type.string, str(key.bits),
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200327 key.expected_usage, key.alg.string, key.alg2.string,
Gilles Peskine897dff92021-03-10 15:03:44 +0100328 '"' + key.material.hex() + '"',
329 '"' + key.hex() + '"',
330 *extra_arguments])
331 return tc
332
Gilles Peskineefb584d2021-04-21 22:05:34 +0200333 def key_for_lifetime(
334 self,
335 lifetime: str,
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200336 ) -> StorageTestData:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200337 """Construct a test key for the given lifetime."""
338 short = lifetime
339 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
340 r'', short)
341 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
342 description = 'lifetime: ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200343 key = StorageTestData(version=self.version,
344 id=1, lifetime=lifetime,
345 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
346 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
347 material=b'L',
348 description=description)
349 return key
Gilles Peskineefb584d2021-04-21 22:05:34 +0200350
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200351 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200352 """Generate test keys covering lifetimes."""
353 lifetimes = sorted(self.constructors.lifetimes)
354 expressions = self.constructors.generate_expressions(lifetimes)
355 for lifetime in expressions:
356 # Don't attempt to create or load a volatile key in storage
357 if 'VOLATILE' in lifetime:
358 continue
359 # Don't attempt to create a read-only key in storage,
360 # but do attempt to load one.
361 if 'READ_ONLY' in lifetime and self.forward:
362 continue
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200363 yield self.key_for_lifetime(lifetime)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200364
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200365 def keys_for_usage_flags(
Gilles Peskine897dff92021-03-10 15:03:44 +0100366 self,
367 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200368 short: Optional[str] = None,
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200369 test_implicit_usage: Optional[bool] = False
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200370 ) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100371 """Construct a test key for the given key usage."""
372 usage = ' | '.join(usage_flags) if usage_flags else '0'
373 if short is None:
374 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200375 extra_desc = ' with implication' if test_implicit_usage else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200376 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200377 key1 = StorageTestData(version=self.version,
378 id=1, lifetime=0x00000001,
379 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
380 expected_usage=usage,
381 usage=usage, alg=0, alg2=0,
382 material=b'K',
383 description=description)
384 yield key1
385
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200386 if test_implicit_usage:
387 description = 'usage without implication' + ': ' + short
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200388 key2 = StorageTestData(version=self.version,
389 id=1, lifetime=0x00000001,
390 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
391 without_implicit_usage=True,
392 usage=usage, alg=0, alg2=0,
393 material=b'K',
394 description=description)
395 yield key2
Gilles Peskine897dff92021-03-10 15:03:44 +0100396
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200397 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100398 """Generate test keys covering usage flags."""
399 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200400 yield from self.keys_for_usage_flags(['0'], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200401 for usage_flag in known_flags:
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200402 yield from self.keys_for_usage_flags([usage_flag], **kwargs)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200403 for flag1, flag2 in zip(known_flags,
404 known_flags[1:] + [known_flags[0]]):
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200405 yield from self.keys_for_usage_flags([flag1, flag2], **kwargs)
gabor-mezei-armbce85272021-06-24 14:38:51 +0200406
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200407 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-armbce85272021-06-24 14:38:51 +0200408 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm912eca32021-06-29 15:39:56 +0200409 yield from self.keys_for_usage_flags(known_flags, short='all known')
gabor-mezei-armbce85272021-06-24 14:38:51 +0200410
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200411 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200412 yield from self.generate_keys_for_usage_flags()
413 yield from self.generate_key_for_all_usage_flags()
Gilles Peskine897dff92021-03-10 15:03:44 +0100414
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100415 def keys_for_type(
416 self,
417 key_type: str,
418 params: Optional[Iterable[str]] = None
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200419 ) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100420 """Generate test keys for the given key type.
421
422 For key types that depend on a parameter (e.g. elliptic curve family),
423 `param` is the parameter to pass to the constructor. Only a single
424 parameter is supported.
425 """
426 kt = crypto_knowledge.KeyType(key_type, params)
427 for bits in kt.sizes_to_test():
428 usage_flags = 'PSA_KEY_USAGE_EXPORT'
429 alg = 0
430 alg2 = 0
431 key_material = kt.key_material(bits)
432 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
433 r'',
434 kt.expression)
435 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200436 key = StorageTestData(version=self.version,
437 id=1, lifetime=0x00000001,
438 type=kt.expression, bits=bits,
439 usage=usage_flags, alg=alg, alg2=alg2,
440 material=key_material,
441 description=description)
442 yield key
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100443
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200444 def all_keys_for_types(self) -> Iterator[StorageTestData]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100445 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200446 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200447 for key_type in self.constructors.generate_expressions(key_types):
448 yield from self.keys_for_type(key_type)
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100449
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200450 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100451 """Generate test keys for the specified algorithm."""
452 # For now, we don't have information on the compatibility of key
453 # types and algorithms. So we just test the encoding of algorithms,
454 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200455 descr = re.sub(r'PSA_ALG_', r'', alg)
456 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100457 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200458 key1 = StorageTestData(version=self.version,
459 id=1, lifetime=0x00000001,
460 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
461 usage=usage, alg=alg, alg2=0,
462 material=b'K',
463 description='alg: ' + descr)
464 yield key1
465 key2 = StorageTestData(version=self.version,
466 id=1, lifetime=0x00000001,
467 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
468 usage=usage, alg=0, alg2=alg,
469 material=b'L',
470 description='alg2: ' + descr)
471 yield key2
Gilles Peskined86bc522021-03-10 15:08:57 +0100472
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200473 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100474 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200475 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200476 for alg in self.constructors.generate_expressions(algorithms):
477 yield from self.keys_for_algorithm(alg)
Gilles Peskined86bc522021-03-10 15:08:57 +0100478
gabor-mezei-armea840de2021-06-29 15:42:57 +0200479 def generate_all_keys(self) -> Iterator[StorageTestData]:
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200480 """Generate all keys for the test cases."""
gabor-mezei-armea840de2021-06-29 15:42:57 +0200481 yield from self.all_keys_for_lifetimes()
482 yield from self.all_keys_for_usage_flags()
483 yield from self.all_keys_for_types()
484 yield from self.all_keys_for_algorithms()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200485
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200486 def all_test_cases(self) -> Iterator[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100487 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200488 # First build a list of all keys, then construct all the corresponding
489 # test cases. This allows all required information to be obtained in
490 # one go, which is a significant performance gain as the information
491 # includes numerical values obtained by compiling a C program.
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200492 for key in self.generate_all_keys():
493 if key.location_value() != 0:
494 # Skip keys with a non-default location, because they
495 # require a driver and we currently have no mechanism to
496 # determine whether a driver is available.
497 continue
498 yield self.make_test_case(key)
Gilles Peskine897dff92021-03-10 15:03:44 +0100499
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200500class StorageFormatForward(StorageFormat):
501 """Storage format stability test cases for forward compatibility."""
502
503 def __init__(self, info: Information, version: int) -> None:
504 super().__init__(info, version, True)
505
506class StorageFormatV0(StorageFormat):
507 """Storage format stability test cases for version 0 compatibility."""
508
509 def __init__(self, info: Information) -> None:
510 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100511
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200512 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200513 """Generate test keys covering usage flags."""
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200514 yield from self.generate_keys_for_usage_flags(test_implicit_usage=True)
515 yield from self.generate_key_for_all_usage_flags()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200516
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200517 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200518 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200519 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200520 alg: str,
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200521 key_type: crypto_knowledge.KeyType
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200522 ) -> StorageTestData:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200523 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200524 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200525 algorithm and key type combination.
526 """
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200527 bits = key_type.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200528 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200529 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200530 material_usage_flags = usage_flags + ' | ' + implyer_usage
531 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200532 alg2 = 0
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200533 key_material = key_type.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200534 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200535 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
536 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
537 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
538 r'',
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200539 key_type.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200540 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200541 usage_expression, alg_expression, key_type_expression, bits)
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200542 key = StorageTestData(version=self.version,
543 id=1, lifetime=0x00000001,
544 type=key_type.expression, bits=bits,
545 usage=material_usage_flags,
546 expected_usage=expected_usage_flags,
547 without_implicit_usage=True,
548 alg=alg, alg2=alg2,
549 material=key_material,
550 description=description)
551 return key
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200552
553 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200554 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200555 """Match possible key types for sign algorithms."""
556 # To create a valid combinaton both the algorithms and key types
557 # must be filtered. Pair them with keywords created from its names.
558 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
559 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
560 keyword_translation = {
561 'ECDSA': 'ECC',
562 'ED[0-9]*.*' : 'EDWARDS'
563 }
564 exclusive_keywords = {
565 'EDWARDS': 'ECC'
566 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200567 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
568 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200569 alg_with_keys = {} #type: Dict[str, List[str]]
570 translation_table = str.maketrans('(', '_', ')')
571 for alg in algorithms:
572 # Generate keywords from the name of the algorithm
573 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
574 # Translate keywords for better matching with the key types
575 for keyword in alg_keywords.copy():
576 for pattern, replace in keyword_translation.items():
577 if re.match(pattern, keyword):
578 alg_keywords.remove(keyword)
579 alg_keywords.add(replace)
580 # Filter out incompatible algortihms
581 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
582 continue
583
584 for key_type in key_types:
585 # Generate keywords from the of the key type
586 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
587
588 # Remove ambigious keywords
589 for keyword1, keyword2 in exclusive_keywords.items():
590 if keyword1 in key_type_keywords:
591 key_type_keywords.remove(keyword2)
592
593 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
594 not key_type_keywords.isdisjoint(alg_keywords):
595 if alg in alg_with_keys:
596 alg_with_keys[alg].append(key_type)
597 else:
598 alg_with_keys[alg] = [key_type]
599 return alg_with_keys
600
gabor-mezei-arme4b74992021-06-29 15:29:24 +0200601 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200602 """Generate test keys for usage flag extensions."""
603 # Generate a key type and algorithm pair for each extendable usage
604 # flag to generate a valid key for exercising. The key is generated
605 # without usage extension to check the extension compatiblity.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200606 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200607
gabor-mezei-arm5ea30372021-06-28 19:26:55 +0200608 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
609 for alg in sorted(alg_with_keys):
610 for key_type in sorted(alg_with_keys[alg]):
611 # The key types must be filtered to fit the specific usage flag.
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200612 kt = crypto_knowledge.KeyType(key_type)
613 if kt.is_valid_for_signature(usage):
614 yield self.keys_for_implicit_usage(usage, alg, kt)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200615
gabor-mezei-armea840de2021-06-29 15:42:57 +0200616 def generate_all_keys(self) -> Iterator[StorageTestData]:
617 yield from super().generate_all_keys()
618 yield from self.all_keys_for_implicit_usage()
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200619
Gilles Peskineb94ea512021-03-10 02:12:08 +0100620class TestGenerator:
621 """Generate test data."""
622
623 def __init__(self, options) -> None:
624 self.test_suite_directory = self.get_option(options, 'directory',
625 'tests/suites')
626 self.info = Information()
627
628 @staticmethod
629 def get_option(options, name: str, default: T) -> T:
630 value = getattr(options, name, None)
631 return default if value is None else value
632
Gilles Peskine0298bda2021-03-10 02:34:37 +0100633 def filename_for(self, basename: str) -> str:
634 """The location of the data file with the specified base name."""
635 return os.path.join(self.test_suite_directory, basename + '.data')
636
Gilles Peskineb94ea512021-03-10 02:12:08 +0100637 def write_test_data_file(self, basename: str,
638 test_cases: Iterable[test_case.TestCase]) -> None:
639 """Write the test cases to a .data file.
640
641 The output file is ``basename + '.data'`` in the test suite directory.
642 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100643 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100644 test_case.write_data_file(filename, test_cases)
645
Gilles Peskine0298bda2021-03-10 02:34:37 +0100646 TARGETS = {
647 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100648 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100649 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200650 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100651 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200652 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100653 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
654
655 def generate_target(self, name: str) -> None:
656 test_cases = self.TARGETS[name](self.info)
657 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100658
Gilles Peskine09940492021-01-26 22:16:30 +0100659def main(args):
660 """Command line entry point."""
661 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100662 parser.add_argument('--list', action='store_true',
663 help='List available targets and exit')
664 parser.add_argument('targets', nargs='*', metavar='TARGET',
665 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100666 options = parser.parse_args(args)
667 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100668 if options.list:
669 for name in sorted(generator.TARGETS):
670 print(generator.filename_for(name))
671 return
672 if options.targets:
673 # Allow "-" as a special case so you can run
674 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
675 # ``$targets`` is empty or not.
676 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
677 for target in options.targets
678 if target != '-']
679 else:
680 options.targets = sorted(generator.TARGETS)
681 for target in options.targets:
682 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100683
684if __name__ == '__main__':
685 main(sys.argv[1:])