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