blob: ebfd55cdb329df84ffeadff5b9753ba70e1048ff [file] [log] [blame]
Gilles Peskine0156a152021-01-26 21:23:56 +01001"""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
Dave Rodgman16799db2023-11-02 19:47:20 +00007# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskine0156a152021-01-26 21:23:56 +01008#
Gilles Peskine0156a152021-01-26 21:23:56 +01009
Gilles Peskineee7554e2021-04-29 20:38:01 +020010import enum
Gilles Peskine0156a152021-01-26 21:23:56 +010011import re
David Horstmann79f14e32023-01-24 18:59:07 +000012from typing import FrozenSet, Iterable, List, Optional, Tuple, Dict
Gilles Peskine0156a152021-01-26 21:23:56 +010013
Gilles Peskine15997bd2022-09-16 22:35:18 +020014from .asymmetric_key_data import ASYMMETRIC_KEY_DATA
Gilles Peskine6f6483f2021-01-27 12:43:24 +010015
Gilles Peskineee7554e2021-04-29 20:38:01 +020016
Gilles Peskine16b25062022-03-18 00:02:15 +010017def short_expression(original: str, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +010018 """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
Manuel Pégourié-Gonnard636d8572023-07-18 11:00:36 +020026 short = re.sub(r'\bPSA_(?:ALG|DH_FAMILY|ECC_FAMILY|KEY_[A-Z]+)_', r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +010027 short = re.sub(r' +', r'', short)
Gilles Peskine16b25062022-03-18 00:02:15 +010028 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)
Gilles Peskinee8e058c2022-03-17 23:42:25 +010037 return short
38
39
Gilles Peskine8345d632021-04-29 20:38:47 +020040BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
Gilles Peskineee7554e2021-04-29 20:38:01 +020041BLOCK_MAC_MODES = frozenset(['CBC_MAC', 'CMAC'])
42BLOCK_CIPHER_MODES = frozenset([
43 'CTR', 'CFB', 'OFB', 'XTS', 'CCM_STAR_NO_TAG',
44 'ECB_NO_PADDING', 'CBC_NO_PADDING', 'CBC_PKCS7',
45])
46BLOCK_AEAD_MODES = frozenset(['CCM', 'GCM'])
47
Gilles Peskine8345d632021-04-29 20:38:47 +020048class 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
Gilles Peskineee7554e2021-04-29 20:38:01 +020067
Gilles Peskine0156a152021-01-26 21:23:56 +010068class KeyType:
69 """Knowledge about a PSA key type."""
70
Gilles Peskineb9dbb7f2021-04-29 20:19:57 +020071 def __init__(self, name: str, params: Optional[Iterable[str]] = None) -> None:
Gilles Peskine0156a152021-01-26 21:23:56 +010072 """Analyze a key type.
73
74 The key type must be specified in PSA syntax. In its simplest form,
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010075 `name` is a string 'PSA_KEY_TYPE_xxx' which is the name of a PSA key
Gilles Peskine0156a152021-01-26 21:23:56 +010076 type macro. For key types that take arguments, the arguments can
77 be passed either through the optional argument `params` or by
Gilles Peskine4d0b0892021-04-12 13:41:52 +020078 passing an expression of the form 'PSA_KEY_TYPE_xxx(param1, ...)'
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010079 in `name` as a string.
Gilles Peskine0156a152021-01-26 21:23:56 +010080 """
Gilles Peskined75adfc2021-02-17 18:04:28 +010081
Gilles Peskine0156a152021-01-26 21:23:56 +010082 self.name = name.strip()
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010083 """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 """
Gilles Peskine0156a152021-01-26 21:23:56 +010088 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)
Gilles Peskine4d0b0892021-04-12 13:41:52 +020093 params = m.group(2).split(',')
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010094 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 """
Gilles Peskined75adfc2021-02-17 18:04:28 +0100100 assert re.match(r'PSA_KEY_TYPE_\w+\Z', self.name)
101
Gilles Peskine0156a152021-01-26 21:23:56 +0100102 self.expression = self.name
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100103 """A C expression whose value is the key type encoding."""
Gilles Peskine0156a152021-01-26 21:23:56 +0100104 if self.params is not None:
105 self.expression += '(' + ', '.join(self.params) + ')'
Gilles Peskined75adfc2021-02-17 18:04:28 +0100106
Gilles Peskine8345d632021-04-29 20:38:47 +0200107 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
Gilles Peskine0156a152021-01-26 21:23:56 +0100112 self.private_type = re.sub(r'_PUBLIC_KEY\Z', r'_KEY_PAIR', self.name)
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100113 """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 """
Gilles Peskinedf639682021-01-26 21:25:34 +0100118
Gilles Peskine16b25062022-03-18 00:02:15 +0100119 def short_expression(self, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100120 """Abbreviate the expression, keeping it human-readable.
121
122 See `crypto_knowledge.short_expression`.
123 """
Gilles Peskine16b25062022-03-18 00:02:15 +0100124 return short_expression(self.expression, level=level)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100125
Gilles Peskinee6300952021-04-29 21:56:59 +0200126 def is_public(self) -> bool:
127 """Whether the key type is for public keys."""
128 return self.name.endswith('_PUBLIC_KEY')
129
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200130 DH_KEY_SIZES = {
131 'PSA_DH_FAMILY_RFC7919': (2048, 3072, 4096, 6144, 8192),
132 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100133 ECC_KEY_SIZES = {
Gilles Peskine478dd842024-01-03 20:50:56 +0100134 'PSA_ECC_FAMILY_SECP_K1': (192, 225, 256),
135 'PSA_ECC_FAMILY_SECP_R1': (224, 256, 384, 521),
Gilles Peskinedf639682021-01-26 21:25:34 +0100136 '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),
Gilles Peskinea00abc62021-03-16 18:25:14 +0100142 'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
David Horstmann79f14e32023-01-24 18:59:07 +0000143 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100144 KEY_TYPE_SIZES = {
145 'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
Gilles Peskinedf639682021-01-26 21:25:34 +0100146 '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
Manuel Pégourié-Gonnardb12de9f2021-05-03 11:02:56 +0200152 '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
Gilles Peskinedf639682021-01-26 21:25:34 +0100155 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
156 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
David Horstmann79f14e32023-01-24 18:59:07 +0000157 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100158 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]]
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200170 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]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100173 return self.KEY_TYPE_SIZES[self.private_type]
Gilles Peskine397b0282021-01-26 21:26:26 +0100174
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 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100190 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]
Gilles Peskine397b0282021-01-26 21:26:26 +0100195 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100196 raise ValueError('Non-integer number of bytes: {} bits for {}'
197 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100198 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]
Gilles Peskine397b0282021-01-26 21:26:26 +0100203 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
204 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200205
Gilles Peskine8345d632021-04-29 20:38:47 +0200206 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 """
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100211 #pylint: disable=too-many-branches,too-many-return-statements
Gilles Peskine72f41562022-12-15 22:41:34 +0100212 if not alg.is_valid_for_operation():
Gilles Peskinee3a08902022-03-19 10:37:33 +0100213 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200214 if self.head == 'HMAC' and alg.head == 'HMAC':
215 return True
Gilles Peskinec47d3a42022-03-18 10:18:58 +0100216 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 ]
Gilles Peskine8345d632021-04-29 20:38:47 +0200222 if self.head in BLOCK_CIPHERS and \
223 alg.head in frozenset.union(BLOCK_MAC_MODES,
224 BLOCK_CIPHER_MODES,
225 BLOCK_AEAD_MODES):
Gilles Peskine7095d472022-03-19 10:49:43 +0100226 if alg.head in ['CMAC', 'OFB'] and \
227 self.head in ['ARIA', 'CAMELLIA']:
228 return False # not implemented in Mbed TLS
Gilles Peskine8345d632021-04-29 20:38:47 +0200229 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
Gilles Peskineac17ec42022-03-19 12:16:45 +0100237 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
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100244 if alg.is_invalid_key_agreement_with_derivation():
245 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200246 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
Manuel Pégourié-Gonnard182eb152023-07-18 17:58:09 +0200259 if self.head == 'DH' and alg.head == 'FFDH':
260 return True
Gilles Peskine8345d632021-04-29 20:38:47 +0200261 return False
262
Gilles Peskineee7554e2021-04-29 20:38:01 +0200263
264class 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:
Gilles Peskinee6300952021-04-29 21:56:59 +0200279 """Whether operations in this category are set up with a key."""
Gilles Peskineee7554e2021-04-29 20:38:01 +0200280 return self not in {self.HASH, self.KEY_DERIVATION}
281
Gilles Peskinee6300952021-04-29 21:56:59 +0200282 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
Gilles Peskineee7554e2021-04-29 20:38:01 +0200290
291class AlgorithmNotRecognized(Exception):
292 def __init__(self, expr: str) -> None:
293 super().__init__('Algorithm not recognized: ' + expr)
294 self.expr = expr
295
296
297class 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,
Andrzej Kurek08d34b82022-07-29 10:00:16 -0400357 'TLS12_ECJPAKE_TO_PMS': AlgorithmCategory.KEY_DERIVATION,
Gilles Peskineee7554e2021-04-29 20:38:01 +0200358 '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)
Gilles Peskinea4013862021-04-29 20:54:40 +0200413
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100414 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 """
Gilles Peskinea4013862021-04-29 20:54:40 +0200419 if self.category != AlgorithmCategory.KEY_AGREEMENT:
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100420 return None
Gilles Peskinea4013862021-04-29 20:54:40 +0200421 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
422 if not m:
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100423 return None
Gilles Peskinea4013862021-04-29 20:54:40 +0200424 kdf_alg = m.group(1)
425 # Assume kdf_alg is either a valid KDF or 0.
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100426 if re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg):
427 return None
428 return kdf_alg
Gilles Peskinea4013862021-04-29 20:54:40 +0200429
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100430 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
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100446
Gilles Peskine16b25062022-03-18 00:02:15 +0100447 def short_expression(self, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100448 """Abbreviate the expression, keeping it human-readable.
449
450 See `crypto_knowledge.short_expression`.
451 """
Gilles Peskine16b25062022-03-18 00:02:15 +0100452 return short_expression(self.expression, level=level)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100453
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100454 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
Gilles Peskinee3a08902022-03-19 10:37:33 +0100469 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 = {
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100475 'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
476 'PSA_ALG_CMAC': 16, # actually the block cipher length
Gilles Peskinee3a08902022-03-19 10:37:33 +0100477 }
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100478 HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
Gilles Peskinee3a08902022-03-19 10:37:33 +0100479 @classmethod
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200480 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 """
Gilles Peskinee3a08902022-03-19 10:37:33 +0100488 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:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100492 m = cls.HMAC_RE.match(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100493 if m:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100494 max_length = cls.hash_length(m.group(1))
Gilles Peskinee3a08902022-03-19 10:37:33 +0100495 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>.*),'
Gilles Peskine2773f262022-04-05 16:31:16 +0200502 r'(?P<length>0[Xx][0-9A-Fa-f]+|[1-9][0-9]*|0[0-7]*)[LUlu]*\)\Z')
Gilles Peskinee3a08902022-03-19 10:37:33 +0100503 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)
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200514 permitted_lengths = self.permitted_truncations(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100515 if to_length not in permitted_lengths:
516 return True
517 return False
518
Gilles Peskine72f41562022-12-15 22:41:34 +0100519 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
Gilles Peskinea4013862021-04-29 20:54:40 +0200532 def can_do(self, category: AlgorithmCategory) -> bool:
Gilles Peskinee3a08902022-03-19 10:37:33 +0100533 """Whether this algorithm can perform operations in the given category.
534 """
Gilles Peskinea4013862021-04-29 20:54:40 +0200535 if category == self.category:
536 return True
537 if category == AlgorithmCategory.KEY_DERIVATION and \
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100538 self.is_valid_key_agreement_with_derivation():
Gilles Peskinea4013862021-04-29 20:54:40 +0200539 return True
540 return False
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100541
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]