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