quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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()