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