blob: f2775265cc896dd93d8d6e987ed6258069ed5d69 [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 Peskinefa70ced2022-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 Peskineee7554e2021-04-29 20:38:01 +020027
Gilles Peskinee8e058c2022-03-17 23:42:25 +010028def short_expression(original: str) -> str:
29 """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)
39 return short
40
41
Gilles Peskine8345d632021-04-29 20:38:47 +020042BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
Gilles Peskineee7554e2021-04-29 20:38:01 +020043BLOCK_MAC_MODES = frozenset(['CBC_MAC', 'CMAC'])
44BLOCK_CIPHER_MODES = frozenset([
45 'CTR', 'CFB', 'OFB', 'XTS', 'CCM_STAR_NO_TAG',
46 'ECB_NO_PADDING', 'CBC_NO_PADDING', 'CBC_PKCS7',
47])
48BLOCK_AEAD_MODES = frozenset(['CCM', 'GCM'])
49
Gilles Peskine8345d632021-04-29 20:38:47 +020050class EllipticCurveCategory(enum.Enum):
51 """Categorization of elliptic curve families.
52
53 The category of a curve determines what algorithms are defined over it.
54 """
55
56 SHORT_WEIERSTRASS = 0
57 MONTGOMERY = 1
58 TWISTED_EDWARDS = 2
59
60 @staticmethod
61 def from_family(family: str) -> 'EllipticCurveCategory':
62 if family == 'PSA_ECC_FAMILY_MONTGOMERY':
63 return EllipticCurveCategory.MONTGOMERY
64 if family == 'PSA_ECC_FAMILY_TWISTED_EDWARDS':
65 return EllipticCurveCategory.TWISTED_EDWARDS
66 # Default to SW, which most curves belong to.
67 return EllipticCurveCategory.SHORT_WEIERSTRASS
68
Gilles Peskineee7554e2021-04-29 20:38:01 +020069
Gilles Peskine0156a152021-01-26 21:23:56 +010070class KeyType:
71 """Knowledge about a PSA key type."""
72
Gilles Peskineb9dbb7f2021-04-29 20:19:57 +020073 def __init__(self, name: str, params: Optional[Iterable[str]] = None) -> None:
Gilles Peskine0156a152021-01-26 21:23:56 +010074 """Analyze a key type.
75
76 The key type must be specified in PSA syntax. In its simplest form,
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010077 `name` is a string 'PSA_KEY_TYPE_xxx' which is the name of a PSA key
Gilles Peskine0156a152021-01-26 21:23:56 +010078 type macro. For key types that take arguments, the arguments can
79 be passed either through the optional argument `params` or by
Gilles Peskine4d0b0892021-04-12 13:41:52 +020080 passing an expression of the form 'PSA_KEY_TYPE_xxx(param1, ...)'
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010081 in `name` as a string.
Gilles Peskine0156a152021-01-26 21:23:56 +010082 """
Gilles Peskined75adfc2021-02-17 18:04:28 +010083
Gilles Peskine0156a152021-01-26 21:23:56 +010084 self.name = name.strip()
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010085 """The key type macro name (``PSA_KEY_TYPE_xxx``).
86
87 For key types constructed from a macro with arguments, this is the
88 name of the macro, and the arguments are in `self.params`.
89 """
Gilles Peskine0156a152021-01-26 21:23:56 +010090 if params is None:
91 if '(' in self.name:
92 m = re.match(r'(\w+)\s*\((.*)\)\Z', self.name)
93 assert m is not None
94 self.name = m.group(1)
Gilles Peskine4d0b0892021-04-12 13:41:52 +020095 params = m.group(2).split(',')
Gilles Peskinefa3c69a2021-02-16 14:29:22 +010096 self.params = (None if params is None else
97 [param.strip() for param in params])
98 """The parameters of the key type, if there are any.
99
100 None if the key type is a macro without arguments.
101 """
Gilles Peskined75adfc2021-02-17 18:04:28 +0100102 assert re.match(r'PSA_KEY_TYPE_\w+\Z', self.name)
103
Gilles Peskine0156a152021-01-26 21:23:56 +0100104 self.expression = self.name
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100105 """A C expression whose value is the key type encoding."""
Gilles Peskine0156a152021-01-26 21:23:56 +0100106 if self.params is not None:
107 self.expression += '(' + ', '.join(self.params) + ')'
Gilles Peskined75adfc2021-02-17 18:04:28 +0100108
Gilles Peskine8345d632021-04-29 20:38:47 +0200109 m = re.match(r'PSA_KEY_TYPE_(\w+)', self.name)
110 assert m
111 self.head = re.sub(r'_(?:PUBLIC_KEY|KEY_PAIR)\Z', r'', m.group(1))
112 """The key type macro name, with common prefixes and suffixes stripped."""
113
Gilles Peskine0156a152021-01-26 21:23:56 +0100114 self.private_type = re.sub(r'_PUBLIC_KEY\Z', r'_KEY_PAIR', self.name)
Gilles Peskinefa3c69a2021-02-16 14:29:22 +0100115 """The key type macro name for the corresponding key pair type.
116
117 For everything other than a public key type, this is the same as
118 `self.name`.
119 """
Gilles Peskinedf639682021-01-26 21:25:34 +0100120
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100121 def short_expression(self) -> str:
122 """Abbreviate the expression, keeping it human-readable.
123
124 See `crypto_knowledge.short_expression`.
125 """
126 return short_expression(self.expression)
127
Gilles Peskinee6300952021-04-29 21:56:59 +0200128 def is_public(self) -> bool:
129 """Whether the key type is for public keys."""
130 return self.name.endswith('_PUBLIC_KEY')
131
Gilles Peskinedf639682021-01-26 21:25:34 +0100132 ECC_KEY_SIZES = {
133 'PSA_ECC_FAMILY_SECP_K1': (192, 224, 256),
Gilles Peskine0ac258e2021-01-27 13:11:59 +0100134 'PSA_ECC_FAMILY_SECP_R1': (225, 256, 384, 521),
Gilles Peskinedf639682021-01-26 21:25:34 +0100135 'PSA_ECC_FAMILY_SECP_R2': (160,),
136 'PSA_ECC_FAMILY_SECT_K1': (163, 233, 239, 283, 409, 571),
137 'PSA_ECC_FAMILY_SECT_R1': (163, 233, 283, 409, 571),
138 'PSA_ECC_FAMILY_SECT_R2': (163,),
139 'PSA_ECC_FAMILY_BRAINPOOL_P_R1': (160, 192, 224, 256, 320, 384, 512),
140 'PSA_ECC_FAMILY_MONTGOMERY': (255, 448),
Gilles Peskinea00abc62021-03-16 18:25:14 +0100141 'PSA_ECC_FAMILY_TWISTED_EDWARDS': (255, 448),
Gilles Peskinedf639682021-01-26 21:25:34 +0100142 }
143 KEY_TYPE_SIZES = {
144 'PSA_KEY_TYPE_AES': (128, 192, 256), # exhaustive
Gilles Peskinedf639682021-01-26 21:25:34 +0100145 'PSA_KEY_TYPE_ARIA': (128, 192, 256), # exhaustive
146 'PSA_KEY_TYPE_CAMELLIA': (128, 192, 256), # exhaustive
147 'PSA_KEY_TYPE_CHACHA20': (256,), # exhaustive
148 'PSA_KEY_TYPE_DERIVE': (120, 128), # sample
149 'PSA_KEY_TYPE_DES': (64, 128, 192), # exhaustive
150 '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 +0200151 'PSA_KEY_TYPE_PASSWORD': (48, 168, 336), # sample
152 'PSA_KEY_TYPE_PASSWORD_HASH': (128, 256), # sample
153 'PSA_KEY_TYPE_PEPPER': (128, 256), # sample
Gilles Peskinedf639682021-01-26 21:25:34 +0100154 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
155 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
156 }
157 def sizes_to_test(self) -> Tuple[int, ...]:
158 """Return a tuple of key sizes to test.
159
160 For key types that only allow a single size, or only a small set of
161 sizes, these are all the possible sizes. For key types that allow a
162 wide range of sizes, these are a representative sample of sizes,
163 excluding large sizes for which a typical resource-constrained platform
164 may run out of memory.
165 """
166 if self.private_type == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
167 assert self.params is not None
168 return self.ECC_KEY_SIZES[self.params[0]]
169 return self.KEY_TYPE_SIZES[self.private_type]
Gilles Peskine397b0282021-01-26 21:26:26 +0100170
171 # "48657265006973206b6579a064617461"
172 DATA_BLOCK = b'Here\000is key\240data'
173 def key_material(self, bits: int) -> bytes:
174 """Return a byte string containing suitable key material with the given bit length.
175
176 Use the PSA export representation. The resulting byte string is one that
177 can be obtained with the following code:
178 ```
179 psa_set_key_type(&attributes, `self.expression`);
180 psa_set_key_bits(&attributes, `bits`);
181 psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT);
182 psa_generate_key(&attributes, &id);
183 psa_export_key(id, `material`, ...);
184 ```
185 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100186 if self.expression in ASYMMETRIC_KEY_DATA:
187 if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
188 raise ValueError('No key data for {}-bit {}'
189 .format(bits, self.expression))
190 return ASYMMETRIC_KEY_DATA[self.expression][bits]
Gilles Peskine397b0282021-01-26 21:26:26 +0100191 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100192 raise ValueError('Non-integer number of bytes: {} bits for {}'
193 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100194 length = bits // 8
195 if self.name == 'PSA_KEY_TYPE_DES':
196 # "644573206b457901644573206b457902644573206b457904"
197 des3 = b'dEs kEy\001dEs kEy\002dEs kEy\004'
198 return des3[:length]
Gilles Peskine397b0282021-01-26 21:26:26 +0100199 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
200 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm2784bfe2021-06-28 20:02:11 +0200201
Gilles Peskine8345d632021-04-29 20:38:47 +0200202 def can_do(self, alg: 'Algorithm') -> bool:
203 """Whether this key type can be used for operations with the given algorithm.
204
205 This function does not currently handle key derivation or PAKE.
206 """
207 #pylint: disable=too-many-return-statements
208 if alg.is_wildcard:
209 return False
210 if self.head == 'HMAC' and alg.head == 'HMAC':
211 return True
212 if self.head in BLOCK_CIPHERS and \
213 alg.head in frozenset.union(BLOCK_MAC_MODES,
214 BLOCK_CIPHER_MODES,
215 BLOCK_AEAD_MODES):
216 return True
217 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
218 return True
219 if self.head in {'ARC4', 'CHACHA20'} and \
220 alg.head == 'STREAM_CIPHER':
221 return True
222 if self.head == 'RSA' and alg.head.startswith('RSA_'):
223 return True
224 if self.head == 'ECC':
225 assert self.params is not None
226 eccc = EllipticCurveCategory.from_family(self.params[0])
227 if alg.head == 'ECDH' and \
228 eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
229 EllipticCurveCategory.MONTGOMERY}:
230 return True
231 if alg.head == 'ECDSA' and \
232 eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
233 return True
234 if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
235 eccc == EllipticCurveCategory.TWISTED_EDWARDS:
236 return True
237 return False
238
Gilles Peskineee7554e2021-04-29 20:38:01 +0200239
240class AlgorithmCategory(enum.Enum):
241 """PSA algorithm categories."""
242 # The numbers are aligned with the category bits in numerical values of
243 # algorithms.
244 HASH = 2
245 MAC = 3
246 CIPHER = 4
247 AEAD = 5
248 SIGN = 6
249 ASYMMETRIC_ENCRYPTION = 7
250 KEY_DERIVATION = 8
251 KEY_AGREEMENT = 9
252 PAKE = 10
253
254 def requires_key(self) -> bool:
Gilles Peskinee6300952021-04-29 21:56:59 +0200255 """Whether operations in this category are set up with a key."""
Gilles Peskineee7554e2021-04-29 20:38:01 +0200256 return self not in {self.HASH, self.KEY_DERIVATION}
257
Gilles Peskinee6300952021-04-29 21:56:59 +0200258 def is_asymmetric(self) -> bool:
259 """Whether operations in this category involve asymmetric keys."""
260 return self in {
261 self.SIGN,
262 self.ASYMMETRIC_ENCRYPTION,
263 self.KEY_AGREEMENT
264 }
265
Gilles Peskineee7554e2021-04-29 20:38:01 +0200266
267class AlgorithmNotRecognized(Exception):
268 def __init__(self, expr: str) -> None:
269 super().__init__('Algorithm not recognized: ' + expr)
270 self.expr = expr
271
272
273class Algorithm:
274 """Knowledge about a PSA algorithm."""
275
276 @staticmethod
277 def determine_base(expr: str) -> str:
278 """Return an expression for the "base" of the algorithm.
279
280 This strips off variants of algorithms such as MAC truncation.
281
282 This function does not attempt to detect invalid inputs.
283 """
284 m = re.match(r'PSA_ALG_(?:'
285 r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
286 r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
287 r')\((.*),[^,]+\)\Z', expr)
288 if m:
289 expr = m.group(1)
290 return expr
291
292 @staticmethod
293 def determine_head(expr: str) -> str:
294 """Return the head of an algorithm expression.
295
296 The head is the first (outermost) constructor, without its PSA_ALG_
297 prefix, and with some normalization of similar algorithms.
298 """
299 m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
300 if not m:
301 raise AlgorithmNotRecognized(expr)
302 head = m.group(1)
303 if head == 'KEY_AGREEMENT':
304 m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
305 if not m:
306 raise AlgorithmNotRecognized(expr)
307 head = m.group(1)
308 head = re.sub(r'_ANY\Z', r'', head)
309 if re.match(r'ED[0-9]+PH\Z', head):
310 head = 'EDDSA_PREHASH'
311 return head
312
313 CATEGORY_FROM_HEAD = {
314 'SHA': AlgorithmCategory.HASH,
315 'SHAKE256_512': AlgorithmCategory.HASH,
316 'MD': AlgorithmCategory.HASH,
317 'RIPEMD': AlgorithmCategory.HASH,
318 'ANY_HASH': AlgorithmCategory.HASH,
319 'HMAC': AlgorithmCategory.MAC,
320 'STREAM_CIPHER': AlgorithmCategory.CIPHER,
321 'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
322 'DSA': AlgorithmCategory.SIGN,
323 'ECDSA': AlgorithmCategory.SIGN,
324 'EDDSA': AlgorithmCategory.SIGN,
325 'PURE_EDDSA': AlgorithmCategory.SIGN,
326 'RSA_PSS': AlgorithmCategory.SIGN,
327 'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
328 'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
329 'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
330 'HKDF': AlgorithmCategory.KEY_DERIVATION,
331 'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
332 'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
333 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
334 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
335 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
336 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
337 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
338 'JPAKE': AlgorithmCategory.PAKE,
339 }
340 for x in BLOCK_MAC_MODES:
341 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
342 for x in BLOCK_CIPHER_MODES:
343 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
344 for x in BLOCK_AEAD_MODES:
345 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
346
347 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
348 """Return the category of the given algorithm expression.
349
350 This function does not attempt to detect invalid inputs.
351 """
352 prefix = head
353 while prefix:
354 if prefix in self.CATEGORY_FROM_HEAD:
355 return self.CATEGORY_FROM_HEAD[prefix]
356 if re.match(r'.*[0-9]\Z', prefix):
357 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
358 else:
359 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
360 raise AlgorithmNotRecognized(expr)
361
362 @staticmethod
363 def determine_wildcard(expr) -> bool:
364 """Whether the given algorithm expression is a wildcard.
365
366 This function does not attempt to detect invalid inputs.
367 """
368 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
369 return True
370 if re.search(r'_AT_LEAST_', expr):
371 return True
372 return False
373
374 def __init__(self, expr: str) -> None:
375 """Analyze an algorithm value.
376
377 The algorithm must be expressed as a C expression containing only
378 calls to PSA algorithm constructor macros and numeric literals.
379
380 This class is only programmed to handle valid expressions. Invalid
381 expressions may result in exceptions or in nonsensical results.
382 """
383 self.expression = re.sub(r'\s+', r'', expr)
384 self.base_expression = self.determine_base(self.expression)
385 self.head = self.determine_head(self.base_expression)
386 self.category = self.determine_category(self.base_expression, self.head)
387 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskinea4013862021-04-29 20:54:40 +0200388
389 def is_key_agreement_with_derivation(self) -> bool:
390 """Whether this is a combined key agreement and key derivation algorithm."""
391 if self.category != AlgorithmCategory.KEY_AGREEMENT:
392 return False
393 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
394 if not m:
395 return False
396 kdf_alg = m.group(1)
397 # Assume kdf_alg is either a valid KDF or 0.
398 return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
399
Gilles Peskinee8e058c2022-03-17 23:42:25 +0100400
401 def short_expression(self) -> str:
402 """Abbreviate the expression, keeping it human-readable.
403
404 See `crypto_knowledge.short_expression`.
405 """
406 return short_expression(self.expression)
407
Gilles Peskinea4013862021-04-29 20:54:40 +0200408 def can_do(self, category: AlgorithmCategory) -> bool:
409 """Whether this algorithm fits the specified operation category."""
410 if category == self.category:
411 return True
412 if category == AlgorithmCategory.KEY_DERIVATION and \
413 self.is_key_agreement_with_derivation():
414 return True
415 return False