diff --git a/error/s2n_errno.c b/error/s2n_errno.c index a380e7b1751..2fed1cb7eec 100644 --- a/error/s2n_errno.c +++ b/error/s2n_errno.c @@ -295,6 +295,7 @@ static const char *no_such_error = "Internal s2n error"; ERR_ENTRY(S2N_ERR_KTLS_UNSUPPORTED_PLATFORM, "kTLS is unsupported on this platform") \ ERR_ENTRY(S2N_ERR_KTLS_UNSUPPORTED_CONN, "kTLS is unsupported for this connection") \ ERR_ENTRY(S2N_ERR_KTLS_ULP, "An error occurred when attempting to configure the socket for kTLS. Ensure the 'tls' kernel module is enabled.") \ + ERR_ENTRY(S2N_ERR_KTLS_ENABLE_CRYPTO, "An error occurred when attempting to enable kTLS on socket.") \ ERR_ENTRY(S2N_ERR_ATOMIC, "Atomic operations in this environment would require locking") \ /* clang-format on */ diff --git a/error/s2n_errno.h b/error/s2n_errno.h index 4e9bca8b071..8558729248c 100644 --- a/error/s2n_errno.h +++ b/error/s2n_errno.h @@ -311,6 +311,7 @@ typedef enum { S2N_ERR_KTLS_UNSUPPORTED_PLATFORM, S2N_ERR_KTLS_UNSUPPORTED_CONN, S2N_ERR_KTLS_ULP, + S2N_ERR_KTLS_ENABLE_CRYPTO, S2N_ERR_ATOMIC, S2N_ERR_T_USAGE_END, } s2n_error; diff --git a/tests/features/S2N_KTLS_SUPPORTED.c b/tests/features/S2N_KTLS_SUPPORTED.c new file mode 100644 index 00000000000..6cb2789d134 --- /dev/null +++ b/tests/features/S2N_KTLS_SUPPORTED.c @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/* Gate kTLS support only to Linux. Add other platforms once they have been tested. */ +#if defined(__linux__) + #include +#endif + +int main() +{ + /* Struct defined when kTLS support was added to linux + * https://github.com/torvalds/linux/blob/3c4d7559159bfe1e3b94df3a657b2cda3a34e218/include/uapi/linux/tls.h + */ + struct tls12_crypto_info_aes_gcm_128 aes_crypto_info; + struct tls_crypto_info crypto_info; + + return 0; +} diff --git a/tests/features/S2N_KTLS_SUPPORTED.flags b/tests/features/S2N_KTLS_SUPPORTED.flags new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/s2n_ktls_test.c b/tests/unit/s2n_ktls_test.c index 03ca8f17320..d5577e1f546 100644 --- a/tests/unit/s2n_ktls_test.c +++ b/tests/unit/s2n_ktls_test.c @@ -15,40 +15,126 @@ #include "tls/s2n_ktls.h" +#include "error/s2n_errno.h" #include "s2n_test.h" #include "testlib/s2n_testlib.h" +#include "utils/s2n_random.h" +#include "utils/s2n_socket.h" -S2N_RESULT s2n_ktls_retrieve_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, int *fd); +#define S2N_TEST_SEND_FD 66 +#define S2N_TEST_RECV_FD 55 -/* set kTLS supported cipher */ -struct s2n_cipher ktls_temp_supported_cipher = { - .ktls_supported = true, -}; -struct s2n_record_algorithm ktls_temp_supported_record_alg = { - .cipher = &ktls_temp_supported_cipher, -}; -struct s2n_cipher_suite ktls_temp_supported_cipher_suite = { - .record_alg = &ktls_temp_supported_record_alg, -}; +#if defined(S2N_KTLS_SUPPORTED) +/* It's difficult to test this method via s2n_connection_ktls_enable_send/recv because + * the key_material is populated via prf, which by definition produces "pseudo-random" + * output */ +S2N_RESULT s2n_ktls_init_aes128_gcm_crypto_info(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, + struct s2n_key_material *key_material, struct tls12_crypto_info_aes_gcm_128 *crypto_info); +#endif + +static int s2n_test_setsockopt_noop(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + return S2N_SUCCESS; +} + +static int s2n_test_setsockopt_eexist_error(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + errno = EEXIST; + return S2N_FAILURE; +} + +static int s2n_test_setsockopt_error(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + errno = EINVAL; + return S2N_FAILURE; +} + +static int s2n_test_setsockopt_tx(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + POSIX_ENSURE_EQ(fd, S2N_TEST_SEND_FD); + + if (level == S2N_SOL_TLS) { + POSIX_ENSURE_EQ(optname, S2N_TLS_TX); +#if defined(S2N_KTLS_SUPPORTED) + POSIX_ENSURE_EQ(optlen, sizeof(struct tls12_crypto_info_aes_gcm_128)); +#endif + } else if (level == S2N_SOL_TCP) { + POSIX_ENSURE_EQ(optname, S2N_TCP_ULP); + POSIX_ENSURE_EQ(optlen, S2N_TLS_ULP_NAME_SIZE); + } else { + POSIX_BAIL(S2N_ERR_SAFETY); + } + return S2N_SUCCESS; +} + +static int s2n_test_setsockopt_rx(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + POSIX_ENSURE_EQ(fd, S2N_TEST_RECV_FD); + + if (level == S2N_SOL_TLS) { + POSIX_ENSURE_EQ(optname, S2N_TLS_RX); +#if defined(S2N_KTLS_SUPPORTED) + POSIX_ENSURE_EQ(optlen, sizeof(struct tls12_crypto_info_aes_gcm_128)); +#endif + } else if (level == S2N_SOL_TCP) { + POSIX_ENSURE_EQ(optname, S2N_TCP_ULP); + POSIX_ENSURE_EQ(optlen, S2N_TLS_ULP_NAME_SIZE); + } else { + POSIX_BAIL(S2N_ERR_SAFETY); + } + return S2N_SUCCESS; +} S2N_RESULT s2n_test_configure_connection_for_ktls(struct s2n_connection *conn) { RESULT_ENSURE_REF(conn); + RESULT_GUARD(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_noop)); + /* config I/O */ - RESULT_GUARD_POSIX(s2n_connection_set_write_fd(conn, 1)); - RESULT_GUARD_POSIX(s2n_connection_set_read_fd(conn, 1)); + RESULT_GUARD_POSIX(s2n_connection_set_write_fd(conn, S2N_TEST_SEND_FD)); + RESULT_GUARD_POSIX(s2n_connection_set_read_fd(conn, S2N_TEST_RECV_FD)); conn->ktls_send_enabled = false; conn->ktls_recv_enabled = false; - /* configure connection so that the handshake is complete */ - conn->secure->cipher_suite = &ktls_temp_supported_cipher_suite; + /* set kTLS supported cipher */ + conn->secure->cipher_suite = &s2n_rsa_with_aes_128_gcm_sha256; conn->actual_protocol_version = S2N_TLS12; + /* configure connection so that the handshake is complete */ RESULT_GUARD(s2n_skip_handshake(conn)); return S2N_RESULT_OK; } +S2N_RESULT s2n_test_generate_fake_crypto_params(struct s2n_connection *conn) +{ + RESULT_ENSURE_REF(conn); + + struct s2n_blob test_data_blob = { 0 }; + + struct s2n_crypto_parameters *server_param = conn->server; + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, server_param->server_implicit_iv, sizeof(server_param->server_implicit_iv))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, server_param->server_sequence_number, sizeof(server_param->server_sequence_number))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, server_param->client_implicit_iv, sizeof(server_param->client_implicit_iv))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, server_param->client_sequence_number, sizeof(server_param->client_sequence_number))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + + struct s2n_crypto_parameters *client_param = conn->client; + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, client_param->server_implicit_iv, sizeof(client_param->server_implicit_iv))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, client_param->server_sequence_number, sizeof(client_param->server_sequence_number))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, client_param->client_implicit_iv, sizeof(client_param->client_implicit_iv))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + RESULT_GUARD_POSIX(s2n_blob_init(&test_data_blob, client_param->client_sequence_number, sizeof(client_param->client_sequence_number))); + RESULT_GUARD(s2n_get_public_random_data(&test_data_blob)); + + return S2N_RESULT_OK; +} + int main(int argc, char **argv) { BEGIN_TEST(); @@ -82,37 +168,47 @@ int main(int argc, char **argv) EXPECT_FALSE(cipher.ktls_supported); }; - /* Test s2n_ktls_retrieve_file_descriptor */ - { - DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), - s2n_connection_ptr_free); - int write_fd_orig = 1; - int read_fd_orig = 2; - int fd_ret = 0; - - EXPECT_SUCCESS(s2n_connection_set_write_fd(server_conn, write_fd_orig)); - EXPECT_OK(s2n_ktls_retrieve_file_descriptor(server_conn, S2N_KTLS_MODE_SEND, &fd_ret)); - EXPECT_EQUAL(write_fd_orig, fd_ret); - - EXPECT_SUCCESS(s2n_connection_set_read_fd(server_conn, read_fd_orig)); - EXPECT_OK(s2n_ktls_retrieve_file_descriptor(server_conn, S2N_KTLS_MODE_RECV, &fd_ret)); - EXPECT_EQUAL(read_fd_orig, fd_ret); - }; - /* Test s2n_connection_ktls_enable_recv/send */ { - /* Success case */ + /* enable TX/RX */ { DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); EXPECT_OK(s2n_test_configure_connection_for_ktls(server_conn)); + EXPECT_OK(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_tx)); EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server_conn)); EXPECT_TRUE(server_conn->ktls_send_enabled); + + EXPECT_OK(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_rx)); EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(server_conn)); EXPECT_TRUE(server_conn->ktls_recv_enabled); }; + /* handle setsockopt error */ + { + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_OK(s2n_test_configure_connection_for_ktls(server_conn)); + EXPECT_OK(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_eexist_error)); + + /* do expect an error when trying to set keys on the socket */ + EXPECT_FAILURE_WITH_ERRNO(s2n_connection_ktls_enable_send(server_conn), S2N_ERR_KTLS_ENABLE_CRYPTO); + EXPECT_FAILURE_WITH_ERRNO(s2n_connection_ktls_enable_recv(server_conn), S2N_ERR_KTLS_ENABLE_CRYPTO); + }; + + /* handle setsockopt EEXIST error from TCP_ULP call */ + { + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_OK(s2n_test_configure_connection_for_ktls(server_conn)); + EXPECT_OK(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_error)); + + /* do expect an error when trying to set keys on the socket */ + EXPECT_FAILURE_WITH_ERRNO(s2n_connection_ktls_enable_send(server_conn), S2N_ERR_KTLS_ULP); + EXPECT_FAILURE_WITH_ERRNO(s2n_connection_ktls_enable_recv(server_conn), S2N_ERR_KTLS_ULP); + }; + /* Noop if kTLS is already enabled */ { DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), @@ -224,7 +320,111 @@ int main(int argc, char **argv) server_conn->managed_recv_io = true; EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(server_conn)); }; - } + }; + + /* Test s2n_ktls_init_aes128_gcm_crypto_info */ + { + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_OK(s2n_test_configure_connection_for_ktls(server_conn)); + + /* prepare test data */ + uint8_t test_data[S2N_MAX_KEY_BLOCK_LEN] = { 0 }; + struct s2n_blob test_data_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&test_data_blob, test_data, sizeof(test_data))); + EXPECT_OK(s2n_get_public_random_data(&test_data_blob)); + + /* copy test data to key_material */ + struct s2n_key_material key_material = { 0 }; + EXPECT_OK(s2n_key_material_init(&key_material, server_conn)); + POSIX_CHECKED_MEMCPY(key_material.key_block, test_data, s2n_array_len(key_material.key_block)); + +#if defined(S2N_KTLS_SUPPORTED) + struct tls12_crypto_info_aes_gcm_128 crypto_info = { 0 }; + + /* generate test data for crypto params: implicit_iv and sequence_number */ + EXPECT_OK(s2n_test_generate_fake_crypto_params(server_conn)); + struct s2n_crypto_parameters *server_param = server_conn->server; + + /* Test crypto_info for happy case */ + { + /* server should send with its own keys */ + int ktls_mode = S2N_KTLS_MODE_SEND; + EXPECT_OK(s2n_ktls_init_aes128_gcm_crypto_info(server_conn, ktls_mode, &key_material, &crypto_info)); + EXPECT_EQUAL(memcmp(crypto_info.key, key_material.server_key.data, key_material.server_key.size), 0); + EXPECT_EQUAL(memcmp(crypto_info.iv, server_param->server_implicit_iv, TLS_CIPHER_AES_GCM_128_IV_SIZE), 0); + EXPECT_EQUAL(memcmp(crypto_info.salt, server_param->server_implicit_iv, TLS_CIPHER_AES_GCM_128_SALT_SIZE), 0); + EXPECT_EQUAL(memcmp(crypto_info.rec_seq, server_param->server_sequence_number, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE), 0); + + /* server should recv with its peer's keys */ + ktls_mode = S2N_KTLS_MODE_RECV; + EXPECT_OK(s2n_ktls_init_aes128_gcm_crypto_info(server_conn, ktls_mode, &key_material, &crypto_info)); + EXPECT_EQUAL(memcmp(crypto_info.key, key_material.client_key.data, key_material.client_key.size), 0); + EXPECT_EQUAL(memcmp(crypto_info.iv, server_param->client_implicit_iv, TLS_CIPHER_AES_GCM_128_IV_SIZE), 0); + EXPECT_EQUAL(memcmp(crypto_info.salt, server_param->client_implicit_iv, TLS_CIPHER_AES_GCM_128_SALT_SIZE), 0); + EXPECT_EQUAL(memcmp(crypto_info.rec_seq, server_param->client_sequence_number, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE), 0); + + EXPECT_EQUAL(crypto_info.info.cipher_type, TLS_CIPHER_AES_GCM_128); + EXPECT_EQUAL(crypto_info.info.version, TLS_1_2_VERSION); + }; + + /* Test crypto_info for invalid cases */ + { + int ktls_mode = S2N_KTLS_MODE_SEND; + server_conn->actual_protocol_version = S2N_TLS13; + EXPECT_ERROR_WITH_ERRNO(s2n_ktls_init_aes128_gcm_crypto_info(server_conn, ktls_mode, &key_material, &crypto_info), + S2N_ERR_KTLS_UNSUPPORTED_CONN); + + /* restore protocol version */ + server_conn->actual_protocol_version = S2N_TLS12; + EXPECT_OK(s2n_ktls_init_aes128_gcm_crypto_info(server_conn, ktls_mode, &key_material, &crypto_info)); + + server_conn->secure->cipher_suite = &s2n_rsa_with_aes_256_gcm_sha384; + EXPECT_ERROR_WITH_ERRNO(s2n_ktls_init_aes128_gcm_crypto_info(server_conn, ktls_mode, &key_material, &crypto_info), + S2N_ERR_KTLS_UNSUPPORTED_CONN); + }; +#endif + }; + + /* selftalk: Success case with a real TLS1.2 negotiated server and client */ + { + DEFER_CLEANUP(struct s2n_cert_chain_and_key * chain_and_key, + s2n_cert_chain_and_key_ptr_free); + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + 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); + + /* setup config */ + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key, + S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_config_disable_x509_verification(config)); + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "20170210")); + EXPECT_OK(s2n_ktls_set_setsockopt_cb(s2n_test_setsockopt_noop)); + EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config)); + EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config)); + + /* setup IO and negotiate */ + DEFER_CLEANUP(struct s2n_test_io_pair test_io_pair = { 0 }, + s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&test_io_pair)); + EXPECT_SUCCESS(s2n_connections_set_io_pair(client_conn, server_conn, &test_io_pair)); + EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn)); + EXPECT_EQUAL(server_conn->actual_protocol_version, S2N_TLS12); + + /* enable kTLS send */ + EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server_conn)); + EXPECT_TRUE(server_conn->ktls_send_enabled); + EXPECT_NOT_EQUAL(server_conn->send, s2n_socket_write); + + /* enable kTLS recv */ + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(server_conn)); + EXPECT_TRUE(server_conn->ktls_recv_enabled); + EXPECT_NOT_EQUAL(server_conn->recv, s2n_socket_read); + }; END_TEST(); } diff --git a/tls/s2n_ktls.c b/tls/s2n_ktls.c index db3d6ea8096..ad552d06a46 100644 --- a/tls/s2n_ktls.c +++ b/tls/s2n_ktls.c @@ -15,13 +15,35 @@ #include "tls/s2n_ktls.h" -#include +#include "tls/s2n_prf.h" -#include "utils/s2n_socket.h" +/* Used for overriding setsockopt calls in testing */ +s2n_setsockopt_fn s2n_setsockopt = setsockopt; + +S2N_RESULT s2n_ktls_set_setsockopt_cb(s2n_setsockopt_fn cb) +{ + RESULT_ENSURE(s2n_in_test(), S2N_ERR_NOT_IN_TEST); + s2n_setsockopt = cb; + return S2N_RESULT_OK; +} bool s2n_ktls_is_supported_on_platform() { - return S2N_KTLS_SUPPORTED; +#if defined(S2N_KTLS_SUPPORTED) + return true; +#else + return false; +#endif +} + +static int s2n_ktls_disabled_read(void *io_context, uint8_t *buf, uint32_t len) +{ + POSIX_BAIL(S2N_ERR_IO); +} + +static int s2n_ktls_disabled_write(void *io_context, const uint8_t *buf, uint32_t len) +{ + POSIX_BAIL(S2N_ERR_IO); } static S2N_RESULT s2n_ktls_validate(struct s2n_connection *conn, s2n_ktls_mode ktls_mode) @@ -72,7 +94,7 @@ static S2N_RESULT s2n_ktls_validate(struct s2n_connection *conn, s2n_ktls_mode k return S2N_RESULT_OK; } -S2N_RESULT s2n_ktls_retrieve_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, int *fd) +static S2N_RESULT s2n_ktls_get_file_descriptor(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, int *fd) { RESULT_ENSURE_REF(conn); RESULT_ENSURE_REF(fd); @@ -82,21 +104,115 @@ S2N_RESULT s2n_ktls_retrieve_file_descriptor(struct s2n_connection *conn, s2n_kt } else if (ktls_mode == S2N_KTLS_MODE_SEND) { RESULT_GUARD_POSIX(s2n_connection_get_write_fd(conn, fd)); } + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_ktls_get_io_mode(s2n_ktls_mode ktls_mode, int *tls_tx_rx_mode) +{ + RESULT_ENSURE_REF(tls_tx_rx_mode); + if (ktls_mode == S2N_KTLS_MODE_SEND) { + *tls_tx_rx_mode = S2N_TLS_TX; + } else { + *tls_tx_rx_mode = S2N_TLS_RX; + } return S2N_RESULT_OK; } -static S2N_RESULT s2n_ktls_configure_socket(struct s2n_connection *conn, s2n_ktls_mode ktls_mode) +#if defined(S2N_KTLS_SUPPORTED) +S2N_RESULT s2n_ktls_init_aes128_gcm_crypto_info(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, + struct s2n_key_material *key_material, struct tls12_crypto_info_aes_gcm_128 *crypto_info) +{ + RESULT_ENSURE_REF(conn); + RESULT_ENSURE_REF(conn->client); + RESULT_ENSURE_REF(conn->server); + RESULT_ENSURE_REF(key_material); + RESULT_ENSURE_REF(crypto_info); + RESULT_ENSURE_REF(conn->secure); + RESULT_ENSURE_REF(conn->secure->cipher_suite); + RESULT_ENSURE_REF(conn->secure->cipher_suite->record_alg); + const struct s2n_cipher *cipher = conn->secure->cipher_suite->record_alg->cipher; + RESULT_ENSURE_REF(cipher); + + RESULT_ENSURE(cipher == &s2n_aes128_gcm, S2N_ERR_KTLS_UNSUPPORTED_CONN); + RESULT_ENSURE(conn->actual_protocol_version == S2N_TLS12, S2N_ERR_KTLS_UNSUPPORTED_CONN); + crypto_info->info.cipher_type = TLS_CIPHER_AES_GCM_128; + crypto_info->info.version = TLS_1_2_VERSION; + + /* set values based on mode of operation */ + struct s2n_blob *key = NULL; + struct s2n_blob implicit_iv = { 0 }; + struct s2n_blob sequence_number = { 0 }; + + bool server_sending = (conn->mode == S2N_SERVER && ktls_mode == S2N_KTLS_MODE_SEND); + bool client_receiving = (conn->mode == S2N_CLIENT && ktls_mode == S2N_KTLS_MODE_RECV); + if (server_sending || client_receiving) { + /* If server is sending or client is receiving then use server key material */ + key = &key_material->server_key; + RESULT_GUARD_POSIX(s2n_blob_init(&implicit_iv, conn->server->server_implicit_iv, sizeof(conn->server->server_implicit_iv))); + RESULT_GUARD_POSIX(s2n_blob_init(&sequence_number, conn->server->server_sequence_number, sizeof(conn->server->server_sequence_number))); + } else { + key = &key_material->client_key; + RESULT_GUARD_POSIX(s2n_blob_init(&implicit_iv, conn->client->client_implicit_iv, sizeof(conn->client->client_implicit_iv))); + RESULT_GUARD_POSIX(s2n_blob_init(&sequence_number, conn->client->client_sequence_number, sizeof(conn->client->client_sequence_number))); + } + + /* The salt is the first 4 bytes of the IV. + * + *= https://www.rfc-editor.org/rfc/rfc4106#section-4 + *# The salt field is a four-octet value that is assigned at the + *# beginning of the security association, and then remains constant + *# for the life of the security association. + */ + RESULT_ENSURE_GTE(implicit_iv.size, TLS_CIPHER_AES_GCM_128_SALT_SIZE); + RESULT_CHECKED_MEMCPY(crypto_info->salt, implicit_iv.data, TLS_CIPHER_AES_GCM_128_SALT_SIZE); + + RESULT_ENSURE_GTE(implicit_iv.size, TLS_CIPHER_AES_GCM_128_IV_SIZE); + RESULT_CHECKED_MEMCPY(crypto_info->iv, implicit_iv.data, TLS_CIPHER_AES_GCM_128_IV_SIZE); + + RESULT_ENSURE_EQ(sequence_number.size, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); + RESULT_CHECKED_MEMCPY(crypto_info->rec_seq, sequence_number.data, TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); + + RESULT_ENSURE_EQ(key->size, TLS_CIPHER_AES_GCM_128_KEY_SIZE); + RESULT_CHECKED_MEMCPY(crypto_info->key, key->data, TLS_CIPHER_AES_GCM_128_KEY_SIZE); + + return S2N_RESULT_OK; +} +#endif + +static S2N_RESULT s2n_ktls_set_keys(struct s2n_connection *conn, s2n_ktls_mode ktls_mode, + struct s2n_key_material *key_material) { RESULT_ENSURE_REF(conn); + RESULT_ENSURE_REF(key_material); int fd = 0; - RESULT_GUARD(s2n_ktls_retrieve_file_descriptor(conn, ktls_mode, &fd)); + RESULT_GUARD(s2n_ktls_get_file_descriptor(conn, ktls_mode, &fd)); - /* Calls to setsockopt require a real socket, which is not used in unit tests. */ - if (s2n_in_unit_test()) { - return S2N_RESULT_OK; + int tls_tx_rx_mode = 0; + RESULT_GUARD(s2n_ktls_get_io_mode(ktls_mode, &tls_tx_rx_mode)); +#if defined(S2N_KTLS_SUPPORTED) + /* Only AES_128_GCM for TLS 1.2 is supported at the moment. */ + struct tls12_crypto_info_aes_gcm_128 crypto_info = { 0 }; + RESULT_GUARD(s2n_ktls_init_aes128_gcm_crypto_info(conn, ktls_mode, key_material, &crypto_info)); + + /* Set crypto_info on the socket; enabling kTLS and offloading the TLS + * protocol to the kernel. */ + int ret = s2n_setsockopt(fd, S2N_SOL_TLS, tls_tx_rx_mode, &crypto_info, sizeof(crypto_info)); + if (ret < 0) { + RESULT_BAIL(S2N_ERR_KTLS_ENABLE_CRYPTO); } +#endif + + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_ktls_configure_socket(struct s2n_connection *conn, s2n_ktls_mode ktls_mode) +{ + RESULT_ENSURE_REF(conn); + + int fd = 0; + RESULT_GUARD(s2n_ktls_get_file_descriptor(conn, ktls_mode, &fd)); /* Enable 'tls' ULP for the socket. https://lwn.net/Articles/730207 * @@ -104,16 +220,21 @@ static S2N_RESULT s2n_ktls_configure_socket(struct s2n_connection *conn, s2n_ktl * the call to setsockopt(..TCP_ULP...) to determine if kTLS is supported. * This is a safe and non destructive operation on Linux. */ - int ret = setsockopt(fd, S2N_SOL_TCP, S2N_TCP_ULP, S2N_TLS_ULP_NAME, S2N_TLS_ULP_NAME_SIZE); - - if (ret != 0) { - /* https://man7.org/linux/man-pages/man3/errno.3.html - * EEXIST indicates that TCP_ULP has already been enabled on the socket. - * This is a noop and therefore safe to ignore. + int ret = s2n_setsockopt(fd, S2N_SOL_TCP, S2N_TCP_ULP, S2N_TLS_ULP_NAME, S2N_TLS_ULP_NAME_SIZE); + if (ret < 0) { + /* https://github.com/torvalds/linux/blob/831fe284d8275987596b7d640518dddba5735f61/net/ipv4/tcp_ulp.c#L64-L65 + * + * EEXIST indicates that TCP_ULP has already been enabled on the + * socket. This is a noop and therefore safe to ignore. */ RESULT_ENSURE(errno == EEXIST, S2N_ERR_KTLS_ULP); } + /* configure crypto */ + struct s2n_key_material key_material = { 0 }; + RESULT_GUARD(s2n_prf_generate_key_material(conn, &key_material)); + RESULT_GUARD(s2n_ktls_set_keys(conn, ktls_mode, &key_material)); + return S2N_RESULT_OK; } @@ -126,6 +247,9 @@ static S2N_RESULT s2n_ktls_configure_socket(struct s2n_connection *conn, s2n_ktl */ int s2n_connection_ktls_enable_send(struct s2n_connection *conn) { + /* gate this feature only to tests until it is ready for release */ + POSIX_ENSURE(s2n_in_test(), S2N_ERR_NOT_IN_TEST); + POSIX_ENSURE_REF(conn); POSIX_GUARD_RESULT(s2n_ktls_validate(conn, S2N_KTLS_MODE_SEND)); @@ -135,13 +259,19 @@ int s2n_connection_ktls_enable_send(struct s2n_connection *conn) } POSIX_GUARD_RESULT(s2n_ktls_configure_socket(conn, S2N_KTLS_MODE_SEND)); + conn->ktls_send_enabled = true; + /* kTLS now handles I/O for the connection */ + conn->send = s2n_ktls_disabled_write; return S2N_SUCCESS; } int s2n_connection_ktls_enable_recv(struct s2n_connection *conn) { + /* gate this feature only to tests until it is ready for release */ + POSIX_ENSURE(s2n_in_test(), S2N_ERR_NOT_IN_TEST); + POSIX_ENSURE_REF(conn); POSIX_GUARD_RESULT(s2n_ktls_validate(conn, S2N_KTLS_MODE_RECV)); @@ -151,7 +281,10 @@ int s2n_connection_ktls_enable_recv(struct s2n_connection *conn) } POSIX_GUARD_RESULT(s2n_ktls_configure_socket(conn, S2N_KTLS_MODE_RECV)); + conn->ktls_recv_enabled = true; + /* kTLS now handles I/O for the connection */ + conn->recv = s2n_ktls_disabled_read; return S2N_SUCCESS; } diff --git a/tls/s2n_ktls.h b/tls/s2n_ktls.h index 36850374c58..f5410127d67 100644 --- a/tls/s2n_ktls.h +++ b/tls/s2n_ktls.h @@ -15,8 +15,9 @@ #pragma once -#include "tls/s2n_config.h" +#include +#include "tls/s2n_connection.h" /* Define headers needed to enable and use kTLS. * * The inline header definitions are required to compile kTLS specific code. @@ -35,6 +36,11 @@ typedef enum { S2N_KTLS_MODE_RECV, } s2n_ktls_mode; +/* Used for overriding setsockopt calls in testing */ +typedef int (*s2n_setsockopt_fn)(int socket, int level, int option_name, + const void *option_value, socklen_t option_len); +S2N_RESULT s2n_ktls_set_setsockopt_cb(s2n_setsockopt_fn cb); + bool s2n_ktls_is_supported_on_platform(); /* These functions will be part of the public API. */ diff --git a/tls/s2n_ktls_parameters.h b/tls/s2n_ktls_parameters.h index 45c31d7bfa3..0137467c242 100644 --- a/tls/s2n_ktls_parameters.h +++ b/tls/s2n_ktls_parameters.h @@ -24,21 +24,35 @@ * - https://elixir.bootlin.com/linux/v6.3.8/A/ident/SOL_TCP */ -#define S2N_TLS_ULP_NAME "tls" -#define S2N_TLS_ULP_NAME_SIZE sizeof(S2N_TLS_ULP_NAME) - -#if defined(__linux__) - #define S2N_KTLS_SUPPORTED true +#if defined(S2N_KTLS_SUPPORTED) + #include /* socket definitions */ #define S2N_TCP_ULP 31 /* Attach a ULP to a TCP connection. */ #define S2N_SOL_TCP 6 /* TCP level */ + #define S2N_SOL_TLS 282 + + /* We typically only define values not available in the linux uapi. However, + * only TLS_TX is defined in the first version of kTLS. Since calling setsockopt + * with TLS_RX fails and is non destructive, define both TX and RX to keep the + * definitions co-located and avoid extra ifdefs. + * https://github.com/torvalds/linux/blob/3c4d7559159bfe1e3b94df3a657b2cda3a34e218/include/uapi/linux/tls.h#L43 + */ + #define S2N_TLS_TX 1 + #define S2N_TLS_RX 2 #else - /* For unsupported platforms 0-init all values. */ - #define S2N_KTLS_SUPPORTED false + /* For unsupported platforms 0-init (array of size 1) all values. */ /* socket definitions */ - #define S2N_TCP_ULP 0 - #define S2N_SOL_TCP 0 + #define S2N_TCP_ULP 0 + #define S2N_SOL_TCP 0 + #define S2N_SOL_TLS 0 + + #define S2N_TLS_TX 0 + #define S2N_TLS_RX 0 #endif + +/* Common */ +#define S2N_TLS_ULP_NAME "tls" +#define S2N_TLS_ULP_NAME_SIZE sizeof(S2N_TLS_ULP_NAME) diff --git a/tls/s2n_prf.c b/tls/s2n_prf.c index 0062443c75b..3e912453c50 100644 --- a/tls/s2n_prf.c +++ b/tls/s2n_prf.c @@ -995,6 +995,30 @@ static int s2n_prf_make_server_key(struct s2n_connection *conn, struct s2n_key_m return 0; } +S2N_RESULT s2n_prf_generate_key_material(struct s2n_connection *conn, struct s2n_key_material *key_material) +{ + RESULT_ENSURE_REF(conn); + RESULT_ENSURE_REF(key_material); + + struct s2n_blob client_random = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&client_random, conn->handshake_params.client_random, sizeof(conn->handshake_params.client_random))); + struct s2n_blob server_random = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&server_random, conn->handshake_params.server_random, sizeof(conn->handshake_params.server_random))); + struct s2n_blob master_secret = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&master_secret, conn->secrets.version.tls12.master_secret, sizeof(conn->secrets.version.tls12.master_secret))); + + struct s2n_blob label = { 0 }; + uint8_t key_expansion_label[] = "key expansion"; + RESULT_GUARD_POSIX(s2n_blob_init(&label, key_expansion_label, sizeof(key_expansion_label) - 1)); + + RESULT_GUARD(s2n_key_material_init(key_material, conn)); + struct s2n_blob prf_out = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&prf_out, key_material->key_block, sizeof(key_material->key_block))); + RESULT_GUARD_POSIX(s2n_prf(conn, &master_secret, &label, &server_random, &client_random, NULL, &prf_out)); + + return S2N_RESULT_OK; +} + int s2n_prf_key_expansion(struct s2n_connection *conn) { POSIX_ENSURE_REF(conn); @@ -1005,21 +1029,8 @@ int s2n_prf_key_expansion(struct s2n_connection *conn) const struct s2n_cipher *cipher = cipher_suite->record_alg->cipher; POSIX_ENSURE_REF(cipher); - struct s2n_blob client_random = { 0 }; - POSIX_GUARD(s2n_blob_init(&client_random, conn->handshake_params.client_random, sizeof(conn->handshake_params.client_random))); - struct s2n_blob server_random = { 0 }; - POSIX_GUARD(s2n_blob_init(&server_random, conn->handshake_params.server_random, sizeof(conn->handshake_params.server_random))); - struct s2n_blob master_secret = { 0 }; - POSIX_GUARD(s2n_blob_init(&master_secret, conn->secrets.version.tls12.master_secret, sizeof(conn->secrets.version.tls12.master_secret))); - struct s2n_blob label = { 0 }; - uint8_t key_expansion_label[] = "key expansion"; - POSIX_GUARD(s2n_blob_init(&label, key_expansion_label, sizeof(key_expansion_label) - 1)); - struct s2n_key_material key_material = { 0 }; - POSIX_GUARD_RESULT(s2n_key_material_init(&key_material, conn)); - struct s2n_blob prf_out = { 0 }; - POSIX_GUARD(s2n_blob_init(&prf_out, key_material.key_block, sizeof(key_material.key_block))); - POSIX_GUARD(s2n_prf(conn, &master_secret, &label, &server_random, &client_random, NULL, &prf_out)); + POSIX_GUARD_RESULT(s2n_prf_generate_key_material(conn, &key_material)); POSIX_ENSURE(cipher_suite->available, S2N_ERR_PRF_INVALID_ALGORITHM); POSIX_GUARD(cipher->init(&conn->secure->client_key)); diff --git a/tls/s2n_prf.h b/tls/s2n_prf.h index d7316f81517..2db2db5e2e3 100644 --- a/tls/s2n_prf.h +++ b/tls/s2n_prf.h @@ -89,6 +89,7 @@ int s2n_tls_prf_master_secret(struct s2n_connection *conn, struct s2n_blob *prem int s2n_hybrid_prf_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret); S2N_RESULT s2n_tls_prf_extended_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret, struct s2n_blob *session_hash, struct s2n_blob *sha1_hash); S2N_RESULT s2n_prf_get_digest_for_ems(struct s2n_connection *conn, struct s2n_blob *message, s2n_hash_algorithm hash_alg, struct s2n_blob *output); +S2N_RESULT s2n_prf_generate_key_material(struct s2n_connection *conn, struct s2n_key_material *key_material); int s2n_prf_key_expansion(struct s2n_connection *conn); int s2n_prf_server_finished(struct s2n_connection *conn); int s2n_prf_client_finished(struct s2n_connection *conn);