Tomás González | 2bff1bf | 2023-10-30 15:29:23 +0000 | [diff] [blame] | 1 | """Generate test data for cryptographic mechanisms. |
| 2 | This module is a work in progress, only implementing a few cases for now. |
| 3 | """ |
| 4 | |
| 5 | # Copyright The Mbed TLS Contributors |
Tomás González | 5fae560 | 2023-11-13 11:45:12 +0000 | [diff] [blame] | 6 | # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| 7 | |
Tomás González | 2bff1bf | 2023-10-30 15:29:23 +0000 | [diff] [blame] | 8 | |
| 9 | import hashlib |
| 10 | from typing import Callable, Dict, Iterator, List, Optional #pylint: disable=unused-import |
| 11 | |
| 12 | from . import crypto_knowledge |
| 13 | from . import psa_information |
| 14 | from . import test_case |
| 15 | |
| 16 | |
| 17 | def psa_low_level_dependencies(*expressions: str) -> List[str]: |
| 18 | """Infer dependencies of a PSA low-level test case by looking for PSA_xxx symbols. |
| 19 | This function generates MBEDTLS_PSA_BUILTIN_xxx symbols. |
| 20 | """ |
| 21 | high_level = psa_information.automatic_dependencies(*expressions) |
| 22 | for dep in high_level: |
| 23 | assert dep.startswith('PSA_WANT_') |
| 24 | return ['MBEDTLS_PSA_BUILTIN_' + dep[9:] for dep in high_level] |
| 25 | |
| 26 | |
| 27 | class HashPSALowLevel: |
| 28 | """Generate test cases for the PSA low-level hash interface.""" |
| 29 | |
| 30 | def __init__(self, info: psa_information.Information) -> None: |
| 31 | self.info = info |
| 32 | base_algorithms = sorted(info.constructors.algorithms) |
| 33 | all_algorithms = \ |
| 34 | [crypto_knowledge.Algorithm(expr) |
| 35 | for expr in info.constructors.generate_expressions(base_algorithms)] |
| 36 | self.algorithms = \ |
| 37 | [alg |
| 38 | for alg in all_algorithms |
| 39 | if (not alg.is_wildcard and |
| 40 | alg.can_do(crypto_knowledge.AlgorithmCategory.HASH))] |
| 41 | |
| 42 | # CALCULATE[alg] = function to return the hash of its argument in hex |
| 43 | # TO-DO: implement the None entries with a third-party library, because |
| 44 | # hashlib might not have everything, depending on the Python version and |
| 45 | # the underlying OpenSSL. On Ubuntu 16.04, truncated sha512 and sha3/shake |
| 46 | # are not available. On Ubuntu 22.04, md2, md4 and ripemd160 are not |
| 47 | # available. |
| 48 | CALCULATE = { |
| 49 | 'PSA_ALG_MD2': None, |
| 50 | 'PSA_ALG_MD4': None, |
| 51 | 'PSA_ALG_MD5': lambda data: hashlib.md5(data).hexdigest(), |
| 52 | 'PSA_ALG_RIPEMD160': None, #lambda data: hashlib.new('ripdemd160').hexdigest() |
| 53 | 'PSA_ALG_SHA_1': lambda data: hashlib.sha1(data).hexdigest(), |
| 54 | 'PSA_ALG_SHA_224': lambda data: hashlib.sha224(data).hexdigest(), |
| 55 | 'PSA_ALG_SHA_256': lambda data: hashlib.sha256(data).hexdigest(), |
| 56 | 'PSA_ALG_SHA_384': lambda data: hashlib.sha384(data).hexdigest(), |
| 57 | 'PSA_ALG_SHA_512': lambda data: hashlib.sha512(data).hexdigest(), |
| 58 | 'PSA_ALG_SHA_512_224': None, #lambda data: hashlib.new('sha512_224').hexdigest() |
| 59 | 'PSA_ALG_SHA_512_256': None, #lambda data: hashlib.new('sha512_256').hexdigest() |
| 60 | 'PSA_ALG_SHA3_224': None, #lambda data: hashlib.sha3_224(data).hexdigest(), |
| 61 | 'PSA_ALG_SHA3_256': None, #lambda data: hashlib.sha3_256(data).hexdigest(), |
| 62 | 'PSA_ALG_SHA3_384': None, #lambda data: hashlib.sha3_384(data).hexdigest(), |
| 63 | 'PSA_ALG_SHA3_512': None, #lambda data: hashlib.sha3_512(data).hexdigest(), |
| 64 | 'PSA_ALG_SHAKE256_512': None, #lambda data: hashlib.shake_256(data).hexdigest(64), |
Gilles Peskine | 9043a2f | 2023-08-09 10:50:58 +0200 | [diff] [blame] | 65 | } #type: Dict[str, Optional[Callable[[bytes], str]]] |
Tomás González | 2bff1bf | 2023-10-30 15:29:23 +0000 | [diff] [blame] | 66 | |
| 67 | @staticmethod |
| 68 | def one_test_case(alg: crypto_knowledge.Algorithm, |
| 69 | function: str, note: str, |
| 70 | arguments: List[str]) -> test_case.TestCase: |
| 71 | """Construct one test case involving a hash.""" |
| 72 | tc = test_case.TestCase() |
| 73 | tc.set_description('{}{} {}' |
| 74 | .format(function, |
| 75 | ' ' + note if note else '', |
| 76 | alg.short_expression())) |
| 77 | tc.set_dependencies(psa_low_level_dependencies(alg.expression)) |
| 78 | tc.set_function(function) |
| 79 | tc.set_arguments([alg.expression] + |
| 80 | ['"{}"'.format(arg) for arg in arguments]) |
| 81 | return tc |
| 82 | |
| 83 | def test_cases_for_hash(self, |
| 84 | alg: crypto_knowledge.Algorithm |
| 85 | ) -> Iterator[test_case.TestCase]: |
| 86 | """Enumerate all test cases for one hash algorithm.""" |
| 87 | calc = self.CALCULATE[alg.expression] |
| 88 | if calc is None: |
| 89 | return # not implemented yet |
| 90 | |
| 91 | short = b'abc' |
| 92 | hash_short = calc(short) |
| 93 | long = (b'Hello, world. Here are 16 unprintable bytes: [' |
| 94 | b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a' |
| 95 | b'\x80\x81\x82\x83\xfe\xff]. ' |
| 96 | b' This message was brought to you by a natural intelligence. ' |
| 97 | b' If you can read this, good luck with your debugging!') |
| 98 | hash_long = calc(long) |
| 99 | |
| 100 | yield self.one_test_case(alg, 'hash_empty', '', [calc(b'')]) |
| 101 | yield self.one_test_case(alg, 'hash_valid_one_shot', '', |
| 102 | [short.hex(), hash_short]) |
| 103 | for n in [0, 1, 64, len(long) - 1, len(long)]: |
| 104 | yield self.one_test_case(alg, 'hash_valid_multipart', |
| 105 | '{} + {}'.format(n, len(long) - n), |
| 106 | [long[:n].hex(), calc(long[:n]), |
| 107 | long[n:].hex(), hash_long]) |
| 108 | |
| 109 | def all_test_cases(self) -> Iterator[test_case.TestCase]: |
| 110 | """Enumerate all test cases for all hash algorithms.""" |
| 111 | for alg in self.algorithms: |
| 112 | yield from self.test_cases_for_hash(alg) |