From 11e4f5ac1c71fe7d803fa5193236560b2e176cea Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sat, 1 Mar 2025 16:09:27 +0100 Subject: [PATCH 01/15] New script to generate handshake tests for ssl-opt.sh Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 42 ++++++++++++ scripts/mbedtls_framework/tls_test_case.py | 74 ++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100755 scripts/generate_tls_handshake_tests.py create mode 100644 scripts/mbedtls_framework/tls_test_case.py diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py new file mode 100755 index 000000000..78c7a42c3 --- /dev/null +++ b/scripts/generate_tls_handshake_tests.py @@ -0,0 +1,42 @@ +#!/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 sys +from typing import Optional + +from mbedtls_framework import tls_test_case +from mbedtls_framework import typing_util +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 {sys.argv[0]}. Do not edit! + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +""") + out.write("""\ +# End of automatically generated file. +""") + +def main() -> None: + """Command line entry point.""" + parser = argparse.ArgumentParser() + parser.add_argument('-o', '--output', + default='tests/opt-testcases/handshake-generated.sh', + help='Output file') + args = parser.parse_args() + with open(args.output, 'w') as out: + write_handshake_tests(out) + +if __name__ == '__main__': + main() diff --git a/scripts/mbedtls_framework/tls_test_case.py b/scripts/mbedtls_framework/tls_test_case.py new file mode 100644 index 000000000..214a7ed46 --- /dev/null +++ b/scripts/mbedtls_framework/tls_test_case.py @@ -0,0 +1,74 @@ +"""Library for constructing an Mbed TLS ssl-opt test case. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import enum +import re +from typing import List + +from . import typing_util + + +class TestCase: + """Data about an ssl-opt test case.""" + #pylint: disable=too-few-public-methods + + def __init__(self, description: str) -> None: + # List of shell snippets to call before run_test, typically + # calls to requires_xxx functions. + self.requirements = [] #type: List[str] + # Test case description (first argument to run_test). + self.description = description + # Client command line. + # This will be placed directly inside double quotes in the shell script. + self.client = '$P_CLI' + # Server command line. + # This will be placed directly inside double quotes in the shell script. + self.server = '$P_SRV' + # Expected client exit code. + self.exit_code = 0 + # BRE for text that must be present in the client log (run_test -c). + self.wanted_client_patterns = [] #type: List[str] + # BRE for text that must be present in the server log (run_test -s). + self.wanted_server_patterns = [] #type: List[str] + # BRE for text that must not be present in the client log (run_test -C). + self.forbidden_client_patterns = [] #type: List[str] + # BRE for text that must not be present in the server log (run_test -S). + self.forbidden_server_patterns = [] #type: List[str] + + @staticmethod + def _quote(raw: str) -> str: + """Quote the given string for sh. + + Use double quotes, because that's currently the norm in ssl-opt.sh. + """ + return '"' + re.sub(r'([$"\\`])', r'\\\1', raw) + '"' + + def write(self, out: typing_util.Writable) -> None: + """Write the test case to the specified file.""" + for req in self.requirements: + out.write(req + '\n') + out.write(f'run_test {self._quote(self.description)} \\\n') + out.write(f' "{self.server}" \\\n') + out.write(f' "{self.client}" \\\n') + out.write(f' {self.exit_code}') + for pat in self.wanted_server_patterns: + out.write(' \\\n -s ' + self._quote(pat)) + for pat in self.forbidden_server_patterns: + out.write(' \\\n -S ' + self._quote(pat)) + for pat in self.wanted_client_patterns: + out.write(' \\\n -c ' + self._quote(pat)) + for pat in self.forbidden_client_patterns: + out.write(' \\\n -C ' + self._quote(pat)) + out.write('\n\n') + + +class Side(enum.Enum): + CLIENT = 0 + SERVER = 1 + +class Version(enum.Enum): + TLS12 = 2 + TLS13 = 3 From e453777af159ba34b932fdbc9dc00c9d4673125c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sat, 1 Mar 2025 16:44:02 +0100 Subject: [PATCH 02/15] Generate handshake defragmentation test cases The output is identical to the manually written tests in `tests/opt-testcases/handshake-manual.sh`, except that the script doesn't generate explanatory comments (they're in the generator script instead). Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 132 ++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 78c7a42c3..cc1325661 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -13,6 +13,137 @@ 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 + + +# 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( + out: typing_util.Writable, + side: Side, + length: Optional[int], + version: Optional[Version] = None +) -> 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 == Version.TLS12 and \ + length is not None and \ + length >= TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH and \ + length < 16 and \ + side == side.CLIENT: + # Skip test cases where the Finished message is fragmented in TLS 1.2. + # This is currently buggy when the symmetric encryption used an + # explicit IV (CBC, GCM or CCM; Chachapoly and null work, as does + # TLS 1.3, because they use a purely implicit IV). + tc.requirements.append('skip_next_test') + + if version is not None: + their_args += ' -tls1_' + str(version.value) + # 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('requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_' + + str(version.value)) + 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 += ' TLS 1.3 ClientHello -> 1.2 Handshake' + + # 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') + + if length is None: + forbidden_patterns = [ + 'reassembled record', + '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'handshake fragment: 0 \.\. {length} of [0-9]\+ msglen {length}', + fr'waiting for more fragments ({length} of', + ] + + 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) + +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) + 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"""\ @@ -24,6 +155,7 @@ def write_handshake_tests(out: typing_util.Writable) -> None: # 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. """) From f88eb21ff11afe2c9ed553dcdba27166198f90d9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sat, 1 Mar 2025 18:32:06 +0100 Subject: [PATCH 03/15] Don't embed a path in the generated output Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index cc1325661..7fdb5528c 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -8,6 +8,7 @@ Generate miscellaneous TLS test cases relating to the handshake. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later import argparse +import os import sys from typing import Optional @@ -149,7 +150,7 @@ def write_handshake_tests(out: typing_util.Writable) -> None: out.write(f"""\ # Miscellaneous tests related to the TLS handshake layer. # -# Automatically generated by {sys.argv[0]}. Do not edit! +# 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 From 69385652d08eabd0756dff68719fd1809719b081 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 4 Mar 2025 18:29:21 +0100 Subject: [PATCH 04/15] Fix TLS 1.3 tests with OpenSSL failing in pure-PSK builds Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 7fdb5528c..53846ba93 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -94,6 +94,10 @@ def write_tls_handshake_defragmentation_test( #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 = [ From 6749a8dcf7607b6252e0d273d86bc9a246ca0192 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 4 Mar 2025 18:45:34 +0100 Subject: [PATCH 05/15] Briefly explain BRE Signed-off-by: Gilles Peskine --- scripts/mbedtls_framework/tls_test_case.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/mbedtls_framework/tls_test_case.py b/scripts/mbedtls_framework/tls_test_case.py index 214a7ed46..47e356445 100644 --- a/scripts/mbedtls_framework/tls_test_case.py +++ b/scripts/mbedtls_framework/tls_test_case.py @@ -29,6 +29,16 @@ class TestCase: self.server = '$P_SRV' # Expected client exit code. self.exit_code = 0 + + # Note that all patterns matched in the logs are in BRE + # (Basic Regular Expression) syntax, more precisely in the BRE + # dialect that is the default for GNU grep. The main difference + # with Python regular expressions is that the operators for + # grouping `\(...\)`, alternation `x\|y`, option `x\?`, + # one-or-more `x\+` and repetition ranges `x\{M,N\}` must be + # preceded by a backslash. The characters `()|?+{}` stand for + # themselves. + # BRE for text that must be present in the client log (run_test -c). self.wanted_client_patterns = [] #type: List[str] # BRE for text that must be present in the server log (run_test -s). From c69a7f6c2613e5815f804049847efb88ef68d9bf Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 4 Mar 2025 18:49:29 +0100 Subject: [PATCH 06/15] Use more abstractions for protocol version formatting Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 5 ++--- scripts/mbedtls_framework/tls_test_case.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 53846ba93..3c23154a5 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -64,12 +64,11 @@ def write_tls_handshake_defragmentation_test( tc.requirements.append('skip_next_test') if version is not None: - their_args += ' -tls1_' + str(version.value) + 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('requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_' + - str(version.value)) + 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: diff --git a/scripts/mbedtls_framework/tls_test_case.py b/scripts/mbedtls_framework/tls_test_case.py index 47e356445..73bb039a8 100644 --- a/scripts/mbedtls_framework/tls_test_case.py +++ b/scripts/mbedtls_framework/tls_test_case.py @@ -80,5 +80,22 @@ class Side(enum.Enum): SERVER = 1 class Version(enum.Enum): + """TLS protocol version. + + This class doesn't know about DTLS yet. + """ + TLS12 = 2 TLS13 = 3 + + def force_version(self) -> str: + """Argument to pass to ssl_client2 or ssl_server2 to force this version.""" + return f'force_version=tls1{self.value}' + + def openssl_option(self) -> str: + """Option to pass to openssl s_client or openssl s_server to select this version.""" + return f'-tls1_{self.value}' + + def requires_command(self) -> str: + """Command to require this protocol version in an ssl-opt.sh test case.""" + return 'requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_' + str(self.value) From 4a009d4b3cf6c55a558d90c92c1aa2d1ea2bb99b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 4 Mar 2025 18:50:33 +0100 Subject: [PATCH 07/15] Improve --help Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 3c23154a5..86bf9f5b0 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -167,9 +167,10 @@ def write_handshake_tests(out: typing_util.Writable) -> None: 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') + 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) From 35c4387550aba1528389749d7a5dec71b500546a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:20:52 +0100 Subject: [PATCH 08/15] Clarify test case description Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 86bf9f5b0..3d6a43c9a 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -80,7 +80,7 @@ def write_tls_handshake_defragmentation_test( # 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 += ' TLS 1.3 ClientHello -> 1.2 Handshake' + 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. From 67e3374460989d510da42ad648a1e1c78a5bf34e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:37:00 +0100 Subject: [PATCH 09/15] Diversify handshake defragmentation tests in TLS 1.2 by encryption The symmetric encryption used for transportation matters for TLS 1.2 defragmentation, since the code is sensitive to the presence of an explicit IV. So have separate test cases for each class of symmetric encryption. Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 55 +++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 3d6a43c9a..ad5c73673 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -14,6 +14,7 @@ from typing import Optional from mbedtls_framework import tls_test_case from mbedtls_framework import typing_util +import translate_ciphers from mbedtls_framework.tls_test_case import Side, Version @@ -29,7 +30,10 @@ def write_tls_handshake_defragmentation_test( out: typing_util.Writable, side: Side, length: Optional[int], - version: Optional[Version] = None + 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. @@ -125,6 +129,31 @@ def write_tls_handshake_defragmentation_test( fr'waiting for more fragments ({length} of', ] + if cipher is not None: + if side == Side.CLIENT: + our_args += ' force_ciphersuite=' + translate_ciphers.translate_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 ' + 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 @@ -139,13 +168,33 @@ def write_tls_handshake_defragmentation_test( 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) - write_tls_handshake_defragmentation_test(out, side, length, Version.TLS12) + 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: From d850a6fe3575eb8c5a02cf039264c9e05d11c697 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:39:14 +0100 Subject: [PATCH 10/15] Tweak "waiting for more handshake fragments" log message In preparation for reworking mbedtls_ssl_prepare_handshake_record(), tweak the "waiting for more handshake fragments" log message in ssl_consume_current_message(), and add a similar one in mbedtls_ssl_prepare_handshake_record(). Assert both. Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index ad5c73673..a34152f28 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -125,8 +125,9 @@ def write_tls_handshake_defragmentation_test( forbidden_patterns = [] wanted_patterns = [ 'reassembled record', - fr'handshake fragment: 0 \.\. {length} of [0-9]\+ msglen {length}', - fr'waiting for more fragments ({length} of', + fr'handshake fragment: {length}, 0\.\.{length} of [0-9]\+', + fr'Prepare: waiting for more handshake fragments {length}/', + fr'Consume: waiting for more handshake fragments {length}/', ] if cipher is not None: From 3c889a4f3a85ca47ed1a89d8fb953bcdca92450d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:48:31 +0100 Subject: [PATCH 11/15] Tweak log message assertions In preparation for reworking mbedtls_ssl_prepare_handshake_record(), don't assert negatively on "reassembled record". Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index a34152f28..6ab7367fe 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -104,7 +104,6 @@ def write_tls_handshake_defragmentation_test( if length is None: forbidden_patterns = [ - 'reassembled record', 'waiting for more fragments', ] wanted_patterns = [] From 52d8701b99b4eb3fc8edce14d73eb3691d330cf2 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:21:06 +0100 Subject: [PATCH 12/15] Don't skip defragmentation test cases wih length < 16 Require the bug with defragmentation of encrypted handshake messages to be fixed. Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 6ab7367fe..17ec13893 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -56,17 +56,6 @@ def write_tls_handshake_defragmentation_test( description = f'Handshake defragmentation on {side.name.lower()}: {description}' tc = tls_test_case.TestCase(description) - if version == Version.TLS12 and \ - length is not None and \ - length >= TLS_HANDSHAKE_FRAGMENT_MIN_LENGTH and \ - length < 16 and \ - side == side.CLIENT: - # Skip test cases where the Finished message is fragmented in TLS 1.2. - # This is currently buggy when the symmetric encryption used an - # explicit IV (CBC, GCM or CCM; Chachapoly and null work, as does - # TLS 1.3, because they use a purely implicit IV). - tc.requirements.append('skip_next_test') - if version is not None: their_args += ' ' + version.openssl_option() # Emit a version requirement, because we're forcing the version via From 38bc126e4bf658c9cf11f48b86678ce4967f588f Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 16:50:43 +0100 Subject: [PATCH 13/15] Assert log for both initial and subsequent handshake fragments Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 17ec13893..abf731061 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -113,7 +113,8 @@ def write_tls_handshake_defragmentation_test( forbidden_patterns = [] wanted_patterns = [ 'reassembled record', - fr'handshake fragment: {length}, 0\.\.{length} of [0-9]\+', + 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}/', ] From 8a534d9d7846ead7ad4843fa91adfc37442e3215 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 3 Mar 2025 21:19:41 +0100 Subject: [PATCH 14/15] Pacify pylint Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index abf731061..a57c6979c 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -14,9 +14,8 @@ from typing import Optional from mbedtls_framework import tls_test_case from mbedtls_framework import typing_util -import translate_ciphers - 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 @@ -27,6 +26,7 @@ TLS12_CLIENT_HELLO_ASSUMED_MAX_LENGTH = 255 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], From 8d85112a44d052a5d89cb0a135e162384da42584 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 4 Mar 2025 10:27:21 +0100 Subject: [PATCH 15/15] Fix cipher suite syntax in requires call Signed-off-by: Gilles Peskine --- scripts/generate_tls_handshake_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index a57c6979c..1e9dbb944 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -120,8 +120,9 @@ def write_tls_handshake_defragmentation_test( ] if cipher is not None: + mbedtls_cipher = translate_ciphers.translate_mbedtls(cipher) if side == Side.CLIENT: - our_args += ' force_ciphersuite=' + translate_ciphers.translate_mbedtls(cipher) + our_args += ' force_ciphersuite=' + mbedtls_cipher if 'NULL' in cipher: their_args += ' -cipher ALL@SECLEVEL=0:COMPLEMENTOFALL@SECLEVEL=0' else: @@ -129,7 +130,7 @@ def write_tls_handshake_defragmentation_test( # 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 ' + cipher) + tc.requirements.append('requires_ciphersuite_enabled ' + mbedtls_cipher) their_args += ' -cipher ' + translate_ciphers.translate_ossl(cipher) if 'NULL' in cipher: their_args += '@SECLEVEL=0'