blob: 4e9503e18ab1a9017adba363a44031b1c99ae015 [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
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
Gilles Peskine0dacd4d2021-04-29 20:38:01 +020021import enum
Gilles Peskine0156a152021-01-26 21:23:56 +010022import re
David Horstmann659d9052023-01-24 18:59:07 +000023from typing import FrozenSet, Iterable, List, Optional, Tuple, Dict
Gilles Peskine0156a152021-01-26 21:23:56 +010024
Gilles Peskine239765a2022-09-16 22:35:18 +020025from .asymmetric_key_data import ASYMMETRIC_KEY_DATA
Gilles Peskine6f6483f2021-01-27 12:43:24 +010026
Gilles Peskine0dacd4d2021-04-29 20:38:01 +020027
Gilles Peskine930ccef2022-03-18 00:02:15 +010028def short_expression(original: str, level: int = 0) -> str:
Gilles Peskined79aef52022-03-17 23:42:25 +010029 """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)
Gilles Peskine930ccef2022-03-18 00:02:15 +010039 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)
Gilles Peskined79aef52022-03-17 23:42:25 +010048 return short
49
50
Gilles Peskine39054332021-04-29 20:38:47 +020051BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
Gilles Peskine0dacd4d2021-04-29 20:38:01 +020052BLOCK_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
Gilles Peskine39054332021-04-29 20:38:47 +020059class 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
Gilles Peskine0dacd4d2021-04-29 20:38:01 +020078
Gilles Peskine0156a152021-01-26 21:23:56 +010079class KeyType:
80 """Knowledge about a PSA key type."""
81
Gilles Peskine2a71b722021-04-29 20:19:57 +020082 def __init__(self, name: str, params: Optional[Iterable[str]] = None) -> None:
Gilles Peskine0156a152021-01-26 21:23:56 +010083 """Analyze a key type.
84
85 The key type must be specified in PSA syntax. In its simplest form,
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010086 `name` is a string 'PSA_KEY_TYPE_xxx' which is the name of a PSA key
Gilles Peskine0156a152021-01-26 21:23:56 +010087 type macro. For key types that take arguments, the arguments can
88 be passed either through the optional argument `params` or by
Gilles Peskine0ba69a42021-04-12 13:41:52 +020089 passing an expression of the form 'PSA_KEY_TYPE_xxx(param1, ...)'
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010090 in `name` as a string.
Gilles Peskine0156a152021-01-26 21:23:56 +010091 """
Gilles Peskined75adfc2021-02-17 18:04:28 +010092
Gilles Peskine0156a152021-01-26 21:23:56 +010093 self.name = name.strip()
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010094 """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 """
Gilles Peskine0156a152021-01-26 21:23:56 +010099 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)
Gilles Peskine0ba69a42021-04-12 13:41:52 +0200104 params = m.group(2).split(',')
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100105 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 """
Gilles Peskined75adfc2021-02-17 18:04:28 +0100111 assert re.match(r'PSA_KEY_TYPE_\w+\Z', self.name)
112
Gilles Peskine0156a152021-01-26 21:23:56 +0100113 self.expression = self.name
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100114 """A C expression whose value is the key type encoding."""
Gilles Peskine0156a152021-01-26 21:23:56 +0100115 if self.params is not None:
116 self.expression += '(' + ', '.join(self.params) + ')'
Gilles Peskined75adfc2021-02-17 18:04:28 +0100117
Gilles Peskine39054332021-04-29 20:38:47 +0200118 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
Gilles Peskine0156a152021-01-26 21:23:56 +0100123 self.private_type = re.sub(r'_PUBLIC_KEY\Z', r'_KEY_PAIR', self.name)
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100124 """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 """
Gilles Peskinedf639682021-01-26 21:25:34 +0100129
Gilles Peskine930ccef2022-03-18 00:02:15 +0100130 def short_expression(self, level: int = 0) -> str:
Gilles Peskined79aef52022-03-17 23:42:25 +0100131 """Abbreviate the expression, keeping it human-readable.
132
133 See `crypto_knowledge.short_expression`.
134 """
Gilles Peskine930ccef2022-03-18 00:02:15 +0100135 return short_expression(self.expression, level=level)
Gilles Peskined79aef52022-03-17 23:42:25 +0100136
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200137 def is_public(self) -> bool:
138 """Whether the key type is for public keys."""
139 return self.name.endswith('_PUBLIC_KEY')
140
Gilles Peskinedf639682021-01-26 21:25:34 +0100141 ECC_KEY_SIZES = {
142 'PSA_ECC_FAMILY_SECP_K1': (192, 224, 256),
Gilles Peskine0ac258e2021-01-27 13:11:59 +0100143 'PSA_ECC_FAMILY_SECP_R1': (225, 256, 384, 521),
Gilles Peskinedf639682021-01-26 21:25:34 +0100144 '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),
Gilles Peskinea00abc62021-03-16 18:25:14 +0100150 'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
David Horstmann659d9052023-01-24 18:59:07 +0000151 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100152 KEY_TYPE_SIZES = {
153 'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
154 'PSA_KEY_TYPE_ARC4': (8, 128, 2048), # extremes + sensible
155 'PSA_KEY_TYPE_ARIA': (128, 192, 256), # exhaustive
156 'PSA_KEY_TYPE_CAMELLIA': (128, 192, 256), # exhaustive
157 'PSA_KEY_TYPE_CHACHA20': (256,), # exhaustive
158 'PSA_KEY_TYPE_DERIVE': (120, 128), # sample
159 'PSA_KEY_TYPE_DES': (64, 128, 192), # exhaustive
160 'PSA_KEY_TYPE_HMAC': (128, 160, 224, 256, 384, 512), # standard size for each supported hash
161 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
162 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
David Horstmann659d9052023-01-24 18:59:07 +0000163 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100164 def sizes_to_test(self) -> Tuple[int, ...]:
165 """Return a tuple of key sizes to test.
166
167 For key types that only allow a single size, or only a small set of
168 sizes, these are all the possible sizes. For key types that allow a
169 wide range of sizes, these are a representative sample of sizes,
170 excluding large sizes for which a typical resource-constrained platform
171 may run out of memory.
172 """
173 if self.private_type == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
174 assert self.params is not None
175 return self.ECC_KEY_SIZES[self.params[0]]
176 return self.KEY_TYPE_SIZES[self.private_type]
Gilles Peskine397b0282021-01-26 21:26:26 +0100177
178 # "48657265006973206b6579a064617461"
179 DATA_BLOCK = b'Here\000is key\240data'
180 def key_material(self, bits: int) -> bytes:
181 """Return a byte string containing suitable key material with the given bit length.
182
183 Use the PSA export representation. The resulting byte string is one that
184 can be obtained with the following code:
185 ```
186 psa_set_key_type(&attributes, `self.expression`);
187 psa_set_key_bits(&attributes, `bits`);
188 psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT);
189 psa_generate_key(&attributes, &id);
190 psa_export_key(id, `material`, ...);
191 ```
192 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100193 if self.expression in ASYMMETRIC_KEY_DATA:
194 if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
195 raise ValueError('No key data for {}-bit {}'
196 .format(bits, self.expression))
197 return ASYMMETRIC_KEY_DATA[self.expression][bits]
Gilles Peskine397b0282021-01-26 21:26:26 +0100198 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100199 raise ValueError('Non-integer number of bytes: {} bits for {}'
200 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100201 length = bits // 8
202 if self.name == 'PSA_KEY_TYPE_DES':
203 # "644573206b457901644573206b457902644573206b457904"
204 des3 = b'dEs kEy\001dEs kEy\002dEs kEy\004'
205 return des3[:length]
Gilles Peskine397b0282021-01-26 21:26:26 +0100206 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
207 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200208
Gilles Peskine39054332021-04-29 20:38:47 +0200209 def can_do(self, alg: 'Algorithm') -> bool:
210 """Whether this key type can be used for operations with the given algorithm.
211
212 This function does not currently handle key derivation or PAKE.
213 """
Gilles Peskine32611242022-03-19 12:09:13 +0100214 #pylint: disable=too-many-branches,too-many-return-statements
Gilles Peskine7f7630c2022-12-15 22:41:34 +0100215 if not alg.is_valid_for_operation():
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100216 return False
Gilles Peskine39054332021-04-29 20:38:47 +0200217 if self.head == 'HMAC' and alg.head == 'HMAC':
218 return True
Gilles Peskine4eb1c7e2022-03-18 10:18:58 +0100219 if self.head == 'DES':
220 # 64-bit block ciphers only allow a reduced set of modes.
221 return alg.head in [
222 'CBC_NO_PADDING', 'CBC_PKCS7',
223 'ECB_NO_PADDING',
224 ]
Gilles Peskine39054332021-04-29 20:38:47 +0200225 if self.head in BLOCK_CIPHERS and \
226 alg.head in frozenset.union(BLOCK_MAC_MODES,
227 BLOCK_CIPHER_MODES,
228 BLOCK_AEAD_MODES):
Gilles Peskineae93ee62022-03-19 10:49:43 +0100229 if alg.head in ['CMAC', 'OFB'] and \
230 self.head in ['ARIA', 'CAMELLIA']:
231 return False # not implemented in Mbed TLS
Gilles Peskine39054332021-04-29 20:38:47 +0200232 return True
233 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
234 return True
235 if self.head in {'ARC4', 'CHACHA20'} and \
236 alg.head == 'STREAM_CIPHER':
237 return True
238 if self.head == 'RSA' and alg.head.startswith('RSA_'):
239 return True
Gilles Peskinecb451702022-03-19 12:16:45 +0100240 if alg.category == AlgorithmCategory.KEY_AGREEMENT and \
241 self.is_public():
242 # The PSA API does not use public key objects in key agreement
243 # operations: it imports the public key as a formatted byte string.
244 # So a public key object with a key agreement algorithm is not
245 # a valid combination.
246 return False
Gilles Peskine39054332021-04-29 20:38:47 +0200247 if self.head == 'ECC':
248 assert self.params is not None
249 eccc = EllipticCurveCategory.from_family(self.params[0])
250 if alg.head == 'ECDH' and \
251 eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
252 EllipticCurveCategory.MONTGOMERY}:
253 return True
254 if alg.head == 'ECDSA' and \
255 eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
256 return True
257 if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
258 eccc == EllipticCurveCategory.TWISTED_EDWARDS:
259 return True
260 return False
261
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200262
263class AlgorithmCategory(enum.Enum):
264 """PSA algorithm categories."""
265 # The numbers are aligned with the category bits in numerical values of
266 # algorithms.
267 HASH = 2
268 MAC = 3
269 CIPHER = 4
270 AEAD = 5
271 SIGN = 6
272 ASYMMETRIC_ENCRYPTION = 7
273 KEY_DERIVATION = 8
274 KEY_AGREEMENT = 9
275 PAKE = 10
276
277 def requires_key(self) -> bool:
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200278 """Whether operations in this category are set up with a key."""
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200279 return self not in {self.HASH, self.KEY_DERIVATION}
280
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200281 def is_asymmetric(self) -> bool:
282 """Whether operations in this category involve asymmetric keys."""
283 return self in {
284 self.SIGN,
285 self.ASYMMETRIC_ENCRYPTION,
286 self.KEY_AGREEMENT
287 }
288
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200289
290class AlgorithmNotRecognized(Exception):
291 def __init__(self, expr: str) -> None:
292 super().__init__('Algorithm not recognized: ' + expr)
293 self.expr = expr
294
295
296class Algorithm:
297 """Knowledge about a PSA algorithm."""
298
299 @staticmethod
300 def determine_base(expr: str) -> str:
301 """Return an expression for the "base" of the algorithm.
302
303 This strips off variants of algorithms such as MAC truncation.
304
305 This function does not attempt to detect invalid inputs.
306 """
307 m = re.match(r'PSA_ALG_(?:'
308 r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
309 r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
310 r')\((.*),[^,]+\)\Z', expr)
311 if m:
312 expr = m.group(1)
313 return expr
314
315 @staticmethod
316 def determine_head(expr: str) -> str:
317 """Return the head of an algorithm expression.
318
319 The head is the first (outermost) constructor, without its PSA_ALG_
320 prefix, and with some normalization of similar algorithms.
321 """
322 m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
323 if not m:
324 raise AlgorithmNotRecognized(expr)
325 head = m.group(1)
326 if head == 'KEY_AGREEMENT':
327 m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
328 if not m:
329 raise AlgorithmNotRecognized(expr)
330 head = m.group(1)
331 head = re.sub(r'_ANY\Z', r'', head)
332 if re.match(r'ED[0-9]+PH\Z', head):
333 head = 'EDDSA_PREHASH'
334 return head
335
336 CATEGORY_FROM_HEAD = {
337 'SHA': AlgorithmCategory.HASH,
338 'SHAKE256_512': AlgorithmCategory.HASH,
339 'MD': AlgorithmCategory.HASH,
340 'RIPEMD': AlgorithmCategory.HASH,
341 'ANY_HASH': AlgorithmCategory.HASH,
342 'HMAC': AlgorithmCategory.MAC,
343 'STREAM_CIPHER': AlgorithmCategory.CIPHER,
344 'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
345 'DSA': AlgorithmCategory.SIGN,
346 'ECDSA': AlgorithmCategory.SIGN,
347 'EDDSA': AlgorithmCategory.SIGN,
348 'PURE_EDDSA': AlgorithmCategory.SIGN,
349 'RSA_PSS': AlgorithmCategory.SIGN,
350 'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
351 'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
352 'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
353 'HKDF': AlgorithmCategory.KEY_DERIVATION,
354 'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
355 'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
356 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
357 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
358 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
359 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
360 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
361 'JPAKE': AlgorithmCategory.PAKE,
362 }
363 for x in BLOCK_MAC_MODES:
364 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
365 for x in BLOCK_CIPHER_MODES:
366 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
367 for x in BLOCK_AEAD_MODES:
368 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
369
370 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
371 """Return the category of the given algorithm expression.
372
373 This function does not attempt to detect invalid inputs.
374 """
375 prefix = head
376 while prefix:
377 if prefix in self.CATEGORY_FROM_HEAD:
378 return self.CATEGORY_FROM_HEAD[prefix]
379 if re.match(r'.*[0-9]\Z', prefix):
380 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
381 else:
382 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
383 raise AlgorithmNotRecognized(expr)
384
385 @staticmethod
386 def determine_wildcard(expr) -> bool:
387 """Whether the given algorithm expression is a wildcard.
388
389 This function does not attempt to detect invalid inputs.
390 """
391 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
392 return True
393 if re.search(r'_AT_LEAST_', expr):
394 return True
395 return False
396
397 def __init__(self, expr: str) -> None:
398 """Analyze an algorithm value.
399
400 The algorithm must be expressed as a C expression containing only
401 calls to PSA algorithm constructor macros and numeric literals.
402
403 This class is only programmed to handle valid expressions. Invalid
404 expressions may result in exceptions or in nonsensical results.
405 """
406 self.expression = re.sub(r'\s+', r'', expr)
407 self.base_expression = self.determine_base(self.expression)
408 self.head = self.determine_head(self.base_expression)
409 self.category = self.determine_category(self.base_expression, self.head)
410 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200411
412 def is_key_agreement_with_derivation(self) -> bool:
413 """Whether this is a combined key agreement and key derivation algorithm."""
414 if self.category != AlgorithmCategory.KEY_AGREEMENT:
415 return False
416 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
417 if not m:
418 return False
419 kdf_alg = m.group(1)
420 # Assume kdf_alg is either a valid KDF or 0.
421 return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
422
Gilles Peskined79aef52022-03-17 23:42:25 +0100423
Gilles Peskine930ccef2022-03-18 00:02:15 +0100424 def short_expression(self, level: int = 0) -> str:
Gilles Peskined79aef52022-03-17 23:42:25 +0100425 """Abbreviate the expression, keeping it human-readable.
426
427 See `crypto_knowledge.short_expression`.
428 """
Gilles Peskine930ccef2022-03-18 00:02:15 +0100429 return short_expression(self.expression, level=level)
Gilles Peskined79aef52022-03-17 23:42:25 +0100430
Gilles Peskine32611242022-03-19 12:09:13 +0100431 HASH_LENGTH = {
432 'PSA_ALG_MD5': 16,
433 'PSA_ALG_SHA_1': 20,
434 }
435 HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z')
436 @classmethod
437 def hash_length(cls, alg: str) -> int:
438 """The length of the given hash algorithm, in bytes."""
439 if alg in cls.HASH_LENGTH:
440 return cls.HASH_LENGTH[alg]
441 m = cls.HASH_LENGTH_BITS_RE.search(alg)
442 if m:
443 return int(m.group(1)) // 8
444 raise ValueError('Unknown hash length for ' + alg)
445
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100446 PERMITTED_TAG_LENGTHS = {
447 'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
448 'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
449 'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
450 }
451 MAC_LENGTH = {
Gilles Peskine32611242022-03-19 12:09:13 +0100452 'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
453 'PSA_ALG_CMAC': 16, # actually the block cipher length
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100454 }
Gilles Peskine32611242022-03-19 12:09:13 +0100455 HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100456 @classmethod
Gilles Peskinece78c962022-04-12 18:51:01 +0200457 def permitted_truncations(cls, base: str) -> FrozenSet[int]:
458 """Permitted output lengths for the given MAC or AEAD base algorithm.
459
460 For a MAC algorithm, this is the set of truncation lengths that
461 Mbed TLS supports.
462 For an AEAD algorithm, this is the set of truncation lengths that
463 are permitted by the algorithm specification.
464 """
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100465 if base in cls.PERMITTED_TAG_LENGTHS:
466 return cls.PERMITTED_TAG_LENGTHS[base]
467 max_length = cls.MAC_LENGTH.get(base, None)
468 if max_length is None:
Gilles Peskine32611242022-03-19 12:09:13 +0100469 m = cls.HMAC_RE.match(base)
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100470 if m:
Gilles Peskine32611242022-03-19 12:09:13 +0100471 max_length = cls.hash_length(m.group(1))
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100472 if max_length is None:
473 raise ValueError('Unknown permitted lengths for ' + base)
474 return frozenset(range(4, max_length + 1))
475
476 TRUNCATED_ALG_RE = re.compile(
477 r'(?P<face>PSA_ALG_(?:AEAD_WITH_SHORTENED_TAG|TRUNCATED_MAC))'
478 r'\((?P<base>.*),'
Gilles Peskine913c01f2022-04-05 16:31:16 +0200479 r'(?P<length>0[Xx][0-9A-Fa-f]+|[1-9][0-9]*|0[0-7]*)[LUlu]*\)\Z')
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100480 def is_invalid_truncation(self) -> bool:
481 """False for a MAC or AEAD algorithm truncated to an invalid length.
482
483 True for a MAC or AEAD algorithm truncated to a valid length or to
484 a length that cannot be determined. True for anything other than
485 a truncated MAC or AEAD.
486 """
487 m = self.TRUNCATED_ALG_RE.match(self.expression)
488 if m:
489 base = m.group('base')
490 to_length = int(m.group('length'), 0)
Gilles Peskinece78c962022-04-12 18:51:01 +0200491 permitted_lengths = self.permitted_truncations(base)
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100492 if to_length not in permitted_lengths:
493 return True
494 return False
495
Gilles Peskine7f7630c2022-12-15 22:41:34 +0100496 def is_valid_for_operation(self) -> bool:
497 """Whether this algorithm construction is valid for an operation.
498
499 This function assumes that the algorithm is constructed in a
500 "grammatically" correct way, and only rejects semantically invalid
501 combinations.
502 """
503 if self.is_wildcard:
504 return False
505 if self.is_invalid_truncation():
506 return False
507 return True
508
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200509 def can_do(self, category: AlgorithmCategory) -> bool:
Gilles Peskineb0537ba2022-03-19 10:37:33 +0100510 """Whether this algorithm can perform operations in the given category.
511 """
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200512 if category == self.category:
513 return True
514 if category == AlgorithmCategory.KEY_DERIVATION and \
515 self.is_key_agreement_with_derivation():
516 return True
517 return False
Gilles Peskine0de11432022-03-18 09:58:09 +0100518
519 def usage_flags(self, public: bool = False) -> List[str]:
520 """The list of usage flags describing operations that can perform this algorithm.
521
522 If public is true, only return public-key operations, not private-key operations.
523 """
524 if self.category == AlgorithmCategory.HASH:
525 flags = []
526 elif self.category == AlgorithmCategory.MAC:
527 flags = ['SIGN_HASH', 'SIGN_MESSAGE',
528 'VERIFY_HASH', 'VERIFY_MESSAGE']
529 elif self.category == AlgorithmCategory.CIPHER or \
530 self.category == AlgorithmCategory.AEAD:
531 flags = ['DECRYPT', 'ENCRYPT']
532 elif self.category == AlgorithmCategory.SIGN:
533 flags = ['VERIFY_HASH', 'VERIFY_MESSAGE']
534 if not public:
535 flags += ['SIGN_HASH', 'SIGN_MESSAGE']
536 elif self.category == AlgorithmCategory.ASYMMETRIC_ENCRYPTION:
537 flags = ['ENCRYPT']
538 if not public:
539 flags += ['DECRYPT']
540 elif self.category == AlgorithmCategory.KEY_DERIVATION or \
541 self.category == AlgorithmCategory.KEY_AGREEMENT:
542 flags = ['DERIVE']
543 else:
544 raise AlgorithmNotRecognized(self.expression)
545 return ['PSA_KEY_USAGE_' + flag for flag in flags]