test_case.py (5899B)
1 """Library for constructing an Mbed TLS test case. 2 """ 3 4 # Copyright The Mbed TLS Contributors 5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 6 # 7 8 import binascii 9 import os 10 import sys 11 from typing import Iterable, List, Optional 12 from enum import Enum 13 14 from . import build_tree 15 from . import psa_information 16 from . import typing_util 17 18 HASHES_3_6 = { 19 "PSA_ALG_MD5" : "MBEDTLS_MD_CAN_MD5", 20 "PSA_ALG_RIPEMD160" : "MBEDTLS_MD_CAN_RIPEMD160", 21 "PSA_ALG_SHA_1" : "MBEDTLS_MD_CAN_SHA1", 22 "PSA_ALG_SHA_224" : "MBEDTLS_MD_CAN_SHA224", 23 "PSA_ALG_SHA_256" : "MBEDTLS_MD_CAN_SHA256", 24 "PSA_ALG_SHA_384" : "MBEDTLS_MD_CAN_SHA384", 25 "PSA_ALG_SHA_512" : "MBEDTLS_MD_CAN_SHA512", 26 "PSA_ALG_SHA3_224" : "MBEDTLS_MD_CAN_SHA3_224", 27 "PSA_ALG_SHA3_256" : "MBEDTLS_MD_CAN_SHA3_256", 28 "PSA_ALG_SHA3_384" : "MBEDTLS_MD_CAN_SHA3_384", 29 "PSA_ALG_SHA3_512" : "MBEDTLS_MD_CAN_SHA3_512" 30 } 31 32 PK_MACROS_3_6 = { 33 "PSA_KEY_TYPE_ECC_PUBLIC_KEY" : "MBEDTLS_PK_HAVE_ECC_KEYS" 34 } 35 36 class Domain36(Enum): 37 PSA = 1 38 TLS_1_3_ONLY = 2 39 USE_PSA = 3 40 LEGACY = 4 41 42 def hex_string(data: bytes) -> str: 43 return '"' + binascii.hexlify(data).decode('ascii') + '"' 44 45 class MissingDescription(Exception): 46 pass 47 48 class MissingFunction(Exception): 49 pass 50 51 class TestCase: 52 """An Mbed TLS test case.""" 53 54 def __init__(self, description: Optional[str] = None): 55 self.comments = [] #type: List[str] 56 self.description = description #type: Optional[str] 57 self.dependencies = [] #type: List[str] 58 self.function = None #type: Optional[str] 59 self.arguments = [] #type: List[str] 60 self.skip_reasons = [] #type: List[str] 61 62 def add_comment(self, *lines: str) -> None: 63 self.comments += lines 64 65 def set_description(self, description: str) -> None: 66 self.description = description 67 68 def get_dependencies(self) -> List[str]: 69 return self.dependencies 70 71 def set_dependencies(self, dependencies: List[str]) -> None: 72 self.dependencies = dependencies 73 74 def set_function(self, function: str) -> None: 75 self.function = function 76 77 def set_arguments(self, arguments: List[str]) -> None: 78 self.arguments = arguments 79 80 def skip_because(self, reason: str) -> None: 81 """Skip this test case. 82 83 It will be included in the output, but commented out. 84 85 This is intended for test cases that are obtained from a 86 systematic enumeration, but that have dependencies that cannot 87 be fulfilled. Since we don't want to have test cases that are 88 never executed, we arrange not to have actual test cases. But 89 we do include comments to make it easier to understand the output 90 of test case generation. 91 92 reason must be a non-empty string explaining to humans why this 93 test case is skipped. 94 """ 95 self.skip_reasons.append(reason) 96 97 def check_completeness(self) -> None: 98 if self.description is None: 99 raise MissingDescription 100 if self.function is None: 101 raise MissingFunction 102 103 def write(self, out: typing_util.Writable) -> None: 104 """Write the .data file paragraph for this test case. 105 106 The output starts and ends with a single newline character. If the 107 surrounding code writes lines (consisting of non-newline characters 108 and a final newline), you will end up with a blank line before, but 109 not after the test case. 110 """ 111 self.check_completeness() 112 assert self.description is not None # guide mypy 113 assert self.function is not None # guide mypy 114 out.write('\n') 115 for line in self.comments: 116 out.write('# ' + line + '\n') 117 prefix = '' 118 if self.skip_reasons: 119 prefix = '## ' 120 for reason in self.skip_reasons: 121 out.write('## # skipped because: ' + reason + '\n') 122 out.write(prefix + self.description + '\n') 123 dependencies = self.get_dependencies() 124 if dependencies: 125 out.write(prefix + 'depends_on:' + 126 ':'.join(dependencies) + '\n') 127 out.write(prefix + self.function + ':' + 128 ':'.join(self.arguments) + '\n') 129 130 def write_data_file(filename: str, 131 test_cases: Iterable[TestCase], 132 caller: Optional[str] = None) -> None: 133 """Write the test cases to the specified file. 134 135 If the file already exists, it is overwritten. 136 """ 137 if caller is None: 138 caller = os.path.basename(sys.argv[0]) 139 tempfile = filename + '.new' 140 with open(tempfile, 'w') as out: 141 out.write('# Automatically generated by {}. Do not edit!\n' 142 .format(caller)) 143 for tc in test_cases: 144 tc.write(out) 145 out.write('\n# End of automatically generated file.\n') 146 os.replace(tempfile, filename) 147 148 def psa_or_3_6_feature_macro(psa_name: str, 149 domain_3_6: Domain36) -> str: 150 """Determine the dependency symbol for a given psa_name based on 151 the domain and Mbed TLS version. For more information about the domains, 152 and MBEDTLS_MD_CAN_ prefixed symbols, see transition-guards.md. 153 This function currently works with hashes and some PK symbols only. 154 It accepts PSA_ALG_xxx or PSA_KEY_TYPE_xxx as inputs for psa_name. 155 """ 156 157 if domain_3_6 == Domain36.PSA or domain_3_6 == Domain36.TLS_1_3_ONLY or \ 158 not build_tree.is_mbedtls_3_6(): 159 if psa_name in PK_MACROS_3_6 or psa_name in HASHES_3_6: 160 return psa_information.psa_want_symbol(psa_name) 161 162 if domain_3_6 == Domain36.USE_PSA: 163 if psa_name in PK_MACROS_3_6: 164 return PK_MACROS_3_6[psa_name] 165 166 if psa_name in HASHES_3_6: 167 return HASHES_3_6[psa_name] 168 169 raise ValueError(f'Unable to determine dependency symbol for {psa_name} in {domain_3_6}')