File: generate_tls_handshake_tests.py

package info (click to toggle)
mbedtls 3.6.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 50,424 kB
  • sloc: ansic: 164,526; sh: 25,295; python: 14,825; makefile: 2,761; perl: 1,043; tcl: 4
file content (219 lines) | stat: -rwxr-xr-x 9,099 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/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()