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