blob: da15d84ec00f1e4996a6051fd6231b8d8d7fa537 [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
230 def __init__(self, *, description: str, **kwargs) -> None:
231 super().__init__(**kwargs)
232 self.description = description #type: str
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200233 self.usage = self.original_usage #type: psa_storage.Expr
234
gabor-mezei-arm09960802021-06-24 09:38:21 +0200235class StorageKeyBuilder:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200236 def __init__(self, usage_extension: bool) -> None:
237 self.usage_extension = usage_extension #type: bool
gabor-mezei-arm09960802021-06-24 09:38:21 +0200238
239 def build(self, **kwargs) -> StorageKey:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200240 return StorageKey(usage_extension = self.usage_extension, **kwargs)
Gilles Peskine897dff92021-03-10 15:03:44 +0100241
242class StorageFormat:
243 """Storage format stability test cases."""
244
245 def __init__(self, info: Information, version: int, forward: bool) -> None:
246 """Prepare to generate test cases for storage format stability.
247
248 * `info`: information about the API. See the `Information` class.
249 * `version`: the storage format version to generate test cases for.
250 * `forward`: if true, generate forward compatibility test cases which
251 save a key and check that its representation is as intended. Otherwise
252 generate backward compatibility test cases which inject a key
253 representation and check that it can be read and used.
254 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200255 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
256 self.version = version #type: int
257 self.forward = forward #type: bool
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200258 self.key_builder = StorageKeyBuilder(usage_extension = True) #type: StorageKeyBuilder
Gilles Peskine897dff92021-03-10 15:03:44 +0100259
260 def make_test_case(self, key: StorageKey) -> test_case.TestCase:
261 """Construct a storage format test case for the given key.
262
263 If ``forward`` is true, generate a forward compatibility test case:
264 create a key and validate that it has the expected representation.
265 Otherwise generate a backward compatibility test case: inject the
266 key representation into storage and validate that it can be read
267 correctly.
268 """
269 verb = 'save' if self.forward else 'read'
270 tc = test_case.TestCase()
271 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100272 dependencies = automatic_dependencies(
273 key.lifetime.string, key.type.string,
274 key.usage.string, key.alg.string, key.alg2.string,
275 )
276 dependencies = finish_family_dependencies(dependencies, key.bits)
277 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100278 tc.set_function('key_storage_' + verb)
279 if self.forward:
280 extra_arguments = []
281 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200282 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100283 # Some test keys have the RAW_DATA type and attributes that don't
284 # necessarily make sense. We do this to validate numerical
285 # encodings of the attributes.
286 # Raw data keys have no useful exercise anyway so there is no
287 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200288 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
289 flags.append('TEST_FLAG_EXERCISE')
290 if 'READ_ONLY' in key.lifetime.string:
291 flags.append('TEST_FLAG_READ_ONLY')
292 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100293 tc.set_arguments([key.lifetime.string,
294 key.type.string, str(key.bits),
295 key.usage.string, key.alg.string, key.alg2.string,
296 '"' + key.material.hex() + '"',
297 '"' + key.hex() + '"',
298 *extra_arguments])
299 return tc
300
Gilles Peskineefb584d2021-04-21 22:05:34 +0200301 def key_for_lifetime(
302 self,
303 lifetime: str,
304 ) -> StorageKey:
305 """Construct a test key for the given lifetime."""
306 short = lifetime
307 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
308 r'', short)
309 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
310 description = 'lifetime: ' + short
gabor-mezei-arm09960802021-06-24 09:38:21 +0200311 key = self.key_builder.build(
312 version=self.version,
313 id=1, lifetime=lifetime,
314 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
315 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
316 material=b'L',
317 description=description)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200318 return key
319
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200320 def all_keys_for_lifetimes(self) -> List[StorageKey]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200321 """Generate test keys covering lifetimes."""
322 lifetimes = sorted(self.constructors.lifetimes)
323 expressions = self.constructors.generate_expressions(lifetimes)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200324 keys = [] #type List[StorageKey]
Gilles Peskineefb584d2021-04-21 22:05:34 +0200325 for lifetime in expressions:
326 # Don't attempt to create or load a volatile key in storage
327 if 'VOLATILE' in lifetime:
328 continue
329 # Don't attempt to create a read-only key in storage,
330 # but do attempt to load one.
331 if 'READ_ONLY' in lifetime and self.forward:
332 continue
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200333 keys.append(self.key_for_lifetime(lifetime))
334 return keys
Gilles Peskineefb584d2021-04-21 22:05:34 +0200335
Gilles Peskine897dff92021-03-10 15:03:44 +0100336 def key_for_usage_flags(
337 self,
338 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200339 short: Optional[str] = None,
340 extra_desc: Optional[str] = None
Gilles Peskine897dff92021-03-10 15:03:44 +0100341 ) -> StorageKey:
342 """Construct a test key for the given key usage."""
343 usage = ' | '.join(usage_flags) if usage_flags else '0'
344 if short is None:
345 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200346 extra_desc = ' ' + extra_desc if extra_desc is not None and len(extra_desc) > 0 else ''
347 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm09960802021-06-24 09:38:21 +0200348 return self.key_builder.build(version=self.version,
349 id=1, lifetime=0x00000001,
350 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
351 usage=usage, alg=0, alg2=0,
352 material=b'K',
353 description=description)
Gilles Peskine897dff92021-03-10 15:03:44 +0100354
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200355 def all_keys_for_usage_flags(
356 self,
357 extra_desc: Optional[str] = None
358 ) -> List[StorageKey]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100359 """Generate test keys covering usage flags."""
360 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200361 keys = [] #type List[StorageKey]
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200362 keys.append(self.key_for_usage_flags(['0'], extra_desc=extra_desc))
363 keys += [self.key_for_usage_flags([usage_flag], extra_desc=extra_desc)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200364 for usage_flag in known_flags]
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200365 keys += [self.key_for_usage_flags([flag1, flag2], extra_desc=extra_desc)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200366 for flag1, flag2 in zip(known_flags,
367 known_flags[1:] + [known_flags[0]])]
368 keys.append(self.key_for_usage_flags(known_flags, short='all known'))
369 return keys
Gilles Peskine897dff92021-03-10 15:03:44 +0100370
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100371 def keys_for_type(
372 self,
373 key_type: str,
374 params: Optional[Iterable[str]] = None
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200375 ) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100376 """Generate test keys for the given key type.
377
378 For key types that depend on a parameter (e.g. elliptic curve family),
379 `param` is the parameter to pass to the constructor. Only a single
380 parameter is supported.
381 """
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200382 keys = [] #type: List[StorageKey]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100383 kt = crypto_knowledge.KeyType(key_type, params)
384 for bits in kt.sizes_to_test():
385 usage_flags = 'PSA_KEY_USAGE_EXPORT'
386 alg = 0
387 alg2 = 0
388 key_material = kt.key_material(bits)
389 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
390 r'',
391 kt.expression)
392 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm09960802021-06-24 09:38:21 +0200393 keys.append(self.key_builder.build(
394 version=self.version,
395 id=1, lifetime=0x00000001,
396 type=kt.expression, bits=bits,
397 usage=usage_flags, alg=alg, alg2=alg2,
398 material=key_material,
399 description=description))
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200400 return keys
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100401
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200402 def all_keys_for_types(self) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100403 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200404 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200405 return [key for key_type in self.constructors.generate_expressions(key_types)
406 for key in self.keys_for_type(key_type)]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100407
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200408 def keys_for_algorithm(self, alg: str) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100409 """Generate test keys for the specified algorithm."""
410 # For now, we don't have information on the compatibility of key
411 # types and algorithms. So we just test the encoding of algorithms,
412 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200413 descr = re.sub(r'PSA_ALG_', r'', alg)
414 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100415 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm09960802021-06-24 09:38:21 +0200416 key1 = self.key_builder.build(version=self.version,
417 id=1, lifetime=0x00000001,
418 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
419 usage=usage, alg=alg, alg2=0,
420 material=b'K',
421 description='alg: ' + descr)
422 key2 = self.key_builder.build(version=self.version,
423 id=1, lifetime=0x00000001,
424 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
425 usage=usage, alg=0, alg2=alg,
426 material=b'L',
427 description='alg2: ' + descr)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200428 return [key1, key2]
Gilles Peskined86bc522021-03-10 15:08:57 +0100429
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200430 def all_keys_for_algorithms(self) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100431 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200432 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200433 return [key for alg in self.constructors.generate_expressions(algorithms)
434 for key in self.keys_for_algorithm(alg)]
Gilles Peskined86bc522021-03-10 15:08:57 +0100435
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200436 def generate_all_keys(self) -> List[StorageKey]:
437 """Generate all keys for the test cases."""
438 keys = [] #type: List[StorageKey]
439 keys += self.all_keys_for_lifetimes()
440 keys += self.all_keys_for_usage_flags()
441 keys += self.all_keys_for_types()
442 keys += self.all_keys_for_algorithms()
443 return keys
444
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200445 def all_test_cases(self) -> List[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100446 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200447 # First build a list of all keys, then construct all the corresponding
448 # test cases. This allows all required information to be obtained in
449 # one go, which is a significant performance gain as the information
450 # includes numerical values obtained by compiling a C program.
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200451 generated_keys = self.generate_all_keys()
452
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200453 # Skip keys with a non-default location, because they
454 # require a driver and we currently have no mechanism to
455 # determine whether a driver is available.
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200456 keys = filter(lambda key: key.location_value() == 0, generated_keys)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200457
458 return [self.make_test_case(key) for key in keys]
Gilles Peskine897dff92021-03-10 15:03:44 +0100459
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200460class StorageFormatForward(StorageFormat):
461 """Storage format stability test cases for forward compatibility."""
462
463 def __init__(self, info: Information, version: int) -> None:
464 super().__init__(info, version, True)
465
466class StorageFormatV0(StorageFormat):
467 """Storage format stability test cases for version 0 compatibility."""
468
469 def __init__(self, info: Information) -> None:
470 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100471
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200472 def all_keys_for_usage_flags(self) -> List[StorageKey]:
473 """Generate test keys covering usage flags."""
474 # First generate keys without usage policy extension for
475 # compatibility testing, then generate the keys with extension
476 # to check the extension is working.
477 keys = [] #type: List[StorageKey]
478 prev_builder = self.key_builder
479
480 self.key_builder = StorageKeyBuilder(usage_extension = False)
481 keys += super().all_keys_for_usage_flags(extra_desc = 'without extension')
482
483 self.key_builder = StorageKeyBuilder(usage_extension = True)
484 keys += super().all_keys_for_usage_flags(extra_desc = 'with extension')
485
486 self.key_builder = prev_builder
487 return keys
488
489
Gilles Peskineb94ea512021-03-10 02:12:08 +0100490class TestGenerator:
491 """Generate test data."""
492
493 def __init__(self, options) -> None:
494 self.test_suite_directory = self.get_option(options, 'directory',
495 'tests/suites')
496 self.info = Information()
497
498 @staticmethod
499 def get_option(options, name: str, default: T) -> T:
500 value = getattr(options, name, None)
501 return default if value is None else value
502
Gilles Peskine0298bda2021-03-10 02:34:37 +0100503 def filename_for(self, basename: str) -> str:
504 """The location of the data file with the specified base name."""
505 return os.path.join(self.test_suite_directory, basename + '.data')
506
Gilles Peskineb94ea512021-03-10 02:12:08 +0100507 def write_test_data_file(self, basename: str,
508 test_cases: Iterable[test_case.TestCase]) -> None:
509 """Write the test cases to a .data file.
510
511 The output file is ``basename + '.data'`` in the test suite directory.
512 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100513 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100514 test_case.write_data_file(filename, test_cases)
515
Gilles Peskine0298bda2021-03-10 02:34:37 +0100516 TARGETS = {
517 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100518 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100519 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200520 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100521 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200522 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100523 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
524
525 def generate_target(self, name: str) -> None:
526 test_cases = self.TARGETS[name](self.info)
527 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100528
Gilles Peskine09940492021-01-26 22:16:30 +0100529def main(args):
530 """Command line entry point."""
531 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100532 parser.add_argument('--list', action='store_true',
533 help='List available targets and exit')
534 parser.add_argument('targets', nargs='*', metavar='TARGET',
535 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100536 options = parser.parse_args(args)
537 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100538 if options.list:
539 for name in sorted(generator.TARGETS):
540 print(generator.filename_for(name))
541 return
542 if options.targets:
543 # Allow "-" as a special case so you can run
544 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
545 # ``$targets`` is empty or not.
546 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
547 for target in options.targets
548 if target != '-']
549 else:
550 options.targets = sorted(generator.TARGETS)
551 for target in options.targets:
552 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100553
554if __name__ == '__main__':
555 main(sys.argv[1:])