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