blob: 9e628abc60b558780668922ac1ccd0cad97a9d2e [file] [log] [blame]
Minos Galanakis2c824b42025-03-20 09:28:45 +00001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
3
4With no arguments, generate all test data. With non-option arguments,
5generate only the specified files.
6"""
7
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
10
11import enum
12import re
13import sys
14from typing import Callable, Dict, Iterable, Iterator, List, Optional
15
16from mbedtls_framework import crypto_data_tests
17from mbedtls_framework import crypto_knowledge
18from mbedtls_framework import macro_collector #pylint: disable=unused-import
19from mbedtls_framework import psa_information
20from mbedtls_framework import psa_storage
21from mbedtls_framework import psa_test_case
22from mbedtls_framework import test_case
23from mbedtls_framework import test_data_generation
24
25
26
27def test_case_for_key_type_not_supported(
28 verb: str, key_type: str, bits: int,
29 not_supported_mechanism: str,
30 *args: str,
31 param_descr: str = ''
32) -> test_case.TestCase:
33 """Return one test case exercising a key creation method
34 for an unsupported key type or size.
35 """
36 tc = psa_test_case.TestCase()
37 short_key_type = crypto_knowledge.short_expression(key_type)
38 tc.set_description('PSA {} {} {}-bit{} not supported'
39 .format(verb, short_key_type, bits,
40 ' ' + param_descr if param_descr else ''))
41 # if tc.description == 'PSA import RSA_KEY_PAIR 1024-bit not supported':
42 # import pdb; pdb.set_trace()
43 tc.set_function(verb + '_not_supported')
44 tc.set_key_bits(bits)
45 tc.set_key_pair_usage([verb.upper()])
46 tc.assumes_not_supported(not_supported_mechanism)
47 tc.set_arguments([key_type] + list(args))
48 return tc
49
50class KeyTypeNotSupported:
51 """Generate test cases for when a key type is not supported."""
52
53 def __init__(self, info: psa_information.Information) -> None:
54 self.constructors = info.constructors
55
56 ALWAYS_SUPPORTED = frozenset([
57 'PSA_KEY_TYPE_DERIVE',
58 'PSA_KEY_TYPE_PASSWORD',
59 'PSA_KEY_TYPE_PASSWORD_HASH',
60 'PSA_KEY_TYPE_RAW_DATA',
61 'PSA_KEY_TYPE_HMAC'
62 ])
63 def test_cases_for_key_type_not_supported(
64 self,
65 kt: crypto_knowledge.KeyType,
66 param: Optional[int] = None,
67 param_descr: str = '',
68 ) -> Iterator[test_case.TestCase]:
69 """Return test cases exercising key creation when the given type is unsupported.
70
71 If param is present and not None, emit test cases conditioned on this
72 parameter not being supported. If it is absent or None, emit test cases
73 conditioned on the base type not being supported.
74 """
75 if kt.name in self.ALWAYS_SUPPORTED:
76 # Don't generate test cases for key types that are always supported.
77 # They would be skipped in all configurations, which is noise.
78 return
79 if param is None:
80 not_supported_mechanism = kt.name
81 else:
82 assert kt.params is not None
83 not_supported_mechanism = kt.params[param]
84 for bits in kt.sizes_to_test():
85 yield test_case_for_key_type_not_supported(
86 'import', kt.expression, bits,
87 not_supported_mechanism,
88 test_case.hex_string(kt.key_material(bits)),
89 param_descr=param_descr,
90 )
91 # Don't generate not-supported test cases for key generation of
92 # public keys. Our implementation always returns
93 # PSA_ERROR_INVALID_ARGUMENT when attempting to generate a
94 # public key, so we cover this together with the positive cases
95 # in the KeyGenerate class.
96 if not kt.is_public():
97 yield test_case_for_key_type_not_supported(
98 'generate', kt.expression, bits,
99 not_supported_mechanism,
100 str(bits),
101 param_descr=param_descr,
102 )
103 # To be added: derive
104
105 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
106 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
107 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
108 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
109
110 def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
111 """Generate test cases that exercise the creation of keys of unsupported types."""
112 for key_type in sorted(self.constructors.key_types):
113 if key_type in self.ECC_KEY_TYPES:
114 continue
115 if key_type in self.DH_KEY_TYPES:
116 continue
117 kt = crypto_knowledge.KeyType(key_type)
118 yield from self.test_cases_for_key_type_not_supported(kt)
119 for curve_family in sorted(self.constructors.ecc_curves):
120 for constr in self.ECC_KEY_TYPES:
121 kt = crypto_knowledge.KeyType(constr, [curve_family])
122 yield from self.test_cases_for_key_type_not_supported(
123 kt, param_descr='type')
124 yield from self.test_cases_for_key_type_not_supported(
125 kt, 0, param_descr='curve')
126 for dh_family in sorted(self.constructors.dh_groups):
127 for constr in self.DH_KEY_TYPES:
128 kt = crypto_knowledge.KeyType(constr, [dh_family])
129 yield from self.test_cases_for_key_type_not_supported(
130 kt, param_descr='type')
131 yield from self.test_cases_for_key_type_not_supported(
132 kt, 0, param_descr='group')
133
134def test_case_for_key_generation(
135 key_type: str, bits: int,
136 *args: str,
137 result: str = ''
138) -> test_case.TestCase:
139 """Return one test case exercising a key generation.
140 """
141 tc = psa_test_case.TestCase()
142 short_key_type = crypto_knowledge.short_expression(key_type)
143 tc.set_description('PSA {} {}-bit'
144 .format(short_key_type, bits))
145 tc.set_function('generate_key')
146 tc.set_key_bits(bits)
147 tc.set_key_pair_usage(['GENERATE'])
148 tc.set_arguments([key_type] + list(args) + [result])
149 return tc
150
151class KeyGenerate:
152 """Generate positive and negative (invalid argument) test cases for key generation."""
153
154 def __init__(self, info: psa_information.Information) -> None:
155 self.constructors = info.constructors
156
157 ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
158 'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
159 DH_KEY_TYPES = ('PSA_KEY_TYPE_DH_KEY_PAIR',
160 'PSA_KEY_TYPE_DH_PUBLIC_KEY')
161
162 @staticmethod
163 def test_cases_for_key_type_key_generation(
164 kt: crypto_knowledge.KeyType
165 ) -> Iterator[test_case.TestCase]:
166 """Return test cases exercising key generation.
167
168 All key types can be generated except for public keys. For public key
169 PSA_ERROR_INVALID_ARGUMENT status is expected.
170 """
171 for bits in kt.sizes_to_test():
172 tc = test_case_for_key_generation(
173 kt.expression, bits,
174 str(bits),
175 'PSA_ERROR_INVALID_ARGUMENT' if kt.is_public() else 'PSA_SUCCESS'
176 )
177 if kt.is_public():
178 # The library checks whether the key type is a public key generically,
179 # before it reaches a point where it needs support for the specific key
180 # type, so it returns INVALID_ARGUMENT for unsupported public key types.
181 tc.set_dependencies([])
182 yield tc
183
184 def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
185 """Generate test cases that exercise the generation of keys."""
186 for key_type in sorted(self.constructors.key_types):
187 if key_type in self.ECC_KEY_TYPES:
188 continue
189 if key_type in self.DH_KEY_TYPES:
190 continue
191 kt = crypto_knowledge.KeyType(key_type)
192 yield from self.test_cases_for_key_type_key_generation(kt)
193 for curve_family in sorted(self.constructors.ecc_curves):
194 for constr in self.ECC_KEY_TYPES:
195 kt = crypto_knowledge.KeyType(constr, [curve_family])
196 yield from self.test_cases_for_key_type_key_generation(kt)
197 for dh_family in sorted(self.constructors.dh_groups):
198 for constr in self.DH_KEY_TYPES:
199 kt = crypto_knowledge.KeyType(constr, [dh_family])
200 yield from self.test_cases_for_key_type_key_generation(kt)
201
202class OpFail:
203 """Generate test cases for operations that must fail."""
204 #pylint: disable=too-few-public-methods
205
206 class Reason(enum.Enum):
207 NOT_SUPPORTED = 0
208 INVALID = 1
209 INCOMPATIBLE = 2
210 PUBLIC = 3
211
212 def __init__(self, info: psa_information.Information) -> None:
213 self.constructors = info.constructors
214 key_type_expressions = self.constructors.generate_expressions(
215 sorted(self.constructors.key_types)
216 )
217 self.key_types = [crypto_knowledge.KeyType(kt_expr)
218 for kt_expr in key_type_expressions]
219
220 def make_test_case(
221 self,
222 alg: crypto_knowledge.Algorithm,
223 category: crypto_knowledge.AlgorithmCategory,
224 reason: 'Reason',
225 kt: Optional[crypto_knowledge.KeyType] = None,
226 not_supported: Optional[str] = None,
227 ) -> test_case.TestCase:
228 """Construct a failure test case for a one-key or keyless operation.
229
230 If `reason` is `Reason.NOT_SUPPORTED`, pass the not-supported
231 dependency symbol as the `not_supported` argument.
232 """
233 #pylint: disable=too-many-arguments,too-many-locals
234 tc = psa_test_case.TestCase()
235 pretty_alg = alg.short_expression()
236 if reason == self.Reason.NOT_SUPPORTED:
237 assert not_supported is not None
238 pretty_reason = '!' + re.sub(r'PSA_WANT_[A-Z]+_', r'', not_supported)
239 else:
240 pretty_reason = reason.name.lower()
241 if kt:
242 key_type = kt.expression
243 pretty_type = kt.short_expression()
244 else:
245 key_type = ''
246 pretty_type = ''
247 tc.set_description('PSA {} {}: {}{}'
248 .format(category.name.lower(),
249 pretty_alg,
250 pretty_reason,
251 ' with ' + pretty_type if pretty_type else ''))
252 tc.set_function(category.name.lower() + '_fail')
253 arguments = [] # type: List[str]
254 if kt:
255 bits = kt.sizes_to_test()[0]
256 tc.set_key_bits(bits)
257 tc.set_key_pair_usage(['IMPORT'])
258 key_material = kt.key_material(bits)
259 arguments += [key_type, test_case.hex_string(key_material)]
260 arguments.append(alg.expression)
261 if category.is_asymmetric():
262 arguments.append('1' if reason == self.Reason.PUBLIC else '0')
263 error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
264 'INVALID_ARGUMENT')
265 arguments.append('PSA_ERROR_' + error)
266 if reason == self.Reason.NOT_SUPPORTED:
267 assert not_supported is not None
268 tc.assumes_not_supported(not_supported)
269 # Special case: if one of deterministic/randomized
270 # ECDSA is supported but not the other, then the one
271 # that is not supported in the signature direction is
272 # still supported in the verification direction,
273 # because the two verification algorithms are
274 # identical. This property is how Mbed TLS chooses to
275 # behave, the specification would also allow it to
276 # reject the algorithm. In the generated test cases,
277 # we avoid this difficulty by not running the
278 # not-supported test case when exactly one of the
279 # two variants is supported.
280 if not_supported == 'PSA_WANT_ALG_ECDSA':
281 tc.add_dependencies(['!PSA_WANT_ALG_DETERMINISTIC_ECDSA'])
282 if not_supported == 'PSA_WANT_ALG_DETERMINISTIC_ECDSA':
283 tc.add_dependencies(['!PSA_WANT_ALG_ECDSA'])
284 tc.set_arguments(arguments)
285 return tc
286
287 def no_key_test_cases(
288 self,
289 alg: crypto_knowledge.Algorithm,
290 category: crypto_knowledge.AlgorithmCategory,
291 ) -> Iterator[test_case.TestCase]:
292 """Generate failure test cases for keyless operations with the specified algorithm."""
293 if alg.can_do(category):
294 # Compatible operation, unsupported algorithm
295 for dep in psa_information.automatic_dependencies(alg.base_expression):
296 yield self.make_test_case(alg, category,
297 self.Reason.NOT_SUPPORTED,
298 not_supported=dep)
299 else:
300 # Incompatible operation, supported algorithm
301 yield self.make_test_case(alg, category, self.Reason.INVALID)
302
303 def one_key_test_cases(
304 self,
305 alg: crypto_knowledge.Algorithm,
306 category: crypto_knowledge.AlgorithmCategory,
307 ) -> Iterator[test_case.TestCase]:
308 """Generate failure test cases for one-key operations with the specified algorithm."""
309 for kt in self.key_types:
310 key_is_compatible = kt.can_do(alg)
311 if key_is_compatible and alg.can_do(category):
312 # Compatible key and operation, unsupported algorithm
313 for dep in psa_information.automatic_dependencies(alg.base_expression):
314 yield self.make_test_case(alg, category,
315 self.Reason.NOT_SUPPORTED,
316 kt=kt, not_supported=dep)
317 # Public key for a private-key operation
318 if category.is_asymmetric() and kt.is_public():
319 yield self.make_test_case(alg, category,
320 self.Reason.PUBLIC,
321 kt=kt)
322 elif key_is_compatible:
323 # Compatible key, incompatible operation, supported algorithm
324 yield self.make_test_case(alg, category,
325 self.Reason.INVALID,
326 kt=kt)
327 elif alg.can_do(category):
328 # Incompatible key, compatible operation, supported algorithm
329 yield self.make_test_case(alg, category,
330 self.Reason.INCOMPATIBLE,
331 kt=kt)
332 else:
333 # Incompatible key and operation. Don't test cases where
334 # multiple things are wrong, to keep the number of test
335 # cases reasonable.
336 pass
337
338 def test_cases_for_algorithm(
339 self,
340 alg: crypto_knowledge.Algorithm,
341 ) -> Iterator[test_case.TestCase]:
342 """Generate operation failure test cases for the specified algorithm."""
343 for category in crypto_knowledge.AlgorithmCategory:
344 if category == crypto_knowledge.AlgorithmCategory.PAKE:
345 # PAKE operations are not implemented yet
346 pass
347 elif category.requires_key():
348 yield from self.one_key_test_cases(alg, category)
349 else:
350 yield from self.no_key_test_cases(alg, category)
351
352 def all_test_cases(self) -> Iterator[test_case.TestCase]:
353 """Generate all test cases for operations that must fail."""
354 algorithms = sorted(self.constructors.algorithms)
355 for expr in self.constructors.generate_expressions(algorithms):
356 alg = crypto_knowledge.Algorithm(expr)
357 yield from self.test_cases_for_algorithm(alg)
358
359
360class StorageKey(psa_storage.Key):
361 """Representation of a key for storage format testing."""
362
363 IMPLICIT_USAGE_FLAGS = {
364 'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
365 'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
366 } #type: Dict[str, str]
367 """Mapping of usage flags to the flags that they imply."""
368
369 def __init__(
370 self,
371 usage: Iterable[str],
372 without_implicit_usage: Optional[bool] = False,
373 **kwargs
374 ) -> None:
375 """Prepare to generate a key.
376
377 * `usage` : The usage flags used for the key.
378 * `without_implicit_usage`: Flag to define to apply the usage extension
379 """
380 usage_flags = set(usage)
381 if not without_implicit_usage:
382 for flag in sorted(usage_flags):
383 if flag in self.IMPLICIT_USAGE_FLAGS:
384 usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
385 if usage_flags:
386 usage_expression = ' | '.join(sorted(usage_flags))
387 else:
388 usage_expression = '0'
389 super().__init__(usage=usage_expression, **kwargs)
390
391class StorageTestData(StorageKey):
392 """Representation of test case data for storage format testing."""
393
394 def __init__(
395 self,
396 description: str,
397 expected_usage: Optional[List[str]] = None,
398 **kwargs
399 ) -> None:
400 """Prepare to generate test data
401
402 * `description` : used for the test case names
403 * `expected_usage`: the usage flags generated as the expected usage flags
404 in the test cases. CAn differ from the usage flags
405 stored in the keys because of the usage flags extension.
406 """
407 super().__init__(**kwargs)
408 self.description = description #type: str
409 if expected_usage is None:
410 self.expected_usage = self.usage #type: psa_storage.Expr
411 elif expected_usage:
412 self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
413 else:
414 self.expected_usage = psa_storage.Expr(0)
415
416class StorageFormat:
417 """Storage format stability test cases."""
418
419 def __init__(self, info: psa_information.Information, version: int, forward: bool) -> None:
420 """Prepare to generate test cases for storage format stability.
421
422 * `info`: information about the API. See the `Information` class.
423 * `version`: the storage format version to generate test cases for.
424 * `forward`: if true, generate forward compatibility test cases which
425 save a key and check that its representation is as intended. Otherwise
426 generate backward compatibility test cases which inject a key
427 representation and check that it can be read and used.
428 """
429 self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
430 self.version = version #type: int
431 self.forward = forward #type: bool
432
433 RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
434 BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
435 @classmethod
436 def exercise_key_with_algorithm(
437 cls,
438 key_type: psa_storage.Expr, bits: int,
439 alg: psa_storage.Expr
440 ) -> bool:
441 """Whether to exercise the given key with the given algorithm.
442
443 Normally only the type and algorithm matter for compatibility, and
444 this is handled in crypto_knowledge.KeyType.can_do(). This function
445 exists to detect exceptional cases. Exceptional cases detected here
446 are not tested in OpFail and should therefore have manually written
447 test cases.
448 """
449 # Some test keys have the RAW_DATA type and attributes that don't
450 # necessarily make sense. We do this to validate numerical
451 # encodings of the attributes.
452 # Raw data keys have no useful exercise anyway so there is no
453 # loss of test coverage.
454 if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
455 return False
456 # OAEP requires room for two hashes plus wrapping
457 m = cls.RSA_OAEP_RE.match(alg.string)
458 if m:
459 hash_alg = m.group(1)
460 hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
461 key_length = (bits + 7) // 8
462 # Leave enough room for at least one byte of plaintext
463 return key_length > 2 * hash_length + 2
464 # There's nothing wrong with ECC keys on Brainpool curves,
465 # but operations with them are very slow. So we only exercise them
466 # with a single algorithm, not with all possible hashes. We do
467 # exercise other curves with all algorithms so test coverage is
468 # perfectly adequate like this.
469 m = cls.BRAINPOOL_RE.match(key_type.string)
470 if m and alg.string != 'PSA_ALG_ECDSA_ANY':
471 return False
472 return True
473
474 def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
475 """Construct a storage format test case for the given key.
476
477 If ``forward`` is true, generate a forward compatibility test case:
478 create a key and validate that it has the expected representation.
479 Otherwise generate a backward compatibility test case: inject the
480 key representation into storage and validate that it can be read
481 correctly.
482 """
483 verb = 'save' if self.forward else 'read'
484 tc = psa_test_case.TestCase()
485 tc.set_description(verb + ' ' + key.description)
486 tc.add_dependencies(psa_information.generate_deps_from_description(key.description))
487 tc.set_function('key_storage_' + verb)
488 tc.set_key_bits(key.bits)
489 tc.set_key_pair_usage(['IMPORT'] if self.forward else ['EXPORT'])
490 if self.forward:
491 extra_arguments = []
492 else:
493 flags = []
494 if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
495 flags.append('TEST_FLAG_EXERCISE')
496 if 'READ_ONLY' in key.lifetime.string:
497 flags.append('TEST_FLAG_READ_ONLY')
498 extra_arguments = [' | '.join(flags) if flags else '0']
499 tc.set_arguments([key.lifetime.string,
500 key.type.string, str(key.bits),
501 key.expected_usage.string,
502 key.alg.string, key.alg2.string,
503 '"' + key.material.hex() + '"',
504 '"' + key.hex() + '"',
505 *extra_arguments])
506 return tc
507
508 def key_for_lifetime(
509 self,
510 lifetime: str,
511 ) -> StorageTestData:
512 """Construct a test key for the given lifetime."""
513 short = lifetime
514 short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
515 r'', short)
516 short = crypto_knowledge.short_expression(short)
517 description = 'lifetime: ' + short
518 key = StorageTestData(version=self.version,
519 id=1, lifetime=lifetime,
520 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
521 usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
522 material=b'L',
523 description=description)
524 return key
525
526 def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
527 """Generate test keys covering lifetimes."""
528 lifetimes = sorted(self.constructors.lifetimes)
529 expressions = self.constructors.generate_expressions(lifetimes)
530 for lifetime in expressions:
531 # Don't attempt to create or load a volatile key in storage
532 if 'VOLATILE' in lifetime:
533 continue
534 # Don't attempt to create a read-only key in storage,
535 # but do attempt to load one.
536 if 'READ_ONLY' in lifetime and self.forward:
537 continue
538 yield self.key_for_lifetime(lifetime)
539
540 def key_for_usage_flags(
541 self,
542 usage_flags: List[str],
543 short: Optional[str] = None,
544 test_implicit_usage: Optional[bool] = True
545 ) -> StorageTestData:
546 """Construct a test key for the given key usage."""
547 extra_desc = ' without implication' if test_implicit_usage else ''
548 description = 'usage' + extra_desc + ': '
549 key1 = StorageTestData(version=self.version,
550 id=1, lifetime=0x00000001,
551 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
552 expected_usage=usage_flags,
553 without_implicit_usage=not test_implicit_usage,
554 usage=usage_flags, alg=0, alg2=0,
555 material=b'K',
556 description=description)
557 if short is None:
558 usage_expr = key1.expected_usage.string
559 key1.description += crypto_knowledge.short_expression(usage_expr)
560 else:
561 key1.description += short
562 return key1
563
564 def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
565 """Generate test keys covering usage flags."""
566 known_flags = sorted(self.constructors.key_usage_flags)
567 yield self.key_for_usage_flags(['0'], **kwargs)
568 for usage_flag in known_flags:
569 yield self.key_for_usage_flags([usage_flag], **kwargs)
570 for flag1, flag2 in zip(known_flags,
571 known_flags[1:] + [known_flags[0]]):
572 yield self.key_for_usage_flags([flag1, flag2], **kwargs)
573
574 def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
575 known_flags = sorted(self.constructors.key_usage_flags)
576 yield self.key_for_usage_flags(known_flags, short='all known')
577
578 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
579 yield from self.generate_keys_for_usage_flags()
580 yield from self.generate_key_for_all_usage_flags()
581
582 def key_for_type_and_alg(
583 self,
584 kt: crypto_knowledge.KeyType,
585 bits: int,
586 alg: Optional[crypto_knowledge.Algorithm] = None,
587 ) -> StorageTestData:
588 """Construct a test key of the given type.
589
590 If alg is not None, this key allows it.
591 """
592 usage_flags = ['PSA_KEY_USAGE_EXPORT']
593 alg1 = 0 #type: psa_storage.Exprable
594 alg2 = 0
595 if alg is not None:
596 alg1 = alg.expression
597 usage_flags += alg.usage_flags(public=kt.is_public())
598 key_material = kt.key_material(bits)
599 description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
600 if alg is not None:
601 description += ', ' + alg.short_expression(1)
602 key = StorageTestData(version=self.version,
603 id=1, lifetime=0x00000001,
604 type=kt.expression, bits=bits,
605 usage=usage_flags, alg=alg1, alg2=alg2,
606 material=key_material,
607 description=description)
608 return key
609
610 def keys_for_type(
611 self,
612 key_type: str,
613 all_algorithms: List[crypto_knowledge.Algorithm],
614 ) -> Iterator[StorageTestData]:
615 """Generate test keys for the given key type."""
616 kt = crypto_knowledge.KeyType(key_type)
617 for bits in kt.sizes_to_test():
618 # Test a non-exercisable key, as well as exercisable keys for
619 # each compatible algorithm.
620 # To do: test reading a key from storage with an incompatible
621 # or unsupported algorithm.
622 yield self.key_for_type_and_alg(kt, bits)
623 compatible_algorithms = [alg for alg in all_algorithms
624 if kt.can_do(alg)]
625 for alg in compatible_algorithms:
626 yield self.key_for_type_and_alg(kt, bits, alg)
627
628 def all_keys_for_types(self) -> Iterator[StorageTestData]:
629 """Generate test keys covering key types and their representations."""
630 key_types = sorted(self.constructors.key_types)
631 all_algorithms = [crypto_knowledge.Algorithm(alg)
632 for alg in self.constructors.generate_expressions(
633 sorted(self.constructors.algorithms)
634 )]
635 for key_type in self.constructors.generate_expressions(key_types):
636 yield from self.keys_for_type(key_type, all_algorithms)
637
638 def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
639 """Generate test keys for the encoding of the specified algorithm."""
640 # These test cases only validate the encoding of algorithms, not
641 # whether the key read from storage is suitable for an operation.
642 # `keys_for_types` generate read tests with an algorithm and a
643 # compatible key.
644 descr = crypto_knowledge.short_expression(alg, 1)
645 usage = ['PSA_KEY_USAGE_EXPORT']
646 key1 = StorageTestData(version=self.version,
647 id=1, lifetime=0x00000001,
648 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
649 usage=usage, alg=alg, alg2=0,
650 material=b'K',
651 description='alg: ' + descr)
652 yield key1
653 key2 = StorageTestData(version=self.version,
654 id=1, lifetime=0x00000001,
655 type='PSA_KEY_TYPE_RAW_DATA', bits=8,
656 usage=usage, alg=0, alg2=alg,
657 material=b'L',
658 description='alg2: ' + descr)
659 yield key2
660
661 def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
662 """Generate test keys covering algorithm encodings."""
663 algorithms = sorted(self.constructors.algorithms)
664 for alg in self.constructors.generate_expressions(algorithms):
665 yield from self.keys_for_algorithm(alg)
666
667 def generate_all_keys(self) -> Iterator[StorageTestData]:
668 """Generate all keys for the test cases."""
669 yield from self.all_keys_for_lifetimes()
670 yield from self.all_keys_for_usage_flags()
671 yield from self.all_keys_for_types()
672 yield from self.all_keys_for_algorithms()
673
674 def all_test_cases(self) -> Iterator[test_case.TestCase]:
675 """Generate all storage format test cases."""
676 # First build a list of all keys, then construct all the corresponding
677 # test cases. This allows all required information to be obtained in
678 # one go, which is a significant performance gain as the information
679 # includes numerical values obtained by compiling a C program.
680 all_keys = list(self.generate_all_keys())
681 for key in all_keys:
682 if key.location_value() != 0:
683 # Skip keys with a non-default location, because they
684 # require a driver and we currently have no mechanism to
685 # determine whether a driver is available.
686 continue
687 yield self.make_test_case(key)
688
689class StorageFormatForward(StorageFormat):
690 """Storage format stability test cases for forward compatibility."""
691
692 def __init__(self, info: psa_information.Information, version: int) -> None:
693 super().__init__(info, version, True)
694
695class StorageFormatV0(StorageFormat):
696 """Storage format stability test cases for version 0 compatibility."""
697
698 def __init__(self, info: psa_information.Information) -> None:
699 super().__init__(info, 0, False)
700
701 def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
702 """Generate test keys covering usage flags."""
703 yield from super().all_keys_for_usage_flags()
704 yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
705
706 def keys_for_implicit_usage(
707 self,
708 implyer_usage: str,
709 alg: str,
710 key_type: crypto_knowledge.KeyType
711 ) -> StorageTestData:
712 # pylint: disable=too-many-locals
713 """Generate test keys for the specified implicit usage flag,
714 algorithm and key type combination.
715 """
716 bits = key_type.sizes_to_test()[0]
717 implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
718 usage_flags = ['PSA_KEY_USAGE_EXPORT']
719 material_usage_flags = usage_flags + [implyer_usage]
720 expected_usage_flags = material_usage_flags + [implicit_usage]
721 alg2 = 0
722 key_material = key_type.key_material(bits)
723 usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
724 alg_expression = crypto_knowledge.short_expression(alg, 1)
725 key_type_expression = key_type.short_expression(1)
726 description = 'implied by {}: {} {} {}-bit'.format(
727 usage_expression, alg_expression, key_type_expression, bits)
728 key = StorageTestData(version=self.version,
729 id=1, lifetime=0x00000001,
730 type=key_type.expression, bits=bits,
731 usage=material_usage_flags,
732 expected_usage=expected_usage_flags,
733 without_implicit_usage=True,
734 alg=alg, alg2=alg2,
735 material=key_material,
736 description=description)
737 return key
738
739 def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
740 # pylint: disable=too-many-locals
741 """Match possible key types for sign algorithms."""
742 # To create a valid combination both the algorithms and key types
743 # must be filtered. Pair them with keywords created from its names.
744 incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
745 incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
746 keyword_translation = {
747 'ECDSA': 'ECC',
748 'ED[0-9]*.*' : 'EDWARDS'
749 }
750 exclusive_keywords = {
751 'EDWARDS': 'ECC'
752 }
753 key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
754 algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
755 alg_with_keys = {} #type: Dict[str, List[str]]
756 translation_table = str.maketrans('(', '_', ')')
757 for alg in algorithms:
758 # Generate keywords from the name of the algorithm
759 alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
760 # Translate keywords for better matching with the key types
761 for keyword in alg_keywords.copy():
762 for pattern, replace in keyword_translation.items():
763 if re.match(pattern, keyword):
764 alg_keywords.remove(keyword)
765 alg_keywords.add(replace)
766 # Filter out incompatible algorithms
767 if not alg_keywords.isdisjoint(incompatible_alg_keyword):
768 continue
769
770 for key_type in key_types:
771 # Generate keywords from the of the key type
772 key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
773
774 # Remove ambiguous keywords
775 for keyword1, keyword2 in exclusive_keywords.items():
776 if keyword1 in key_type_keywords:
777 key_type_keywords.remove(keyword2)
778
779 if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
780 not key_type_keywords.isdisjoint(alg_keywords):
781 if alg in alg_with_keys:
782 alg_with_keys[alg].append(key_type)
783 else:
784 alg_with_keys[alg] = [key_type]
785 return alg_with_keys
786
787 def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
788 """Generate test keys for usage flag extensions."""
789 # Generate a key type and algorithm pair for each extendable usage
790 # flag to generate a valid key for exercising. The key is generated
791 # without usage extension to check the extension compatibility.
792 alg_with_keys = self.gather_key_types_for_sign_alg()
793
794 for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
795 for alg in sorted(alg_with_keys):
796 for key_type in sorted(alg_with_keys[alg]):
797 # The key types must be filtered to fit the specific usage flag.
798 kt = crypto_knowledge.KeyType(key_type)
799 if kt.is_public() and '_SIGN_' in usage:
800 # Can't sign with a public key
801 continue
802 yield self.keys_for_implicit_usage(usage, alg, kt)
803
804 def generate_all_keys(self) -> Iterator[StorageTestData]:
805 yield from super().generate_all_keys()
806 yield from self.all_keys_for_implicit_usage()
807
808
809class PSATestGenerator(test_data_generation.TestGenerator):
810 """Test generator subclass including PSA targets and info."""
811 # Note that targets whose names contain 'test_format' have their content
812 # validated by `abi_check.py`.
813 targets = {
814 'test_suite_psa_crypto_generate_key.generated':
815 lambda info: KeyGenerate(info).test_cases_for_key_generation(),
816 'test_suite_psa_crypto_not_supported.generated':
817 lambda info: KeyTypeNotSupported(info).test_cases_for_not_supported(),
818 'test_suite_psa_crypto_low_hash.generated':
819 lambda info: crypto_data_tests.HashPSALowLevel(info).all_test_cases(),
820 'test_suite_psa_crypto_op_fail.generated':
821 lambda info: OpFail(info).all_test_cases(),
822 'test_suite_psa_crypto_storage_format.current':
823 lambda info: StorageFormatForward(info, 0).all_test_cases(),
824 'test_suite_psa_crypto_storage_format.v0':
825 lambda info: StorageFormatV0(info).all_test_cases(),
826 } #type: Dict[str, Callable[[psa_information.Information], Iterable[test_case.TestCase]]]
827
828 def __init__(self, options):
829 super().__init__(options)
830 self.info = psa_information.Information()
831
832 def generate_target(self, name: str, *target_args) -> None:
833 super().generate_target(name, self.info)
834
835
836if __name__ == '__main__':
837 test_data_generation.main(sys.argv[1:], __doc__, PSATestGenerator)