blob: 592fc0afe2fc8a01b43d6250c9b65517e516b38b [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 Peskineee7554e2021-04-29 20:38:01 +020021import enum
Gilles Peskine0156a152021-01-26 21:23:56 +010022import re
Gilles Peskinee3a08902022-03-19 10:37:33 +010023from typing import FrozenSet, Iterable, List, Optional, Tuple
Gilles Peskine0156a152021-01-26 21:23:56 +010024
Gilles Peskine6f6483f2021-01-27 12:43:24 +010025from mbedtls_dev.asymmetric_key_data import ASYMMETRIC_KEY_DATA
26
Gilles Peskineee7554e2021-04-29 20:38:01 +020027
Gilles Peskine16b25062022-03-18 00:02:15 +010028def short_expression(original: str, level: int = 0) -> str:
Gilles Peskinee8e058c2022-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 Peskine16b25062022-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 Peskinee8e058c2022-03-17 23:42:25 +010048 return short
49
50
Gilles Peskine8345d632021-04-29 20:38:47 +020051BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
Gilles Peskineee7554e2021-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 Peskine8345d632021-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 Peskineee7554e2021-04-29 20:38:01 +020078
Gilles Peskine0156a152021-01-26 21:23:56 +010079class KeyType:
80 """Knowledge about a PSA key type."""
81
Gilles Peskineb9dbb7f2021-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 Peskine4d0b0892021-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 Peskine4d0b0892021-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 Peskine8345d632021-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 Peskine16b25062022-03-18 00:02:15 +0100130 def short_expression(self, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100131 """Abbreviate the expression, keeping it human-readable.
132
133 See `crypto_knowledge.short_expression`.
134 """
Gilles Peskine16b25062022-03-18 00:02:15 +0100135 return short_expression(self.expression, level=level)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100136
Gilles Peskinee6300952021-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),
Gilles Peskinedf639682021-01-26 21:25:34 +0100151 }
152 KEY_TYPE_SIZES = {
153 'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
Gilles Peskinedf639682021-01-26 21:25:34 +0100154 '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
Manuel Pégourié-Gonnardb12de9f2021-05-03 11:02:56 +0200160 '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
Gilles Peskinedf639682021-01-26 21:25:34 +0100163 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
164 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
165 }
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]
Gilles Peskine397b0282021-01-26 21:26:26 +0100179
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 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100195 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]
Gilles Peskine397b0282021-01-26 21:26:26 +0100200 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100201 raise ValueError('Non-integer number of bytes: {} bits for {}'
202 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100203 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]
Gilles Peskine397b0282021-01-26 21:26:26 +0100208 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
209 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200210
Gilles Peskine8345d632021-04-29 20:38:47 +0200211 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 """
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100216 #pylint: disable=too-many-branches,too-many-return-statements
Gilles Peskine8345d632021-04-29 20:38:47 +0200217 if alg.is_wildcard:
218 return False
Gilles Peskinee3a08902022-03-19 10:37:33 +0100219 if alg.is_invalid_truncation():
220 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200221 if self.head == 'HMAC' and alg.head == 'HMAC':
222 return True
Gilles Peskinec47d3a42022-03-18 10:18:58 +0100223 if self.head == 'DES':
224 # 64-bit block ciphers only allow a reduced set of modes.
225 return alg.head in [
226 'CBC_NO_PADDING', 'CBC_PKCS7',
227 'ECB_NO_PADDING',
228 ]
Gilles Peskine8345d632021-04-29 20:38:47 +0200229 if self.head in BLOCK_CIPHERS and \
230 alg.head in frozenset.union(BLOCK_MAC_MODES,
231 BLOCK_CIPHER_MODES,
232 BLOCK_AEAD_MODES):
Gilles Peskine7095d472022-03-19 10:49:43 +0100233 if alg.head in ['CMAC', 'OFB'] and \
234 self.head in ['ARIA', 'CAMELLIA']:
235 return False # not implemented in Mbed TLS
Gilles Peskine8345d632021-04-29 20:38:47 +0200236 return True
237 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
238 return True
239 if self.head in {'ARC4', 'CHACHA20'} and \
240 alg.head == 'STREAM_CIPHER':
241 return True
242 if self.head == 'RSA' and alg.head.startswith('RSA_'):
243 return True
Gilles Peskineac17ec42022-03-19 12:16:45 +0100244 if alg.category == AlgorithmCategory.KEY_AGREEMENT and \
245 self.is_public():
246 # The PSA API does not use public key objects in key agreement
247 # operations: it imports the public key as a formatted byte string.
248 # So a public key object with a key agreement algorithm is not
249 # a valid combination.
250 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200251 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
Gilles Peskineee7554e2021-04-29 20:38:01 +0200266
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:
Gilles Peskinee6300952021-04-29 21:56:59 +0200282 """Whether operations in this category are set up with a key."""
Gilles Peskineee7554e2021-04-29 20:38:01 +0200283 return self not in {self.HASH, self.KEY_DERIVATION}
284
Gilles Peskinee6300952021-04-29 21:56:59 +0200285 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
Gilles Peskineee7554e2021-04-29 20:38:01 +0200293
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 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
361 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
362 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
363 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
364 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
365 'JPAKE': AlgorithmCategory.PAKE,
366 }
367 for x in BLOCK_MAC_MODES:
368 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
369 for x in BLOCK_CIPHER_MODES:
370 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
371 for x in BLOCK_AEAD_MODES:
372 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
373
374 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
375 """Return the category of the given algorithm expression.
376
377 This function does not attempt to detect invalid inputs.
378 """
379 prefix = head
380 while prefix:
381 if prefix in self.CATEGORY_FROM_HEAD:
382 return self.CATEGORY_FROM_HEAD[prefix]
383 if re.match(r'.*[0-9]\Z', prefix):
384 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
385 else:
386 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
387 raise AlgorithmNotRecognized(expr)
388
389 @staticmethod
390 def determine_wildcard(expr) -> bool:
391 """Whether the given algorithm expression is a wildcard.
392
393 This function does not attempt to detect invalid inputs.
394 """
395 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
396 return True
397 if re.search(r'_AT_LEAST_', expr):
398 return True
399 return False
400
401 def __init__(self, expr: str) -> None:
402 """Analyze an algorithm value.
403
404 The algorithm must be expressed as a C expression containing only
405 calls to PSA algorithm constructor macros and numeric literals.
406
407 This class is only programmed to handle valid expressions. Invalid
408 expressions may result in exceptions or in nonsensical results.
409 """
410 self.expression = re.sub(r'\s+', r'', expr)
411 self.base_expression = self.determine_base(self.expression)
412 self.head = self.determine_head(self.base_expression)
413 self.category = self.determine_category(self.base_expression, self.head)
414 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskinea4013862021-04-29 20:54:40 +0200415
416 def is_key_agreement_with_derivation(self) -> bool:
417 """Whether this is a combined key agreement and key derivation algorithm."""
418 if self.category != AlgorithmCategory.KEY_AGREEMENT:
419 return False
420 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
421 if not m:
422 return False
423 kdf_alg = m.group(1)
424 # Assume kdf_alg is either a valid KDF or 0.
425 return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
426
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100427
Gilles Peskine16b25062022-03-18 00:02:15 +0100428 def short_expression(self, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100429 """Abbreviate the expression, keeping it human-readable.
430
431 See `crypto_knowledge.short_expression`.
432 """
Gilles Peskine16b25062022-03-18 00:02:15 +0100433 return short_expression(self.expression, level=level)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100434
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100435 HASH_LENGTH = {
436 'PSA_ALG_MD5': 16,
437 'PSA_ALG_SHA_1': 20,
438 }
439 HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z')
440 @classmethod
441 def hash_length(cls, alg: str) -> int:
442 """The length of the given hash algorithm, in bytes."""
443 if alg in cls.HASH_LENGTH:
444 return cls.HASH_LENGTH[alg]
445 m = cls.HASH_LENGTH_BITS_RE.search(alg)
446 if m:
447 return int(m.group(1)) // 8
448 raise ValueError('Unknown hash length for ' + alg)
449
Gilles Peskinee3a08902022-03-19 10:37:33 +0100450 PERMITTED_TAG_LENGTHS = {
451 'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
452 'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
453 'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
454 }
455 MAC_LENGTH = {
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100456 'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
457 'PSA_ALG_CMAC': 16, # actually the block cipher length
Gilles Peskinee3a08902022-03-19 10:37:33 +0100458 }
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100459 HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
Gilles Peskinee3a08902022-03-19 10:37:33 +0100460 @classmethod
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200461 def permitted_truncations(cls, base: str) -> FrozenSet[int]:
462 """Permitted output lengths for the given MAC or AEAD base algorithm.
463
464 For a MAC algorithm, this is the set of truncation lengths that
465 Mbed TLS supports.
466 For an AEAD algorithm, this is the set of truncation lengths that
467 are permitted by the algorithm specification.
468 """
Gilles Peskinee3a08902022-03-19 10:37:33 +0100469 if base in cls.PERMITTED_TAG_LENGTHS:
470 return cls.PERMITTED_TAG_LENGTHS[base]
471 max_length = cls.MAC_LENGTH.get(base, None)
472 if max_length is None:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100473 m = cls.HMAC_RE.match(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100474 if m:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100475 max_length = cls.hash_length(m.group(1))
Gilles Peskinee3a08902022-03-19 10:37:33 +0100476 if max_length is None:
477 raise ValueError('Unknown permitted lengths for ' + base)
478 return frozenset(range(4, max_length + 1))
479
480 TRUNCATED_ALG_RE = re.compile(
481 r'(?P<face>PSA_ALG_(?:AEAD_WITH_SHORTENED_TAG|TRUNCATED_MAC))'
482 r'\((?P<base>.*),'
Gilles Peskine2773f262022-04-05 16:31:16 +0200483 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 +0100484 def is_invalid_truncation(self) -> bool:
485 """False for a MAC or AEAD algorithm truncated to an invalid length.
486
487 True for a MAC or AEAD algorithm truncated to a valid length or to
488 a length that cannot be determined. True for anything other than
489 a truncated MAC or AEAD.
490 """
491 m = self.TRUNCATED_ALG_RE.match(self.expression)
492 if m:
493 base = m.group('base')
494 to_length = int(m.group('length'), 0)
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200495 permitted_lengths = self.permitted_truncations(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100496 if to_length not in permitted_lengths:
497 return True
498 return False
499
Gilles Peskinea4013862021-04-29 20:54:40 +0200500 def can_do(self, category: AlgorithmCategory) -> bool:
Gilles Peskinee3a08902022-03-19 10:37:33 +0100501 """Whether this algorithm can perform operations in the given category.
502 """
Gilles Peskinea4013862021-04-29 20:54:40 +0200503 if category == self.category:
504 return True
505 if category == AlgorithmCategory.KEY_DERIVATION and \
506 self.is_key_agreement_with_derivation():
507 return True
508 return False
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100509
510 def usage_flags(self, public: bool = False) -> List[str]:
511 """The list of usage flags describing operations that can perform this algorithm.
512
513 If public is true, only return public-key operations, not private-key operations.
514 """
515 if self.category == AlgorithmCategory.HASH:
516 flags = []
517 elif self.category == AlgorithmCategory.MAC:
518 flags = ['SIGN_HASH', 'SIGN_MESSAGE',
519 'VERIFY_HASH', 'VERIFY_MESSAGE']
520 elif self.category == AlgorithmCategory.CIPHER or \
521 self.category == AlgorithmCategory.AEAD:
522 flags = ['DECRYPT', 'ENCRYPT']
523 elif self.category == AlgorithmCategory.SIGN:
524 flags = ['VERIFY_HASH', 'VERIFY_MESSAGE']
525 if not public:
526 flags += ['SIGN_HASH', 'SIGN_MESSAGE']
527 elif self.category == AlgorithmCategory.ASYMMETRIC_ENCRYPTION:
528 flags = ['ENCRYPT']
529 if not public:
530 flags += ['DECRYPT']
531 elif self.category == AlgorithmCategory.KEY_DERIVATION or \
532 self.category == AlgorithmCategory.KEY_AGREEMENT:
533 flags = ['DERIVE']
534 else:
535 raise AlgorithmNotRecognized(self.expression)
536 return ['PSA_KEY_USAGE_' + flag for flag in flags]