blob: 08cb2f0fab0ec851a853b452042bf3413a7f49bd [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-arm044fefc2021-06-24 10:16:44 +0200230 def __init__(
231 self,
232 description: str,
233 expected_usage: Optional[str] = None,
234 **kwargs
235 ) -> None:
236 """Prepare to generate a key.
237
238 * `description`: used for the the test case names
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200239 * `implicit_usage`: the usage flags generated as the expected usage
240 flags in the test cases. When testing implicit
241 usage flags, they can differ in the generated keys
242 and the expected usage flags in the test cases.
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200243 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100244 super().__init__(**kwargs)
245 self.description = description #type: str
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200246 self.usage = psa_storage.as_expr(expected_usage) if expected_usage is not None else\
247 self.original_usage #type: psa_storage.Expr
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200248
gabor-mezei-arm09960802021-06-24 09:38:21 +0200249class StorageKeyBuilder:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200250 # pylint: disable=too-few-public-methods
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200251 def __init__(self, usage_extension: bool) -> None:
252 self.usage_extension = usage_extension #type: bool
gabor-mezei-arm09960802021-06-24 09:38:21 +0200253
254 def build(self, **kwargs) -> StorageKey:
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200255 return StorageKey(implicit_usage=self.usage_extension, **kwargs)
Gilles Peskine897dff92021-03-10 15:03:44 +0100256
257class StorageFormat:
258 """Storage format stability test cases."""
259
260 def __init__(self, info: Information, version: int, forward: bool) -> None:
261 """Prepare to generate test cases for storage format stability.
262
263 * `info`: information about the API. See the `Information` class.
264 * `version`: the storage format version to generate test cases for.
265 * `forward`: if true, generate forward compatibility test cases which
266 save a key and check that its representation is as intended. Otherwise
267 generate backward compatibility test cases which inject a key
268 representation and check that it can be read and used.
269 """
gabor-mezei-arm0bdb84e2021-06-23 17:01:44 +0200270 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
271 self.version = version #type: int
272 self.forward = forward #type: bool
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200273 self.key_builder = StorageKeyBuilder(usage_extension=True) #type: StorageKeyBuilder
Gilles Peskine897dff92021-03-10 15:03:44 +0100274
275 def make_test_case(self, key: StorageKey) -> test_case.TestCase:
276 """Construct a storage format test case for the given key.
277
278 If ``forward`` is true, generate a forward compatibility test case:
279 create a key and validate that it has the expected representation.
280 Otherwise generate a backward compatibility test case: inject the
281 key representation into storage and validate that it can be read
282 correctly.
283 """
284 verb = 'save' if self.forward else 'read'
285 tc = test_case.TestCase()
286 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100287 dependencies = automatic_dependencies(
288 key.lifetime.string, key.type.string,
289 key.usage.string, key.alg.string, key.alg2.string,
290 )
291 dependencies = finish_family_dependencies(dependencies, key.bits)
292 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100293 tc.set_function('key_storage_' + verb)
294 if self.forward:
295 extra_arguments = []
296 else:
Gilles Peskine643eb832021-04-21 20:11:33 +0200297 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100298 # Some test keys have the RAW_DATA type and attributes that don't
299 # necessarily make sense. We do this to validate numerical
300 # encodings of the attributes.
301 # Raw data keys have no useful exercise anyway so there is no
302 # loss of test coverage.
Gilles Peskine643eb832021-04-21 20:11:33 +0200303 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
304 flags.append('TEST_FLAG_EXERCISE')
305 if 'READ_ONLY' in key.lifetime.string:
306 flags.append('TEST_FLAG_READ_ONLY')
307 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100308 tc.set_arguments([key.lifetime.string,
309 key.type.string, str(key.bits),
310 key.usage.string, key.alg.string, key.alg2.string,
311 '"' + key.material.hex() + '"',
312 '"' + key.hex() + '"',
313 *extra_arguments])
314 return tc
315
Gilles Peskineefb584d2021-04-21 22:05:34 +0200316 def key_for_lifetime(
317 self,
318 lifetime: str,
319 ) -> StorageKey:
320 """Construct a test key for the given lifetime."""
321 short = lifetime
322 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
323 r'', short)
324 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
325 description = 'lifetime: ' + short
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200326 key = self.key_builder.build(version=self.version,
327 id=1, lifetime=lifetime,
328 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
329 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
330 material=b'L',
331 description=description)
Gilles Peskineefb584d2021-04-21 22:05:34 +0200332 return key
333
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200334 def all_keys_for_lifetimes(self) -> List[StorageKey]:
Gilles Peskineefb584d2021-04-21 22:05:34 +0200335 """Generate test keys covering lifetimes."""
336 lifetimes = sorted(self.constructors.lifetimes)
337 expressions = self.constructors.generate_expressions(lifetimes)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200338 keys = [] #type List[StorageKey]
Gilles Peskineefb584d2021-04-21 22:05:34 +0200339 for lifetime in expressions:
340 # Don't attempt to create or load a volatile key in storage
341 if 'VOLATILE' in lifetime:
342 continue
343 # Don't attempt to create a read-only key in storage,
344 # but do attempt to load one.
345 if 'READ_ONLY' in lifetime and self.forward:
346 continue
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200347 keys.append(self.key_for_lifetime(lifetime))
348 return keys
Gilles Peskineefb584d2021-04-21 22:05:34 +0200349
Gilles Peskine897dff92021-03-10 15:03:44 +0100350 def key_for_usage_flags(
351 self,
352 usage_flags: List[str],
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200353 short: Optional[str] = None,
354 extra_desc: Optional[str] = None
Gilles Peskine897dff92021-03-10 15:03:44 +0100355 ) -> StorageKey:
356 """Construct a test key for the given key usage."""
357 usage = ' | '.join(usage_flags) if usage_flags else '0'
358 if short is None:
359 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm705c4522021-06-28 15:09:02 +0200360 extra_desc = ' ' + extra_desc if extra_desc else ''
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200361 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm09960802021-06-24 09:38:21 +0200362 return self.key_builder.build(version=self.version,
363 id=1, lifetime=0x00000001,
364 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
365 usage=usage, alg=0, alg2=0,
366 material=b'K',
367 description=description)
Gilles Peskine897dff92021-03-10 15:03:44 +0100368
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200369 def generate_keys_for_usage_flags(
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200370 self,
371 extra_desc: Optional[str] = None
372 ) -> List[StorageKey]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100373 """Generate test keys covering usage flags."""
374 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200375 keys = [] #type List[StorageKey]
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200376 keys.append(self.key_for_usage_flags(['0'], extra_desc=extra_desc))
377 keys += [self.key_for_usage_flags([usage_flag], extra_desc=extra_desc)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200378 for usage_flag in known_flags]
gabor-mezei-armd71659f2021-06-24 09:42:02 +0200379 keys += [self.key_for_usage_flags([flag1, flag2], extra_desc=extra_desc)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200380 for flag1, flag2 in zip(known_flags,
381 known_flags[1:] + [known_flags[0]])]
gabor-mezei-armbce85272021-06-24 14:38:51 +0200382 return keys
383
384 def generate_key_for_all_usage_flags(self) -> StorageKey:
385 known_flags = sorted(self.constructors.key_usage_flags)
386 return self.key_for_usage_flags(known_flags, short='all known')
387
388 def all_keys_for_usage_flags(
389 self,
390 extra_desc: Optional[str] = None
391 ) -> List[StorageKey]:
392 keys = self.generate_keys_for_usage_flags(extra_desc=extra_desc)
393 keys.append(self.generate_key_for_all_usage_flags())
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200394 return keys
Gilles Peskine897dff92021-03-10 15:03:44 +0100395
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100396 def keys_for_type(
397 self,
398 key_type: str,
399 params: Optional[Iterable[str]] = None
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200400 ) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100401 """Generate test keys for the given key type.
402
403 For key types that depend on a parameter (e.g. elliptic curve family),
404 `param` is the parameter to pass to the constructor. Only a single
405 parameter is supported.
406 """
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200407 keys = [] #type: List[StorageKey]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100408 kt = crypto_knowledge.KeyType(key_type, params)
409 for bits in kt.sizes_to_test():
410 usage_flags = 'PSA_KEY_USAGE_EXPORT'
411 alg = 0
412 alg2 = 0
413 key_material = kt.key_material(bits)
414 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
415 r'',
416 kt.expression)
417 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200418 keys.append(self.key_builder.build(version=self.version,
419 id=1, lifetime=0x00000001,
420 type=kt.expression, bits=bits,
421 usage=usage_flags, alg=alg, alg2=alg2,
422 material=key_material,
423 description=description))
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200424 return keys
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100425
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200426 def all_keys_for_types(self) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100427 """Generate test keys covering key types and their representations."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200428 key_types = sorted(self.constructors.key_types)
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200429 return [key
430 for key_type in self.constructors.generate_expressions(key_types)
431 for key in self.keys_for_type(key_type)]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100432
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200433 def keys_for_algorithm(self, alg: str) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100434 """Generate test keys for the specified algorithm."""
435 # For now, we don't have information on the compatibility of key
436 # types and algorithms. So we just test the encoding of algorithms,
437 # and not that operations can be performed with them.
Gilles Peskineff9629f2021-04-21 10:18:19 +0200438 descr = re.sub(r'PSA_ALG_', r'', alg)
439 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100440 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm09960802021-06-24 09:38:21 +0200441 key1 = self.key_builder.build(version=self.version,
442 id=1, lifetime=0x00000001,
443 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
444 usage=usage, alg=alg, alg2=0,
445 material=b'K',
446 description='alg: ' + descr)
447 key2 = self.key_builder.build(version=self.version,
448 id=1, lifetime=0x00000001,
449 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
450 usage=usage, alg=0, alg2=alg,
451 material=b'L',
452 description='alg2: ' + descr)
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200453 return [key1, key2]
Gilles Peskined86bc522021-03-10 15:08:57 +0100454
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200455 def all_keys_for_algorithms(self) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100456 """Generate test keys covering algorithm encodings."""
Gilles Peskineb93f8542021-04-19 13:50:25 +0200457 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200458 return [key
459 for alg in self.constructors.generate_expressions(algorithms)
460 for key in self.keys_for_algorithm(alg)]
Gilles Peskined86bc522021-03-10 15:08:57 +0100461
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200462 def generate_all_keys(self) -> List[StorageKey]:
463 """Generate all keys for the test cases."""
464 keys = [] #type: List[StorageKey]
465 keys += self.all_keys_for_lifetimes()
466 keys += self.all_keys_for_usage_flags()
467 keys += self.all_keys_for_types()
468 keys += self.all_keys_for_algorithms()
469 return keys
470
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200471 def all_test_cases(self) -> List[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100472 """Generate all storage format test cases."""
Gilles Peskineae9f14b2021-04-12 14:43:05 +0200473 # First build a list of all keys, then construct all the corresponding
474 # test cases. This allows all required information to be obtained in
475 # one go, which is a significant performance gain as the information
476 # includes numerical values obtained by compiling a C program.
gabor-mezei-arm705c4522021-06-28 15:09:02 +0200477 keys = self.generate_all_keys()
gabor-mezei-arm8b0c91c2021-06-24 09:49:50 +0200478
gabor-mezei-arm9774dcf2021-06-23 17:33:30 +0200479 # Skip keys with a non-default location, because they
480 # require a driver and we currently have no mechanism to
481 # determine whether a driver is available.
gabor-mezei-arm705c4522021-06-28 15:09:02 +0200482 return [self.make_test_case(key) for key in keys if key.location_value() == 0]
Gilles Peskine897dff92021-03-10 15:03:44 +0100483
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200484class StorageFormatForward(StorageFormat):
485 """Storage format stability test cases for forward compatibility."""
486
487 def __init__(self, info: Information, version: int) -> None:
488 super().__init__(info, version, True)
489
490class StorageFormatV0(StorageFormat):
491 """Storage format stability test cases for version 0 compatibility."""
492
493 def __init__(self, info: Information) -> None:
494 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100495
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200496 def all_keys_for_usage_flags(
497 self,
498 extra_desc: Optional[str] = None
499 ) -> List[StorageKey]:
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200500 """Generate test keys covering usage flags."""
501 # First generate keys without usage policy extension for
502 # compatibility testing, then generate the keys with extension
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200503 # to check the extension is working. Finally generate key for all known
504 # usage flag which needs to be separted because it is not affected by
505 # usage extension.
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200506 keys = [] #type: List[StorageKey]
507 prev_builder = self.key_builder
508
gabor-mezei-armbce85272021-06-24 14:38:51 +0200509 self.key_builder = StorageKeyBuilder(usage_extension=False)
510 keys += self.generate_keys_for_usage_flags(extra_desc='without extension')
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200511
gabor-mezei-armbce85272021-06-24 14:38:51 +0200512 self.key_builder = StorageKeyBuilder(usage_extension=True)
513 keys += self.generate_keys_for_usage_flags(extra_desc='with extension')
514
515 keys.append(self.generate_key_for_all_usage_flags())
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200516
517 self.key_builder = prev_builder
518 return keys
519
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200520 def keys_for_implicit_usage(
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200521 self,
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200522 implyer_usage: str,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200523 alg: str,
524 key_type: str,
525 params: Optional[Iterable[str]] = None
526 ) -> List[StorageKey]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200527 # pylint: disable=too-many-locals
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200528 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200529 algorithm and key type combination.
530 """
531 keys = [] #type: List[StorageKey]
532 kt = crypto_knowledge.KeyType(key_type, params)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200533 bits = kt.sizes_to_test()[0]
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200534 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-arm47812632021-06-28 16:35:48 +0200535 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200536 material_usage_flags = usage_flags + ' | ' + implyer_usage
537 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-arm47812632021-06-28 16:35:48 +0200538 alg2 = 0
539 key_material = kt.key_material(bits)
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200540 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-arm47812632021-06-28 16:35:48 +0200541 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
542 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
543 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
544 r'',
545 kt.expression)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200546 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-arm47812632021-06-28 16:35:48 +0200547 usage_expression, alg_expression, key_type_expression, bits)
548 keys.append(self.key_builder.build(version=self.version,
549 id=1, lifetime=0x00000001,
550 type=kt.expression, bits=bits,
551 usage=material_usage_flags,
552 expected_usage=expected_usage_flags,
553 alg=alg, alg2=alg2,
554 material=key_material,
555 description=description))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200556 return keys
557
558 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200559 # pylint: disable=too-many-locals
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200560 """Match possible key types for sign algorithms."""
561 # To create a valid combinaton both the algorithms and key types
562 # must be filtered. Pair them with keywords created from its names.
563 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
564 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
565 keyword_translation = {
566 'ECDSA': 'ECC',
567 'ED[0-9]*.*' : 'EDWARDS'
568 }
569 exclusive_keywords = {
570 'EDWARDS': 'ECC'
571 }
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200572 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
573 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200574 alg_with_keys = {} #type: Dict[str, List[str]]
575 translation_table = str.maketrans('(', '_', ')')
576 for alg in algorithms:
577 # Generate keywords from the name of the algorithm
578 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
579 # Translate keywords for better matching with the key types
580 for keyword in alg_keywords.copy():
581 for pattern, replace in keyword_translation.items():
582 if re.match(pattern, keyword):
583 alg_keywords.remove(keyword)
584 alg_keywords.add(replace)
585 # Filter out incompatible algortihms
586 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
587 continue
588
589 for key_type in key_types:
590 # Generate keywords from the of the key type
591 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
592
593 # Remove ambigious keywords
594 for keyword1, keyword2 in exclusive_keywords.items():
595 if keyword1 in key_type_keywords:
596 key_type_keywords.remove(keyword2)
597
598 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
599 not key_type_keywords.isdisjoint(alg_keywords):
600 if alg in alg_with_keys:
601 alg_with_keys[alg].append(key_type)
602 else:
603 alg_with_keys[alg] = [key_type]
604 return alg_with_keys
605
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200606 def all_keys_for_implicit_usage(self) -> List[StorageKey]:
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200607 """Generate test keys for usage flag extensions."""
608 # Generate a key type and algorithm pair for each extendable usage
609 # flag to generate a valid key for exercising. The key is generated
610 # without usage extension to check the extension compatiblity.
611 keys = [] #type: List[StorageKey]
612 prev_builder = self.key_builder
613
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200614 # Generate the keys without usage extension
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200615 self.key_builder = StorageKeyBuilder(usage_extension=False)
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200616 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm927742e2021-06-28 16:27:29 +0200617 key_filter = StorageKey.IMPLICIT_USAGE_FLAGS_KEY_RESTRICTION
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200618
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200619 # Walk through all combintion. The key types must be filtered to fit
620 # the specific usage flag.
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200621 keys += [key
gabor-mezei-arme84d3212021-06-28 16:54:11 +0200622 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str)
gabor-mezei-armb92d61b2021-06-24 14:38:25 +0200623 for alg in sorted(alg_with_keys)
gabor-mezei-arm7d2ec9a2021-06-24 16:35:01 +0200624 for key_type in sorted(alg_with_keys[alg]) if re.match(key_filter[usage], key_type)
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200625 for key in self.keys_for_implicit_usage(usage, alg, key_type)]
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200626
627 self.key_builder = prev_builder
628 return keys
629
630 def generate_all_keys(self) -> List[StorageKey]:
631 keys = super().generate_all_keys()
gabor-mezei-armacfcc182021-06-28 17:40:32 +0200632 keys += self.all_keys_for_implicit_usage()
gabor-mezei-arm044fefc2021-06-24 10:16:44 +0200633 return keys
gabor-mezei-arm15c1f032021-06-24 10:04:38 +0200634
Gilles Peskineb94ea512021-03-10 02:12:08 +0100635class TestGenerator:
636 """Generate test data."""
637
638 def __init__(self, options) -> None:
639 self.test_suite_directory = self.get_option(options, 'directory',
640 'tests/suites')
641 self.info = Information()
642
643 @staticmethod
644 def get_option(options, name: str, default: T) -> T:
645 value = getattr(options, name, None)
646 return default if value is None else value
647
Gilles Peskine0298bda2021-03-10 02:34:37 +0100648 def filename_for(self, basename: str) -> str:
649 """The location of the data file with the specified base name."""
650 return os.path.join(self.test_suite_directory, basename + '.data')
651
Gilles Peskineb94ea512021-03-10 02:12:08 +0100652 def write_test_data_file(self, basename: str,
653 test_cases: Iterable[test_case.TestCase]) -> None:
654 """Write the test cases to a .data file.
655
656 The output file is ``basename + '.data'`` in the test suite directory.
657 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100658 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100659 test_case.write_data_file(filename, test_cases)
660
Gilles Peskine0298bda2021-03-10 02:34:37 +0100661 TARGETS = {
662 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100663 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100664 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200665 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100666 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arm4d9fb732021-06-24 09:53:26 +0200667 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100668 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
669
670 def generate_target(self, name: str) -> None:
671 test_cases = self.TARGETS[name](self.info)
672 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100673
Gilles Peskine09940492021-01-26 22:16:30 +0100674def main(args):
675 """Command line entry point."""
676 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100677 parser.add_argument('--list', action='store_true',
678 help='List available targets and exit')
679 parser.add_argument('targets', nargs='*', metavar='TARGET',
680 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100681 options = parser.parse_args(args)
682 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100683 if options.list:
684 for name in sorted(generator.TARGETS):
685 print(generator.filename_for(name))
686 return
687 if options.targets:
688 # Allow "-" as a special case so you can run
689 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
690 # ``$targets`` is empty or not.
691 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
692 for target in options.targets
693 if target != '-']
694 else:
695 options.targets = sorted(generator.TARGETS)
696 for target in options.targets:
697 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100698
699if __name__ == '__main__':
700 main(sys.argv[1:])