generate_tls_handshake_tests.py (9099B)
1 #!/usr/bin/env python3 2 3 """ 4 Generate miscellaneous TLS test cases relating to the handshake. 5 """ 6 7 # Copyright The Mbed TLS Contributors 8 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 9 10 import argparse 11 import os 12 import sys 13 from typing import Optional 14 15 from mbedtls_framework import tls_test_case 16 from mbedtls_framework import typing_util 17 from mbedtls_framework.tls_test_case import Side, Version 18 import translate_ciphers 19 20 21 # Assume that a TLS 1.2 ClientHello used in these tests will be at most 22 # this many bytes long. 23 TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH = 255 24 25 # Minimum handshake fragment length that Mbed TLS supports. 26 TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH = 4 27 28 def write_tls_handshake_defragmentation_test( 29 #pylint: disable=too-many-arguments 30 out: typing_util.Writable, 31 side: Side, 32 length: Optional[int], 33 version: Optional[Version] = None, 34 cipher: Optional[str] = None, 35 etm: Optional[bool] = None, #encrypt-then-mac (only relevant for CBC) 36 variant: str = '' 37 ) -> None: 38 """Generate one TLS handshake defragmentation test. 39 40 :param out: file to write to. 41 :param side: which side is Mbed TLS. 42 :param length: fragment length, or None to not fragment. 43 :param version: protocol version, if forced. 44 """ 45 #pylint: disable=chained-comparison,too-many-branches,too-many-statements 46 47 our_args = '' 48 their_args = '' 49 50 if length is None: 51 description = 'no fragmentation, for reference' 52 else: 53 description = 'len=' + str(length) 54 if version is not None: 55 description += ', TLS 1.' + str(version.value) 56 description = f'Handshake defragmentation on {side.name.lower()}: {description}' 57 tc = tls_test_case.TestCase(description) 58 59 if version is not None: 60 their_args += ' ' + version.openssl_option() 61 # Emit a version requirement, because we're forcing the version via 62 # OpenSSL, not via Mbed TLS, and the automatic depdendencies in 63 # ssl-opt.sh only handle forcing the version via Mbed TLS. 64 tc.requirements.append(version.requires_command()) 65 if side == Side.SERVER and version == Version.TLS12 and \ 66 length is not None and \ 67 length <= TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH: 68 # Server-side ClientHello defragmentation is only supported in 69 # the TLS 1.3 message parser. When that parser sees an 1.2-only 70 # ClientHello, it forwards the reassembled record to the 71 # TLS 1.2 ClientHello parser so the ClientHello can be fragmented. 72 # When TLS 1.3 support is disabled in the server (at compile-time 73 # or at runtime), the TLS 1.2 ClientHello parser only sees 74 # the first fragment of the ClientHello. 75 tc.requirements.append('requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3') 76 tc.description += ' with 1.3 support' 77 78 # To guarantee that the handhake messages are large enough and need to be 79 # split into fragments, the tests require certificate authentication. 80 # The party in control of the fragmentation operations is OpenSSL and 81 # will always use server5.crt (548 Bytes). 82 if length is not None and \ 83 length >= TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: 84 tc.requirements.append('requires_certificate_authentication') 85 if version == Version.TLS12 and side == Side.CLIENT: 86 #The server uses an ECDSA cert, so make sure we have a compatible key exchange 87 tc.requirements.append( 88 'requires_config_enabled MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED') 89 else: 90 # This test case may run in a pure-PSK configuration. OpenSSL doesn't 91 # allow this by default with TLS 1.3. 92 their_args += ' -allow_no_dhe_kex' 93 94 if length is None: 95 forbidden_patterns = [ 96 'waiting for more fragments', 97 ] 98 wanted_patterns = [] 99 elif length < TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: 100 their_args += ' -split_send_frag ' + str(length) 101 tc.exit_code = 1 102 forbidden_patterns = [] 103 wanted_patterns = [ 104 'handshake message too short: ' + str(length), 105 'SSL - An invalid SSL record was received', 106 ] 107 if side == Side.SERVER: 108 wanted_patterns[0:0] = ['<= parse client hello'] 109 elif version == Version.TLS13: 110 wanted_patterns[0:0] = ['=> ssl_tls13_process_server_hello'] 111 else: 112 their_args += ' -split_send_frag ' + str(length) 113 forbidden_patterns = [] 114 wanted_patterns = [ 115 'reassembled record', 116 fr'initial handshake fragment: {length}, 0\.\.{length} of [0-9]\+', 117 fr'subsequent handshake fragment: [0-9]\+, {length}\.\.', 118 fr'Prepare: waiting for more handshake fragments {length}/', 119 fr'Consume: waiting for more handshake fragments {length}/', 120 ] 121 122 if cipher is not None: 123 mbedtls_cipher = translate_ciphers.translate_mbedtls(cipher) 124 if side == Side.CLIENT: 125 our_args += ' force_ciphersuite=' + mbedtls_cipher 126 if 'NULL' in cipher: 127 their_args += ' -cipher ALL@SECLEVEL=0:COMPLEMENTOFALL@SECLEVEL=0' 128 else: 129 # For TLS 1.2, when Mbed TLS is the server, we must force the 130 # cipher suite on the client side, because passing 131 # force_ciphersuite to ssl_server2 would force a TLS-1.2-only 132 # server, which does not support a fragmented ClientHello. 133 tc.requirements.append('requires_ciphersuite_enabled ' + mbedtls_cipher) 134 their_args += ' -cipher ' + translate_ciphers.translate_ossl(cipher) 135 if 'NULL' in cipher: 136 their_args += '@SECLEVEL=0' 137 138 if etm is not None: 139 if etm: 140 tc.requirements.append('requires_config_enabled MBEDTLS_SSL_ENCRYPT_THEN_MAC') 141 our_args += ' etm=' + str(int(etm)) 142 (wanted_patterns if etm else forbidden_patterns)[0:0] = [ 143 'using encrypt then mac', 144 ] 145 146 tc.description += variant 147 148 if side == Side.CLIENT: 149 tc.client = '$P_CLI debug_level=4' + our_args 150 tc.server = '$O_NEXT_SRV' + their_args 151 tc.wanted_client_patterns = wanted_patterns 152 tc.forbidden_client_patterns = forbidden_patterns 153 else: 154 their_args += ' -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key' 155 our_args += ' auth_mode=required' 156 tc.client = '$O_NEXT_CLI' + their_args 157 tc.server = '$P_SRV debug_level=4' + our_args 158 tc.wanted_server_patterns = wanted_patterns 159 tc.forbidden_server_patterns = forbidden_patterns 160 tc.write(out) 161 162 163 CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION = [ 164 (None, 'default', None), 165 ('TLS_ECDHE_ECDSA_WITH_NULL_SHA', 'null', None), 166 ('TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 'ChachaPoly', None), 167 ('TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 'GCM', None), 168 ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=n', False), 169 ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=y', True), 170 ] 171 172 def write_tls_handshake_defragmentation_tests(out: typing_util.Writable) -> None: 173 """Generate TLS handshake defragmentation tests.""" 174 for side in Side.CLIENT, Side.SERVER: 175 write_tls_handshake_defragmentation_test(out, side, None) 176 for length in [512, 513, 256, 128, 64, 36, 32, 16, 13, 5, 4, 3]: 177 write_tls_handshake_defragmentation_test(out, side, length, 178 Version.TLS13) 179 if length == 4: 180 for (cipher_suite, nickname, etm) in \ 181 CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION: 182 write_tls_handshake_defragmentation_test( 183 out, side, length, Version.TLS12, 184 cipher=cipher_suite, etm=etm, 185 variant=', '+nickname) 186 else: 187 write_tls_handshake_defragmentation_test(out, side, length, 188 Version.TLS12) 189 190 191 def write_handshake_tests(out: typing_util.Writable) -> None: 192 """Generate handshake tests.""" 193 out.write(f"""\ 194 # Miscellaneous tests related to the TLS handshake layer. 195 # 196 # Automatically generated by {os.path.basename(sys.argv[0])}. Do not edit! 197 198 # Copyright The Mbed TLS Contributors 199 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 200 201 """) 202 write_tls_handshake_defragmentation_tests(out) 203 out.write("""\ 204 # End of automatically generated file. 205 """) 206 207 def main() -> None: 208 """Command line entry point.""" 209 parser = argparse.ArgumentParser() 210 parser = argparse.ArgumentParser(description=__doc__) 211 parser.add_argument('-o', '--output', 212 default='tests/opt-testcases/handshake-generated.sh', 213 help='Output file (default: tests/opt-testcases/handshake-generated.sh)') 214 args = parser.parse_args() 215 with open(args.output, 'w') as out: 216 write_handshake_tests(out) 217 218 if __name__ == '__main__': 219 main()