quickjs-tart

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

crypto_knowledge.py (23122B)


      1 """Knowledge about cryptographic mechanisms implemented in Mbed TLS.
      2 
      3 This module is entirely based on the PSA API.
      4 """
      5 
      6 # Copyright The Mbed TLS Contributors
      7 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
      8 #
      9 
     10 import enum
     11 import re
     12 from typing import FrozenSet, Iterable, List, Optional, Tuple, Dict
     13 
     14 from .asymmetric_key_data import ASYMMETRIC_KEY_DATA
     15 
     16 
     17 def short_expression(original: str, level: int = 0) -> str:
     18     """Abbreviate the expression, keeping it human-readable.
     19 
     20     If `level` is 0, just remove parts that are implicit from context,
     21     such as a leading ``PSA_KEY_TYPE_``.
     22     For larger values of `level`, also abbreviate some names in an
     23     unambiguous, but ad hoc way.
     24     """
     25     short = original
     26     short = re.sub(r'\bPSA_(?:ALG|DH_FAMILY|ECC_FAMILY|KEY_[A-Z]+)_', r'', short)
     27     short = re.sub(r' +', r'', short)
     28     if level >= 1:
     29         short = re.sub(r'PUBLIC_KEY\b', r'PUB', short)
     30         short = re.sub(r'KEY_PAIR\b', r'PAIR', short)
     31         short = re.sub(r'\bBRAINPOOL_P', r'BP', short)
     32         short = re.sub(r'\bMONTGOMERY\b', r'MGM', short)
     33         short = re.sub(r'AEAD_WITH_SHORTENED_TAG\b', r'AEAD_SHORT', short)
     34         short = re.sub(r'\bDETERMINISTIC_', r'DET_', short)
     35         short = re.sub(r'\bKEY_AGREEMENT\b', r'KA', short)
     36         short = re.sub(r'_PSK_TO_MS\b', r'_PSK2MS', short)
     37     return short
     38 
     39 
     40 BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
     41 BLOCK_MAC_MODES = frozenset(['CBC_MAC', 'CMAC'])
     42 BLOCK_CIPHER_MODES = frozenset([
     43     'CTR', 'CFB', 'OFB', 'XTS', 'CCM_STAR_NO_TAG',
     44     'ECB_NO_PADDING', 'CBC_NO_PADDING', 'CBC_PKCS7',
     45 ])
     46 BLOCK_AEAD_MODES = frozenset(['CCM', 'GCM'])
     47 
     48 class EllipticCurveCategory(enum.Enum):
     49     """Categorization of elliptic curve families.
     50 
     51     The category of a curve determines what algorithms are defined over it.
     52     """
     53 
     54     SHORT_WEIERSTRASS = 0
     55     MONTGOMERY = 1
     56     TWISTED_EDWARDS = 2
     57 
     58     @staticmethod
     59     def from_family(family: str) -> 'EllipticCurveCategory':
     60         if family == 'PSA_ECC_FAMILY_MONTGOMERY':
     61             return EllipticCurveCategory.MONTGOMERY
     62         if family == 'PSA_ECC_FAMILY_TWISTED_EDWARDS':
     63             return EllipticCurveCategory.TWISTED_EDWARDS
     64         # Default to SW, which most curves belong to.
     65         return EllipticCurveCategory.SHORT_WEIERSTRASS
     66 
     67 
     68 class KeyType:
     69     """Knowledge about a PSA key type."""
     70 
     71     def __init__(self, name: str, params: Optional[Iterable[str]] = None) -> None:
     72         """Analyze a key type.
     73 
     74         The key type must be specified in PSA syntax. In its simplest form,
     75         `name` is a string 'PSA_KEY_TYPE_xxx' which is the name of a PSA key
     76         type macro. For key types that take arguments, the arguments can
     77         be passed either through the optional argument `params` or by
     78         passing an expression of the form 'PSA_KEY_TYPE_xxx(param1, ...)'
     79         in `name` as a string.
     80         """
     81 
     82         self.name = name.strip()
     83         """The key type macro name (``PSA_KEY_TYPE_xxx``).
     84 
     85         For key types constructed from a macro with arguments, this is the
     86         name of the macro, and the arguments are in `self.params`.
     87         """
     88         if params is None:
     89             if '(' in self.name:
     90                 m = re.match(r'(\w+)\s*\((.*)\)\Z', self.name)
     91                 assert m is not None
     92                 self.name = m.group(1)
     93                 params = m.group(2).split(',')
     94         self.params = (None if params is None else
     95                        [param.strip() for param in params])
     96         """The parameters of the key type, if there are any.
     97 
     98         None if the key type is a macro without arguments.
     99         """
    100         assert re.match(r'PSA_KEY_TYPE_\w+\Z', self.name)
    101 
    102         self.expression = self.name
    103         """A C expression whose value is the key type encoding."""
    104         if self.params is not None:
    105             self.expression += '(' + ', '.join(self.params) + ')'
    106 
    107         m = re.match(r'PSA_KEY_TYPE_(\w+)', self.name)
    108         assert m
    109         self.head = re.sub(r'_(?:PUBLIC_KEY|KEY_PAIR)\Z', r'', m.group(1))
    110         """The key type macro name, with common prefixes and suffixes stripped."""
    111 
    112         self.private_type = re.sub(r'_PUBLIC_KEY\Z', r'_KEY_PAIR', self.name)
    113         """The key type macro name for the corresponding key pair type.
    114 
    115         For everything other than a public key type, this is the same as
    116         `self.name`.
    117         """
    118 
    119     def short_expression(self, level: int = 0) -> str:
    120         """Abbreviate the expression, keeping it human-readable.
    121 
    122         See `crypto_knowledge.short_expression`.
    123         """
    124         return short_expression(self.expression, level=level)
    125 
    126     def is_public(self) -> bool:
    127         """Whether the key type is for public keys."""
    128         return self.name.endswith('_PUBLIC_KEY')
    129 
    130     DH_KEY_SIZES = {
    131         'PSA_DH_FAMILY_RFC7919': (2048, 3072, 4096, 6144, 8192),
    132     } # type: Dict[str, Tuple[int, ...]]
    133     ECC_KEY_SIZES = {
    134         'PSA_ECC_FAMILY_SECP_K1': (192, 225, 256),
    135         'PSA_ECC_FAMILY_SECP_R1': (224, 256, 384, 521),
    136         'PSA_ECC_FAMILY_SECP_R2': (160,),
    137         'PSA_ECC_FAMILY_SECT_K1': (163, 233, 239, 283, 409, 571),
    138         'PSA_ECC_FAMILY_SECT_R1': (163, 233, 283, 409, 571),
    139         'PSA_ECC_FAMILY_SECT_R2': (163,),
    140         'PSA_ECC_FAMILY_BRAINPOOL_P_R1': (160, 192, 224, 256, 320, 384, 512),
    141         'PSA_ECC_FAMILY_MONTGOMERY': (255, 448),
    142         'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
    143     } # type: Dict[str, Tuple[int, ...]]
    144     KEY_TYPE_SIZES = {
    145         'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
    146         'PSA_KEY_TYPE_ARIA': (128, 192, 256), # exhaustive
    147         'PSA_KEY_TYPE_CAMELLIA': (128, 192, 256), # exhaustive
    148         'PSA_KEY_TYPE_CHACHA20': (256,), # exhaustive
    149         'PSA_KEY_TYPE_DERIVE': (120, 128), # sample
    150         'PSA_KEY_TYPE_DES': (64, 128, 192), # exhaustive
    151         'PSA_KEY_TYPE_HMAC': (128, 160, 224, 256, 384, 512), # standard size for each supported hash
    152         'PSA_KEY_TYPE_PASSWORD': (48, 168, 336), # sample
    153         'PSA_KEY_TYPE_PASSWORD_HASH': (128, 256), # sample
    154         'PSA_KEY_TYPE_PEPPER': (128, 256), # sample
    155         'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
    156         'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
    157     } # type: Dict[str, Tuple[int, ...]]
    158     def sizes_to_test(self) -> Tuple[int, ...]:
    159         """Return a tuple of key sizes to test.
    160 
    161         For key types that only allow a single size, or only a small set of
    162         sizes, these are all the possible sizes. For key types that allow a
    163         wide range of sizes, these are a representative sample of sizes,
    164         excluding large sizes for which a typical resource-constrained platform
    165         may run out of memory.
    166         """
    167         if self.private_type == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
    168             assert self.params is not None
    169             return self.ECC_KEY_SIZES[self.params[0]]
    170         if self.private_type == 'PSA_KEY_TYPE_DH_KEY_PAIR':
    171             assert self.params is not None
    172             return self.DH_KEY_SIZES[self.params[0]]
    173         return self.KEY_TYPE_SIZES[self.private_type]
    174 
    175     # "48657265006973206b6579a064617461"
    176     DATA_BLOCK = b'Here\000is key\240data'
    177     def key_material(self, bits: int) -> bytes:
    178         """Return a byte string containing suitable key material with the given bit length.
    179 
    180         Use the PSA export representation. The resulting byte string is one that
    181         can be obtained with the following code:
    182         ```
    183         psa_set_key_type(&attributes, `self.expression`);
    184         psa_set_key_bits(&attributes, `bits`);
    185         psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT);
    186         psa_generate_key(&attributes, &id);
    187         psa_export_key(id, `material`, ...);
    188         ```
    189         """
    190         if self.expression in ASYMMETRIC_KEY_DATA:
    191             if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
    192                 raise ValueError('No key data for {}-bit {}'
    193                                  .format(bits, self.expression))
    194             return ASYMMETRIC_KEY_DATA[self.expression][bits]
    195         if bits % 8 != 0:
    196             raise ValueError('Non-integer number of bytes: {} bits for {}'
    197                              .format(bits, self.expression))
    198         length = bits // 8
    199         if self.name == 'PSA_KEY_TYPE_DES':
    200             # "644573206b457901644573206b457902644573206b457904"
    201             des3 = b'dEs kEy\001dEs kEy\002dEs kEy\004'
    202             return des3[:length]
    203         return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
    204                         [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
    205 
    206     def can_do(self, alg: 'Algorithm') -> bool:
    207         """Whether this key type can be used for operations with the given algorithm.
    208 
    209         This function does not currently handle key derivation or PAKE.
    210         """
    211         #pylint: disable=too-many-branches,too-many-return-statements
    212         if not alg.is_valid_for_operation():
    213             return False
    214         if self.head == 'HMAC' and alg.head == 'HMAC':
    215             return True
    216         if self.head == 'DES':
    217             # 64-bit block ciphers only allow a reduced set of modes.
    218             return alg.head in [
    219                 'CBC_NO_PADDING', 'CBC_PKCS7',
    220                 'ECB_NO_PADDING',
    221             ]
    222         if self.head in BLOCK_CIPHERS and \
    223            alg.head in frozenset.union(BLOCK_MAC_MODES,
    224                                        BLOCK_CIPHER_MODES,
    225                                        BLOCK_AEAD_MODES):
    226             if alg.head in ['CMAC', 'OFB'] and \
    227                self.head in ['ARIA', 'CAMELLIA']:
    228                 return False # not implemented in Mbed TLS
    229             return True
    230         if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
    231             return True
    232         if self.head in {'ARC4', 'CHACHA20'} and \
    233            alg.head == 'STREAM_CIPHER':
    234             return True
    235         if self.head == 'RSA' and alg.head.startswith('RSA_'):
    236             return True
    237         if alg.category == AlgorithmCategory.KEY_AGREEMENT and \
    238            self.is_public():
    239             # The PSA API does not use public key objects in key agreement
    240             # operations: it imports the public key as a formatted byte string.
    241             # So a public key object with a key agreement algorithm is not
    242             # a valid combination.
    243             return False
    244         if alg.is_invalid_key_agreement_with_derivation():
    245             return False
    246         if self.head == 'ECC':
    247             assert self.params is not None
    248             eccc = EllipticCurveCategory.from_family(self.params[0])
    249             if alg.head == 'ECDH' and \
    250                eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
    251                         EllipticCurveCategory.MONTGOMERY}:
    252                 return True
    253             if alg.head == 'ECDSA' and \
    254                eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
    255                 return True
    256             if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
    257                eccc == EllipticCurveCategory.TWISTED_EDWARDS:
    258                 return True
    259         if self.head == 'DH' and alg.head == 'FFDH':
    260             return True
    261         return False
    262 
    263 
    264 class AlgorithmCategory(enum.Enum):
    265     """PSA algorithm categories."""
    266     # The numbers are aligned with the category bits in numerical values of
    267     # algorithms.
    268     HASH = 2
    269     MAC = 3
    270     CIPHER = 4
    271     AEAD = 5
    272     SIGN = 6
    273     ASYMMETRIC_ENCRYPTION = 7
    274     KEY_DERIVATION = 8
    275     KEY_AGREEMENT = 9
    276     PAKE = 10
    277 
    278     def requires_key(self) -> bool:
    279         """Whether operations in this category are set up with a key."""
    280         return self not in {self.HASH, self.KEY_DERIVATION}
    281 
    282     def is_asymmetric(self) -> bool:
    283         """Whether operations in this category involve asymmetric keys."""
    284         return self in {
    285             self.SIGN,
    286             self.ASYMMETRIC_ENCRYPTION,
    287             self.KEY_AGREEMENT
    288         }
    289 
    290 
    291 class AlgorithmNotRecognized(Exception):
    292     def __init__(self, expr: str) -> None:
    293         super().__init__('Algorithm not recognized: ' + expr)
    294         self.expr = expr
    295 
    296 
    297 class Algorithm:
    298     """Knowledge about a PSA algorithm."""
    299 
    300     @staticmethod
    301     def determine_base(expr: str) -> str:
    302         """Return an expression for the "base" of the algorithm.
    303 
    304         This strips off variants of algorithms such as MAC truncation.
    305 
    306         This function does not attempt to detect invalid inputs.
    307         """
    308         m = re.match(r'PSA_ALG_(?:'
    309                      r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
    310                      r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
    311                      r')\((.*),[^,]+\)\Z', expr)
    312         if m:
    313             expr = m.group(1)
    314         return expr
    315 
    316     @staticmethod
    317     def determine_head(expr: str) -> str:
    318         """Return the head of an algorithm expression.
    319 
    320         The head is the first (outermost) constructor, without its PSA_ALG_
    321         prefix, and with some normalization of similar algorithms.
    322         """
    323         m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
    324         if not m:
    325             raise AlgorithmNotRecognized(expr)
    326         head = m.group(1)
    327         if head == 'KEY_AGREEMENT':
    328             m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
    329             if not m:
    330                 raise AlgorithmNotRecognized(expr)
    331             head = m.group(1)
    332         head = re.sub(r'_ANY\Z', r'', head)
    333         if re.match(r'ED[0-9]+PH\Z', head):
    334             head = 'EDDSA_PREHASH'
    335         return head
    336 
    337     CATEGORY_FROM_HEAD = {
    338         'SHA': AlgorithmCategory.HASH,
    339         'SHAKE256_512': AlgorithmCategory.HASH,
    340         'MD': AlgorithmCategory.HASH,
    341         'RIPEMD': AlgorithmCategory.HASH,
    342         'ANY_HASH': AlgorithmCategory.HASH,
    343         'HMAC': AlgorithmCategory.MAC,
    344         'STREAM_CIPHER': AlgorithmCategory.CIPHER,
    345         'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
    346         'DSA': AlgorithmCategory.SIGN,
    347         'ECDSA': AlgorithmCategory.SIGN,
    348         'EDDSA': AlgorithmCategory.SIGN,
    349         'PURE_EDDSA': AlgorithmCategory.SIGN,
    350         'RSA_PSS': AlgorithmCategory.SIGN,
    351         'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
    352         'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
    353         'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
    354         'HKDF': AlgorithmCategory.KEY_DERIVATION,
    355         'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
    356         'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
    357         'TLS12_ECJPAKE_TO_PMS': AlgorithmCategory.KEY_DERIVATION,
    358         'PBKDF': AlgorithmCategory.KEY_DERIVATION,
    359         'ECDH': AlgorithmCategory.KEY_AGREEMENT,
    360         'FFDH': AlgorithmCategory.KEY_AGREEMENT,
    361         # KEY_AGREEMENT(...) is a key derivation with a key agreement component
    362         'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
    363         'JPAKE': AlgorithmCategory.PAKE,
    364     }
    365     for x in BLOCK_MAC_MODES:
    366         CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
    367     for x in BLOCK_CIPHER_MODES:
    368         CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
    369     for x in BLOCK_AEAD_MODES:
    370         CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
    371 
    372     def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
    373         """Return the category of the given algorithm expression.
    374 
    375         This function does not attempt to detect invalid inputs.
    376         """
    377         prefix = head
    378         while prefix:
    379             if prefix in self.CATEGORY_FROM_HEAD:
    380                 return self.CATEGORY_FROM_HEAD[prefix]
    381             if re.match(r'.*[0-9]\Z', prefix):
    382                 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
    383             else:
    384                 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
    385         raise AlgorithmNotRecognized(expr)
    386 
    387     @staticmethod
    388     def determine_wildcard(expr) -> bool:
    389         """Whether the given algorithm expression is a wildcard.
    390 
    391         This function does not attempt to detect invalid inputs.
    392         """
    393         if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
    394             return True
    395         if re.search(r'_AT_LEAST_', expr):
    396             return True
    397         return False
    398 
    399     def __init__(self, expr: str) -> None:
    400         """Analyze an algorithm value.
    401 
    402         The algorithm must be expressed as a C expression containing only
    403         calls to PSA algorithm constructor macros and numeric literals.
    404 
    405         This class is only programmed to handle valid expressions. Invalid
    406         expressions may result in exceptions or in nonsensical results.
    407         """
    408         self.expression = re.sub(r'\s+', r'', expr)
    409         self.base_expression = self.determine_base(self.expression)
    410         self.head = self.determine_head(self.base_expression)
    411         self.category = self.determine_category(self.base_expression, self.head)
    412         self.is_wildcard = self.determine_wildcard(self.expression)
    413 
    414     def get_key_agreement_derivation(self) -> Optional[str]:
    415         """For a combined key agreement and key derivation algorithm, get the derivation part.
    416 
    417         For anything else, return None.
    418         """
    419         if self.category != AlgorithmCategory.KEY_AGREEMENT:
    420             return None
    421         m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
    422         if not m:
    423             return None
    424         kdf_alg = m.group(1)
    425         # Assume kdf_alg is either a valid KDF or 0.
    426         if re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg):
    427             return None
    428         return kdf_alg
    429 
    430     KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT = frozenset([
    431         'PSA_ALG_TLS12_ECJPAKE_TO_PMS', # secret input in specific format
    432     ])
    433     def is_valid_key_agreement_with_derivation(self) -> bool:
    434         """Whether this is a valid combined key agreement and key derivation algorithm."""
    435         kdf_alg = self.get_key_agreement_derivation()
    436         if kdf_alg is None:
    437             return False
    438         return kdf_alg not in self.KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT
    439 
    440     def is_invalid_key_agreement_with_derivation(self) -> bool:
    441         """Whether this is an invalid combined key agreement and key derivation algorithm."""
    442         kdf_alg = self.get_key_agreement_derivation()
    443         if kdf_alg is None:
    444             return False
    445         return kdf_alg in self.KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT
    446 
    447     def short_expression(self, level: int = 0) -> str:
    448         """Abbreviate the expression, keeping it human-readable.
    449 
    450         See `crypto_knowledge.short_expression`.
    451         """
    452         return short_expression(self.expression, level=level)
    453 
    454     HASH_LENGTH = {
    455         'PSA_ALG_MD5': 16,
    456         'PSA_ALG_SHA_1': 20,
    457     }
    458     HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z')
    459     @classmethod
    460     def hash_length(cls, alg: str) -> int:
    461         """The length of the given hash algorithm, in bytes."""
    462         if alg in cls.HASH_LENGTH:
    463             return cls.HASH_LENGTH[alg]
    464         m = cls.HASH_LENGTH_BITS_RE.search(alg)
    465         if m:
    466             return int(m.group(1)) // 8
    467         raise ValueError('Unknown hash length for ' + alg)
    468 
    469     PERMITTED_TAG_LENGTHS = {
    470         'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
    471         'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
    472         'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
    473     }
    474     MAC_LENGTH = {
    475         'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
    476         'PSA_ALG_CMAC': 16, # actually the block cipher length
    477     }
    478     HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
    479     @classmethod
    480     def permitted_truncations(cls, base: str) -> FrozenSet[int]:
    481         """Permitted output lengths for the given MAC or AEAD base algorithm.
    482 
    483         For a MAC algorithm, this is the set of truncation lengths that
    484         Mbed TLS supports.
    485         For an AEAD algorithm, this is the set of truncation lengths that
    486         are permitted by the algorithm specification.
    487         """
    488         if base in cls.PERMITTED_TAG_LENGTHS:
    489             return cls.PERMITTED_TAG_LENGTHS[base]
    490         max_length = cls.MAC_LENGTH.get(base, None)
    491         if max_length is None:
    492             m = cls.HMAC_RE.match(base)
    493             if m:
    494                 max_length = cls.hash_length(m.group(1))
    495         if max_length is None:
    496             raise ValueError('Unknown permitted lengths for ' + base)
    497         return frozenset(range(4, max_length + 1))
    498 
    499     TRUNCATED_ALG_RE = re.compile(
    500         r'(?P<face>PSA_ALG_(?:AEAD_WITH_SHORTENED_TAG|TRUNCATED_MAC))'
    501         r'\((?P<base>.*),'
    502         r'(?P<length>0[Xx][0-9A-Fa-f]+|[1-9][0-9]*|0[0-7]*)[LUlu]*\)\Z')
    503     def is_invalid_truncation(self) -> bool:
    504         """False for a MAC or AEAD algorithm truncated to an invalid length.
    505 
    506         True for a MAC or AEAD algorithm truncated to a valid length or to
    507         a length that cannot be determined. True for anything other than
    508         a truncated MAC or AEAD.
    509         """
    510         m = self.TRUNCATED_ALG_RE.match(self.expression)
    511         if m:
    512             base = m.group('base')
    513             to_length = int(m.group('length'), 0)
    514             permitted_lengths = self.permitted_truncations(base)
    515             if to_length not in permitted_lengths:
    516                 return True
    517         return False
    518 
    519     def is_valid_for_operation(self) -> bool:
    520         """Whether this algorithm construction is valid for an operation.
    521 
    522         This function assumes that the algorithm is constructed in a
    523         "grammatically" correct way, and only rejects semantically invalid
    524         combinations.
    525         """
    526         if self.is_wildcard:
    527             return False
    528         if self.is_invalid_truncation():
    529             return False
    530         return True
    531 
    532     def can_do(self, category: AlgorithmCategory) -> bool:
    533         """Whether this algorithm can perform operations in the given category.
    534         """
    535         if category == self.category:
    536             return True
    537         if category == AlgorithmCategory.KEY_DERIVATION and \
    538            self.is_valid_key_agreement_with_derivation():
    539             return True
    540         return False
    541 
    542     def usage_flags(self, public: bool = False) -> List[str]:
    543         """The list of usage flags describing operations that can perform this algorithm.
    544 
    545         If public is true, only return public-key operations, not private-key operations.
    546         """
    547         if self.category == AlgorithmCategory.HASH:
    548             flags = []
    549         elif self.category == AlgorithmCategory.MAC:
    550             flags = ['SIGN_HASH', 'SIGN_MESSAGE',
    551                      'VERIFY_HASH', 'VERIFY_MESSAGE']
    552         elif self.category == AlgorithmCategory.CIPHER or \
    553              self.category == AlgorithmCategory.AEAD:
    554             flags = ['DECRYPT', 'ENCRYPT']
    555         elif self.category == AlgorithmCategory.SIGN:
    556             flags = ['VERIFY_HASH', 'VERIFY_MESSAGE']
    557             if not public:
    558                 flags += ['SIGN_HASH', 'SIGN_MESSAGE']
    559         elif self.category == AlgorithmCategory.ASYMMETRIC_ENCRYPTION:
    560             flags = ['ENCRYPT']
    561             if not public:
    562                 flags += ['DECRYPT']
    563         elif self.category == AlgorithmCategory.KEY_DERIVATION or \
    564              self.category == AlgorithmCategory.KEY_AGREEMENT:
    565             flags = ['DERIVE']
    566         else:
    567             raise AlgorithmNotRecognized(self.expression)
    568         return ['PSA_KEY_USAGE_' + flag for flag in flags]