blob: 748a943ce051e209e138462cc0036e392ccbed39 [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
Gilles Peskine989c13d2022-03-17 12:52:24 +010023from typing import Iterable, 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 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),
Gilles Peskinedf639682021-01-26 21:25:34 +0100151 }
152 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
163 }
164 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 """
214 #pylint: disable=too-many-return-statements
215 if alg.is_wildcard:
216 return False
217 if self.head == 'HMAC' and alg.head == 'HMAC':
218 return True
219 if self.head in BLOCK_CIPHERS and \
220 alg.head in frozenset.union(BLOCK_MAC_MODES,
221 BLOCK_CIPHER_MODES,
222 BLOCK_AEAD_MODES):
223 return True
224 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
225 return True
226 if self.head in {'ARC4', 'CHACHA20'} and \
227 alg.head == 'STREAM_CIPHER':
228 return True
229 if self.head == 'RSA' and alg.head.startswith('RSA_'):
230 return True
231 if self.head == 'ECC':
232 assert self.params is not None
233 eccc = EllipticCurveCategory.from_family(self.params[0])
234 if alg.head == 'ECDH' and \
235 eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
236 EllipticCurveCategory.MONTGOMERY}:
237 return True
238 if alg.head == 'ECDSA' and \
239 eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
240 return True
241 if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
242 eccc == EllipticCurveCategory.TWISTED_EDWARDS:
243 return True
244 return False
245
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200246
247class AlgorithmCategory(enum.Enum):
248 """PSA algorithm categories."""
249 # The numbers are aligned with the category bits in numerical values of
250 # algorithms.
251 HASH = 2
252 MAC = 3
253 CIPHER = 4
254 AEAD = 5
255 SIGN = 6
256 ASYMMETRIC_ENCRYPTION = 7
257 KEY_DERIVATION = 8
258 KEY_AGREEMENT = 9
259 PAKE = 10
260
261 def requires_key(self) -> bool:
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200262 """Whether operations in this category are set up with a key."""
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200263 return self not in {self.HASH, self.KEY_DERIVATION}
264
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200265 def is_asymmetric(self) -> bool:
266 """Whether operations in this category involve asymmetric keys."""
267 return self in {
268 self.SIGN,
269 self.ASYMMETRIC_ENCRYPTION,
270 self.KEY_AGREEMENT
271 }
272
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200273
274class AlgorithmNotRecognized(Exception):
275 def __init__(self, expr: str) -> None:
276 super().__init__('Algorithm not recognized: ' + expr)
277 self.expr = expr
278
279
280class Algorithm:
281 """Knowledge about a PSA algorithm."""
282
283 @staticmethod
284 def determine_base(expr: str) -> str:
285 """Return an expression for the "base" of the algorithm.
286
287 This strips off variants of algorithms such as MAC truncation.
288
289 This function does not attempt to detect invalid inputs.
290 """
291 m = re.match(r'PSA_ALG_(?:'
292 r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
293 r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
294 r')\((.*),[^,]+\)\Z', expr)
295 if m:
296 expr = m.group(1)
297 return expr
298
299 @staticmethod
300 def determine_head(expr: str) -> str:
301 """Return the head of an algorithm expression.
302
303 The head is the first (outermost) constructor, without its PSA_ALG_
304 prefix, and with some normalization of similar algorithms.
305 """
306 m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
307 if not m:
308 raise AlgorithmNotRecognized(expr)
309 head = m.group(1)
310 if head == 'KEY_AGREEMENT':
311 m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
312 if not m:
313 raise AlgorithmNotRecognized(expr)
314 head = m.group(1)
315 head = re.sub(r'_ANY\Z', r'', head)
316 if re.match(r'ED[0-9]+PH\Z', head):
317 head = 'EDDSA_PREHASH'
318 return head
319
320 CATEGORY_FROM_HEAD = {
321 'SHA': AlgorithmCategory.HASH,
322 'SHAKE256_512': AlgorithmCategory.HASH,
323 'MD': AlgorithmCategory.HASH,
324 'RIPEMD': AlgorithmCategory.HASH,
325 'ANY_HASH': AlgorithmCategory.HASH,
326 'HMAC': AlgorithmCategory.MAC,
327 'STREAM_CIPHER': AlgorithmCategory.CIPHER,
328 'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
329 'DSA': AlgorithmCategory.SIGN,
330 'ECDSA': AlgorithmCategory.SIGN,
331 'EDDSA': AlgorithmCategory.SIGN,
332 'PURE_EDDSA': AlgorithmCategory.SIGN,
333 'RSA_PSS': AlgorithmCategory.SIGN,
334 'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
335 'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
336 'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
337 'HKDF': AlgorithmCategory.KEY_DERIVATION,
338 'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
339 'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
340 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
341 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
342 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
343 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
344 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
345 'JPAKE': AlgorithmCategory.PAKE,
346 }
347 for x in BLOCK_MAC_MODES:
348 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
349 for x in BLOCK_CIPHER_MODES:
350 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
351 for x in BLOCK_AEAD_MODES:
352 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
353
354 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
355 """Return the category of the given algorithm expression.
356
357 This function does not attempt to detect invalid inputs.
358 """
359 prefix = head
360 while prefix:
361 if prefix in self.CATEGORY_FROM_HEAD:
362 return self.CATEGORY_FROM_HEAD[prefix]
363 if re.match(r'.*[0-9]\Z', prefix):
364 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
365 else:
366 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
367 raise AlgorithmNotRecognized(expr)
368
369 @staticmethod
370 def determine_wildcard(expr) -> bool:
371 """Whether the given algorithm expression is a wildcard.
372
373 This function does not attempt to detect invalid inputs.
374 """
375 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
376 return True
377 if re.search(r'_AT_LEAST_', expr):
378 return True
379 return False
380
381 def __init__(self, expr: str) -> None:
382 """Analyze an algorithm value.
383
384 The algorithm must be expressed as a C expression containing only
385 calls to PSA algorithm constructor macros and numeric literals.
386
387 This class is only programmed to handle valid expressions. Invalid
388 expressions may result in exceptions or in nonsensical results.
389 """
390 self.expression = re.sub(r'\s+', r'', expr)
391 self.base_expression = self.determine_base(self.expression)
392 self.head = self.determine_head(self.base_expression)
393 self.category = self.determine_category(self.base_expression, self.head)
394 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200395
396 def is_key_agreement_with_derivation(self) -> bool:
397 """Whether this is a combined key agreement and key derivation algorithm."""
398 if self.category != AlgorithmCategory.KEY_AGREEMENT:
399 return False
400 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
401 if not m:
402 return False
403 kdf_alg = m.group(1)
404 # Assume kdf_alg is either a valid KDF or 0.
405 return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
406
Gilles Peskined79aef52022-03-17 23:42:25 +0100407
Gilles Peskine930ccef2022-03-18 00:02:15 +0100408 def short_expression(self, level: int = 0) -> str:
Gilles Peskined79aef52022-03-17 23:42:25 +0100409 """Abbreviate the expression, keeping it human-readable.
410
411 See `crypto_knowledge.short_expression`.
412 """
Gilles Peskine930ccef2022-03-18 00:02:15 +0100413 return short_expression(self.expression, level=level)
Gilles Peskined79aef52022-03-17 23:42:25 +0100414
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200415 def can_do(self, category: AlgorithmCategory) -> bool:
416 """Whether this algorithm fits the specified operation category."""
417 if category == self.category:
418 return True
419 if category == AlgorithmCategory.KEY_DERIVATION and \
420 self.is_key_agreement_with_derivation():
421 return True
422 return False