blob: 25d5c9dd9b9e10c22de40ef4271538b0972ca10d [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
Gilles Peskine0298bda2021-03-10 02:34:37 +01003
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
Gilles Peskine09940492021-01-26 22:16:30 +01006"""
7
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0
10#
11# Licensed under the Apache License, Version 2.0 (the "License"); you may
12# not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22
23import argparse
Gilles Peskine14e428f2021-01-26 22:19:21 +010024import os
Bence Szépkúti9e84ec72021-05-07 11:49:17 +020025import posixpath
Gilles Peskine14e428f2021-01-26 22:19:21 +010026import re
Gilles Peskine09940492021-01-26 22:16:30 +010027import sys
Gilles Peskine3d778392021-02-17 15:11:05 +010028from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010029
30import scripts_path # pylint: disable=unused-import
Gilles Peskinec86f20a2021-04-22 00:20:47 +020031from mbedtls_dev import build_tree
Gilles Peskine14e428f2021-01-26 22:19:21 +010032from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010033from mbedtls_dev import macro_collector
Gilles Peskine897dff92021-03-10 15:03:44 +010034from mbedtls_dev import psa_storage
Gilles Peskine14e428f2021-01-26 22:19:21 +010035from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010036
37T = TypeVar('T') #pylint: disable=invalid-name
38
Gilles Peskine14e428f2021-01-26 22:19:21 +010039
Gilles Peskine7f756872021-02-16 12:13:12 +010040def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010041 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
42 if name.startswith('PSA_'):
43 return name[:4] + 'WANT_' + name[4:]
44 else:
45 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
46
Gilles Peskine7f756872021-02-16 12:13:12 +010047def finish_family_dependency(dep: str, bits: int) -> str:
48 """Finish dep if it's a family dependency symbol prefix.
49
50 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
51 qualified by the key size. If dep is such a symbol, finish it by adjusting
52 the prefix and appending the key size. Other symbols are left unchanged.
53 """
54 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
55
56def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
57 """Finish any family dependency symbol prefixes.
58
59 Apply `finish_family_dependency` to each element of `dependencies`.
60 """
61 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010062
Gilles Peskinec5d086f2021-04-20 23:23:45 +020063SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
64 'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
65 'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
66 'PSA_ALG_ANY_HASH', # only in policies
67 'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
68 'PSA_ALG_KEY_AGREEMENT', # chaining
69 'PSA_ALG_TRUNCATED_MAC', # modifier
70])
Gilles Peskinef8223ab2021-03-10 15:07:16 +010071def automatic_dependencies(*expressions: str) -> List[str]:
72 """Infer dependencies of a test case by looking for PSA_xxx symbols.
73
74 The arguments are strings which should be C expressions. Do not use
75 string literals or comments as this function is not smart enough to
76 skip them.
77 """
78 used = set()
79 for expr in expressions:
80 used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
Gilles Peskinec5d086f2021-04-20 23:23:45 +020081 used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
Gilles Peskinef8223ab2021-03-10 15:07:16 +010082 return sorted(psa_want_symbol(name) for name in used)
83
Gilles Peskined169d602021-02-16 14:16:25 +010084# A temporary hack: at the time of writing, not all dependency symbols
85# are implemented yet. Skip test cases for which the dependency symbols are
86# not available. Once all dependency symbols are available, this hack must
87# be removed so that a bug in the dependency symbols proprely leads to a test
88# failure.
89def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
90 return frozenset(symbol
91 for line in open(filename)
92 for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
Gilles Peskinec86f20a2021-04-22 00:20:47 +020093_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
Gilles Peskined169d602021-02-16 14:16:25 +010094def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
Gilles Peskinec86f20a2021-04-22 00:20:47 +020095 global _implemented_dependencies #pylint: disable=global-statement,invalid-name
96 if _implemented_dependencies is None:
97 _implemented_dependencies = \
98 read_implemented_dependencies('include/psa/crypto_config.h')
99 if not all(dep.lstrip('!') in _implemented_dependencies
Gilles Peskined169d602021-02-16 14:16:25 +0100100 for dep in dependencies):
101 dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
102
Gilles Peskine14e428f2021-01-26 22:19:21 +0100103
Gilles Peskineb94ea512021-03-10 02:12:08 +0100104class Information:
105 """Gather information about PSA constructors."""
Gilles Peskine09940492021-01-26 22:16:30 +0100106
Gilles Peskineb94ea512021-03-10 02:12:08 +0100107 def __init__(self) -> None:
Gilles Peskine09940492021-01-26 22:16:30 +0100108 self.constructors = self.read_psa_interface()
109
110 @staticmethod
Gilles Peskine09940492021-01-26 22:16:30 +0100111 def remove_unwanted_macros(
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200112 constructors: macro_collector.PSAMacroEnumerator
Gilles Peskine09940492021-01-26 22:16:30 +0100113 ) -> None:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200114 # Mbed TLS doesn't support finite-field DH yet and will not support
115 # finite-field DSA. Don't attempt to generate any related test case.
116 constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
117 constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100118 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
119 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
Gilles Peskine09940492021-01-26 22:16:30 +0100120
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200121 def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
Gilles Peskine09940492021-01-26 22:16:30 +0100122 """Return the list of known key types, algorithms, etc."""
Gilles Peskine3d404b82021-03-30 21:46:35 +0200123 constructors = macro_collector.InputsForTest()
Gilles Peskine09940492021-01-26 22:16:30 +0100124 header_file_names = ['include/psa/crypto_values.h',
125 'include/psa/crypto_extra.h']
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200126 test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
Gilles Peskine09940492021-01-26 22:16:30 +0100127 for header_file_name in header_file_names:
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200128 constructors.parse_header(header_file_name)
129 for test_cases in test_suites:
130 constructors.parse_test_cases(test_cases)
Gilles Peskine09940492021-01-26 22:16:30 +0100131 self.remove_unwanted_macros(constructors)
Gilles Peskine3d404b82021-03-30 21:46:35 +0200132 constructors.gather_arguments()
Gilles Peskine09940492021-01-26 22:16:30 +0100133 return constructors
134
Gilles Peskine14e428f2021-01-26 22:19:21 +0100135
Gilles Peskineb94ea512021-03-10 02:12:08 +0100136def test_case_for_key_type_not_supported(
137 verb: str, key_type: str, bits: int,
138 dependencies: List[str],
139 *args: str,
140 param_descr: str = ''
141) -> test_case.TestCase:
142 """Return one test case exercising a key creation method
143 for an unsupported key type or size.
144 """
145 hack_dependencies_not_implemented(dependencies)
146 tc = test_case.TestCase()
147 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
148 adverb = 'not' if dependencies else 'never'
149 if param_descr:
150 adverb = param_descr + ' ' + adverb
151 tc.set_description('PSA {} {} {}-bit {} supported'
152 .format(verb, short_key_type, bits, adverb))
153 tc.set_dependencies(dependencies)
154 tc.set_function(verb + '_not_supported')
155 tc.set_arguments([key_type] + list(args))
156 return tc
157
158class NotSupported:
159 """Generate test cases for when something is not supported."""
160
161 def __init__(self, info: Information) -> None:
162 self.constructors = info.constructors
Gilles Peskine14e428f2021-01-26 22:19:21 +0100163
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100164 ALWAYS_SUPPORTED = frozenset([
165 'PSA_KEY_TYPE_DERIVE',
166 'PSA_KEY_TYPE_RAW_DATA',
167 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100168 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100169 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100170 kt: crypto_knowledge.KeyType,
171 param: Optional[int] = None,
172 param_descr: str = '',
Gilles Peskine3d778392021-02-17 15:11:05 +0100173 ) -> Iterator[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100174 """Return test cases exercising key creation when the given type is unsupported.
175
176 If param is present and not None, emit test cases conditioned on this
177 parameter not being supported. If it is absent or None, emit test cases
178 conditioned on the base type not being supported.
179 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100180 if kt.name in self.ALWAYS_SUPPORTED:
181 # Don't generate test cases for key types that are always supported.
182 # They would be skipped in all configurations, which is noise.
Gilles Peskine3d778392021-02-17 15:11:05 +0100183 return
Gilles Peskineaf172842021-01-27 18:24:48 +0100184 import_dependencies = [('!' if param is None else '') +
185 psa_want_symbol(kt.name)]
186 if kt.params is not None:
187 import_dependencies += [('!' if param == i else '') +
188 psa_want_symbol(sym)
189 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100190 if kt.name.endswith('_PUBLIC_KEY'):
191 generate_dependencies = []
192 else:
193 generate_dependencies = import_dependencies
Gilles Peskine14e428f2021-01-26 22:19:21 +0100194 for bits in kt.sizes_to_test():
Gilles Peskine3d778392021-02-17 15:11:05 +0100195 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100196 'import', kt.expression, bits,
197 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100198 test_case.hex_string(kt.key_material(bits)),
199 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100200 )
Gilles Peskineaf172842021-01-27 18:24:48 +0100201 if not generate_dependencies and param is not None:
202 # If generation is impossible for this key type, rather than
203 # supported or not depending on implementation capabilities,
204 # only generate the test case once.
205 continue
Gilles Peskine3d778392021-02-17 15:11:05 +0100206 yield test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100207 'generate', kt.expression, bits,
208 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100209 str(bits),
210 param_descr=param_descr,
Gilles Peskine3d778392021-02-17 15:11:05 +0100211 )
Gilles Peskine14e428f2021-01-26 22:19:21 +0100212 # To be added: derive
Gilles Peskine14e428f2021-01-26 22:19:21 +0100213
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200214 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
215 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
216
Gilles Peskine3d778392021-02-17 15:11:05 +0100217 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
Gilles Peskine14e428f2021-01-26 22:19:21 +0100218 """Generate test cases that exercise the creation of keys of unsupported types."""
Gilles Peskine14e428f2021-01-26 22:19:21 +0100219 for key_type in sorted(self.constructors.key_types):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200220 if key_type in self.ECC_KEY_TYPES:
221 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100222 kt = crypto_knowledge.KeyType(key_type)
Gilles Peskine3d778392021-02-17 15:11:05 +0100223 yield from self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100224 for curve_family in sorted(self.constructors.ecc_curves):
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200225 for constr in self.ECC_KEY_TYPES:
Gilles Peskineaf172842021-01-27 18:24:48 +0100226 kt = crypto_knowledge.KeyType(constr, [curve_family])
Gilles Peskine3d778392021-02-17 15:11:05 +0100227 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100228 kt, param_descr='type')
Gilles Peskine3d778392021-02-17 15:11:05 +0100229 yield from self.test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100230 kt, 0, param_descr='curve')
Gilles Peskineb94ea512021-03-10 02:12:08 +0100231
232
Gilles Peskine897dff92021-03-10 15:03:44 +0100233class StorageKey(psa_storage.Key):
234 """Representation of a key for storage format testing."""
235
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200236 def __init__(
237 self,
238 description: str,
239 expected_usage: Optional[str] = None,
240 **kwargs
241 ) -> None:
242 """Prepare to generate a key.
243
244 * `description`: used for the the test case names
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200245 * `implicit_usage`: the usage flags generated as the expected usage
246 flags in the test cases. When testing implicit
247 usage flags, they can differ in the generated keys
248 and the expected usage flags in the test cases.
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200249 """
Gilles Peskine897dff92021-03-10 15:03:44 +0100250 super().__init__(**kwargs)
251 self.description = description #type: str
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200252 self.usage = psa_storage.as_expr(expected_usage) if expected_usage is not None else\
253 self.original_usage #type: psa_storage.Expr
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200254
gabor-mezei-arm68c030a2021-06-24 09:38:21 +0200255class StorageKeyBuilder:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200256 # pylint: disable=too-few-public-methods
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200257 def __init__(self, usage_extension: bool) -> None:
258 self.usage_extension = usage_extension #type: bool
gabor-mezei-arm68c030a2021-06-24 09:38:21 +0200259
260 def build(self, **kwargs) -> StorageKey:
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200261 return StorageKey(implicit_usage=self.usage_extension, **kwargs)
Gilles Peskine897dff92021-03-10 15:03:44 +0100262
263class StorageFormat:
264 """Storage format stability test cases."""
265
266 def __init__(self, info: Information, version: int, forward: bool) -> None:
267 """Prepare to generate test cases for storage format stability.
268
269 * `info`: information about the API. See the `Information` class.
270 * `version`: the storage format version to generate test cases for.
271 * `forward`: if true, generate forward compatibility test cases which
272 save a key and check that its representation is as intended. Otherwise
273 generate backward compatibility test cases which inject a key
274 representation and check that it can be read and used.
275 """
gabor-mezei-arm7b5c4e22021-06-23 17:01:44 +0200276 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
277 self.version = version #type: int
278 self.forward = forward #type: bool
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200279 self.key_builder = StorageKeyBuilder(usage_extension=True) #type: StorageKeyBuilder
Gilles Peskine897dff92021-03-10 15:03:44 +0100280
281 def make_test_case(self, key: StorageKey) -> test_case.TestCase:
282 """Construct a storage format test case for the given key.
283
284 If ``forward`` is true, generate a forward compatibility test case:
285 create a key and validate that it has the expected representation.
286 Otherwise generate a backward compatibility test case: inject the
287 key representation into storage and validate that it can be read
288 correctly.
289 """
290 verb = 'save' if self.forward else 'read'
291 tc = test_case.TestCase()
292 tc.set_description('PSA storage {}: {}'.format(verb, key.description))
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100293 dependencies = automatic_dependencies(
294 key.lifetime.string, key.type.string,
295 key.usage.string, key.alg.string, key.alg2.string,
296 )
297 dependencies = finish_family_dependencies(dependencies, key.bits)
298 tc.set_dependencies(dependencies)
Gilles Peskine897dff92021-03-10 15:03:44 +0100299 tc.set_function('key_storage_' + verb)
300 if self.forward:
301 extra_arguments = []
302 else:
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200303 flags = []
Gilles Peskine897dff92021-03-10 15:03:44 +0100304 # Some test keys have the RAW_DATA type and attributes that don't
305 # necessarily make sense. We do this to validate numerical
306 # encodings of the attributes.
307 # Raw data keys have no useful exercise anyway so there is no
308 # loss of test coverage.
Gilles Peskine45f1cd72021-04-21 20:11:33 +0200309 if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
310 flags.append('TEST_FLAG_EXERCISE')
311 if 'READ_ONLY' in key.lifetime.string:
312 flags.append('TEST_FLAG_READ_ONLY')
313 extra_arguments = [' | '.join(flags) if flags else '0']
Gilles Peskine897dff92021-03-10 15:03:44 +0100314 tc.set_arguments([key.lifetime.string,
315 key.type.string, str(key.bits),
316 key.usage.string, key.alg.string, key.alg2.string,
317 '"' + key.material.hex() + '"',
318 '"' + key.hex() + '"',
319 *extra_arguments])
320 return tc
321
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200322 def key_for_lifetime(
323 self,
324 lifetime: str,
325 ) -> StorageKey:
326 """Construct a test key for the given lifetime."""
327 short = lifetime
328 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
329 r'', short)
330 short = re.sub(r'PSA_KEY_[A-Z]+_', r'', short)
331 description = 'lifetime: ' + short
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200332 key = self.key_builder.build(version=self.version,
333 id=1, lifetime=lifetime,
334 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
335 usage='PSA_KEY_USAGE_EXPORT', alg=0, alg2=0,
336 material=b'L',
337 description=description)
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200338 return key
339
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200340 def all_keys_for_lifetimes(self) -> List[StorageKey]:
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200341 """Generate test keys covering lifetimes."""
342 lifetimes = sorted(self.constructors.lifetimes)
343 expressions = self.constructors.generate_expressions(lifetimes)
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200344 keys = [] #type List[StorageKey]
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200345 for lifetime in expressions:
346 # Don't attempt to create or load a volatile key in storage
347 if 'VOLATILE' in lifetime:
348 continue
349 # Don't attempt to create a read-only key in storage,
350 # but do attempt to load one.
351 if 'READ_ONLY' in lifetime and self.forward:
352 continue
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200353 keys.append(self.key_for_lifetime(lifetime))
354 return keys
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200355
Gilles Peskine897dff92021-03-10 15:03:44 +0100356 def key_for_usage_flags(
357 self,
358 usage_flags: List[str],
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200359 short: Optional[str] = None,
360 extra_desc: Optional[str] = None
Gilles Peskine897dff92021-03-10 15:03:44 +0100361 ) -> StorageKey:
362 """Construct a test key for the given key usage."""
363 usage = ' | '.join(usage_flags) if usage_flags else '0'
364 if short is None:
365 short = re.sub(r'\bPSA_KEY_USAGE_', r'', usage)
gabor-mezei-arm35929eb2021-06-28 15:09:02 +0200366 extra_desc = ' ' + extra_desc if extra_desc else ''
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200367 description = 'usage' + extra_desc + ': ' + short
gabor-mezei-arm68c030a2021-06-24 09:38:21 +0200368 return self.key_builder.build(version=self.version,
369 id=1, lifetime=0x00000001,
370 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
371 usage=usage, alg=0, alg2=0,
372 material=b'K',
373 description=description)
Gilles Peskine897dff92021-03-10 15:03:44 +0100374
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200375 def generate_keys_for_usage_flags(
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200376 self,
377 extra_desc: Optional[str] = None
378 ) -> List[StorageKey]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100379 """Generate test keys covering usage flags."""
380 known_flags = sorted(self.constructors.key_usage_flags)
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200381 keys = [] #type List[StorageKey]
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200382 keys.append(self.key_for_usage_flags(['0'], extra_desc=extra_desc))
383 keys += [self.key_for_usage_flags([usage_flag], extra_desc=extra_desc)
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200384 for usage_flag in known_flags]
gabor-mezei-arm6ee72532021-06-24 09:42:02 +0200385 keys += [self.key_for_usage_flags([flag1, flag2], extra_desc=extra_desc)
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200386 for flag1, flag2 in zip(known_flags,
387 known_flags[1:] + [known_flags[0]])]
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200388 return keys
389
390 def generate_key_for_all_usage_flags(self) -> StorageKey:
391 known_flags = sorted(self.constructors.key_usage_flags)
392 return self.key_for_usage_flags(known_flags, short='all known')
393
394 def all_keys_for_usage_flags(
395 self,
396 extra_desc: Optional[str] = None
397 ) -> List[StorageKey]:
398 keys = self.generate_keys_for_usage_flags(extra_desc=extra_desc)
399 keys.append(self.generate_key_for_all_usage_flags())
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200400 return keys
Gilles Peskine897dff92021-03-10 15:03:44 +0100401
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100402 def keys_for_type(
403 self,
404 key_type: str,
405 params: Optional[Iterable[str]] = None
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200406 ) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100407 """Generate test keys for the given key type.
408
409 For key types that depend on a parameter (e.g. elliptic curve family),
410 `param` is the parameter to pass to the constructor. Only a single
411 parameter is supported.
412 """
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200413 keys = [] #type: List[StorageKey]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100414 kt = crypto_knowledge.KeyType(key_type, params)
415 for bits in kt.sizes_to_test():
416 usage_flags = 'PSA_KEY_USAGE_EXPORT'
417 alg = 0
418 alg2 = 0
419 key_material = kt.key_material(bits)
420 short_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
421 r'',
422 kt.expression)
423 description = 'type: {} {}-bit'.format(short_expression, bits)
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200424 keys.append(self.key_builder.build(version=self.version,
425 id=1, lifetime=0x00000001,
426 type=kt.expression, bits=bits,
427 usage=usage_flags, alg=alg, alg2=alg2,
428 material=key_material,
429 description=description))
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200430 return keys
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100431
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200432 def all_keys_for_types(self) -> List[StorageKey]:
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100433 """Generate test keys covering key types and their representations."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200434 key_types = sorted(self.constructors.key_types)
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200435 return [key
436 for key_type in self.constructors.generate_expressions(key_types)
437 for key in self.keys_for_type(key_type)]
Gilles Peskinef8223ab2021-03-10 15:07:16 +0100438
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200439 def keys_for_algorithm(self, alg: str) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100440 """Generate test keys for the specified algorithm."""
441 # For now, we don't have information on the compatibility of key
442 # types and algorithms. So we just test the encoding of algorithms,
443 # and not that operations can be performed with them.
Gilles Peskine20f55f62021-04-21 10:18:19 +0200444 descr = re.sub(r'PSA_ALG_', r'', alg)
445 descr = re.sub(r',', r', ', re.sub(r' +', r'', descr))
Gilles Peskined86bc522021-03-10 15:08:57 +0100446 usage = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm68c030a2021-06-24 09:38:21 +0200447 key1 = 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=alg, alg2=0,
451 material=b'K',
452 description='alg: ' + descr)
453 key2 = self.key_builder.build(version=self.version,
454 id=1, lifetime=0x00000001,
455 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
456 usage=usage, alg=0, alg2=alg,
457 material=b'L',
458 description='alg2: ' + descr)
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200459 return [key1, key2]
Gilles Peskined86bc522021-03-10 15:08:57 +0100460
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200461 def all_keys_for_algorithms(self) -> List[StorageKey]:
Gilles Peskined86bc522021-03-10 15:08:57 +0100462 """Generate test keys covering algorithm encodings."""
Gilles Peskine537d5fa2021-04-19 13:50:25 +0200463 algorithms = sorted(self.constructors.algorithms)
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200464 return [key
465 for alg in self.constructors.generate_expressions(algorithms)
466 for key in self.keys_for_algorithm(alg)]
Gilles Peskined86bc522021-03-10 15:08:57 +0100467
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200468 def generate_all_keys(self) -> List[StorageKey]:
469 """Generate all keys for the test cases."""
470 keys = [] #type: List[StorageKey]
471 keys += self.all_keys_for_lifetimes()
472 keys += self.all_keys_for_usage_flags()
473 keys += self.all_keys_for_types()
474 keys += self.all_keys_for_algorithms()
475 return keys
476
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200477 def all_test_cases(self) -> List[test_case.TestCase]:
Gilles Peskine897dff92021-03-10 15:03:44 +0100478 """Generate all storage format test cases."""
Gilles Peskine3c9d4232021-04-12 14:43:05 +0200479 # First build a list of all keys, then construct all the corresponding
480 # test cases. This allows all required information to be obtained in
481 # one go, which is a significant performance gain as the information
482 # includes numerical values obtained by compiling a C program.
gabor-mezei-arm35929eb2021-06-28 15:09:02 +0200483 keys = self.generate_all_keys()
gabor-mezei-arm780cf9d2021-06-24 09:49:50 +0200484
gabor-mezei-armb5db2c42021-06-23 17:33:30 +0200485 # Skip keys with a non-default location, because they
486 # require a driver and we currently have no mechanism to
487 # determine whether a driver is available.
gabor-mezei-arm35929eb2021-06-28 15:09:02 +0200488 return [self.make_test_case(key) for key in keys if key.location_value() == 0]
Gilles Peskine897dff92021-03-10 15:03:44 +0100489
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200490class StorageFormatForward(StorageFormat):
491 """Storage format stability test cases for forward compatibility."""
492
493 def __init__(self, info: Information, version: int) -> None:
494 super().__init__(info, version, True)
495
496class StorageFormatV0(StorageFormat):
497 """Storage format stability test cases for version 0 compatibility."""
498
499 def __init__(self, info: Information) -> None:
500 super().__init__(info, 0, False)
Gilles Peskine897dff92021-03-10 15:03:44 +0100501
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200502 def all_keys_for_usage_flags(
503 self,
504 extra_desc: Optional[str] = None
505 ) -> List[StorageKey]:
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200506 """Generate test keys covering usage flags."""
507 # First generate keys without usage policy extension for
508 # compatibility testing, then generate the keys with extension
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200509 # to check the extension is working. Finally generate key for all known
510 # usage flag which needs to be separted because it is not affected by
511 # usage extension.
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200512 keys = [] #type: List[StorageKey]
513 prev_builder = self.key_builder
514
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200515 self.key_builder = StorageKeyBuilder(usage_extension=False)
516 keys += self.generate_keys_for_usage_flags(extra_desc='without extension')
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200517
gabor-mezei-arm49d6ea92021-06-24 14:38:51 +0200518 self.key_builder = StorageKeyBuilder(usage_extension=True)
519 keys += self.generate_keys_for_usage_flags(extra_desc='with extension')
520
521 keys.append(self.generate_key_for_all_usage_flags())
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200522
523 self.key_builder = prev_builder
524 return keys
525
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200526 def keys_for_implicit_usage(
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200527 self,
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200528 implyer_usage: str,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200529 alg: str,
530 key_type: str,
531 params: Optional[Iterable[str]] = None
532 ) -> List[StorageKey]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200533 # pylint: disable=too-many-locals
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200534 """Generate test keys for the specified implicit usage flag,
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200535 algorithm and key type combination.
536 """
537 keys = [] #type: List[StorageKey]
538 kt = crypto_knowledge.KeyType(key_type, params)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200539 bits = kt.sizes_to_test()[0]
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200540 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200541 usage_flags = 'PSA_KEY_USAGE_EXPORT'
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200542 material_usage_flags = usage_flags + ' | ' + implyer_usage
543 expected_usage_flags = material_usage_flags + ' | ' + implicit_usage
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200544 alg2 = 0
545 key_material = kt.key_material(bits)
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200546 usage_expression = re.sub(r'PSA_KEY_USAGE_', r'', implyer_usage)
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200547 alg_expression = re.sub(r'PSA_ALG_', r'', alg)
548 alg_expression = re.sub(r',', r', ', re.sub(r' +', r'', alg_expression))
549 key_type_expression = re.sub(r'\bPSA_(?:KEY_TYPE|ECC_FAMILY)_',
550 r'',
551 kt.expression)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200552 description = 'implied by {}: {} {} {}-bit'.format(
gabor-mezei-armd9050a52021-06-28 16:35:48 +0200553 usage_expression, alg_expression, key_type_expression, bits)
554 keys.append(self.key_builder.build(version=self.version,
555 id=1, lifetime=0x00000001,
556 type=kt.expression, bits=bits,
557 usage=material_usage_flags,
558 expected_usage=expected_usage_flags,
559 alg=alg, alg2=alg2,
560 material=key_material,
561 description=description))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200562 return keys
563
564 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200565 # pylint: disable=too-many-locals
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200566 """Match possible key types for sign algorithms."""
567 # To create a valid combinaton both the algorithms and key types
568 # must be filtered. Pair them with keywords created from its names.
569 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
570 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
571 keyword_translation = {
572 'ECDSA': 'ECC',
573 'ED[0-9]*.*' : 'EDWARDS'
574 }
575 exclusive_keywords = {
576 'EDWARDS': 'ECC'
577 }
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200578 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
579 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200580 alg_with_keys = {} #type: Dict[str, List[str]]
581 translation_table = str.maketrans('(', '_', ')')
582 for alg in algorithms:
583 # Generate keywords from the name of the algorithm
584 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
585 # Translate keywords for better matching with the key types
586 for keyword in alg_keywords.copy():
587 for pattern, replace in keyword_translation.items():
588 if re.match(pattern, keyword):
589 alg_keywords.remove(keyword)
590 alg_keywords.add(replace)
591 # Filter out incompatible algortihms
592 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
593 continue
594
595 for key_type in key_types:
596 # Generate keywords from the of the key type
597 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
598
599 # Remove ambigious keywords
600 for keyword1, keyword2 in exclusive_keywords.items():
601 if keyword1 in key_type_keywords:
602 key_type_keywords.remove(keyword2)
603
604 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
605 not key_type_keywords.isdisjoint(alg_keywords):
606 if alg in alg_with_keys:
607 alg_with_keys[alg].append(key_type)
608 else:
609 alg_with_keys[alg] = [key_type]
610 return alg_with_keys
611
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200612 def all_keys_for_implicit_usage(self) -> List[StorageKey]:
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200613 """Generate test keys for usage flag extensions."""
614 # Generate a key type and algorithm pair for each extendable usage
615 # flag to generate a valid key for exercising. The key is generated
616 # without usage extension to check the extension compatiblity.
617 keys = [] #type: List[StorageKey]
618 prev_builder = self.key_builder
619
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200620 # Generate the keys without usage extension
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200621 self.key_builder = StorageKeyBuilder(usage_extension=False)
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200622 alg_with_keys = self.gather_key_types_for_sign_alg()
gabor-mezei-arm8f405102021-06-28 16:27:29 +0200623 key_filter = StorageKey.IMPLICIT_USAGE_FLAGS_KEY_RESTRICTION
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200624
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200625 # Walk through all combintion. The key types must be filtered to fit
626 # the specific usage flag.
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200627 keys += [key
gabor-mezei-arm2710bb12021-06-28 16:54:11 +0200628 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str)
gabor-mezei-arm0f8136a2021-06-24 14:38:25 +0200629 for alg in sorted(alg_with_keys)
gabor-mezei-arm11e48382021-06-24 16:35:01 +0200630 for key_type in sorted(alg_with_keys[alg]) if re.match(key_filter[usage], key_type)
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200631 for key in self.keys_for_implicit_usage(usage, alg, key_type)]
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200632
633 self.key_builder = prev_builder
634 return keys
635
636 def generate_all_keys(self) -> List[StorageKey]:
637 keys = super().generate_all_keys()
gabor-mezei-arm5df1dee2021-06-28 17:40:32 +0200638 keys += self.all_keys_for_implicit_usage()
gabor-mezei-arm672e3762021-06-24 10:16:44 +0200639 return keys
gabor-mezei-arm7748b6f2021-06-24 10:04:38 +0200640
Gilles Peskineb94ea512021-03-10 02:12:08 +0100641class TestGenerator:
642 """Generate test data."""
643
644 def __init__(self, options) -> None:
645 self.test_suite_directory = self.get_option(options, 'directory',
646 'tests/suites')
647 self.info = Information()
648
649 @staticmethod
650 def get_option(options, name: str, default: T) -> T:
651 value = getattr(options, name, None)
652 return default if value is None else value
653
Gilles Peskine0298bda2021-03-10 02:34:37 +0100654 def filename_for(self, basename: str) -> str:
655 """The location of the data file with the specified base name."""
Bence Szépkúti9e84ec72021-05-07 11:49:17 +0200656 return posixpath.join(self.test_suite_directory, basename + '.data')
Gilles Peskine0298bda2021-03-10 02:34:37 +0100657
Gilles Peskineb94ea512021-03-10 02:12:08 +0100658 def write_test_data_file(self, basename: str,
659 test_cases: Iterable[test_case.TestCase]) -> None:
660 """Write the test cases to a .data file.
661
662 The output file is ``basename + '.data'`` in the test suite directory.
663 """
Gilles Peskine0298bda2021-03-10 02:34:37 +0100664 filename = self.filename_for(basename)
Gilles Peskineb94ea512021-03-10 02:12:08 +0100665 test_case.write_data_file(filename, test_cases)
666
Gilles Peskine0298bda2021-03-10 02:34:37 +0100667 TARGETS = {
668 'test_suite_psa_crypto_not_supported.generated':
Gilles Peskine3d778392021-02-17 15:11:05 +0100669 lambda info: NotSupported(info).test_cases_for_not_supported(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100670 'test_suite_psa_crypto_storage_format.current':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200671 lambda info: StorageFormatForward(info, 0).all_test_cases(),
Gilles Peskine897dff92021-03-10 15:03:44 +0100672 'test_suite_psa_crypto_storage_format.v0':
gabor-mezei-arma4102cb2021-06-24 09:53:26 +0200673 lambda info: StorageFormatV0(info).all_test_cases(),
Gilles Peskine0298bda2021-03-10 02:34:37 +0100674 } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
675
676 def generate_target(self, name: str) -> None:
677 test_cases = self.TARGETS[name](self.info)
678 self.write_test_data_file(name, test_cases)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100679
Gilles Peskine09940492021-01-26 22:16:30 +0100680def main(args):
681 """Command line entry point."""
682 parser = argparse.ArgumentParser(description=__doc__)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100683 parser.add_argument('--list', action='store_true',
684 help='List available targets and exit')
685 parser.add_argument('targets', nargs='*', metavar='TARGET',
686 help='Target file to generate (default: all; "-": none)')
Gilles Peskine09940492021-01-26 22:16:30 +0100687 options = parser.parse_args(args)
Gilles Peskinec86f20a2021-04-22 00:20:47 +0200688 build_tree.chdir_to_root()
Gilles Peskine09940492021-01-26 22:16:30 +0100689 generator = TestGenerator(options)
Gilles Peskine0298bda2021-03-10 02:34:37 +0100690 if options.list:
691 for name in sorted(generator.TARGETS):
692 print(generator.filename_for(name))
693 return
694 if options.targets:
695 # Allow "-" as a special case so you can run
696 # ``generate_psa_tests.py - $targets`` and it works uniformly whether
697 # ``$targets`` is empty or not.
698 options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
699 for target in options.targets
700 if target != '-']
701 else:
702 options.targets = sorted(generator.TARGETS)
703 for target in options.targets:
704 generator.generate_target(target)
Gilles Peskine09940492021-01-26 22:16:30 +0100705
706if __name__ == '__main__':
707 main(sys.argv[1:])