mirror of
https://github.com/Mbed-TLS/mbedtls-framework.git
synced 2026-06-05 21:15:09 +00:00
Merge pull request #274 from ronald-cron-arm/dtls-client-hello-defragmentation-prep
Some preparatory work for DTLS client hello defragmentation
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user