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