From dae8e673be0089034e5013bb0563422e9ba4fe0a Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Mon, 26 Jun 2023 12:23:10 -0700 Subject: [PATCH] ktls: add self talk inet socket test --- tests/testlib/s2n_self_talk_test_utils.c | 21 +++ tests/testlib/s2n_testlib.h | 7 + tests/unit/s2n_self_talk_inet_socket_test.c | 182 ++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 tests/testlib/s2n_self_talk_test_utils.c create mode 100644 tests/unit/s2n_self_talk_inet_socket_test.c diff --git a/tests/testlib/s2n_self_talk_test_utils.c b/tests/testlib/s2n_self_talk_test_utils.c new file mode 100644 index 00000000000..030c7dae013 --- /dev/null +++ b/tests/testlib/s2n_self_talk_test_utils.c @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "testlib/s2n_testlib.h" + +S2N_RESULT noop_cb(struct s2n_connection *conn) +{ + return S2N_RESULT_OK; +} diff --git a/tests/testlib/s2n_testlib.h b/tests/testlib/s2n_testlib.h index 9e4dd6d4e99..e111569d726 100644 --- a/tests/testlib/s2n_testlib.h +++ b/tests/testlib/s2n_testlib.h @@ -244,3 +244,10 @@ extern const s2n_parsed_extension EMPTY_PARSED_EXTENSIONS[S2N_PARSED_EXTENSIONS_ int s2n_kem_recv_public_key_fuzz_test(const uint8_t *buf, size_t len, struct s2n_kem_params *kem_params); int s2n_kem_recv_ciphertext_fuzz_test(const uint8_t *buf, size_t len, struct s2n_kem_params *kem_params); int s2n_kem_recv_ciphertext_fuzz_test_init(const char *kat_file_path, struct s2n_kem_params *kem_params); + +/* A collection of callbacks run during inet socket self talk tests */ +struct self_talk_inet_socket_callbacks { + S2N_RESULT (*server_post_handshake_cb)(struct s2n_connection *conn); + S2N_RESULT (*client_post_handshake_cb)(struct s2n_connection *conn); +}; +S2N_RESULT noop_cb(struct s2n_connection *conn); diff --git a/tests/unit/s2n_self_talk_inet_socket_test.c b/tests/unit/s2n_self_talk_inet_socket_test.c new file mode 100644 index 00000000000..02da5a16aac --- /dev/null +++ b/tests/unit/s2n_self_talk_inet_socket_test.c @@ -0,0 +1,182 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include + +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" + +/* There are issues with MacOS and FreeBSD so we define the constant ourselves. + * https://stackoverflow.com/a/34042435 */ +#define S2N_TEST_INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */ + +const char CHAR_A = 'a'; +const char CHAR_B = 'b'; + +S2N_RESULT start_client(int fd, int read_pipe, const struct self_talk_inet_socket_callbacks *socket_cb) +{ + /* Setup connections */ + DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + DEFER_CLEANUP(struct s2n_cert_chain_and_key * chain_and_key, s2n_cert_chain_and_key_ptr_free); + RESULT_GUARD_POSIX(s2n_test_cert_chain_and_key_new(&chain_and_key, + S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + + /* Setup config */ + RESULT_GUARD_POSIX(s2n_connection_set_blinding(client_conn, S2N_SELF_SERVICE_BLINDING)); + RESULT_GUARD_POSIX(s2n_connection_set_fd(client_conn, fd)); + RESULT_GUARD_POSIX(s2n_config_set_cipher_preferences(config, "default")); + RESULT_GUARD_POSIX(s2n_config_set_unsafe_for_testing(config)); + RESULT_GUARD_POSIX(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + RESULT_GUARD_POSIX(s2n_connection_set_config(client_conn, config)); + + /* Do handshake */ + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + RESULT_GUARD_POSIX(s2n_negotiate(client_conn, &blocked)); + RESULT_ENSURE_EQ(client_conn->actual_protocol_version, S2N_TLS12); + + RESULT_GUARD(socket_cb->client_post_handshake_cb(client_conn)); + + /* used to synchronize with the server; blocking until the server has written some data. */ + char sync = 0; + char recv_buffer[1] = { 0 }; + + { + RESULT_GUARD_POSIX(read(read_pipe, &sync, 1)); + RESULT_GUARD_POSIX(s2n_recv(client_conn, recv_buffer, 1, &blocked)); + RESULT_ENSURE_EQ(memcmp(&CHAR_A, &recv_buffer[0], 1), 0); + + RESULT_GUARD_POSIX(read(read_pipe, &sync, 1)); + RESULT_GUARD_POSIX(s2n_recv(client_conn, recv_buffer, 1, &blocked)); + RESULT_ENSURE_EQ(memcmp(&CHAR_B, &recv_buffer[0], 1), 0); + } + + return S2N_RESULT_OK; +} + +S2N_RESULT start_server(int fd, int write_pipe, const struct self_talk_inet_socket_callbacks *socket_cb) +{ + /* Setup connections */ + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + DEFER_CLEANUP(struct s2n_cert_chain_and_key * chain_and_key, s2n_cert_chain_and_key_ptr_free); + RESULT_GUARD_POSIX(s2n_test_cert_chain_and_key_new(&chain_and_key, + S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + + /* Setup config */ + RESULT_GUARD_POSIX(s2n_connection_set_blinding(server_conn, S2N_SELF_SERVICE_BLINDING)); + RESULT_GUARD_POSIX(s2n_connection_set_fd(server_conn, fd)); + RESULT_GUARD_POSIX(s2n_config_set_cipher_preferences(config, "default")); + RESULT_GUARD_POSIX(s2n_config_set_unsafe_for_testing(config)); + RESULT_GUARD_POSIX(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + RESULT_GUARD_POSIX(s2n_connection_set_config(server_conn, config)); + + /* Do handshake */ + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + RESULT_GUARD_POSIX(s2n_negotiate(server_conn, &blocked)); + RESULT_ENSURE_EQ(server_conn->actual_protocol_version, S2N_TLS12); + + RESULT_GUARD(socket_cb->server_post_handshake_cb(server_conn)); + + /* used to synchronize with the client; indicating that data is ready for reading. */ + char sync = 0; + char send_buffer[1] = { 0 }; + { + send_buffer[0] = CHAR_A; + RESULT_GUARD_POSIX(s2n_send(server_conn, send_buffer, 1, &blocked)); + RESULT_GUARD_POSIX(write(write_pipe, &sync, 1)); + + send_buffer[0] = CHAR_B; + RESULT_GUARD_POSIX(s2n_send(server_conn, send_buffer, 1, &blocked)); + RESULT_GUARD_POSIX(write(write_pipe, &sync, 1)); + } + + return S2N_RESULT_OK; +} + +S2N_RESULT launch_test(const struct self_talk_inet_socket_callbacks *socket_cb) +{ + /* configure real socket */ + int listener = socket(AF_INET, SOCK_STREAM, 0); + RESULT_GUARD_POSIX(listener); + struct sockaddr_in saddr = { 0 }; + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(S2N_TEST_INADDR_LOOPBACK); + saddr.sin_port = 0; + + /* listen on socket address */ + socklen_t addrlen = sizeof(saddr); + RESULT_GUARD_POSIX(bind(listener, (struct sockaddr *) &saddr, addrlen)); + RESULT_GUARD_POSIX(getsockname(listener, (struct sockaddr *) &saddr, &addrlen)); + + /* used for synchronizing send and recv between client and server. The server + * sends data and the client receives data. */ + int sync_pipe[2] = { 0 }; + RESULT_GUARD_POSIX(pipe(sync_pipe)); + + pid_t child = fork(); + RESULT_ENSURE(child >= 0, S2N_ERR_SAFETY); + int status = 0; + int fd = 0; + if (child) { + /* server */ + RESULT_GUARD_POSIX(listen(listener, 1)); + fd = accept(listener, NULL, NULL); + RESULT_GUARD_POSIX(fd); + RESULT_GUARD_POSIX(close(sync_pipe[0])); + + RESULT_GUARD(start_server(fd, sync_pipe[1], socket_cb)); + + RESULT_ENSURE_EQ(waitpid(-1, &status, 0), child); + RESULT_ENSURE_EQ(status, 0); + } else { + /* client */ + fd = socket(AF_INET, SOCK_STREAM, 0); + RESULT_GUARD_POSIX(fd); + + /* wait for server to start up */ + sleep(1); + RESULT_GUARD_POSIX(connect(fd, (struct sockaddr *) &saddr, addrlen)); + RESULT_GUARD_POSIX(close(sync_pipe[1])); + + RESULT_GUARD(start_client(fd, sync_pipe[0], socket_cb)); + exit(0); + } + + return S2N_RESULT_OK; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + /* SIGPIPE is received when a process tries to write to a socket which + * has been shutdown. Ignore it and handle it gracefully. */ + signal(SIGPIPE, SIG_IGN); + + /* A regular connection */ + const struct self_talk_inet_socket_callbacks noop_inet_cb = { + .server_post_handshake_cb = noop_cb, + .client_post_handshake_cb = noop_cb, + }; + EXPECT_OK(launch_test(&noop_inet_cb)); + + END_TEST(); +}