diff --git a/scripts/check-python-files.sh b/scripts/check-python-files.sh index 83767fc38..d15647b47 100755 --- a/scripts/check-python-files.sh +++ b/scripts/check-python-files.sh @@ -58,8 +58,13 @@ echo 'Running pylint ...' # Temporary workaround while moving the bulk of abi_check.py to the framework # Check abi_check.py separately from the rest of the files, so it's not flagged # for code duplication. +# Temporary workaround for the transitional wrapper framework/scripts/make_generated_files.py +# as well. Once make_generated_files.py exists in both MbedTLS:development and +# TF-PSA-Crypto:development branches, we will be able to remove +# framework/scripts/make_generated_files.py and, consequently, this exception. find framework/scripts/*.py framework/scripts/mbedtls_framework/*.py scripts/*.py tests/scripts/*.py \ - -path scripts/abi_check.py \ + ! -path scripts/abi_check.py \ + ! -path framework/scripts/make_generated_files.py \ -exec $PYTHON -m pylint {} + \ -o -exec $PYTHON -m pylint {} + || { echo >&2 "pylint reported errors" diff --git a/scripts/generate_tls_handshake_tests.py b/scripts/generate_tls_handshake_tests.py index 1e9dbb944..53cb79be3 100755 --- a/scripts/generate_tls_handshake_tests.py +++ b/scripts/generate_tls_handshake_tests.py @@ -1,219 +1,16 @@ #!/usr/bin/env python3 - """ Generate miscellaneous TLS test cases relating to the handshake. + +Transitional wrapper to facilitate the migration of consuming branches. """ # 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) +from mbedtls_framework import tls_handshake_tests if __name__ == '__main__': - main() + sys.argv[1:1] = ["--no-tls12-client-hello-defragmentation-support"] + tls_handshake_tests.main() diff --git a/scripts/make_generated_files.py b/scripts/make_generated_files.py index 99a0512d5..ad8c5b8a9 100755 --- a/scripts/make_generated_files.py +++ b/scripts/make_generated_files.py @@ -1,77 +1,17 @@ #!/usr/bin/env python3 +"""Generate, check and list the generated files +Transitional wrapper to facilitate the migration of consuming branches. +""" -# make_generated_files.py -# # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -""" -Generate the TF-PSA-Crypto generated files -""" -import argparse -import filecmp -import shutil -import subprocess import sys - from pathlib import Path -from typing import List, Optional from mbedtls_framework import build_tree - -class GenerationScript: - """ - Representation of a script generating a configuration independent file. - """ - # pylint: disable=too-few-public-methods,too-many-arguments - def __init__(self, script: Path, files: List[Path], - output_dir_option: Optional[str] = None, - output_file_option: Optional[str] = None, - optional: bool = False) -> None: - # Path from the root of Mbed TLS or TF-PSA-Crypto of the generation script - self.script = script - - # Executable to run the script, needed for Windows - if script.suffix == ".py": - self.exe = sys.executable - elif script.suffix == ".pl": - self.exe = "perl" - - # List of the default paths from the Mbed TLS or TF-PSA-Crypto root of the - # files the script generates. - self.files = files - - # Output directory script argument. Can be an empty string in case it is a - # positional argument. - self.output_dir_option = output_dir_option - - # Output file script argument. Can be an empty string in case it is a - # positional argument. - self.output_file_option = output_file_option - - # Optional files are skipped in --check mode if they don't exist. - # This normally shouldn't happen, but it can happen during transition - # periods where we're adding a new script or a new file, and a - # consuming repository hasn't been updated yet. - self.optional = optional - -def get_generation_script_files(generation_script: str) -> List[Path]: - """ - Get the list of the default paths of the files that a given script - generates. It is assumed that the script supports the "--list" option. - """ - files = [] - if generation_script.endswith(".py"): - cmd = [sys.executable] - elif generation_script.endswith(".pl"): - cmd = ["perl"] - cmd += [generation_script, "--list"] - - output = subprocess.check_output(cmd, universal_newlines=True) - for line in output.splitlines(): - files.append(Path(line)) - - return files +from mbedtls_framework import generated_files +from mbedtls_framework.generated_files import GenerationScript, get_generation_script_files COMMON_GENERATION_SCRIPTS = [ GenerationScript( @@ -182,93 +122,7 @@ if build_tree.looks_like_mbedtls_root(".") and not build_tree.is_mbedtls_3_6(): get_generation_script_files("scripts/generate_visualc_files.pl"), "--directory", None)) -def get_generated_files(generation_scripts: List[GenerationScript]) -> List[Path]: - """ - List the generated files in Mbed TLS or TF-PSA-Crypto. The path from root - is returned for each generated files. - """ - files = [] - for generation_script in generation_scripts: - files += generation_script.files - - return files - -def make_generated_files(generation_scripts: List[GenerationScript]) -> None: - """ - Generate the configuration independent files in their default location in - the Mbed TLS or TF-PSA-Crypto tree. - """ - for generation_script in generation_scripts: - subprocess.run([generation_script.exe, str(generation_script.script)], check=True) - -def check_generated_files(generation_scripts: List[GenerationScript], - root: Path) -> bool: - """ - Check that the given root directory contains the generated files as expected/ - generated by this script. - """ - ok = True - for generation_script in generation_scripts: - for file in generation_script.files: - file = root / file - if not file.exists(): - # If the script is just being added, allow its files not - # to exist. This can happen, at least, when adding a new - # generation script in crypto: until mbedtls is updated, - # the files from that script won't be present when - # the updated crypto is built from mbedtls development. - if generation_script.optional: - continue - raise Exception(f"Expected generated file does not exist: {file}") - bak_file = file.with_name(file.name + ".bak") - if bak_file.exists(): - bak_file.unlink() - file.rename(bak_file) - - command = [generation_script.exe, str(generation_script.script)] - if generation_script.output_dir_option is not None: - command += [generation_script.output_dir_option, - str(root / Path(generation_script.files[0].parent))] - elif generation_script.output_file_option is not None: - command += generation_script.output_file_option.split() - command += [str(root / Path(generation_script.files[0]))] - subprocess.run([item for item in command if item.strip()], check=True) - - for file in generation_script.files: - file = root / file - bak_file = file.with_name(file.name + ".bak") - if generation_script.optional and not bak_file.exists(): - # This file is optional and didn't exist before, so - # there's nothing to compare to, or clean up. - continue - if not filecmp.cmp(file, bak_file): - ok = False - ref_file = file.with_name(file.name + ".ref") - ref_file = root / ref_file - if ref_file.exists(): - ref_file.unlink() - shutil.copy(file, ref_file) - print(f"Generated file {file} not identical to the reference one {ref_file}.") - file.unlink() - bak_file.rename(file) - return ok - def main() -> int: - """ - Main function of this program - """ - parser = argparse.ArgumentParser() - - parser.add_argument('--list', action='store_true', - default=False, help='List generated files.') - parser.add_argument('--root', metavar='DIR', - help='Root of the tree containing the generated files \ - to check (default: Mbed TLS or TF-PSA-Cryto root.)') - parser.add_argument('--check', action='store_true', - default=False, help='Check the generated files in root') - - args = parser.parse_args() - if not build_tree.looks_like_root("."): raise RuntimeError("This script must be run from Mbed TLS or TF-PSA-Crypto root.") @@ -280,17 +134,7 @@ def main() -> int: raise Exception("No support for Mbed TLS 3.6") generation_scripts += COMMON_GENERATION_SCRIPTS - if args.list: - files = get_generated_files(generation_scripts) - for file in files: - print(str(file)) - return 0 - elif args.check: - ok = check_generated_files(generation_scripts, Path(args.root or ".")) - return 0 if ok else 1 - else: - make_generated_files(generation_scripts) - return 0 # Any error causes an exception + return generated_files.main(generation_scripts) if __name__ == "__main__": sys.exit(main()) diff --git a/scripts/mbedtls_framework/generated_files.py b/scripts/mbedtls_framework/generated_files.py new file mode 100644 index 000000000..2ceb45dea --- /dev/null +++ b/scripts/mbedtls_framework/generated_files.py @@ -0,0 +1,167 @@ +"""Generic code to generate, check and list the generated files +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import filecmp +import shutil +import subprocess +import sys + +from pathlib import Path +from typing import List, Optional + +class GenerationScript: + """ + Representation of a script generating a configuration independent file. + """ + # pylint: disable=too-few-public-methods,too-many-arguments + def __init__(self, script: Path, files: List[Path], + output_dir_option: Optional[str] = None, + output_file_option: Optional[str] = None, + optional: bool = False) -> None: + # Path from the root of Mbed TLS or TF-PSA-Crypto of the generation script + self.script = script + + # Executable to run the script, needed for Windows + if script.suffix == ".py": + self.exe = sys.executable + elif script.suffix == ".pl": + self.exe = "perl" + + # List of the default paths from the Mbed TLS or TF-PSA-Crypto root of the + # files the script generates. + self.files = files + + # Output directory script argument. Can be an empty string in case it is a + # positional argument. + self.output_dir_option = output_dir_option + + # Output file script argument. Can be an empty string in case it is a + # positional argument. + self.output_file_option = output_file_option + + # Optional files are skipped in --check mode if they don't exist. + # This normally shouldn't happen, but it can happen during transition + # periods where we're adding a new script or a new file, and a + # consuming repository hasn't been updated yet. + self.optional = optional + +def get_generation_script_files(generation_script: str) -> List[Path]: + """ + Get the list of the default paths of the files that a given script + generates. It is assumed that the script supports the "--list" option. + """ + files = [] + if generation_script.endswith(".py"): + cmd = [sys.executable] + elif generation_script.endswith(".pl"): + cmd = ["perl"] + cmd += [generation_script, "--list"] + + output = subprocess.check_output(cmd, universal_newlines=True) + for line in output.splitlines(): + files.append(Path(line)) + + return files + +def get_generated_files(generation_scripts: List[GenerationScript]) -> List[Path]: + """ + List the generated files in Mbed TLS or TF-PSA-Crypto. The path from root + is returned for each generated files. + """ + files = [] + for generation_script in generation_scripts: + files += generation_script.files + + return files + +def make_generated_files(generation_scripts: List[GenerationScript]) -> None: + """ + Generate the configuration independent files in their default location in + the Mbed TLS or TF-PSA-Crypto tree. + """ + for generation_script in generation_scripts: + subprocess.run([generation_script.exe, str(generation_script.script)], check=True) + +def check_generated_files(generation_scripts: List[GenerationScript], + root: Path) -> bool: + """ + Check that the given root directory contains the generated files as expected/ + generated by this script. + """ + ok = True + for generation_script in generation_scripts: + for file in generation_script.files: + file = root / file + if not file.exists(): + # If the script is just being added, allow its files not + # to exist. This can happen, at least, when adding a new + # generation script in crypto: until mbedtls is updated, + # the files from that script won't be present when + # the updated crypto is built from mbedtls development. + if generation_script.optional: + continue + raise Exception(f"Expected generated file does not exist: {file}") + bak_file = file.with_name(file.name + ".bak") + if bak_file.exists(): + bak_file.unlink() + file.rename(bak_file) + + command = [generation_script.exe, str(generation_script.script)] + if generation_script.output_dir_option is not None: + command += [generation_script.output_dir_option, + str(root / Path(generation_script.files[0].parent))] + elif generation_script.output_file_option is not None: + command += generation_script.output_file_option.split() + command += [str(root / Path(generation_script.files[0]))] + subprocess.run([item for item in command if item.strip()], check=True) + + for file in generation_script.files: + file = root / file + bak_file = file.with_name(file.name + ".bak") + if generation_script.optional and not bak_file.exists(): + # This file is optional and didn't exist before, so + # there's nothing to compare to, or clean up. + continue + if not filecmp.cmp(file, bak_file): + ok = False + ref_file = file.with_name(file.name + ".ref") + ref_file = root / ref_file + if ref_file.exists(): + ref_file.unlink() + shutil.copy(file, ref_file) + print(f"Generated file {file} not identical to the reference one {ref_file}.") + file.unlink() + bak_file.rename(file) + return ok + +def main(generation_scripts: List[GenerationScript]) -> int: + """ + Main function of this program + """ + parser = argparse.ArgumentParser() + + parser.add_argument('--list', action='store_true', + default=False, help='List generated files.') + parser.add_argument('--root', metavar='DIR', + help='Root of the tree containing the generated files \ + to check (default: Mbed TLS or TF-PSA-Cryto root.)') + parser.add_argument('--check', action='store_true', + default=False, help='Check the generated files in root') + + args = parser.parse_args() + + if args.list: + files = get_generated_files(generation_scripts) + for file in files: + print(str(file)) + return 0 + elif args.check: + ok = check_generated_files(generation_scripts, Path(args.root or ".")) + return 0 if ok else 1 + else: + make_generated_files(generation_scripts) + return 0 # Any error causes an exception diff --git a/scripts/mbedtls_framework/tls_handshake_tests.py b/scripts/mbedtls_framework/tls_handshake_tests.py new file mode 100644 index 000000000..5de91299b --- /dev/null +++ b/scripts/mbedtls_framework/tls_handshake_tests.py @@ -0,0 +1,228 @@ +""" +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) + tls12_client_hello_defragmentation: Optional[bool] = True, + 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 and \ + not tls12_client_hello_defragmentation: + # If Server-side ClientHello defragmentation is only supported in + # the TLS 1.3 message parser, not in the TLS 1.2 message parser, + # a TLS 1.2 fragmented ClientHello is handled properly only if it + # is first reassembled by the TLS 1.3 parser before to be passed to + # the TLS 1.2 ClientHello parser in a TLS 1.3 or TLS 1.2 version + # negotiation scenario. + # 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 a fragmented 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(args, 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, + tls12_client_hello_defragmentation= \ + args.tls12_client_hello_defragmentation) + else: + write_tls_handshake_defragmentation_test( + out, side, length, Version.TLS12, + tls12_client_hello_defragmentation= + args.tls12_client_hello_defragmentation) + + +def write_handshake_tests(args, 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(args, 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)') + parser.add_argument('--no-tls12-client-hello-defragmentation-support', + action="store_false", + dest="tls12_client_hello_defragmentation", + help="Whether the TLS 1.2 ClientHello defragmentation is " + "fully supported or not (default: True)") + args = parser.parse_args() + + with open(args.output, 'w') as out: + write_handshake_tests(args, out)