blob: 94ed085fa93e4e6aaa1a121791090205ea4f90aa [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 Peskined79aef52022-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 Peskine39054332021-04-29 20:38:47 +020042BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
Gilles Peskine0dacd4d2021-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 Peskine39054332021-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 Peskine0dacd4d2021-04-29 20:38:01 +020069
Gilles Peskine0156a152021-01-26 21:23:56 +010070class KeyType:
71 """Knowledge about a PSA key type."""
72
Gilles Peskine2a71b722021-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 Peskine0ba69a42021-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 Peskine0ba69a42021-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 Peskine39054332021-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 Peskined79aef52022-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 Peskinec2fc2412021-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
145 'PSA_KEY_TYPE_ARC4': (8, 128, 2048), # extremes + sensible
146 'PSA_KEY_TYPE_ARIA': (128, 192, 256), # exhaustive
147 'PSA_KEY_TYPE_CAMELLIA': (128, 192, 256), # exhaustive
148 'PSA_KEY_TYPE_CHACHA20': (256,), # exhaustive
149 'PSA_KEY_TYPE_DERIVE': (120, 128), # sample
150 'PSA_KEY_TYPE_DES': (64, 128, 192), # exhaustive
151 'PSA_KEY_TYPE_HMAC': (128, 160, 224, 256, 384, 512), # standard size for each supported hash
152 'PSA_KEY_TYPE_RAW_DATA': (8, 40, 128), # sample
153 'PSA_KEY_TYPE_RSA_KEY_PAIR': (1024, 1536), # small sample
154 }
155 def sizes_to_test(self) -> Tuple[int, ...]:
156 """Return a tuple of key sizes to test.
157
158 For key types that only allow a single size, or only a small set of
159 sizes, these are all the possible sizes. For key types that allow a
160 wide range of sizes, these are a representative sample of sizes,
161 excluding large sizes for which a typical resource-constrained platform
162 may run out of memory.
163 """
164 if self.private_type == 'PSA_KEY_TYPE_ECC_KEY_PAIR':
165 assert self.params is not None
166 return self.ECC_KEY_SIZES[self.params[0]]
167 return self.KEY_TYPE_SIZES[self.private_type]
Gilles Peskine397b0282021-01-26 21:26:26 +0100168
169 # "48657265006973206b6579a064617461"
170 DATA_BLOCK = b'Here\000is key\240data'
171 def key_material(self, bits: int) -> bytes:
172 """Return a byte string containing suitable key material with the given bit length.
173
174 Use the PSA export representation. The resulting byte string is one that
175 can be obtained with the following code:
176 ```
177 psa_set_key_type(&attributes, `self.expression`);
178 psa_set_key_bits(&attributes, `bits`);
179 psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_EXPORT);
180 psa_generate_key(&attributes, &id);
181 psa_export_key(id, `material`, ...);
182 ```
183 """
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100184 if self.expression in ASYMMETRIC_KEY_DATA:
185 if bits not in ASYMMETRIC_KEY_DATA[self.expression]:
186 raise ValueError('No key data for {}-bit {}'
187 .format(bits, self.expression))
188 return ASYMMETRIC_KEY_DATA[self.expression][bits]
Gilles Peskine397b0282021-01-26 21:26:26 +0100189 if bits % 8 != 0:
Gilles Peskine6f6483f2021-01-27 12:43:24 +0100190 raise ValueError('Non-integer number of bytes: {} bits for {}'
191 .format(bits, self.expression))
Gilles Peskine397b0282021-01-26 21:26:26 +0100192 length = bits // 8
193 if self.name == 'PSA_KEY_TYPE_DES':
194 # "644573206b457901644573206b457902644573206b457904"
195 des3 = b'dEs kEy\001dEs kEy\002dEs kEy\004'
196 return des3[:length]
Gilles Peskine397b0282021-01-26 21:26:26 +0100197 return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
198 [self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
gabor-mezei-arm805c7352021-06-28 20:02:11 +0200199
Gilles Peskine39054332021-04-29 20:38:47 +0200200 def can_do(self, alg: 'Algorithm') -> bool:
201 """Whether this key type can be used for operations with the given algorithm.
202
203 This function does not currently handle key derivation or PAKE.
204 """
205 #pylint: disable=too-many-return-statements
206 if alg.is_wildcard:
207 return False
208 if self.head == 'HMAC' and alg.head == 'HMAC':
209 return True
210 if self.head in BLOCK_CIPHERS and \
211 alg.head in frozenset.union(BLOCK_MAC_MODES,
212 BLOCK_CIPHER_MODES,
213 BLOCK_AEAD_MODES):
214 return True
215 if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
216 return True
217 if self.head in {'ARC4', 'CHACHA20'} and \
218 alg.head == 'STREAM_CIPHER':
219 return True
220 if self.head == 'RSA' and alg.head.startswith('RSA_'):
221 return True
222 if self.head == 'ECC':
223 assert self.params is not None
224 eccc = EllipticCurveCategory.from_family(self.params[0])
225 if alg.head == 'ECDH' and \
226 eccc in {EllipticCurveCategory.SHORT_WEIERSTRASS,
227 EllipticCurveCategory.MONTGOMERY}:
228 return True
229 if alg.head == 'ECDSA' and \
230 eccc == EllipticCurveCategory.SHORT_WEIERSTRASS:
231 return True
232 if alg.head in {'PURE_EDDSA', 'EDDSA_PREHASH'} and \
233 eccc == EllipticCurveCategory.TWISTED_EDWARDS:
234 return True
235 return False
236
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200237
238class AlgorithmCategory(enum.Enum):
239 """PSA algorithm categories."""
240 # The numbers are aligned with the category bits in numerical values of
241 # algorithms.
242 HASH = 2
243 MAC = 3
244 CIPHER = 4
245 AEAD = 5
246 SIGN = 6
247 ASYMMETRIC_ENCRYPTION = 7
248 KEY_DERIVATION = 8
249 KEY_AGREEMENT = 9
250 PAKE = 10
251
252 def requires_key(self) -> bool:
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200253 """Whether operations in this category are set up with a key."""
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200254 return self not in {self.HASH, self.KEY_DERIVATION}
255
Gilles Peskinec2fc2412021-04-29 21:56:59 +0200256 def is_asymmetric(self) -> bool:
257 """Whether operations in this category involve asymmetric keys."""
258 return self in {
259 self.SIGN,
260 self.ASYMMETRIC_ENCRYPTION,
261 self.KEY_AGREEMENT
262 }
263
Gilles Peskine0dacd4d2021-04-29 20:38:01 +0200264
265class AlgorithmNotRecognized(Exception):
266 def __init__(self, expr: str) -> None:
267 super().__init__('Algorithm not recognized: ' + expr)
268 self.expr = expr
269
270
271class Algorithm:
272 """Knowledge about a PSA algorithm."""
273
274 @staticmethod
275 def determine_base(expr: str) -> str:
276 """Return an expression for the "base" of the algorithm.
277
278 This strips off variants of algorithms such as MAC truncation.
279
280 This function does not attempt to detect invalid inputs.
281 """
282 m = re.match(r'PSA_ALG_(?:'
283 r'(?:TRUNCATED|AT_LEAST_THIS_LENGTH)_MAC|'
284 r'AEAD_WITH_(?:SHORTENED|AT_LEAST_THIS_LENGTH)_TAG'
285 r')\((.*),[^,]+\)\Z', expr)
286 if m:
287 expr = m.group(1)
288 return expr
289
290 @staticmethod
291 def determine_head(expr: str) -> str:
292 """Return the head of an algorithm expression.
293
294 The head is the first (outermost) constructor, without its PSA_ALG_
295 prefix, and with some normalization of similar algorithms.
296 """
297 m = re.match(r'PSA_ALG_(?:DETERMINISTIC_)?(\w+)', expr)
298 if not m:
299 raise AlgorithmNotRecognized(expr)
300 head = m.group(1)
301 if head == 'KEY_AGREEMENT':
302 m = re.match(r'PSA_ALG_KEY_AGREEMENT\s*\(\s*PSA_ALG_(\w+)', expr)
303 if not m:
304 raise AlgorithmNotRecognized(expr)
305 head = m.group(1)
306 head = re.sub(r'_ANY\Z', r'', head)
307 if re.match(r'ED[0-9]+PH\Z', head):
308 head = 'EDDSA_PREHASH'
309 return head
310
311 CATEGORY_FROM_HEAD = {
312 'SHA': AlgorithmCategory.HASH,
313 'SHAKE256_512': AlgorithmCategory.HASH,
314 'MD': AlgorithmCategory.HASH,
315 'RIPEMD': AlgorithmCategory.HASH,
316 'ANY_HASH': AlgorithmCategory.HASH,
317 'HMAC': AlgorithmCategory.MAC,
318 'STREAM_CIPHER': AlgorithmCategory.CIPHER,
319 'CHACHA20_POLY1305': AlgorithmCategory.AEAD,
320 'DSA': AlgorithmCategory.SIGN,
321 'ECDSA': AlgorithmCategory.SIGN,
322 'EDDSA': AlgorithmCategory.SIGN,
323 'PURE_EDDSA': AlgorithmCategory.SIGN,
324 'RSA_PSS': AlgorithmCategory.SIGN,
325 'RSA_PKCS1V15_SIGN': AlgorithmCategory.SIGN,
326 'RSA_PKCS1V15_CRYPT': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
327 'RSA_OAEP': AlgorithmCategory.ASYMMETRIC_ENCRYPTION,
328 'HKDF': AlgorithmCategory.KEY_DERIVATION,
329 'TLS12_PRF': AlgorithmCategory.KEY_DERIVATION,
330 'TLS12_PSK_TO_MS': AlgorithmCategory.KEY_DERIVATION,
331 'PBKDF': AlgorithmCategory.KEY_DERIVATION,
332 'ECDH': AlgorithmCategory.KEY_AGREEMENT,
333 'FFDH': AlgorithmCategory.KEY_AGREEMENT,
334 # KEY_AGREEMENT(...) is a key derivation with a key agreement component
335 'KEY_AGREEMENT': AlgorithmCategory.KEY_DERIVATION,
336 'JPAKE': AlgorithmCategory.PAKE,
337 }
338 for x in BLOCK_MAC_MODES:
339 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.MAC
340 for x in BLOCK_CIPHER_MODES:
341 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.CIPHER
342 for x in BLOCK_AEAD_MODES:
343 CATEGORY_FROM_HEAD[x] = AlgorithmCategory.AEAD
344
345 def determine_category(self, expr: str, head: str) -> AlgorithmCategory:
346 """Return the category of the given algorithm expression.
347
348 This function does not attempt to detect invalid inputs.
349 """
350 prefix = head
351 while prefix:
352 if prefix in self.CATEGORY_FROM_HEAD:
353 return self.CATEGORY_FROM_HEAD[prefix]
354 if re.match(r'.*[0-9]\Z', prefix):
355 prefix = re.sub(r'_*[0-9]+\Z', r'', prefix)
356 else:
357 prefix = re.sub(r'_*[^_]*\Z', r'', prefix)
358 raise AlgorithmNotRecognized(expr)
359
360 @staticmethod
361 def determine_wildcard(expr) -> bool:
362 """Whether the given algorithm expression is a wildcard.
363
364 This function does not attempt to detect invalid inputs.
365 """
366 if re.search(r'\bPSA_ALG_ANY_HASH\b', expr):
367 return True
368 if re.search(r'_AT_LEAST_', expr):
369 return True
370 return False
371
372 def __init__(self, expr: str) -> None:
373 """Analyze an algorithm value.
374
375 The algorithm must be expressed as a C expression containing only
376 calls to PSA algorithm constructor macros and numeric literals.
377
378 This class is only programmed to handle valid expressions. Invalid
379 expressions may result in exceptions or in nonsensical results.
380 """
381 self.expression = re.sub(r'\s+', r'', expr)
382 self.base_expression = self.determine_base(self.expression)
383 self.head = self.determine_head(self.base_expression)
384 self.category = self.determine_category(self.base_expression, self.head)
385 self.is_wildcard = self.determine_wildcard(self.expression)
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200386
387 def is_key_agreement_with_derivation(self) -> bool:
388 """Whether this is a combined key agreement and key derivation algorithm."""
389 if self.category != AlgorithmCategory.KEY_AGREEMENT:
390 return False
391 m = re.match(r'PSA_ALG_KEY_AGREEMENT\(\w+,\s*(.*)\)\Z', self.expression)
392 if not m:
393 return False
394 kdf_alg = m.group(1)
395 # Assume kdf_alg is either a valid KDF or 0.
396 return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
397
Gilles Peskined79aef52022-03-17 23:42:25 +0100398
399 def short_expression(self) -> str:
400 """Abbreviate the expression, keeping it human-readable.
401
402 See `crypto_knowledge.short_expression`.
403 """
404 return short_expression(self.expression)
405
Gilles Peskine23cb12e2021-04-29 20:54:40 +0200406 def can_do(self, category: AlgorithmCategory) -> bool:
407 """Whether this algorithm fits the specified operation category."""
408 if category == self.category:
409 return True
410 if category == AlgorithmCategory.KEY_DERIVATION and \
411 self.is_key_agreement_with_derivation():
412 return True
413 return False