quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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)