| #!/usr/bin/env python3 |
| |
| """ |
| Generate miscellaneous TLS test cases relating to the handshake. |
| """ |
| |
| # Copyright The Mbed TLS Contributors |
| # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| |
| import argparse |
| import os |
| import sys |
| from typing import Optional |
| |
| from mbedtls_framework import tls_test_case |
| from mbedtls_framework import typing_util |
| from mbedtls_framework.tls_test_case import Side, Version |
| import translate_ciphers |
| |
| |
| # Assume that a TLS 1.2 ClientHello used in these tests will be at most |
| # this many bytes long. |
| TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH = 255 |
| |
| # Minimum handshake fragment length that Mbed TLS supports. |
| TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH = 4 |
| |
| def write_tls_handshake_defragmentation_test( |
| #pylint: disable=too-many-arguments |
| out: typing_util.Writable, |
| side: Side, |
| length: Optional[int], |
| version: Optional[Version] = None, |
| cipher: Optional[str] = None, |
| etm: Optional[bool] = None, #encrypt-then-mac (only relevant for CBC) |
| variant: str = '' |
| ) -> None: |
| """Generate one TLS handshake defragmentation test. |
| |
| :param out: file to write to. |
| :param side: which side is Mbed TLS. |
| :param length: fragment length, or None to not fragment. |
| :param version: protocol version, if forced. |
| """ |
| #pylint: disable=chained-comparison,too-many-branches,too-many-statements |
| |
| our_args = '' |
| their_args = '' |
| |
| if length is None: |
| description = 'no fragmentation, for reference' |
| else: |
| description = 'len=' + str(length) |
| if version is not None: |
| description += ', TLS 1.' + str(version.value) |
| description = f'Handshake defragmentation on {side.name.lower()}: {description}' |
| tc = tls_test_case.TestCase(description) |
| |
| if version is not None: |
| their_args += ' ' + version.openssl_option() |
| # Emit a version requirement, because we're forcing the version via |
| # OpenSSL, not via Mbed TLS, and the automatic depdendencies in |
| # ssl-opt.sh only handle forcing the version via Mbed TLS. |
| tc.requirements.append(version.requires_command()) |
| if side == Side.SERVER and version == Version.TLS12 and \ |
| length is not None and \ |
| length <= TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH: |
| # Server-side ClientHello defragmentation is only supported in |
| # the TLS 1.3 message parser. When that parser sees an 1.2-only |
| # ClientHello, it forwards the reassembled record to the |
| # TLS 1.2 ClientHello parser so the ClientHello can be fragmented. |
| # When TLS 1.3 support is disabled in the server (at compile-time |
| # or at runtime), the TLS 1.2 ClientHello parser only sees |
| # the first fragment of the ClientHello. |
| tc.requirements.append('requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3') |
| tc.description += ' with 1.3 support' |
| |
| # To guarantee that the handhake messages are large enough and need to be |
| # split into fragments, the tests require certificate authentication. |
| # The party in control of the fragmentation operations is OpenSSL and |
| # will always use server5.crt (548 Bytes). |
| if length is not None and \ |
| length >= TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: |
| tc.requirements.append('requires_certificate_authentication') |
| if version == Version.TLS12 and side == Side.CLIENT: |
| #The server uses an ECDSA cert, so make sure we have a compatible key exchange |
| tc.requirements.append( |
| 'requires_config_enabled MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED') |
| else: |
| # This test case may run in a pure-PSK configuration. OpenSSL doesn't |
| # allow this by default with TLS 1.3. |
| their_args += ' -allow_no_dhe_kex' |
| |
| if length is None: |
| forbidden_patterns = [ |
| 'waiting for more fragments', |
| ] |
| wanted_patterns = [] |
| elif length < TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH: |
| their_args += ' -split_send_frag ' + str(length) |
| tc.exit_code = 1 |
| forbidden_patterns = [] |
| wanted_patterns = [ |
| 'handshake message too short: ' + str(length), |
| 'SSL - An invalid SSL record was received', |
| ] |
| if side == Side.SERVER: |
| wanted_patterns[0:0] = ['<= parse client hello'] |
| elif version == Version.TLS13: |
| wanted_patterns[0:0] = ['=> ssl_tls13_process_server_hello'] |
| else: |
| their_args += ' -split_send_frag ' + str(length) |
| forbidden_patterns = [] |
| wanted_patterns = [ |
| 'reassembled record', |
| fr'initial handshake fragment: {length}, 0\.\.{length} of [0-9]\+', |
| fr'subsequent handshake fragment: [0-9]\+, {length}\.\.', |
| fr'Prepare: waiting for more handshake fragments {length}/', |
| fr'Consume: waiting for more handshake fragments {length}/', |
| ] |
| |
| if cipher is not None: |
| mbedtls_cipher = translate_ciphers.translate_mbedtls(cipher) |
| if side == Side.CLIENT: |
| our_args += ' force_ciphersuite=' + mbedtls_cipher |
| if 'NULL' in cipher: |
| their_args += ' -cipher ALL@SECLEVEL=0:COMPLEMENTOFALL@SECLEVEL=0' |
| else: |
| # For TLS 1.2, when Mbed TLS is the server, we must force the |
| # cipher suite on the client side, because passing |
| # force_ciphersuite to ssl_server2 would force a TLS-1.2-only |
| # server, which does not support a fragmented ClientHello. |
| tc.requirements.append('requires_ciphersuite_enabled ' + mbedtls_cipher) |
| their_args += ' -cipher ' + translate_ciphers.translate_ossl(cipher) |
| if 'NULL' in cipher: |
| their_args += '@SECLEVEL=0' |
| |
| if etm is not None: |
| if etm: |
| tc.requirements.append('requires_config_enabled MBEDTLS_SSL_ENCRYPT_THEN_MAC') |
| our_args += ' etm=' + str(int(etm)) |
| (wanted_patterns if etm else forbidden_patterns)[0:0] = [ |
| 'using encrypt then mac', |
| ] |
| |
| tc.description += variant |
| |
| if side == Side.CLIENT: |
| tc.client = '$P_CLI debug_level=4' + our_args |
| tc.server = '$O_NEXT_SRV' + their_args |
| tc.wanted_client_patterns = wanted_patterns |
| tc.forbidden_client_patterns = forbidden_patterns |
| else: |
| their_args += ' -cert $DATA_FILES_PATH/server5.crt -key $DATA_FILES_PATH/server5.key' |
| our_args += ' auth_mode=required' |
| tc.client = '$O_NEXT_CLI' + their_args |
| tc.server = '$P_SRV debug_level=4' + our_args |
| tc.wanted_server_patterns = wanted_patterns |
| tc.forbidden_server_patterns = forbidden_patterns |
| tc.write(out) |
| |
| |
| CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION = [ |
| (None, 'default', None), |
| ('TLS_ECDHE_ECDSA_WITH_NULL_SHA', 'null', None), |
| ('TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', 'ChachaPoly', None), |
| ('TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', 'GCM', None), |
| ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=n', False), |
| ('TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'CBC, etm=y', True), |
| ] |
| |
| def write_tls_handshake_defragmentation_tests(out: typing_util.Writable) -> None: |
| """Generate TLS handshake defragmentation tests.""" |
| for side in Side.CLIENT, Side.SERVER: |
| write_tls_handshake_defragmentation_test(out, side, None) |
| for length in [512, 513, 256, 128, 64, 36, 32, 16, 13, 5, 4, 3]: |
| write_tls_handshake_defragmentation_test(out, side, length, |
| Version.TLS13) |
| if length == 4: |
| for (cipher_suite, nickname, etm) in \ |
| CIPHERS_FOR_TLS12_HANDSHAKE_DEFRAGMENTATION: |
| write_tls_handshake_defragmentation_test( |
| out, side, length, Version.TLS12, |
| cipher=cipher_suite, etm=etm, |
| variant=', '+nickname) |
| else: |
| write_tls_handshake_defragmentation_test(out, side, length, |
| Version.TLS12) |
| |
| |
| def write_handshake_tests(out: typing_util.Writable) -> None: |
| """Generate handshake tests.""" |
| out.write(f"""\ |
| # Miscellaneous tests related to the TLS handshake layer. |
| # |
| # Automatically generated by {os.path.basename(sys.argv[0])}. Do not edit! |
| |
| # Copyright The Mbed TLS Contributors |
| # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| |
| """) |
| write_tls_handshake_defragmentation_tests(out) |
| out.write("""\ |
| # End of automatically generated file. |
| """) |
| |
| def main() -> None: |
| """Command line entry point.""" |
| parser = argparse.ArgumentParser() |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('-o', '--output', |
| default='tests/opt-testcases/handshake-generated.sh', |
| help='Output file (default: tests/opt-testcases/handshake-generated.sh)') |
| args = parser.parse_args() |
| with open(args.output, 'w') as out: |
| write_handshake_tests(out) |
| |
| if __name__ == '__main__': |
| main() |