Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions .builder/actions/tls_server_setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""
Setup local TLS server for tests
Setup local TLS servers for tests
"""

import Builder

import os
import sys
from pathlib import Path
import subprocess
import atexit
import time


class TlsServerSetup(Builder.Action):
Expand All @@ -18,26 +16,47 @@ 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")
return

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)
4 changes: 3 additions & 1 deletion builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
},
"windows": {
"+pre_build_steps": ["tls-server-setup"]

},
"macos": {
"+pre_build_steps": ["tls-server-setup"]
}
},
"build_env": {
Expand Down
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ 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)
if (WIN32)
# Currently, this test doesn't work on macOS. It will be enabled when migration to dispatch queue is done.
add_net_test_case(tls_client_channel_negotiation_error_tls1_3_to_tls1_2_server)
endif()

# 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
Expand Down
157 changes: 140 additions & 17 deletions tests/tls_handler_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
62 changes: 55 additions & 7 deletions tests/tls_server/tls_server.py
Original file line number Diff line number Diff line change
@@ -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}")
Loading