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