blob: 45d253b9b65882345eac6e92e18f59029247a128 [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
David Horstmann79f14e32023-01-24 18:59:07 +000023from typing import FrozenSet, Iterable, List, Optional, Tuple, Dict
Gilles Peskine0156a152021-01-26 21:23:56 +010024
Gilles Peskine15997bd2022-09-16 22:35:18 +020025from .asymmetric_key_data import ASYMMETRIC_KEY_DATA
Gilles Peskine6f6483f2021-01-27 12:43:24 +010026
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
Manuel Pégourié-Gonnard636d8572023-07-18 11:00:36 +020037 short = re.sub(r'\bPSA_(?:ALG|DH_FAMILY|ECC_FAMILY|KEY_[A-Z]+)_', r'', short)
Gilles Peskinee8e058c2022-03-17 23:42:25 +010038 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
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200141 DH_KEY_SIZES = {
142 'PSA_DH_FAMILY_RFC7919': (2048, 3072, 4096, 6144, 8192),
143 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100144 ECC_KEY_SIZES = {
145 'PSA_ECC_FAMILY_SECP_K1': (192, 224, 256),
Gilles Peskine0ac258e2021-01-27 13:11:59 +0100146 'PSA_ECC_FAMILY_SECP_R1': (225, 256, 384, 521),
Gilles Peskinedf639682021-01-26 21:25:34 +0100147 'PSA_ECC_FAMILY_SECP_R2': (160,),
148 'PSA_ECC_FAMILY_SECT_K1': (163, 233, 239, 283, 409, 571),
149 'PSA_ECC_FAMILY_SECT_R1': (163, 233, 283, 409, 571),
150 'PSA_ECC_FAMILY_SECT_R2': (163,),
151 'PSA_ECC_FAMILY_BRAINPOOL_P_R1': (160, 192, 224, 256, 320, 384, 512),
152 'PSA_ECC_FAMILY_MONTGOMERY': (255, 448),
Gilles Peskinea00abc62021-03-16 18:25:14 +0100153 'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
David Horstmann79f14e32023-01-24 18:59:07 +0000154 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100155 KEY_TYPE_SIZES = {
156 'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
Gilles Peskinedf639682021-01-26 21:25:34 +0100157 'PSA_KEY_TYPE_ARIA': (128, 192, 256), # exhaustive
158 'PSA_KEY_TYPE_CAMELLIA': (128, 192, 256), # exhaustive
159 'PSA_KEY_TYPE_CHACHA20': (256,), # exhaustive
160 'PSA_KEY_TYPE_DERIVE': (120, 128), # sample
161 'PSA_KEY_TYPE_DES': (64, 128, 192), # exhaustive
162 '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 +0200163 'PSA_KEY_TYPE_PASSWORD': (48, 168, 336), # sample
164 'PSA_KEY_TYPE_PASSWORD_HASH': (128, 256), # sample
165 'PSA_KEY_TYPE_PEPPER': (128, 256), # sample
Gilles Peskinedf639682021-01-26 21:25:34 +0100166 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
167 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
David Horstmann79f14e32023-01-24 18:59:07 +0000168 } # type: Dict[str, Tuple[int, ...]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100169 def sizes_to_test(self) -> Tuple[int, ...]:
170 """Return a tuple of key sizes to test.
171
172 For key types that only allow a single size, or only a small set of
173 sizes, these are all the possible sizes. For key types that allow a
174 wide range of sizes, these are a representative sample of sizes,
175 excluding large sizes for which a typical resource-constrained platform
176 may run out of memory.
177 """
178 if self.private_type == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
179 assert self.params is not None
180 return self.ECC_KEY_SIZES[self.params[0]]
Manuel Pégourié-Gonnardafe4b792023-07-11 10:23:02 +0200181 if self.private_type == 'PSA_KEY_TYPE_DH_KEY_PAIR':
182 assert self.params is not None
183 return self.DH_KEY_SIZES[self.params[0]]
Gilles Peskinedf639682021-01-26 21:25:34 +0100184 return self.KEY_TYPE_SIZES[self.private_type]
Gilles Peskine397b0282021-01-26 21:26:26 +0100185
186 # "48657265006973206b6579a064617461"
187 DATA_BLOCK = b'Here\000is key\240data'
188 def key_material(self, bits: int) -> bytes:
189 """Return a byte string containing suitable key material with the given bit length.
190
191 Use the PSA export representation. The resulting byte string is one that
192 can be obtained with the following code:
193 ```
194 psa_set_key_type(&attributes, `self.expression`);
195 psa_set_key_bits(&attributes, `bits`);
196 psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT);
197 psa_generate_key(&attributes, &id);
198 psa_export_key(id, `material`, ...);
199 ```
200 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100201 if self.expression in ASYMMETRIC_KEY_DATA:
202 if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
203 raise ValueError('No key data for {}-bit {}'
204 .format(bits, self.expression))
205 return ASYMMETRIC_KEY_DATA[self.expression][bits]
Gilles Peskine397b0282021-01-26 21:26:26 +0100206 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100207 raise ValueError('Non-integer number of bytes: {} bits for {}'
208 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100209 length = bits // 8
210 if self.name == 'PSA_KEY_TYPE_DES':
211 # "644573206b457901644573206b457902644573206b457904"
212 des3 = b'dEs kEy\001dEs kEy\002dEs kEy\004'
213 return des3[:length]
Gilles Peskine397b0282021-01-26 21:26:26 +0100214 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
215 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200216
Gilles Peskine8345d632021-04-29 20:38:47 +0200217 def can_do(self, alg: 'Algorithm') -> bool:
218 """Whether this key type can be used for operations with the given algorithm.
219
220 This function does not currently handle key derivation or PAKE.
221 """
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100222 #pylint: disable=too-many-branches,too-many-return-statements
Gilles Peskine72f41562022-12-15 22:41:34 +0100223 if not alg.is_valid_for_operation():
Gilles Peskinee3a08902022-03-19 10:37:33 +0100224 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200225 if self.head == 'HMAC' and alg.head == 'HMAC':
226 return True
Gilles Peskinec47d3a42022-03-18 10:18:58 +0100227 if self.head == 'DES':
228 # 64-bit block ciphers only allow a reduced set of modes.
229 return alg.head in [
230 'CBC_NO_PADDING', 'CBC_PKCS7',
231 'ECB_NO_PADDING',
232 ]
Gilles Peskine8345d632021-04-29 20:38:47 +0200233 if self.head in BLOCK_CIPHERS and \
234 alg.head in frozenset.union(BLOCK_MAC_MODES,
235 BLOCK_CIPHER_MODES,
236 BLOCK_AEAD_MODES):
Gilles Peskine7095d472022-03-19 10:49:43 +0100237 if alg.head in ['CMAC', 'OFB'] and \
238 self.head in ['ARIA', 'CAMELLIA']:
239 return False # not implemented in Mbed TLS
Gilles Peskine8345d632021-04-29 20:38:47 +0200240 return True
241 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
242 return True
243 if self.head in {'ARC4', 'CHACHA20'} and \
244 alg.head == 'STREAM_CIPHER':
245 return True
246 if self.head == 'RSA' and alg.head.startswith('RSA_'):
247 return True
Gilles Peskineac17ec42022-03-19 12:16:45 +0100248 if alg.category == AlgorithmCategory.KEY_AGREEMENT and \
249 self.is_public():
250 # The PSA API does not use public key objects in key agreement
251 # operations: it imports the public key as a formatted byte string.
252 # So a public key object with a key agreement algorithm is not
253 # a valid combination.
254 return False
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100255 if alg.is_invalid_key_agreement_with_derivation():
256 return False
Gilles Peskine8345d632021-04-29 20:38:47 +0200257 if self.head == 'ECC':
258 assert self.params is not None
259 eccc = EllipticCurveCategory.from_family(self.params[0])
260 if alg.head == 'ECDH' and \
261 eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
262 EllipticCurveCategory.MONTGOMERY}:
263 return True
264 if alg.head == 'ECDSA' and \
265 eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
266 return True
267 if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
268 eccc == EllipticCurveCategory.TWISTED_EDWARDS:
269 return True
Manuel Pégourié-Gonnard182eb152023-07-18 17:58:09 +0200270 if self.head == 'DH' and alg.head == 'FFDH':
271 return True
Gilles Peskine8345d632021-04-29 20:38:47 +0200272 return False
273
Gilles Peskineee7554e2021-04-29 20:38:01 +0200274
275class AlgorithmCategory(enum.Enum):
276 """PSA algorithm categories."""
277 # The numbers are aligned with the category bits in numerical values of
278 # algorithms.
279 HASH = 2
280 MAC = 3
281 CIPHER = 4
282 AEAD = 5
283 SIGN = 6
284 ASYMMETRIC_ENCRYPTION = 7
285 KEY_DERIVATION = 8
286 KEY_AGREEMENT = 9
287 PAKE = 10
288
289 def requires_key(self) -> bool:
Gilles Peskinee6300952021-04-29 21:56:59 +0200290 """Whether operations in this category are set up with a key."""
Gilles Peskineee7554e2021-04-29 20:38:01 +0200291 return self not in {self.HASH, self.KEY_DERIVATION}
292
Gilles Peskinee6300952021-04-29 21:56:59 +0200293 def is_asymmetric(self) -> bool:
294 """Whether operations in this category involve asymmetric keys."""
295 return self in {
296 self.SIGN,
297 self.ASYMMETRIC_ENCRYPTION,
298 self.KEY_AGREEMENT
299 }
300
Gilles Peskineee7554e2021-04-29 20:38:01 +0200301
302class AlgorithmNotRecognized(Exception):
303 def __init__(self, expr: str) -> None:
304 super().__init__('Algorithm not recognized: ' + expr)
305 self.expr = expr
306
307
308class Algorithm:
309 """Knowledge about a PSA algorithm."""
310
311 @staticmethod
312 def determine_base(expr: str) -> str:
313 """Return an expression for the "base" of the algorithm.
314
315 This strips off variants of algorithms such as MAC truncation.
316
317 This function does not attempt to detect invalid inputs.
318 """
319 m = re.match(r'PSA_ALG_(?:'
320 r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
321 r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
322 r')\((.*),[^,]+\)\Z', expr)
323 if m:
324 expr = m.group(1)
325 return expr
326
327 @staticmethod
328 def determine_head(expr: str) -> str:
329 """Return the head of an algorithm expression.
330
331 The head is the first (outermost) constructor, without its PSA_ALG_
332 prefix, and with some normalization of similar algorithms.
333 """
334 m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
335 if not m:
336 raise AlgorithmNotRecognized(expr)
337 head = m.group(1)
338 if head == 'KEY_AGREEMENT':
339 m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
340 if not m:
341 raise AlgorithmNotRecognized(expr)
342 head = m.group(1)
343 head = re.sub(r'_ANY\Z', r'', head)
344 if re.match(r'ED[0-9]+PH\Z', head):
345 head = 'EDDSA_PREHASH'
346 return head
347
348 CATEGORY_FROM_HEAD = {
349 'SHA': AlgorithmCategory.HASH,
350 'SHAKE256_512': AlgorithmCategory.HASH,
351 'MD': AlgorithmCategory.HASH,
352 'RIPEMD': AlgorithmCategory.HASH,
353 'ANY_HASH': AlgorithmCategory.HASH,
354 'HMAC': AlgorithmCategory.MAC,
355 'STREAM_CIPHER': AlgorithmCategory.CIPHER,
356 'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
357 'DSA': AlgorithmCategory.SIGN,
358 'ECDSA': AlgorithmCategory.SIGN,
359 'EDDSA': AlgorithmCategory.SIGN,
360 'PURE_EDDSA': AlgorithmCategory.SIGN,
361 'RSA_PSS': AlgorithmCategory.SIGN,
362 'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
363 'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
364 'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
365 'HKDF': AlgorithmCategory.KEY_DERIVATION,
366 'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
367 'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
Andrzej Kurek08d34b82022-07-29 10:00:16 -0400368 'TLS12_ECJPAKE_TO_PMS': AlgorithmCategory.KEY_DERIVATION,
Gilles Peskineee7554e2021-04-29 20:38:01 +0200369 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
370 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
371 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
372 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
373 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
374 'JPAKE': AlgorithmCategory.PAKE,
375 }
376 for x in BLOCK_MAC_MODES:
377 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
378 for x in BLOCK_CIPHER_MODES:
379 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
380 for x in BLOCK_AEAD_MODES:
381 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
382
383 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
384 """Return the category of the given algorithm expression.
385
386 This function does not attempt to detect invalid inputs.
387 """
388 prefix = head
389 while prefix:
390 if prefix in self.CATEGORY_FROM_HEAD:
391 return self.CATEGORY_FROM_HEAD[prefix]
392 if re.match(r'.*[0-9]\Z', prefix):
393 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
394 else:
395 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
396 raise AlgorithmNotRecognized(expr)
397
398 @staticmethod
399 def determine_wildcard(expr) -> bool:
400 """Whether the given algorithm expression is a wildcard.
401
402 This function does not attempt to detect invalid inputs.
403 """
404 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
405 return True
406 if re.search(r'_AT_LEAST_', expr):
407 return True
408 return False
409
410 def __init__(self, expr: str) -> None:
411 """Analyze an algorithm value.
412
413 The algorithm must be expressed as a C expression containing only
414 calls to PSA algorithm constructor macros and numeric literals.
415
416 This class is only programmed to handle valid expressions. Invalid
417 expressions may result in exceptions or in nonsensical results.
418 """
419 self.expression = re.sub(r'\s+', r'', expr)
420 self.base_expression = self.determine_base(self.expression)
421 self.head = self.determine_head(self.base_expression)
422 self.category = self.determine_category(self.base_expression, self.head)
423 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskinea4013862021-04-29 20:54:40 +0200424
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100425 def get_key_agreement_derivation(self) -> Optional[str]:
426 """For a combined key agreement and key derivation algorithm, get the derivation part.
427
428 For anything else, return None.
429 """
Gilles Peskinea4013862021-04-29 20:54:40 +0200430 if self.category != AlgorithmCategory.KEY_AGREEMENT:
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100431 return None
Gilles Peskinea4013862021-04-29 20:54:40 +0200432 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
433 if not m:
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100434 return None
Gilles Peskinea4013862021-04-29 20:54:40 +0200435 kdf_alg = m.group(1)
436 # Assume kdf_alg is either a valid KDF or 0.
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100437 if re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg):
438 return None
439 return kdf_alg
Gilles Peskinea4013862021-04-29 20:54:40 +0200440
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100441 KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT = frozenset([
442 'PSA_ALG_TLS12_ECJPAKE_TO_PMS', # secret input in specific format
443 ])
444 def is_valid_key_agreement_with_derivation(self) -> bool:
445 """Whether this is a valid combined key agreement and key derivation algorithm."""
446 kdf_alg = self.get_key_agreement_derivation()
447 if kdf_alg is None:
448 return False
449 return kdf_alg not in self.KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT
450
451 def is_invalid_key_agreement_with_derivation(self) -> bool:
452 """Whether this is an invalid combined key agreement and key derivation algorithm."""
453 kdf_alg = self.get_key_agreement_derivation()
454 if kdf_alg is None:
455 return False
456 return kdf_alg in self.KEY_DERIVATIONS_INCOMPATIBLE_WITH_AGREEMENT
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100457
Gilles Peskine16b25062022-03-18 00:02:15 +0100458 def short_expression(self, level: int = 0) -> str:
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100459 """Abbreviate the expression, keeping it human-readable.
460
461 See `crypto_knowledge.short_expression`.
462 """
Gilles Peskine16b25062022-03-18 00:02:15 +0100463 return short_expression(self.expression, level=level)
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100464
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100465 HASH_LENGTH = {
466 'PSA_ALG_MD5': 16,
467 'PSA_ALG_SHA_1': 20,
468 }
469 HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z')
470 @classmethod
471 def hash_length(cls, alg: str) -> int:
472 """The length of the given hash algorithm, in bytes."""
473 if alg in cls.HASH_LENGTH:
474 return cls.HASH_LENGTH[alg]
475 m = cls.HASH_LENGTH_BITS_RE.search(alg)
476 if m:
477 return int(m.group(1)) // 8
478 raise ValueError('Unknown hash length for ' + alg)
479
Gilles Peskinee3a08902022-03-19 10:37:33 +0100480 PERMITTED_TAG_LENGTHS = {
481 'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
482 'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
483 'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
484 }
485 MAC_LENGTH = {
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100486 'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
487 'PSA_ALG_CMAC': 16, # actually the block cipher length
Gilles Peskinee3a08902022-03-19 10:37:33 +0100488 }
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100489 HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
Gilles Peskinee3a08902022-03-19 10:37:33 +0100490 @classmethod
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200491 def permitted_truncations(cls, base: str) -> FrozenSet[int]:
492 """Permitted output lengths for the given MAC or AEAD base algorithm.
493
494 For a MAC algorithm, this is the set of truncation lengths that
495 Mbed TLS supports.
496 For an AEAD algorithm, this is the set of truncation lengths that
497 are permitted by the algorithm specification.
498 """
Gilles Peskinee3a08902022-03-19 10:37:33 +0100499 if base in cls.PERMITTED_TAG_LENGTHS:
500 return cls.PERMITTED_TAG_LENGTHS[base]
501 max_length = cls.MAC_LENGTH.get(base, None)
502 if max_length is None:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100503 m = cls.HMAC_RE.match(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100504 if m:
Gilles Peskine4bd90dc2022-03-19 12:09:13 +0100505 max_length = cls.hash_length(m.group(1))
Gilles Peskinee3a08902022-03-19 10:37:33 +0100506 if max_length is None:
507 raise ValueError('Unknown permitted lengths for ' + base)
508 return frozenset(range(4, max_length + 1))
509
510 TRUNCATED_ALG_RE = re.compile(
511 r'(?P<face>PSA_ALG_(?:AEAD_WITH_SHORTENED_TAG|TRUNCATED_MAC))'
512 r'\((?P<base>.*),'
Gilles Peskine2773f262022-04-05 16:31:16 +0200513 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 +0100514 def is_invalid_truncation(self) -> bool:
515 """False for a MAC or AEAD algorithm truncated to an invalid length.
516
517 True for a MAC or AEAD algorithm truncated to a valid length or to
518 a length that cannot be determined. True for anything other than
519 a truncated MAC or AEAD.
520 """
521 m = self.TRUNCATED_ALG_RE.match(self.expression)
522 if m:
523 base = m.group('base')
524 to_length = int(m.group('length'), 0)
Gilles Peskinedce7d8f2022-04-12 18:51:01 +0200525 permitted_lengths = self.permitted_truncations(base)
Gilles Peskinee3a08902022-03-19 10:37:33 +0100526 if to_length not in permitted_lengths:
527 return True
528 return False
529
Gilles Peskine72f41562022-12-15 22:41:34 +0100530 def is_valid_for_operation(self) -> bool:
531 """Whether this algorithm construction is valid for an operation.
532
533 This function assumes that the algorithm is constructed in a
534 "grammatically" correct way, and only rejects semantically invalid
535 combinations.
536 """
537 if self.is_wildcard:
538 return False
539 if self.is_invalid_truncation():
540 return False
541 return True
542
Gilles Peskinea4013862021-04-29 20:54:40 +0200543 def can_do(self, category: AlgorithmCategory) -> bool:
Gilles Peskinee3a08902022-03-19 10:37:33 +0100544 """Whether this algorithm can perform operations in the given category.
545 """
Gilles Peskinea4013862021-04-29 20:54:40 +0200546 if category == self.category:
547 return True
548 if category == AlgorithmCategory.KEY_DERIVATION and \
Gilles Peskinef6c6b642022-12-16 00:20:50 +0100549 self.is_valid_key_agreement_with_derivation():
Gilles Peskinea4013862021-04-29 20:54:40 +0200550 return True
551 return False
Gilles Peskinee6b85b42022-03-18 09:58:09 +0100552
553 def usage_flags(self, public: bool = False) -> List[str]:
554 """The list of usage flags describing operations that can perform this algorithm.
555
556 If public is true, only return public-key operations, not private-key operations.
557 """
558 if self.category == AlgorithmCategory.HASH:
559 flags = []
560 elif self.category == AlgorithmCategory.MAC:
561 flags = ['SIGN_HASH', 'SIGN_MESSAGE',
562 'VERIFY_HASH', 'VERIFY_MESSAGE']
563 elif self.category == AlgorithmCategory.CIPHER or \
564 self.category == AlgorithmCategory.AEAD:
565 flags = ['DECRYPT', 'ENCRYPT']
566 elif self.category == AlgorithmCategory.SIGN:
567 flags = ['VERIFY_HASH', 'VERIFY_MESSAGE']
568 if not public:
569 flags += ['SIGN_HASH', 'SIGN_MESSAGE']
570 elif self.category == AlgorithmCategory.ASYMMETRIC_ENCRYPTION:
571 flags = ['ENCRYPT']
572 if not public:
573 flags += ['DECRYPT']
574 elif self.category == AlgorithmCategory.KEY_DERIVATION or \
575 self.category == AlgorithmCategory.KEY_AGREEMENT:
576 flags = ['DERIVE']
577 else:
578 raise AlgorithmNotRecognized(self.expression)
579 return ['PSA_KEY_USAGE_' + flag for flag in flags]