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