Skip to content

Commit

Permalink
ktls: recv alerts (aws#4199)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart authored Sep 15, 2023
1 parent 765afea commit 87f0c9e
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 6 deletions.
85 changes: 85 additions & 0 deletions tests/unit/s2n_ktls_io_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "testlib/s2n_mem_testlib.h"
#include "testlib/s2n_testlib.h"
#include "tls/s2n_ktls.h"
#include "tls/s2n_tls.h"
#include "utils/s2n_random.h"

#define S2N_TEST_TO_SEND 10
Expand Down Expand Up @@ -1056,5 +1057,89 @@ int main(int argc, char **argv)
};
};

/* Test: s2n_ktls_read_full_record */
{
const struct iovec test_iovec = {
.iov_base = test_data,
.iov_len = sizeof(test_data),
};
s2n_blocked_status blocked = S2N_NOT_BLOCKED;

const size_t max_frag_len = S2N_DEFAULT_FRAGMENT_LENGTH;
/* Our test assumptions are wrong if this isn't true */
EXPECT_TRUE(max_frag_len < sizeof(test_data));

/* Safety */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);
uint8_t record_type = 0;
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_read_full_record(NULL, &record_type),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_read_full_record(conn, NULL),
S2N_ERR_NULL);
};

/* Test: Basic read succeeds */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair pair = { 0 },
s2n_ktls_io_stuffer_pair_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer(conn, conn, &pair));
struct s2n_test_ktls_io_stuffer *ctx = &pair.client_in;

size_t written = 0;
EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_ALERT, &test_iovec, 1, &blocked, &written));
EXPECT_EQUAL(written, sizeof(test_data));

uint8_t record_type = 0;
EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type));
EXPECT_EQUAL(record_type, TLS_ALERT);

EXPECT_EQUAL(conn->in.blob.allocated, max_frag_len);
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), max_frag_len);
uint8_t *read = s2n_stuffer_raw_read(&conn->in, max_frag_len);
EXPECT_BYTEARRAY_EQUAL(read, test_data, max_frag_len);
};

/* Test: Receive does not completely fill the output buffer */
{
const size_t small_frag_len = 10;
EXPECT_TRUE(small_frag_len < max_frag_len);
EXPECT_TRUE(small_frag_len < sizeof(test_data));
struct iovec small_test_iovec = test_iovec;
small_test_iovec.iov_len = small_frag_len;

DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(conn);

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair pair = { 0 },
s2n_ktls_io_stuffer_pair_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer(conn, conn, &pair));
struct s2n_test_ktls_io_stuffer *ctx = &pair.client_in;

size_t written = 0;
EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_ALERT, &small_test_iovec, 1, &blocked, &written));
EXPECT_EQUAL(written, small_frag_len);

uint8_t record_type = 0;
EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type));
EXPECT_EQUAL(record_type, TLS_ALERT);

/* Verify that conn->in reflects the correct size of the "record"
* read and doesn't just assume the maximum read size.
*/
EXPECT_EQUAL(conn->in.blob.allocated, max_frag_len);
EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), small_frag_len);
uint8_t *read = s2n_stuffer_raw_read(&conn->in, small_frag_len);
EXPECT_BYTEARRAY_EQUAL(read, test_data, small_frag_len);
};
};

END_TEST();
}
100 changes: 94 additions & 6 deletions tests/unit/s2n_self_talk_ktls_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "s2n_test.h"
#include "testlib/s2n_testlib.h"
#include "tls/s2n_ktls.h"
#include "tls/s2n_tls.h"
#include "utils/s2n_random.h"

/* There are issues with MacOS and FreeBSD so we define the constant ourselves.
Expand Down Expand Up @@ -116,6 +117,12 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config));
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default"));

/* Even if we detected ktls support at compile time, enabling ktls
* can fail at runtime if the system is not properly configured.
*/
bool ktls_send_supported = true;
bool ktls_recv_supported = true;

/* Test enabling ktls for sending */
{
DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
Expand Down Expand Up @@ -144,18 +151,27 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.client));
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server, client));

if (s2n_connection_ktls_enable_send(client) != S2N_SUCCESS) {
/* Even if we detected ktls support at compile time, enabling ktls
* can fail at runtime if the system is not properly configured.
*/
if (s2n_connection_ktls_enable_send(client) == S2N_SUCCESS) {
EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server));
} else {
EXPECT_FALSE(ktls_expected);
END_TEST();
ktls_send_supported = false;
}

if (s2n_connection_ktls_enable_recv(client) == S2N_SUCCESS) {
EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(server));
} else {
EXPECT_FALSE(ktls_expected);
ktls_recv_supported = false;
}
EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server));
};

/* Test sending with ktls */
for (size_t mode_i = 0; mode_i < s2n_array_len(modes); mode_i++) {
if (!ktls_send_supported) {
break;
}

const s2n_mode mode = modes[mode_i];

DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
Expand Down Expand Up @@ -264,6 +280,78 @@ int main(int argc, char **argv)

EXPECT_SUCCESS(s2n_shutdown(reader, &blocked));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED));
};
};

/* Test receiving with ktls */
for (size_t mode_i = 0; mode_i < s2n_array_len(modes); mode_i++) {
if (!ktls_recv_supported) {
break;
}

const s2n_mode mode = modes[mode_i];

DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client);
EXPECT_SUCCESS(s2n_connection_set_config(client, config));

DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client);
EXPECT_SUCCESS(s2n_connection_set_config(server, config));

DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
EXPECT_OK(s2n_new_inet_socket_pair(&io_pair));
EXPECT_SUCCESS(s2n_connections_set_io_pair(client, server, &io_pair));

/* The test negotiate method assumes non-blocking sockets */
EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.server));
EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.client));
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server, client));

struct s2n_connection *conns[] = {
[S2N_CLIENT] = client,
[S2N_SERVER] = server,
};
struct s2n_connection *reader = conns[mode];
struct s2n_connection *writer = conns[S2N_PEER_MODE(mode)];
EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(reader));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;

/* Our IO methods are more predictable if they use blocking sockets. */
EXPECT_SUCCESS(s2n_fd_set_blocking(io_pair.server));
EXPECT_SUCCESS(s2n_fd_set_blocking(io_pair.client));

/* Test: s2n_recv not implemented yet */
{
uint8_t buffer[10] = { 0 };
int received = s2n_recv(reader, buffer, sizeof(buffer), &blocked);
EXPECT_FAILURE_WITH_ERRNO(received, S2N_ERR_UNIMPLEMENTED);
}

/* Test: s2n_shutdown */
{
/* Send some application data for the reader to skip */
for (size_t i = 0; i < 3; i++) {
EXPECT_SUCCESS(s2n_send(writer, test_data, 10, &blocked));
}

/* Send the close_notify */
EXPECT_SUCCESS(s2n_shutdown_send(writer, &blocked));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);

/* Verify that the reader skips the application data and successfully
* receives the close_notify.
*
* The close_notify was sent after the application data, so if the
* close_notify was received, then the application data was also received.
*/
EXPECT_SUCCESS(s2n_shutdown(reader, &blocked));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED));
};
};

Expand Down
135 changes: 135 additions & 0 deletions tests/unit/s2n_shutdown_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -704,5 +704,140 @@ int main(int argc, char **argv)
};
};

/* Test: ktls enabled */
{
/* Test: Successfully shutdown */
{
DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client);
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(client));

DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server);
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(server));

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 },
s2n_ktls_io_stuffer_pair_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
EXPECT_SUCCESS(s2n_shutdown_send(client, &blocked));
EXPECT_TRUE(client->alert_sent);

EXPECT_SUCCESS(s2n_shutdown(server, &blocked));
EXPECT_TRUE(server->alert_sent);
EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_CLOSED));
};

/* Test: Successfully shutdown after blocking */
{
DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client);
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(client));

DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server);
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(server));

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 },
s2n_ktls_io_stuffer_pair_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair));

/* Setup the client->server stuffer to not fit the entire close_notify */
EXPECT_SUCCESS(s2n_stuffer_free(&io_pair.server_in.data_buffer));
EXPECT_SUCCESS(s2n_stuffer_alloc(&io_pair.server_in.data_buffer, 1));

s2n_blocked_status blocked = S2N_NOT_BLOCKED;
EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(client, &blocked), S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE);
EXPECT_FALSE(s2n_connection_check_io_status(client, S2N_IO_WRITABLE));
EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_READABLE));

EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(server, &blocked), S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ);
EXPECT_FALSE(s2n_connection_check_io_status(server, S2N_IO_WRITABLE));
EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_READABLE));

/* Reuse the client->server stuffer for the remaining close_notify */
EXPECT_SUCCESS(s2n_stuffer_wipe(&io_pair.server_in.data_buffer));

EXPECT_SUCCESS(s2n_shutdown(client, &blocked));
EXPECT_SUCCESS(s2n_shutdown(server, &blocked));
};

/* Test: Skip application data when waiting for close_notify */
{
DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client);
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(client));

DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server);
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND));
EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV));
EXPECT_OK(s2n_skip_handshake(server));

DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 },
s2n_ktls_io_stuffer_pair_free);
EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair));

/* Send some application data for shutdown to skip */
uint8_t app_data[] = "hello world";
size_t app_data_size = sizeof(app_data);
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
size_t app_data_count = 5;
for (size_t i = 0; i < app_data_count; i++) {
EXPECT_SUCCESS(s2n_send(client, app_data, app_data_size, &blocked));
EXPECT_SUCCESS(s2n_send(server, app_data, app_data_size, &blocked));
}
EXPECT_OK(s2n_test_validate_ancillary(&io_pair.client_in, TLS_APPLICATION_DATA, app_data_size));
EXPECT_OK(s2n_test_validate_ancillary(&io_pair.server_in, TLS_APPLICATION_DATA, app_data_size));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, app_data_count));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, app_data_count));

/* Client's first shutdown blocks on reading the close_notify,
* but successfully writes the close_notify and skips all the app data.*/
EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(client, &blocked), S2N_ERR_IO_BLOCKED);
EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ);
EXPECT_FALSE(s2n_connection_check_io_status(client, S2N_IO_WRITABLE));
EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_READABLE));
EXPECT_TRUE(client->alert_sent);
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 0));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, app_data_count + 1));

/* Server's first shutdown successfully skips all the app data
* and receives the close_notify */
EXPECT_SUCCESS(s2n_shutdown(server, &blocked));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_CLOSED));
EXPECT_TRUE(server->alert_sent);
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 1));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, 0));

/* Client's second shutdown successfully receives the close_notify */
EXPECT_SUCCESS(s2n_shutdown(client, &blocked));
EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED);
EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_CLOSED));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 0));
EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, 0));
};
};

END_TEST();
}
1 change: 1 addition & 0 deletions tls/s2n_ktls.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ ssize_t s2n_ktls_sendv_with_offset(struct s2n_connection *conn, const struct iov
ssize_t count, ssize_t offs, s2n_blocked_status *blocked);
int s2n_ktls_record_writev(struct s2n_connection *conn, uint8_t content_type,
const struct iovec *in, int in_count, size_t offs, size_t to_write);
int s2n_ktls_read_full_record(struct s2n_connection *conn, uint8_t *record_type);

/* These functions will be part of the public API. */
int s2n_connection_ktls_enable_send(struct s2n_connection *conn);
Expand Down
Loading

0 comments on commit 87f0c9e

Please sign in to comment.