diff --git a/.builder/actions/tls_server_setup.py b/.builder/actions/tls_server_setup.py index 4b296f710..d0a5a6eda 100644 --- a/.builder/actions/tls_server_setup.py +++ b/.builder/actions/tls_server_setup.py @@ -1,14 +1,13 @@ """ -Setup local TLS server for tests +Setup local TLS servers for tests """ import Builder -import os +from pathlib import Path import sys import subprocess import atexit -import time class TlsServerSetup(Builder.Action): @@ -18,6 +17,17 @@ class TlsServerSetup(Builder.Action): This action should be run in the 'pre_build_steps' or 'build_steps' stage. """ + @staticmethod + def cleanup_tls_server(tls_server_process): + tls_server_process.terminate() + out, err = tls_server_process.communicate() + print("TLS server stdout:") + for line in out.splitlines(): + print(f" = {line.decode('utf-8')}") + print("TLS server stderr:") + for line in err.splitlines(): + print(f" = {line.decode('utf-8')}") + def run(self, env): if not env.project.needs_tests(env): print("Skipping TLS server setup because tests disabled for project") @@ -25,19 +35,29 @@ def run(self, env): self.env = env - base_dir = os.path.dirname(os.path.realpath(__file__)) - dir = os.path.join(base_dir, "..", "..", "tests", "tls_server") + root_dir = Path(__file__).resolve().parent / '..' / '..' + tls_server_dir = root_dir / 'tests' / 'tls_server' + resource_dir = root_dir / 'tests' / 'resources' + + print("Running TLS servers") + + python_path = env.config['variables']['python'] - print("Running openssl TLS server") + tls12_server_process = subprocess.Popen( + [python_path, tls_server_dir / 'tls_server.py', '--port', '58443', '--resource-dir', resource_dir, + '--min-tls', '1.2', + '--max-tls', '1.2'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) - python_path = sys.executable - p = subprocess.Popen([python_path, "tls_server.py", - ], cwd=dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + tls13_server_process = subprocess.Popen( + [python_path, tls_server_dir / 'tls_server.py', '--port', '59443', '--resource-dir', resource_dir, + '--min-tls', '1.3', + '--max-tls', '1.3'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) @atexit.register - def close_tls_server(): - print("Terminating openssl TLS server") - p.terminate() - out, err = p.communicate() - print("TLS server stdout:\n{}".format(out)) - print("TLS server stderr:\n{}".format(err)) + def close_tls_servers(): + print('Terminating TLS 1.2 server') + TlsServerSetup.cleanup_tls_server(tls12_server_process) + print('Terminating TLS 1.3 server') + TlsServerSetup.cleanup_tls_server(tls13_server_process) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a855535bd..7d3876889 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,8 +6,8 @@ on: - 'main' env: - BUILDER_VERSION: v0.9.72 - BUILDER_SOURCE: releases + BUILDER_VERSION: ubuntu-update + BUILDER_SOURCE: channels BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-c-io LINUX_BASE_IMAGE: ubuntu-18-x64 diff --git a/builder.json b/builder.json index 7dd9cec51..8b10a84bc 100644 --- a/builder.json +++ b/builder.json @@ -13,14 +13,11 @@ { "name": "aws-c-mqtt" }, { "name": "aws-c-event-stream" } ], + "pre_build_steps": ["tls-server-setup"], "targets": { "linux": { "_comment": "set up SoftHSM2 for PKCS#11 tests (see: ./builder/actions/pkcs11_test_setup.py)", "+pre_build_steps": ["pkcs11-test-setup"] - }, - "windows": { - "+pre_build_steps": ["tls-server-setup"] - } }, "build_env": { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5b751326c..26b05d537 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -231,6 +231,7 @@ if(NOT BYO_CRYPTO) add_net_test_case(tls_client_channel_negotiation_error_broken_crypto_dh480) add_net_test_case(tls_client_channel_negotiation_error_broken_crypto_dh512) add_net_test_case(tls_client_channel_negotiation_error_broken_crypto_null) + add_net_test_case(tls_client_channel_negotiation_error_tls1_3_to_tls1_2_server) # Badssl - Legacy crypto suite, includes both negative and positive tests, with override checks where appropriate # Our current baseline/default is platform-specific, whereas badssl expects a baseline of 1.2 diff --git a/tests/tls_handler_test.c b/tests/tls_handler_test.c index 08fc2fe5c..2150199b1 100644 --- a/tests/tls_handler_test.c +++ b/tests/tls_handler_test.c @@ -1225,6 +1225,10 @@ static void s_raise_tls_version_to_12(struct aws_tls_ctx_options *options) { aws_tls_ctx_options_set_minimum_tls_version(options, AWS_IO_TLSv1_2); } +static void s_raise_tls_version_to_13(struct aws_tls_ctx_options *options) { + aws_tls_ctx_options_set_minimum_tls_version(options, AWS_IO_TLSv1_3); +} + static int s_tls_client_channel_negotiation_error_override_legacy_crypto_tls11_fn( struct aws_allocator *allocator, void *ctx) { @@ -1444,7 +1448,7 @@ static int s_verify_good_host( return AWS_OP_SUCCESS; } -static int s_verify_good_host_mqtt_connect( +static int s_verify_good_host_connect( struct aws_allocator *allocator, const struct aws_string *host_name, uint32_t port, @@ -1494,7 +1498,6 @@ static int s_verify_good_host_mqtt_connect( /* tls13_server_root_ca.pem.crt is self-signed, so peer verification fails without additional OS configuration. */ aws_tls_ctx_options_set_verify_peer(&tls_options, false); - aws_tls_ctx_options_set_alpn_list(&tls_options, "x-amzn-mqtt-ca"); if (override_tls_options_fn) { (*override_tls_options_fn)(&tls_options); @@ -1511,7 +1514,6 @@ static int s_verify_good_host_mqtt_connect( struct aws_byte_cursor host_name_cur = aws_byte_cursor_from_string(host_name); aws_tls_connection_options_set_server_name(&tls_client_conn_options, allocator, &host_name_cur); - aws_tls_connection_options_set_alpn_list(&tls_client_conn_options, allocator, "x-amzn-mqtt-ca"); struct aws_socket_options options; AWS_ZERO_STRUCT(options); @@ -1549,16 +1551,8 @@ static int s_verify_good_host_mqtt_connect( ASSERT_SUCCESS(aws_mutex_unlock(&c_tester.mutex)); ASSERT_FALSE(outgoing_args.error_invoked); - struct aws_byte_buf expected_protocol = aws_byte_buf_from_c_str("x-amzn-mqtt-ca"); - /* check ALPN and SNI was properly negotiated */ - if (aws_tls_is_alpn_available() && tls_options.verify_peer) { - ASSERT_BIN_ARRAYS_EQUALS( - expected_protocol.buffer, - expected_protocol.len, - outgoing_args.negotiated_protocol.buffer, - outgoing_args.negotiated_protocol.len); - } + /* check SNI was properly negotiated */ ASSERT_BIN_ARRAYS_EQUALS( host_name->bytes, host_name->len, outgoing_args.server_name.buffer, outgoing_args.server_name.len); @@ -1580,6 +1574,125 @@ static int s_verify_good_host_mqtt_connect( return AWS_OP_SUCCESS; } +static int s_verify_negotiation_fails_connect( + struct aws_allocator *allocator, + const struct aws_string *host_name, + uint32_t port, + void (*override_tls_options_fn)(struct aws_tls_ctx_options *)) { + + struct aws_byte_buf cert_buf = {0}; + struct aws_byte_buf key_buf = {0}; + struct aws_byte_buf ca_buf = {0}; + + ASSERT_SUCCESS(aws_byte_buf_init_from_file(&cert_buf, allocator, "tls13_device.pem.crt")); + ASSERT_SUCCESS(aws_byte_buf_init_from_file(&key_buf, allocator, "tls13_device.key")); + ASSERT_SUCCESS(aws_byte_buf_init_from_file(&ca_buf, allocator, "tls13_server_root_ca.pem.crt")); + + struct aws_byte_cursor cert_cur = aws_byte_cursor_from_buf(&cert_buf); + struct aws_byte_cursor key_cur = aws_byte_cursor_from_buf(&key_buf); + struct aws_byte_cursor ca_cur = aws_byte_cursor_from_buf(&ca_buf); + + aws_io_library_init(allocator); + + ASSERT_SUCCESS(s_tls_common_tester_init(allocator, &c_tester)); + + uint8_t outgoing_received_message[128] = {0}; + + struct tls_test_rw_args outgoing_rw_args; + ASSERT_SUCCESS(s_tls_rw_args_init( + &outgoing_rw_args, + &c_tester, + aws_byte_buf_from_empty_array(outgoing_received_message, sizeof(outgoing_received_message)))); + + struct tls_test_args outgoing_args = { + .mutex = &c_tester.mutex, + .allocator = allocator, + .condition_variable = &c_tester.condition_variable, + .error_invoked = 0, + .expects_error = true, + .rw_handler = NULL, + .server = false, + .tls_levels_negotiated = 0, + .desired_tls_levels = 1, + .shutdown_finished = false, + }; + + struct aws_tls_ctx_options tls_options = {0}; + AWS_ZERO_STRUCT(tls_options); + + AWS_FATAL_ASSERT( + AWS_OP_SUCCESS == aws_tls_ctx_options_init_client_mtls(&tls_options, allocator, &cert_cur, &key_cur)); + + /* tls13_server_root_ca.pem.crt is self-signed, so peer verification fails without additional OS configuration. */ + aws_tls_ctx_options_set_verify_peer(&tls_options, false); + + if (override_tls_options_fn) { + (*override_tls_options_fn)(&tls_options); + } + + struct aws_tls_ctx *tls_context = aws_tls_client_ctx_new(allocator, &tls_options); + ASSERT_NOT_NULL(tls_context); + + struct aws_tls_connection_options tls_client_conn_options; + aws_tls_connection_options_init_from_ctx(&tls_client_conn_options, tls_context); + aws_tls_connection_options_set_callbacks(&tls_client_conn_options, s_tls_on_negotiated, NULL, NULL, &outgoing_args); + + aws_tls_ctx_options_override_default_trust_store(&tls_options, &ca_cur); + + struct aws_byte_cursor host_name_cur = aws_byte_cursor_from_string(host_name); + aws_tls_connection_options_set_server_name(&tls_client_conn_options, allocator, &host_name_cur); + + struct aws_socket_options options; + AWS_ZERO_STRUCT(options); + options.connect_timeout_ms = 10000; + options.type = AWS_SOCKET_STREAM; + options.domain = AWS_SOCKET_IPV4; + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = c_tester.el_group, + .host_resolver = c_tester.resolver, + }; + struct aws_client_bootstrap *client_bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + ASSERT_NOT_NULL(client_bootstrap); + + struct aws_socket_channel_bootstrap_options channel_options; + AWS_ZERO_STRUCT(channel_options); + channel_options.bootstrap = client_bootstrap; + channel_options.host_name = aws_string_c_str(host_name); + channel_options.port = port; + channel_options.socket_options = &options; + channel_options.tls_options = &tls_client_conn_options; + channel_options.setup_callback = s_tls_handler_test_client_setup_callback; + channel_options.shutdown_callback = s_tls_handler_test_client_shutdown_callback; + channel_options.user_data = &outgoing_args; + + ASSERT_SUCCESS(aws_client_bootstrap_new_socket_channel(&channel_options)); + + /* put this here to verify ownership semantics are correct. This should NOT cause a segfault. If it does, ya + * done messed up. */ + aws_tls_connection_options_clean_up(&tls_client_conn_options); + + ASSERT_SUCCESS(aws_mutex_lock(&c_tester.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + &c_tester.condition_variable, &c_tester.mutex, s_tls_channel_shutdown_predicate, &outgoing_args)); + ASSERT_SUCCESS(aws_mutex_unlock(&c_tester.mutex)); + + ASSERT_TRUE(outgoing_args.error_invoked); + + ASSERT_TRUE(aws_error_code_is_tls(outgoing_args.last_error_code)); + + /* cleanups */ + aws_byte_buf_clean_up(&cert_buf); + aws_byte_buf_clean_up(&key_buf); + aws_byte_buf_clean_up(&ca_buf); + aws_tls_ctx_release(tls_context); + aws_tls_ctx_options_clean_up(&tls_options); + aws_client_bootstrap_release(client_bootstrap); + ASSERT_SUCCESS(s_tls_common_tester_clean_up(&c_tester)); + + return AWS_OP_SUCCESS; +} + static int s_tls_client_channel_negotiation_success_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; return s_verify_good_host(allocator, s_amazon_host_name, 443, NULL); @@ -1623,20 +1736,30 @@ AWS_TEST_CASE( s_tls_client_channel_negotiation_success_ecc384_SCHANNEL_CREDS_fn) # endif -static void s_raise_tls_version_to_13(struct aws_tls_ctx_options *options) { - aws_tls_ctx_options_set_minimum_tls_version(options, AWS_IO_TLSv1_3); -} - AWS_STATIC_STRING_FROM_LITERAL(s_aws_ecc384_host_name, "127.0.0.1"); static int s_tls_client_channel_negotiation_success_mtls_tls1_3_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_verify_good_host_mqtt_connect(allocator, s_aws_ecc384_host_name, 59443, s_raise_tls_version_to_13); + uint32_t server_tls1_3_port = 59443; + return s_verify_good_host_connect(allocator, s_aws_ecc384_host_name, server_tls1_3_port, s_raise_tls_version_to_13); } AWS_TEST_CASE( tls_client_channel_negotiation_success_mtls_tls1_3, s_tls_client_channel_negotiation_success_mtls_tls1_3_fn) +static int s_tls_client_channel_negotiation_error_tls1_3_to_tls1_2_server_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + uint32_t server_tls1_2_port = 58443; + return s_verify_negotiation_fails_connect( + allocator, s_aws_ecc384_host_name, server_tls1_2_port, s_raise_tls_version_to_13); +} + +AWS_TEST_CASE( + tls_client_channel_negotiation_error_tls1_3_to_tls1_2_server, + s_tls_client_channel_negotiation_error_tls1_3_to_tls1_2_server_fn) + AWS_STATIC_STRING_FROM_LITERAL(s3_host_name, "s3.amazonaws.com"); static void s_disable_verify_peer(struct aws_tls_ctx_options *options) { diff --git a/tests/tls_server/tls_server.py b/tests/tls_server/tls_server.py index 49b9f188e..405f7509e 100644 --- a/tests/tls_server/tls_server.py +++ b/tests/tls_server/tls_server.py @@ -1,23 +1,71 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0. +import argparse +import pathlib +import signal import socket import ssl +import sys + + +def parse_tls(tls_str): + if tls_str == '1.1': + return ssl.TLSVersion.TLSv1_1 + elif tls_str == '1.2': + return ssl.TLSVersion.TLSv1_2 + elif tls_str == '1.3': + return ssl.TLSVersion.TLSv1_3 + raise ValueError('Unknown TLS version') + + +print(f"Starting TLS server") + +parser = argparse.ArgumentParser( + description="TLS test server", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +optional = parser.add_argument_group("optional arguments") + +optional.add_argument("--host", dest="host", default="127.0.0.1", help="Listening host") +optional.add_argument("--port", type=int, dest="port", default=59443, help="Listening port") +optional.add_argument("--min-tls", choices=['1.1', '1.2', '1.3'], dest="min_tls", default='1.2', + help="Minimum acceptable TLS version") +optional.add_argument("--max-tls", choices=['1.1', '1.2', '1.3'], dest="max_tls", default='1.3', + help="Maximum acceptable TLS version") +optional.add_argument("--resource-dir", type=pathlib.Path, dest="resource_dir", default='./tests/resources/', + help="Path to keys and certificates") + +args = parser.parse_args() context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) -context.minimum_version = ssl.TLSVersion.TLSv1_3 -context.maximum_version = ssl.TLSVersion.TLSv1_3 -context.load_cert_chain('../resources/tls13_server.pem.crt', '../resources/tls13_server.key') -context.load_verify_locations('../resources/tls13_device_root_ca.pem.crt') +context.minimum_version = parse_tls(args.min_tls) +context.maximum_version = parse_tls(args.max_tls) +context.load_cert_chain(args.resource_dir / 'tls13_server.pem.crt', args.resource_dir / 'tls13_server.key') +context.load_verify_locations(args.resource_dir / 'tls13_device_root_ca.pem.crt') context.verify_mode = ssl.CERT_REQUIRED + +def signal_handler(signum, frame): + sys.stdout.flush() + sys.exit(0) + + +signal.signal(signal.SIGTERM, signal_handler) + +print(f"Running TLS server on {args.host}:{args.port}") +print(f"Minimum TLS version: {context.minimum_version.name}") +print(f"Maximum TLS version: {context.maximum_version.name}") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: - sock.bind(('127.0.0.1', 59443)) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((args.host, args.port)) sock.listen(1) with context.wrap_socket(sock, server_side=True) as ssock: while True: try: conn, addr = ssock.accept() - print("accepted new connection: {}".format(addr)) + print("Accepted new connection: {}".format(addr)) except Exception as e: - print("accept failed: {}".format(e)) + print(f"Accept failed: {e}")