• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
3
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
6"""
7
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
10
11import enum
12import re
13import sys
14from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional
15
16import scripts_path # pylint: disable=unused-import
17from mbedtls_dev import crypto_knowledge
18from mbedtls_dev import macro_collector
19from mbedtls_dev import psa_storage
20from mbedtls_dev import test_case
21from mbedtls_dev import test_data_generation
22
23
24def psa_want_symbol(name: str) -> str:
25    """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
26    if name.startswith('PSA_'):
27        return name[:4] + 'WANT_' + name[4:]
28    else:
29        raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
30
31def finish_family_dependency(dep: str, bits: int) -> str:
32    """Finish dep if it's a family dependency symbol prefix.
33
34    A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
35    qualified by the key size. If dep is such a symbol, finish it by adjusting
36    the prefix and appending the key size. Other symbols are left unchanged.
37    """
38    return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
39
40def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
41    """Finish any family dependency symbol prefixes.
42
43    Apply `finish_family_dependency` to each element of `dependencies`.
44    """
45    return [finish_family_dependency(dep, bits) for dep in dependencies]
46
47SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
48    'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
49    'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
50    'PSA_ALG_ANY_HASH', # only in policies
51    'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
52    'PSA_ALG_KEY_AGREEMENT', # chaining
53    'PSA_ALG_TRUNCATED_MAC', # modifier
54])
55def automatic_dependencies(*expressions: str) -> List[str]:
56    """Infer dependencies of a test case by looking for PSA_xxx symbols.
57
58    The arguments are strings which should be C expressions. Do not use
59    string literals or comments as this function is not smart enough to
60    skip them.
61    """
62    used = set()
63    for expr in expressions:
64        used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
65    used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
66    return sorted(psa_want_symbol(name) for name in used)
67
68# A temporary hack: at the time of writing, not all dependency symbols
69# are implemented yet. Skip test cases for which the dependency symbols are
70# not available. Once all dependency symbols are available, this hack must
71# be removed so that a bug in the dependency symbols properly leads to a test
72# failure.
73def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
74    return frozenset(symbol
75                     for line in open(filename)
76                     for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
77_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
78def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
79    global _implemented_dependencies #pylint: disable=global-statement,invalid-name
80    if _implemented_dependencies is None:
81        _implemented_dependencies = \
82            read_implemented_dependencies('include/psa/crypto_config.h')
83    if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
84               for dep in dependencies):
85        dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
86
87
88class Information:
89    """Gather information about PSA constructors."""
90
91    def __init__(self) -> None:
92        self.constructors = self.read_psa_interface()
93
94    @staticmethod
95    def remove_unwanted_macros(
96            constructors: macro_collector.PSAMacroEnumerator
97    ) -> None:
98        # Mbed TLS doesn't support finite-field DH yet and will not support
99        # finite-field DSA. Don't attempt to generate any related test case.
100        constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
101        constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
102        constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
103        constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
104
105    def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
106        """Return the list of known key types, algorithms, etc."""
107        constructors = macro_collector.InputsForTest()
108        header_file_names = ['include/psa/crypto_values.h',
109                             'include/psa/crypto_extra.h']
110        test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
111        for header_file_name in header_file_names:
112            constructors.parse_header(header_file_name)
113        for test_cases in test_suites:
114            constructors.parse_test_cases(test_cases)
115        self.remove_unwanted_macros(constructors)
116        constructors.gather_arguments()
117        return constructors
118
119
120def test_case_for_key_type_not_supported(
121        verb: str, key_type: str, bits: int,
122        dependencies: List[str],
123        *args: str,
124        param_descr: str = ''
125) -> test_case.TestCase:
126    """Return one test case exercising a key creation method
127    for an unsupported key type or size.
128    """
129    hack_dependencies_not_implemented(dependencies)
130    tc = test_case.TestCase()
131    short_key_type = crypto_knowledge.short_expression(key_type)
132    adverb = 'not' if dependencies else 'never'
133    if param_descr:
134        adverb = param_descr + ' ' + adverb
135    tc.set_description('PSA {} {} {}-bit {} supported'
136                       .format(verb, short_key_type, bits, adverb))
137    tc.set_dependencies(dependencies)
138    tc.set_function(verb + '_not_supported')
139    tc.set_arguments([key_type] + list(args))
140    return tc
141
142class KeyTypeNotSupported:
143    """Generate test cases for when a key type is not supported."""
144
145    def __init__(self, info: Information) -> None:
146        self.constructors = info.constructors
147
148    ALWAYS_SUPPORTED = frozenset([
149        'PSA_KEY_TYPE_DERIVE',
150        'PSA_KEY_TYPE_RAW_DATA',
151    ])
152    def test_cases_for_key_type_not_supported(
153            self,
154            kt: crypto_knowledge.KeyType,
155            param: Optional[int] = None,
156            param_descr: str = '',
157    ) -> Iterator[test_case.TestCase]:
158        """Return test cases exercising key creation when the given type is unsupported.
159
160        If param is present and not None, emit test cases conditioned on this
161        parameter not being supported. If it is absent or None, emit test cases
162        conditioned on the base type not being supported.
163        """
164        if kt.name in self.ALWAYS_SUPPORTED:
165            # Don't generate test cases for key types that are always supported.
166            # They would be skipped in all configurations, which is noise.
167            return
168        import_dependencies = [('!' if param is None else '') +
169                               psa_want_symbol(kt.name)]
170        if kt.params is not None:
171            import_dependencies += [('!' if param == i else '') +
172                                    psa_want_symbol(sym)
173                                    for i, sym in enumerate(kt.params)]
174        if kt.name.endswith('_PUBLIC_KEY'):
175            generate_dependencies = []
176        else:
177            generate_dependencies = import_dependencies
178        for bits in kt.sizes_to_test():
179            yield test_case_for_key_type_not_supported(
180                'import', kt.expression, bits,
181                finish_family_dependencies(import_dependencies, bits),
182                test_case.hex_string(kt.key_material(bits)),
183                param_descr=param_descr,
184            )
185            if not generate_dependencies and param is not None:
186                # If generation is impossible for this key type, rather than
187                # supported or not depending on implementation capabilities,
188                # only generate the test case once.
189                continue
190                # For public key we expect that key generation fails with
191                # INVALID_ARGUMENT. It is handled by KeyGenerate class.
192            if not kt.is_public():
193                yield test_case_for_key_type_not_supported(
194                    'generate', kt.expression, bits,
195                    finish_family_dependencies(generate_dependencies, bits),
196                    str(bits),
197                    param_descr=param_descr,
198                )
199            # To be added: derive
200
201    ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
202                     'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
203
204    def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
205        """Generate test cases that exercise the creation of keys of unsupported types."""
206        for key_type in sorted(self.constructors.key_types):
207            if key_type in self.ECC_KEY_TYPES:
208                continue
209            kt = crypto_knowledge.KeyType(key_type)
210            yield from self.test_cases_for_key_type_not_supported(kt)
211        for curve_family in sorted(self.constructors.ecc_curves):
212            for constr in self.ECC_KEY_TYPES:
213                kt = crypto_knowledge.KeyType(constr, [curve_family])
214                yield from self.test_cases_for_key_type_not_supported(
215                    kt, param_descr='type')
216                yield from self.test_cases_for_key_type_not_supported(
217                    kt, 0, param_descr='curve')
218
219def test_case_for_key_generation(
220        key_type: str, bits: int,
221        dependencies: List[str],
222        *args: str,
223        result: str = ''
224) -> test_case.TestCase:
225    """Return one test case exercising a key generation.
226    """
227    hack_dependencies_not_implemented(dependencies)
228    tc = test_case.TestCase()
229    short_key_type = crypto_knowledge.short_expression(key_type)
230    tc.set_description('PSA {} {}-bit'
231                       .format(short_key_type, bits))
232    tc.set_dependencies(dependencies)
233    tc.set_function('generate_key')
234    tc.set_arguments([key_type] + list(args) + [result])
235
236    return tc
237
238class KeyGenerate:
239    """Generate positive and negative (invalid argument) test cases for key generation."""
240
241    def __init__(self, info: Information) -> None:
242        self.constructors = info.constructors
243
244    ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
245                     'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
246
247    @staticmethod
248    def test_cases_for_key_type_key_generation(
249            kt: crypto_knowledge.KeyType
250    ) -> Iterator[test_case.TestCase]:
251        """Return test cases exercising key generation.
252
253        All key types can be generated except for public keys. For public key
254        PSA_ERROR_INVALID_ARGUMENT status is expected.
255        """
256        result = 'PSA_SUCCESS'
257
258        import_dependencies = [psa_want_symbol(kt.name)]
259        if kt.params is not None:
260            import_dependencies += [psa_want_symbol(sym)
261                                    for i, sym in enumerate(kt.params)]
262        if kt.name.endswith('_PUBLIC_KEY'):
263            # The library checks whether the key type is a public key generically,
264            # before it reaches a point where it needs support for the specific key
265            # type, so it returns INVALID_ARGUMENT for unsupported public key types.
266            generate_dependencies = []
267            result = 'PSA_ERROR_INVALID_ARGUMENT'
268        else:
269            generate_dependencies = import_dependencies
270            if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
271                generate_dependencies.append("MBEDTLS_GENPRIME")
272        for bits in kt.sizes_to_test():
273            yield test_case_for_key_generation(
274                kt.expression, bits,
275                finish_family_dependencies(generate_dependencies, bits),
276                str(bits),
277                result
278            )
279
280    def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
281        """Generate test cases that exercise the generation of keys."""
282        for key_type in sorted(self.constructors.key_types):
283            if key_type in self.ECC_KEY_TYPES:
284                continue
285            kt = crypto_knowledge.KeyType(key_type)
286            yield from self.test_cases_for_key_type_key_generation(kt)
287        for curve_family in sorted(self.constructors.ecc_curves):
288            for constr in self.ECC_KEY_TYPES:
289                kt = crypto_knowledge.KeyType(constr, [curve_family])
290                yield from self.test_cases_for_key_type_key_generation(kt)
291
292class OpFail:
293    """Generate test cases for operations that must fail."""
294    #pylint: disable=too-few-public-methods
295
296    class Reason(enum.Enum):
297        NOT_SUPPORTED = 0
298        INVALID = 1
299        INCOMPATIBLE = 2
300        PUBLIC = 3
301
302    def __init__(self, info: Information) -> None:
303        self.constructors = info.constructors
304        key_type_expressions = self.constructors.generate_expressions(
305            sorted(self.constructors.key_types)
306        )
307        self.key_types = [crypto_knowledge.KeyType(kt_expr)
308                          for kt_expr in key_type_expressions]
309
310    def make_test_case(
311            self,
312            alg: crypto_knowledge.Algorithm,
313            category: crypto_knowledge.AlgorithmCategory,
314            reason: 'Reason',
315            kt: Optional[crypto_knowledge.KeyType] = None,
316            not_deps: FrozenSet[str] = frozenset(),
317    ) -> test_case.TestCase:
318        """Construct a failure test case for a one-key or keyless operation."""
319        #pylint: disable=too-many-arguments,too-many-locals
320        tc = test_case.TestCase()
321        pretty_alg = alg.short_expression()
322        if reason == self.Reason.NOT_SUPPORTED:
323            short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
324                          for dep in not_deps]
325            pretty_reason = '!' + '&'.join(sorted(short_deps))
326        else:
327            pretty_reason = reason.name.lower()
328        if kt:
329            key_type = kt.expression
330            pretty_type = kt.short_expression()
331        else:
332            key_type = ''
333            pretty_type = ''
334        tc.set_description('PSA {} {}: {}{}'
335                           .format(category.name.lower(),
336                                   pretty_alg,
337                                   pretty_reason,
338                                   ' with ' + pretty_type if pretty_type else ''))
339        dependencies = automatic_dependencies(alg.base_expression, key_type)
340        for i, dep in enumerate(dependencies):
341            if dep in not_deps:
342                dependencies[i] = '!' + dep
343        tc.set_dependencies(dependencies)
344        tc.set_function(category.name.lower() + '_fail')
345        arguments = [] # type: List[str]
346        if kt:
347            key_material = kt.key_material(kt.sizes_to_test()[0])
348            arguments += [key_type, test_case.hex_string(key_material)]
349        arguments.append(alg.expression)
350        if category.is_asymmetric():
351            arguments.append('1' if reason == self.Reason.PUBLIC else '0')
352        error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
353                 'INVALID_ARGUMENT')
354        arguments.append('PSA_ERROR_' + error)
355        tc.set_arguments(arguments)
356        return tc
357
358    def no_key_test_cases(
359            self,
360            alg: crypto_knowledge.Algorithm,
361            category: crypto_knowledge.AlgorithmCategory,
362    ) -> Iterator[test_case.TestCase]:
363        """Generate failure test cases for keyless operations with the specified algorithm."""
364        if alg.can_do(category):
365            # Compatible operation, unsupported algorithm
366            for dep in automatic_dependencies(alg.base_expression):
367                yield self.make_test_case(alg, category,
368                                          self.Reason.NOT_SUPPORTED,
369                                          not_deps=frozenset([dep]))
370        else:
371            # Incompatible operation, supported algorithm
372            yield self.make_test_case(alg, category, self.Reason.INVALID)
373
374    def one_key_test_cases(
375            self,
376            alg: crypto_knowledge.Algorithm,
377            category: crypto_knowledge.AlgorithmCategory,
378    ) -> Iterator[test_case.TestCase]:
379        """Generate failure test cases for one-key operations with the specified algorithm."""
380        for kt in self.key_types:
381            key_is_compatible = kt.can_do(alg)
382            if key_is_compatible and alg.can_do(category):
383                # Compatible key and operation, unsupported algorithm
384                for dep in automatic_dependencies(alg.base_expression):
385                    yield self.make_test_case(alg, category,
386                                              self.Reason.NOT_SUPPORTED,
387                                              kt=kt, not_deps=frozenset([dep]))
388                # Public key for a private-key operation
389                if category.is_asymmetric() and kt.is_public():
390                    yield self.make_test_case(alg, category,
391                                              self.Reason.PUBLIC,
392                                              kt=kt)
393            elif key_is_compatible:
394                # Compatible key, incompatible operation, supported algorithm
395                yield self.make_test_case(alg, category,
396                                          self.Reason.INVALID,
397                                          kt=kt)
398            elif alg.can_do(category):
399                # Incompatible key, compatible operation, supported algorithm
400                yield self.make_test_case(alg, category,
401                                          self.Reason.INCOMPATIBLE,
402                                          kt=kt)
403            else:
404                # Incompatible key and operation. Don't test cases where
405                # multiple things are wrong, to keep the number of test
406                # cases reasonable.
407                pass
408
409    def test_cases_for_algorithm(
410            self,
411            alg: crypto_knowledge.Algorithm,
412    ) -> Iterator[test_case.TestCase]:
413        """Generate operation failure test cases for the specified algorithm."""
414        for category in crypto_knowledge.AlgorithmCategory:
415            if category == crypto_knowledge.AlgorithmCategory.PAKE:
416                # PAKE operations are not implemented yet
417                pass
418            elif category.requires_key():
419                yield from self.one_key_test_cases(alg, category)
420            else:
421                yield from self.no_key_test_cases(alg, category)
422
423    def all_test_cases(self) -> Iterator[test_case.TestCase]:
424        """Generate all test cases for operations that must fail."""
425        algorithms = sorted(self.constructors.algorithms)
426        for expr in self.constructors.generate_expressions(algorithms):
427            alg = crypto_knowledge.Algorithm(expr)
428            yield from self.test_cases_for_algorithm(alg)
429
430
431class StorageKey(psa_storage.Key):
432    """Representation of a key for storage format testing."""
433
434    IMPLICIT_USAGE_FLAGS = {
435        'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
436        'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
437    } #type: Dict[str, str]
438    """Mapping of usage flags to the flags that they imply."""
439
440    def __init__(
441            self,
442            usage: Iterable[str],
443            without_implicit_usage: Optional[bool] = False,
444            **kwargs
445    ) -> None:
446        """Prepare to generate a key.
447
448        * `usage`                 : The usage flags used for the key.
449        * `without_implicit_usage`: Flag to define to apply the usage extension
450        """
451        usage_flags = set(usage)
452        if not without_implicit_usage:
453            for flag in sorted(usage_flags):
454                if flag in self.IMPLICIT_USAGE_FLAGS:
455                    usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
456        if usage_flags:
457            usage_expression = ' | '.join(sorted(usage_flags))
458        else:
459            usage_expression = '0'
460        super().__init__(usage=usage_expression, **kwargs)
461
462class StorageTestData(StorageKey):
463    """Representation of test case data for storage format testing."""
464
465    def __init__(
466            self,
467            description: str,
468            expected_usage: Optional[List[str]] = None,
469            **kwargs
470    ) -> None:
471        """Prepare to generate test data
472
473        * `description`   : used for the the test case names
474        * `expected_usage`: the usage flags generated as the expected usage flags
475                            in the test cases. CAn differ from the usage flags
476                            stored in the keys because of the usage flags extension.
477        """
478        super().__init__(**kwargs)
479        self.description = description #type: str
480        if expected_usage is None:
481            self.expected_usage = self.usage #type: psa_storage.Expr
482        elif expected_usage:
483            self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
484        else:
485            self.expected_usage = psa_storage.Expr(0)
486
487class StorageFormat:
488    """Storage format stability test cases."""
489
490    def __init__(self, info: Information, version: int, forward: bool) -> None:
491        """Prepare to generate test cases for storage format stability.
492
493        * `info`: information about the API. See the `Information` class.
494        * `version`: the storage format version to generate test cases for.
495        * `forward`: if true, generate forward compatibility test cases which
496          save a key and check that its representation is as intended. Otherwise
497          generate backward compatibility test cases which inject a key
498          representation and check that it can be read and used.
499        """
500        self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
501        self.version = version #type: int
502        self.forward = forward #type: bool
503
504    RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
505    BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
506    @classmethod
507    def exercise_key_with_algorithm(
508            cls,
509            key_type: psa_storage.Expr, bits: int,
510            alg: psa_storage.Expr
511    ) -> bool:
512        """Whether to exercise the given key with the given algorithm.
513
514        Normally only the type and algorithm matter for compatibility, and
515        this is handled in crypto_knowledge.KeyType.can_do(). This function
516        exists to detect exceptional cases. Exceptional cases detected here
517        are not tested in OpFail and should therefore have manually written
518        test cases.
519        """
520        # Some test keys have the RAW_DATA type and attributes that don't
521        # necessarily make sense. We do this to validate numerical
522        # encodings of the attributes.
523        # Raw data keys have no useful exercise anyway so there is no
524        # loss of test coverage.
525        if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
526            return False
527        # Mbed TLS only supports 128-bit keys for RC4.
528        if key_type.string == 'PSA_KEY_TYPE_ARC4' and bits != 128:
529            return False
530        # OAEP requires room for two hashes plus wrapping
531        m = cls.RSA_OAEP_RE.match(alg.string)
532        if m:
533            hash_alg = m.group(1)
534            hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
535            key_length = (bits + 7) // 8
536            # Leave enough room for at least one byte of plaintext
537            return key_length > 2 * hash_length + 2
538        # There's nothing wrong with ECC keys on Brainpool curves,
539        # but operations with them are very slow. So we only exercise them
540        # with a single algorithm, not with all possible hashes. We do
541        # exercise other curves with all algorithms so test coverage is
542        # perfectly adequate like this.
543        m = cls.BRAINPOOL_RE.match(key_type.string)
544        if m and alg.string != 'PSA_ALG_ECDSA_ANY':
545            return False
546        return True
547
548    def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
549        """Construct a storage format test case for the given key.
550
551        If ``forward`` is true, generate a forward compatibility test case:
552        create a key and validate that it has the expected representation.
553        Otherwise generate a backward compatibility test case: inject the
554        key representation into storage and validate that it can be read
555        correctly.
556        """
557        verb = 'save' if self.forward else 'read'
558        tc = test_case.TestCase()
559        tc.set_description(verb + ' ' + key.description)
560        dependencies = automatic_dependencies(
561            key.lifetime.string, key.type.string,
562            key.alg.string, key.alg2.string,
563        )
564        dependencies = finish_family_dependencies(dependencies, key.bits)
565        tc.set_dependencies(dependencies)
566        tc.set_function('key_storage_' + verb)
567        if self.forward:
568            extra_arguments = []
569        else:
570            flags = []
571            if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
572                flags.append('TEST_FLAG_EXERCISE')
573            if 'READ_ONLY' in key.lifetime.string:
574                flags.append('TEST_FLAG_READ_ONLY')
575            extra_arguments = [' | '.join(flags) if flags else '0']
576        tc.set_arguments([key.lifetime.string,
577                          key.type.string, str(key.bits),
578                          key.expected_usage.string,
579                          key.alg.string, key.alg2.string,
580                          '"' + key.material.hex() + '"',
581                          '"' + key.hex() + '"',
582                          *extra_arguments])
583        return tc
584
585    def key_for_lifetime(
586            self,
587            lifetime: str,
588    ) -> StorageTestData:
589        """Construct a test key for the given lifetime."""
590        short = lifetime
591        short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
592                       r'', short)
593        short = crypto_knowledge.short_expression(short)
594        description = 'lifetime: ' + short
595        key = StorageTestData(version=self.version,
596                              id=1, lifetime=lifetime,
597                              type='PSA_KEY_TYPE_RAW_DATA', bits=8,
598                              usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
599                              material=b'L',
600                              description=description)
601        return key
602
603    def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
604        """Generate test keys covering lifetimes."""
605        lifetimes = sorted(self.constructors.lifetimes)
606        expressions = self.constructors.generate_expressions(lifetimes)
607        for lifetime in expressions:
608            # Don't attempt to create or load a volatile key in storage
609            if 'VOLATILE' in lifetime:
610                continue
611            # Don't attempt to create a read-only key in storage,
612            # but do attempt to load one.
613            if 'READ_ONLY' in lifetime and self.forward:
614                continue
615            yield self.key_for_lifetime(lifetime)
616
617    def key_for_usage_flags(
618            self,
619            usage_flags: List[str],
620            short: Optional[str] = None,
621            test_implicit_usage: Optional[bool] = True
622    ) -> StorageTestData:
623        """Construct a test key for the given key usage."""
624        extra_desc = ' without implication' if test_implicit_usage else ''
625        description = 'usage' + extra_desc + ': '
626        key1 = StorageTestData(version=self.version,
627                               id=1, lifetime=0x00000001,
628                               type='PSA_KEY_TYPE_RAW_DATA', bits=8,
629                               expected_usage=usage_flags,
630                               without_implicit_usage=not test_implicit_usage,
631                               usage=usage_flags, alg=0, alg2=0,
632                               material=b'K',
633                               description=description)
634        if short is None:
635            usage_expr = key1.expected_usage.string
636            key1.description += crypto_knowledge.short_expression(usage_expr)
637        else:
638            key1.description += short
639        return key1
640
641    def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
642        """Generate test keys covering usage flags."""
643        known_flags = sorted(self.constructors.key_usage_flags)
644        yield self.key_for_usage_flags(['0'], **kwargs)
645        for usage_flag in known_flags:
646            yield self.key_for_usage_flags([usage_flag], **kwargs)
647        for flag1, flag2 in zip(known_flags,
648                                known_flags[1:] + [known_flags[0]]):
649            yield self.key_for_usage_flags([flag1, flag2], **kwargs)
650
651    def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
652        known_flags = sorted(self.constructors.key_usage_flags)
653        yield self.key_for_usage_flags(known_flags, short='all known')
654
655    def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
656        yield from self.generate_keys_for_usage_flags()
657        yield from self.generate_key_for_all_usage_flags()
658
659    def key_for_type_and_alg(
660            self,
661            kt: crypto_knowledge.KeyType,
662            bits: int,
663            alg: Optional[crypto_knowledge.Algorithm] = None,
664    ) -> StorageTestData:
665        """Construct a test key of the given type.
666
667        If alg is not None, this key allows it.
668        """
669        usage_flags = ['PSA_KEY_USAGE_EXPORT']
670        alg1 = 0 #type: psa_storage.Exprable
671        alg2 = 0
672        if alg is not None:
673            alg1 = alg.expression
674            usage_flags += alg.usage_flags(public=kt.is_public())
675        key_material = kt.key_material(bits)
676        description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
677        if alg is not None:
678            description += ', ' + alg.short_expression(1)
679        key = StorageTestData(version=self.version,
680                              id=1, lifetime=0x00000001,
681                              type=kt.expression, bits=bits,
682                              usage=usage_flags, alg=alg1, alg2=alg2,
683                              material=key_material,
684                              description=description)
685        return key
686
687    def keys_for_type(
688            self,
689            key_type: str,
690            all_algorithms: List[crypto_knowledge.Algorithm],
691    ) -> Iterator[StorageTestData]:
692        """Generate test keys for the given key type."""
693        kt = crypto_knowledge.KeyType(key_type)
694        for bits in kt.sizes_to_test():
695            # Test a non-exercisable key, as well as exercisable keys for
696            # each compatible algorithm.
697            # To do: test reading a key from storage with an incompatible
698            # or unsupported algorithm.
699            yield self.key_for_type_and_alg(kt, bits)
700            compatible_algorithms = [alg for alg in all_algorithms
701                                     if kt.can_do(alg)]
702            for alg in compatible_algorithms:
703                yield self.key_for_type_and_alg(kt, bits, alg)
704
705    def all_keys_for_types(self) -> Iterator[StorageTestData]:
706        """Generate test keys covering key types and their representations."""
707        key_types = sorted(self.constructors.key_types)
708        all_algorithms = [crypto_knowledge.Algorithm(alg)
709                          for alg in self.constructors.generate_expressions(
710                              sorted(self.constructors.algorithms)
711                          )]
712        for key_type in self.constructors.generate_expressions(key_types):
713            yield from self.keys_for_type(key_type, all_algorithms)
714
715    def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
716        """Generate test keys for the encoding of the specified algorithm."""
717        # These test cases only validate the encoding of algorithms, not
718        # whether the key read from storage is suitable for an operation.
719        # `keys_for_types` generate read tests with an algorithm and a
720        # compatible key.
721        descr = crypto_knowledge.short_expression(alg, 1)
722        usage = ['PSA_KEY_USAGE_EXPORT']
723        key1 = StorageTestData(version=self.version,
724                               id=1, lifetime=0x00000001,
725                               type='PSA_KEY_TYPE_RAW_DATA', bits=8,
726                               usage=usage, alg=alg, alg2=0,
727                               material=b'K',
728                               description='alg: ' + descr)
729        yield key1
730        key2 = StorageTestData(version=self.version,
731                               id=1, lifetime=0x00000001,
732                               type='PSA_KEY_TYPE_RAW_DATA', bits=8,
733                               usage=usage, alg=0, alg2=alg,
734                               material=b'L',
735                               description='alg2: ' + descr)
736        yield key2
737
738    def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
739        """Generate test keys covering algorithm encodings."""
740        algorithms = sorted(self.constructors.algorithms)
741        for alg in self.constructors.generate_expressions(algorithms):
742            yield from self.keys_for_algorithm(alg)
743
744    def generate_all_keys(self) -> Iterator[StorageTestData]:
745        """Generate all keys for the test cases."""
746        yield from self.all_keys_for_lifetimes()
747        yield from self.all_keys_for_usage_flags()
748        yield from self.all_keys_for_types()
749        yield from self.all_keys_for_algorithms()
750
751    def all_test_cases(self) -> Iterator[test_case.TestCase]:
752        """Generate all storage format test cases."""
753        # First build a list of all keys, then construct all the corresponding
754        # test cases. This allows all required information to be obtained in
755        # one go, which is a significant performance gain as the information
756        # includes numerical values obtained by compiling a C program.
757        all_keys = list(self.generate_all_keys())
758        for key in all_keys:
759            if key.location_value() != 0:
760                # Skip keys with a non-default location, because they
761                # require a driver and we currently have no mechanism to
762                # determine whether a driver is available.
763                continue
764            yield self.make_test_case(key)
765
766class StorageFormatForward(StorageFormat):
767    """Storage format stability test cases for forward compatibility."""
768
769    def __init__(self, info: Information, version: int) -> None:
770        super().__init__(info, version, True)
771
772class StorageFormatV0(StorageFormat):
773    """Storage format stability test cases for version 0 compatibility."""
774
775    def __init__(self, info: Information) -> None:
776        super().__init__(info, 0, False)
777
778    def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
779        """Generate test keys covering usage flags."""
780        yield from super().all_keys_for_usage_flags()
781        yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
782
783    def keys_for_implicit_usage(
784            self,
785            implyer_usage: str,
786            alg: str,
787            key_type: crypto_knowledge.KeyType
788    ) -> StorageTestData:
789        # pylint: disable=too-many-locals
790        """Generate test keys for the specified implicit usage flag,
791           algorithm and key type combination.
792        """
793        bits = key_type.sizes_to_test()[0]
794        implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
795        usage_flags = ['PSA_KEY_USAGE_EXPORT']
796        material_usage_flags = usage_flags + [implyer_usage]
797        expected_usage_flags = material_usage_flags + [implicit_usage]
798        alg2 = 0
799        key_material = key_type.key_material(bits)
800        usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
801        alg_expression = crypto_knowledge.short_expression(alg, 1)
802        key_type_expression = key_type.short_expression(1)
803        description = 'implied by {}: {} {} {}-bit'.format(
804            usage_expression, alg_expression, key_type_expression, bits)
805        key = StorageTestData(version=self.version,
806                              id=1, lifetime=0x00000001,
807                              type=key_type.expression, bits=bits,
808                              usage=material_usage_flags,
809                              expected_usage=expected_usage_flags,
810                              without_implicit_usage=True,
811                              alg=alg, alg2=alg2,
812                              material=key_material,
813                              description=description)
814        return key
815
816    def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
817        # pylint: disable=too-many-locals
818        """Match possible key types for sign algorithms."""
819        # To create a valid combination both the algorithms and key types
820        # must be filtered. Pair them with keywords created from its names.
821        incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
822        incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
823        keyword_translation = {
824            'ECDSA': 'ECC',
825            'ED[0-9]*.*' : 'EDWARDS'
826        }
827        exclusive_keywords = {
828            'EDWARDS': 'ECC'
829        }
830        key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
831        algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
832        alg_with_keys = {} #type: Dict[str, List[str]]
833        translation_table = str.maketrans('(', '_', ')')
834        for alg in algorithms:
835            # Generate keywords from the name of the algorithm
836            alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
837            # Translate keywords for better matching with the key types
838            for keyword in alg_keywords.copy():
839                for pattern, replace in keyword_translation.items():
840                    if re.match(pattern, keyword):
841                        alg_keywords.remove(keyword)
842                        alg_keywords.add(replace)
843            # Filter out incompatible algorithms
844            if not alg_keywords.isdisjoint(incompatible_alg_keyword):
845                continue
846
847            for key_type in key_types:
848                # Generate keywords from the of the key type
849                key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
850
851                # Remove ambiguous keywords
852                for keyword1, keyword2 in exclusive_keywords.items():
853                    if keyword1 in key_type_keywords:
854                        key_type_keywords.remove(keyword2)
855
856                if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
857                   not key_type_keywords.isdisjoint(alg_keywords):
858                    if alg in alg_with_keys:
859                        alg_with_keys[alg].append(key_type)
860                    else:
861                        alg_with_keys[alg] = [key_type]
862        return alg_with_keys
863
864    def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
865        """Generate test keys for usage flag extensions."""
866        # Generate a key type and algorithm pair for each extendable usage
867        # flag to generate a valid key for exercising. The key is generated
868        # without usage extension to check the extension compatibility.
869        alg_with_keys = self.gather_key_types_for_sign_alg()
870
871        for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
872            for alg in sorted(alg_with_keys):
873                for key_type in sorted(alg_with_keys[alg]):
874                    # The key types must be filtered to fit the specific usage flag.
875                    kt = crypto_knowledge.KeyType(key_type)
876                    if kt.is_public() and '_SIGN_' in usage:
877                        # Can't sign with a public key
878                        continue
879                    yield self.keys_for_implicit_usage(usage, alg, kt)
880
881    def generate_all_keys(self) -> Iterator[StorageTestData]:
882        yield from super().generate_all_keys()
883        yield from self.all_keys_for_implicit_usage()
884
885class PSATestGenerator(test_data_generation.TestGenerator):
886    """Test generator subclass including PSA targets and info."""
887    # Note that targets whose names contain 'test_format' have their content
888    # validated by `abi_check.py`.
889    targets = {
890        'test_suite_psa_crypto_generate_key.generated':
891        lambda info: KeyGenerate(info).test_cases_for_key_generation(),
892        'test_suite_psa_crypto_not_supported.generated':
893        lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
894        'test_suite_psa_crypto_op_fail.generated':
895        lambda info: OpFail(info).all_test_cases(),
896        'test_suite_psa_crypto_storage_format.current':
897        lambda info: StorageFormatForward(info, 0).all_test_cases(),
898        'test_suite_psa_crypto_storage_format.v0':
899        lambda info: StorageFormatV0(info).all_test_cases(),
900    } #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
901
902    def __init__(self, options):
903        super().__init__(options)
904        self.info = Information()
905
906    def generate_target(self, name: str, *target_args) -> None:
907        super().generate_target(name, self.info)
908
909if __name__ == '__main__':
910    test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)
911