From 6462916d2f6810409d5da1e13c4a0720f412c166 Mon Sep 17 00:00:00 2001 From: Hedgehog5040 Date: Fri, 14 May 2021 15:56:06 +0200 Subject: ktls: basic implementation of SW mode ktls enables us to offload encryption/decryption to the kernel prerequisites: - configured with `--enable-ktls` - tls module `modprobe tls` check with 'lsmod | grep tls' - per connection: gnutls_transport_set_int{2} must be set When prerequisities are met then ktls is used by default. If GnuTLS encounters a error during KTLS initialization, it will not use ktls and fallback to userspace. Signed-off-by: Frantisek Krenzelok --- .gitignore | 1 + configure.ac | 1 + lib/Makefile.am | 2 +- lib/alert.c | 18 ++- lib/gnutls_int.h | 8 + lib/handshake.c | 16 ++ lib/record.c | 51 ++++--- lib/system/ktls.c | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/system/ktls.h | 16 ++ m4/hooks.m4 | 18 +++ tests/Makefile.am | 4 + tests/gnutls_ktls.c | 282 +++++++++++++++++++++++++++++++++++ 12 files changed, 812 insertions(+), 25 deletions(-) create mode 100644 lib/system/ktls.c create mode 100644 lib/system/ktls.h create mode 100644 tests/gnutls_ktls.c diff --git a/.gitignore b/.gitignore index 0ad607835a..22ddfdde66 100644 --- a/.gitignore +++ b/.gitignore @@ -923,6 +923,7 @@ tests/x509sign-verify-gost tests/x509sign-verify-rsa tests/x509sign-verify2 tests/x509signself +tests/gnutls_ktls *.tmp tmp-* *.trs diff --git a/configure.ac b/configure.ac index 10b25e216d..c22acef7c3 100644 --- a/configure.ac +++ b/configure.ac @@ -1220,6 +1220,7 @@ AC_MSG_NOTICE([External hardware support: Random gen. variant: $rnd_variant PKCS#11 support: $with_p11_kit TPM support: $with_tpm + KTLS support: $enable_ktls ]) if test -n "$ac_trousers_lib";then AC_MSG_NOTICE([ diff --git a/lib/Makefile.am b/lib/Makefile.am index f213be19a9..1a6a7f963c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -81,7 +81,7 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls cert-session.c handshake-checks.c dtls-sw.c dh-primes.c openpgp_compat.c \ crypto-selftests.c crypto-selftests-pk.c secrets.c extv.c extv.h \ hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c cert-cred-rawpk.c \ - iov.c iov.h + iov.c iov.h system/ktls.c system/ktls.h if ENABLE_GOST COBJECTS += vko.c diff --git a/lib/alert.c b/lib/alert.c index c8ca99286c..eda931a1c5 100644 --- a/lib/alert.c +++ b/lib/alert.c @@ -25,6 +25,7 @@ #include #include #include "str.h" +#include typedef struct { gnutls_alert_description_t alert; @@ -181,13 +182,16 @@ gnutls_alert_send(gnutls_session_t session, gnutls_alert_level_t level, return ret; } - if ((ret = - _gnutls_send_int(session, GNUTLS_ALERT, -1, - EPOCH_WRITE_CURRENT, data, 2, - MBUFFER_FLUSH)) >= 0) - return 0; - else - return ret; + if (IS_KTLS_ENABLED(session)) { + ret = + _gnutls_ktls_send_control_msg(session, GNUTLS_ALERT, data, 2); + } else { + ret = + _gnutls_send_int(session, GNUTLS_ALERT, -1, + EPOCH_WRITE_CURRENT, data, 2, + MBUFFER_FLUSH); + } + return (ret < 0) ? ret : 0; } /** diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 62a061e67a..88f0c28a00 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -175,6 +175,9 @@ typedef enum record_send_state_t { /* To check whether we have a DTLS session */ #define IS_DTLS(session) (session->internals.transport == GNUTLS_DGRAM) +/* To check whether we have a KTLS enabled */ +#define IS_KTLS_ENABLED(session) (session->internals.ktls_enabled) + /* the maximum size of encrypted packets */ #define DEFAULT_MAX_RECORD_SIZE 16384 #define DEFAULT_MAX_EARLY_DATA_SIZE 16384 @@ -1488,6 +1491,11 @@ typedef struct { * called in parallel when false start is used and false start is used. */ void *epoch_lock; + /* indicates whether or not was KTLS initialized properly. */ + bool ktls_enabled; + int recv_fd; + int send_fd; + /* If you add anything here, check _gnutls_handshake_internal_state_clear(). */ } internals_st; diff --git a/lib/handshake.c b/lib/handshake.c index 565012d499..9d36446e54 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -58,6 +58,8 @@ #include "tls13/early_data.h" #include "tls13/session_ticket.h" #include "locks.h" +#include "system/ktls.h" + static int check_if_null_comp_present(gnutls_session_t session, uint8_t * data, int datalen); @@ -2808,6 +2810,12 @@ int gnutls_handshake(gnutls_session_t session) const version_entry_st *vers = get_version(session); int ret; +#ifdef ENABLE_KTLS + int sockin, sockout; + gnutls_transport_get_int2(session, &sockin, &sockout); + _gnutls_ktls_enable(session, sockin, sockout); +#endif + if (unlikely(session->internals.initial_negotiation_completed)) { if (vers->tls13_sem) { if (session->security_parameters.entity == GNUTLS_CLIENT) { @@ -2903,6 +2911,14 @@ int gnutls_handshake(gnutls_session_t session) } } +#ifdef ENABLE_KTLS + if (IS_KTLS_ENABLED(session)) { + ret = _gnutls_ktls_set_keys(session); + if (ret < 0) + return ret; + } +#endif + return 0; } diff --git a/lib/record.c b/lib/record.c index 3f16ae6505..ebc07d9e1c 100644 --- a/lib/record.c +++ b/lib/record.c @@ -54,6 +54,7 @@ #include #include #include "locks.h" +#include "system/ktls.h" struct tls_record_st { uint16_t header_size; @@ -288,7 +289,8 @@ int gnutls_bye(gnutls_session_t session, gnutls_close_request_t how) switch (BYE_STATE) { case BYE_STATE0: - ret = _gnutls_io_write_flush(session); + if (!IS_KTLS_ENABLED(session)) + ret = _gnutls_io_write_flush(session); BYE_STATE = BYE_STATE0; if (ret < 0) { gnutls_assert(); @@ -296,9 +298,8 @@ int gnutls_bye(gnutls_session_t session, gnutls_close_request_t how) } FALLTHROUGH; case BYE_STATE1: - ret = - gnutls_alert_send(session, GNUTLS_AL_WARNING, - GNUTLS_A_CLOSE_NOTIFY); + ret = gnutls_alert_send(session, GNUTLS_AL_WARNING, + GNUTLS_A_CLOSE_NOTIFY); BYE_STATE = BYE_STATE1; if (ret < 0) { gnutls_assert(); @@ -308,14 +309,22 @@ int gnutls_bye(gnutls_session_t session, gnutls_close_request_t how) case BYE_STATE2: BYE_STATE = BYE_STATE2; if (how == GNUTLS_SHUT_RDWR) { - do { - ret = - _gnutls_recv_int(session, GNUTLS_ALERT, - NULL, 0, NULL, - session->internals. - record_timeout_ms); + if (IS_KTLS_ENABLED(session)){ + do { + ret = _gnutls_ktls_recv_int(session, + GNUTLS_ALERT, NULL, 0); + } + while (ret == GNUTLS_E_GOT_APPLICATION_DATA); + } else { + do { + ret = + _gnutls_recv_int(session, GNUTLS_ALERT, + NULL, 0, NULL, + session->internals. + record_timeout_ms); + } + while (ret == GNUTLS_E_GOT_APPLICATION_DATA); } - while (ret == GNUTLS_E_GOT_APPLICATION_DATA); if (ret >= 0) session->internals.may_not_read = 1; @@ -2026,9 +2035,13 @@ gnutls_record_send2(gnutls_session_t session, const void *data, switch(session->internals.rsend_state) { case RECORD_SEND_NORMAL: - return _gnutls_send_tlen_int(session, GNUTLS_APPLICATION_DATA, - -1, EPOCH_WRITE_CURRENT, data, - data_size, pad, MBUFFER_FLUSH); + if (IS_KTLS_ENABLED(session)) { + return _gnutls_ktls_send(session, data, data_size); + } else { + return _gnutls_send_tlen_int(session, GNUTLS_APPLICATION_DATA, + -1, EPOCH_WRITE_CURRENT, data, + data_size, pad, MBUFFER_FLUSH); + } case RECORD_SEND_CORKED: case RECORD_SEND_CORKED_TO_KU: return append_data_to_corked(session, data, data_size); @@ -2293,9 +2306,13 @@ gnutls_record_recv(gnutls_session_t session, void *data, size_t data_size) return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE); } - return _gnutls_recv_int(session, GNUTLS_APPLICATION_DATA, - data, data_size, NULL, - session->internals.record_timeout_ms); + if (IS_KTLS_ENABLED(session)) { + return _gnutls_ktls_recv(session, data, data_size); + } else { + return _gnutls_recv_int(session, GNUTLS_APPLICATION_DATA, + data, data_size, NULL, + session->internals.record_timeout_ms); + } } /** diff --git a/lib/system/ktls.c b/lib/system/ktls.c new file mode 100644 index 0000000000..7ab1d3215d --- /dev/null +++ b/lib/system/ktls.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2021 Free Software Foundation, Inc. + * + * Author: Fratnišek Krenželok + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +#include "config.h" +#include "system/ktls.h" + +#ifdef ENABLE_KTLS + +#include +#include +#include +#include +#include +#include +#include "ext/session_ticket.h" + +/** + * gnutls_transport_set_ktls: + * @session: is a #gnutls_session_t type. + * @sockin: is a socket descriptor. + * @sockout: is a socket descriptor. + * + * Enables Kernel TLS for the @session + * Requieres `tls` kernel module and + * gnutls configuration with `--enable-ktls` + * + * Returns: 0 on success error otherwise + * + * Since: 3.7.2 + **/ +int _gnutls_ktls_enable(gnutls_session_t session, int sockin, int sockout) +{ + if (setsockopt(sockin, SOL_TCP, TCP_ULP, "tls", sizeof ("tls")) < 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + session->internals.recv_fd = sockin; + session->internals.send_fd = sockin; + + if (sockin != sockout){ + if (setsockopt(sockout, SOL_TCP, TCP_ULP, "tls", sizeof ("tls")) < 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + session->internals.send_fd = sockout; + } + + session->internals.ktls_enabled = 1; + return 0; +} + +int _gnutls_ktls_set_keys(gnutls_session_t session) +{ + gnutls_cipher_algorithm_t cipher = gnutls_cipher_get(session); + gnutls_datum_t mac_key; + gnutls_datum_t iv; + gnutls_datum_t cipher_key; + unsigned char seq_number[8]; + int ret; + + session->internals.ktls_enabled = 0; + + /* check whether or not cipher suite supports ktls + */ + int version = gnutls_protocol_get_version(session); + if ((version != GNUTLS_TLS1_3 && version != GNUTLS_TLS1_2) || + (gnutls_cipher_get(session) != GNUTLS_CIPHER_AES_128_GCM && + gnutls_cipher_get(session) != GNUTLS_CIPHER_AES_256_GCM)) { + return GNUTLS_E_UNIMPLEMENTED_FEATURE; + } + + version = (version == GNUTLS_TLS1_2) ? TLS_1_2_VERSION : TLS_1_3_VERSION; + + ret = gnutls_record_get_state(session, 1, &mac_key, &iv, &cipher_key, + seq_number); + if (ret < 0) { + return ret; + } + + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + { + struct tls12_crypto_info_aes_gcm_128 crypto_info; + + crypto_info.info.version = version; + crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_128; + + assert(cipher_key.size == TLS_CIPHER_AES_GCM_128_KEY_SIZE); + + /* for TLS 1.2 IV is generated in kernel */ + if (version == TLS_1_2_VERSION) { + assert(iv.size == TLS_CIPHER_AES_GCM_128_SALT_SIZE); + } else { + assert(iv.size == TLS_CIPHER_AES_GCM_128_SALT_SIZE + + TLS_CIPHER_AES_GCM_128_IV_SIZE); + + memcpy(crypto_info.iv, iv.data + + TLS_CIPHER_AES_GCM_128_SALT_SIZE, + TLS_CIPHER_AES_GCM_128_IV_SIZE); + } + + memcpy(crypto_info.salt, iv.data, + TLS_CIPHER_AES_GCM_128_SALT_SIZE); + memcpy(crypto_info.rec_seq, seq_number, + TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); + memcpy(crypto_info.key, cipher_key.data, + TLS_CIPHER_AES_GCM_128_KEY_SIZE); + + if (setsockopt(session->internals.recv_fd, SOL_TLS, TLS_RX, + &crypto_info, sizeof (crypto_info))) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + } + break; + case GNUTLS_CIPHER_AES_256_GCM: + { + struct tls12_crypto_info_aes_gcm_256 crypto_info; + + crypto_info.info.version = version; + crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_256; + + assert(cipher_key.size == TLS_CIPHER_AES_GCM_256_KEY_SIZE); + + /* for TLS 1.2 IV is generated in kernel */ + if (version == TLS_1_2_VERSION) { + assert(iv.size == TLS_CIPHER_AES_GCM_256_SALT_SIZE); + } else { + assert(iv.size == TLS_CIPHER_AES_GCM_256_SALT_SIZE + + TLS_CIPHER_AES_GCM_256_IV_SIZE); + + memcpy(crypto_info.iv, iv.data + TLS_CIPHER_AES_GCM_256_SALT_SIZE, + TLS_CIPHER_AES_GCM_256_IV_SIZE); + } + + memcpy(crypto_info.salt, iv.data, + TLS_CIPHER_AES_GCM_256_SALT_SIZE); + memcpy(crypto_info.rec_seq, seq_number, + TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE); + memcpy(crypto_info.key, cipher_key.data, + TLS_CIPHER_AES_GCM_256_KEY_SIZE); + + if (setsockopt(session->internals.recv_fd, SOL_TLS, TLS_RX, + &crypto_info, sizeof(crypto_info))) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + } + break; + default: + assert(0); + } + + ret = gnutls_record_get_state(session, 0, &mac_key, &iv, &cipher_key, + seq_number); + if (ret < 0) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + { + struct tls12_crypto_info_aes_gcm_128 crypto_info; + + crypto_info.info.version = version; + crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_128; + + assert(cipher_key.size == TLS_CIPHER_AES_GCM_128_KEY_SIZE); + + /* for TLS 1.2 IV is generated in kernel */ + if (version == TLS_1_2_VERSION) { + assert(iv.size == TLS_CIPHER_AES_GCM_128_SALT_SIZE); + } else { + assert(iv.size == TLS_CIPHER_AES_GCM_128_SALT_SIZE + + TLS_CIPHER_AES_GCM_128_IV_SIZE); + + memcpy(crypto_info.iv, iv.data + TLS_CIPHER_AES_GCM_128_SALT_SIZE, + TLS_CIPHER_AES_GCM_128_IV_SIZE); + } + + memcpy(crypto_info.salt, iv.data, + TLS_CIPHER_AES_GCM_128_SALT_SIZE); + memcpy(crypto_info.rec_seq, seq_number, + TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); + memcpy(crypto_info.key, cipher_key.data, + TLS_CIPHER_AES_GCM_128_KEY_SIZE); + + if (setsockopt(session->internals.send_fd, SOL_TLS, TLS_TX, + &crypto_info, sizeof(crypto_info))) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + } + break; + case GNUTLS_CIPHER_AES_256_GCM: + { + struct tls12_crypto_info_aes_gcm_256 crypto_info; + + crypto_info.info.version = version; + crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_256; + assert(cipher_key.size == TLS_CIPHER_AES_GCM_256_KEY_SIZE); + + /* for TLS 1.2 IV is generated in kernel */ + if (version == TLS_1_2_VERSION) { + assert(iv.size == TLS_CIPHER_AES_GCM_256_SALT_SIZE); + } else { + assert(iv.size == TLS_CIPHER_AES_GCM_256_SALT_SIZE + + TLS_CIPHER_AES_GCM_256_IV_SIZE); + + memcpy(crypto_info.iv, iv.data + TLS_CIPHER_AES_GCM_256_SALT_SIZE, + TLS_CIPHER_AES_GCM_256_IV_SIZE); + } + + memcpy(crypto_info.salt, iv.data, + TLS_CIPHER_AES_GCM_256_SALT_SIZE); + memcpy(crypto_info.rec_seq, seq_number, + TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE); + memcpy(crypto_info.key, cipher_key.data, + TLS_CIPHER_AES_GCM_256_KEY_SIZE); + + if (setsockopt(session->internals.send_fd, SOL_TLS, TLS_TX, + &crypto_info, sizeof(crypto_info))) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + } + break; + default: + assert(0); + + } + + session->internals.ktls_enabled = 1; + return 0; +} + +int _gnutls_ktls_send_control_msg(gnutls_session_t session, + unsigned char record_type, const void *data, size_t data_size) +{ + const char *buf = data; + ssize_t ret; + + assert(session != NULL); + + while (data_size > 0) { + char cmsg[CMSG_SPACE(sizeof (unsigned char))]; + struct msghdr msg = { 0 }; + struct iovec msg_iov; /* Vector of data to send/receive into. */ + struct cmsghdr *hdr; + + msg.msg_control = cmsg; + msg.msg_controllen = sizeof cmsg; + + hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_TLS; + hdr->cmsg_type = TLS_SET_RECORD_TYPE; + hdr->cmsg_len = CMSG_LEN(sizeof (unsigned char)); + + // construct record header + *CMSG_DATA(hdr) = record_type; + msg.msg_controllen = hdr->cmsg_len; + + msg_iov.iov_base = (void *)buf; + msg_iov.iov_len = data_size; + + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + ret = sendmsg(session->internals.send_fd, &msg, MSG_DONTWAIT); + + if (ret == -1) { + switch (errno) { + case EINTR: + return GNUTLS_E_INTERRUPTED; + case EAGAIN: + return GNUTLS_E_AGAIN; + default: + return GNUTLS_E_PUSH_ERROR; + } + } + + buf += ret; + data_size -= ret; + } + + return 0; +} + +int _gnutls_ktls_recv_control_msg(gnutls_session_t session, + unsigned char *record_type, void *data, size_t data_size) +{ + char *buf = data; + ssize_t ret; + + char cmsg[CMSG_SPACE(sizeof (unsigned char))]; + struct msghdr msg = { 0 }; + struct iovec msg_iov; + struct cmsghdr *hdr; + + assert(session != NULL); + + if (session->internals.read_eof != 0) { + return 0; + } else if (session->internals.invalid_connection != 0 || + session->internals.may_not_read != 0) + return GNUTLS_E_INVALID_SESSION; + + /* receive message */ + msg.msg_control = cmsg; + msg.msg_controllen = sizeof cmsg; + + msg_iov.iov_base = buf; + msg_iov.iov_len = data_size; + + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + ret = recvmsg(session->internals.recv_fd, &msg, MSG_DONTWAIT); + + if (ret == -1){ + switch(errno){ + case EAGAIN: + return GNUTLS_E_AGAIN; + case EINVAL: + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + case EMSGSIZE: + return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + case EBADMSG: + return GNUTLS_E_DECRYPTION_FAILED; + default: + return GNUTLS_E_PULL_ERROR; + } + } + + /* connection closed */ + if (ret == 0) + return 0; + + /* get record type from header */ + hdr = CMSG_FIRSTHDR(&msg); + if (hdr == NULL){ + return GNUTLS_E_PULL_ERROR; + } + if (hdr->cmsg_level == SOL_TLS && hdr->cmsg_type == TLS_GET_RECORD_TYPE) + *record_type = *(unsigned char *)CMSG_DATA(hdr); + else + *record_type = GNUTLS_APPLICATION_DATA; + + return ret; +} + +int _gnutls_ktls_recv_int(gnutls_session_t session, content_type_t type, + void *data, size_t data_size) +{ + unsigned char record_type; + int ret; + + ret = _gnutls_ktls_recv_control_msg(session, + &record_type, data, data_size); + + if (ret > 0) { + switch (record_type){ + case GNUTLS_CHANGE_CIPHER_SPEC: + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); + break; + case GNUTLS_ALERT: + session_invalidate(session); + ret = 0; + break; + case GNUTLS_HANDSHAKE: + // ignore post-handshake messages + if (type != record_type) + return GNUTLS_E_AGAIN; + break; + case GNUTLS_APPLICATION_DATA: + if (type != record_type) + ret = GNUTLS_E_GOT_APPLICATION_DATA; + break; + case GNUTLS_HEARTBEAT: + break; + default: + gnutls_assert(); + return GNUTLS_E_UNEXPECTED_PACKET; + } + } + return ret; +} + +#else //ENABLE_KTLS + +int _gnutls_ktls_enable(gnutls_session_t session, int sockin, int sockout){ + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); +} + +int _gnutls_ktls_set_keys(gnutls_session_t session) { + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); +} + +int _gnutls_ktls_send_control_msg(gnutls_session_t session, + unsigned char record_type, const void *data, size_t data_size) { + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); +} + +int _gnutls_ktls_recv_int(gnutls_session_t session, content_type_t type, void *data, size_t data_size) { + return gnutls_assert_val(GNUTLS_E_UNIMPLEMENTED_FEATURE); +} + +#endif //ENABLE_KTLS diff --git a/lib/system/ktls.h b/lib/system/ktls.h new file mode 100644 index 0000000000..3955052f58 --- /dev/null +++ b/lib/system/ktls.h @@ -0,0 +1,16 @@ +#ifndef GNUTLS_LIB_ACCELERATED_KTLS_H +#define GNUTLS_LIB_ACCELERATED_KTLS_H + +#include "gnutls_int.h" + +int _gnutls_ktls_enable(gnutls_session_t session, int sockin, int sockout); +int _gnutls_ktls_set_keys(gnutls_session_t session); +int _gnutls_ktls_send_control_msg(gnutls_session_t session, unsigned char record_type, + const void *data, size_t data_size); +#define _gnutls_ktls_send(x, y, z) _gnutls_ktls_send_control_msg(x, GNUTLS_APPLICATION_DATA, y, z); +int _gnutls_ktls_recv_control_msg(gnutls_session_t session, unsigned char *record_type, + void *data, size_t data_size); +int _gnutls_ktls_recv_int(gnutls_session_t session, content_type_t type, void *data, size_t data_size); +#define _gnutls_ktls_recv(x, y, z) _gnutls_ktls_recv_int(x, GNUTLS_APPLICATION_DATA, y, z) + +#endif /* GNUTLS_LIB_ACCELERATED_KTLS_H */ diff --git a/m4/hooks.m4 b/m4/hooks.m4 index 4d3d22107d..f0efe52cf6 100644 --- a/m4/hooks.m4 +++ b/m4/hooks.m4 @@ -359,6 +359,24 @@ LIBTASN1_MINIMUM=4.9 fi AM_CONDITIONAL(ENABLE_AFALG, test "$enable_afalg" != "no") + # For KTLS + AC_MSG_CHECKING([whether to add KTLS support]) + AC_ARG_ENABLE(ktls, + AS_HELP_STRING([--enable-ktls], [enable KTLS support]), + enable_ktls=$enableval,enable_ktls=no) + AC_MSG_RESULT($enable_ktls) + + if test "$enable_ktls" = "yes"; then + AC_CHECK_HEADERS([linux/tls.h], [ + AC_DEFINE([HAVE_KTLS],[1],[KTLS headers found at compile time]) + ], [ + AC_MSG_ERROR([ not found]) + ]) + AC_DEFINE([ENABLE_KTLS], 1, [Enable KTLS support]) + fi + AM_CONDITIONAL(ENABLE_KTLS, test "$enable_ktls" != "no") + + # For OCSP AC_MSG_CHECKING([whether to disable OCSP support]) AC_ARG_ENABLE(ocsp, AS_HELP_STRING([--disable-ocsp], diff --git a/tests/Makefile.am b/tests/Makefile.am index 156f6a6e97..95a447b997 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -437,6 +437,10 @@ ctests += x509self x509dn anonself pskself pskself2 dhepskself \ resume-with-record-size-limit endif +if ENABLE_KTLS +ctests += gnutls_ktls +endif + gc_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/gl \ -I$(top_builddir)/gl diff --git a/tests/gnutls_ktls.c b/tests/gnutls_ktls.c new file mode 100644 index 0000000000..9482e22b31 --- /dev/null +++ b/tests/gnutls_ktls.c @@ -0,0 +1,282 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#if defined(_WIN32) + +int main(void) +{ + exit(77); +} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cert-common.h" +#include "utils.h" + +static void server_log_func(int level, const char *str) +{ + fprintf(stderr, "server|<%d>| %s", level, str); +} + +static void client_log_func(int level, const char *str) +{ + fprintf(stderr, "client|<%d>| %s", level, str); +} + +#define MAX_BUF 1024 +#define MSG "Hello world!" + + +static void client(int fd, const char *prio) +{ + int ret; + char buffer[MAX_BUF + 1]; + gnutls_certificate_credentials_t x509_cred; + gnutls_session_t session; + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(7); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + + gnutls_init(&session, GNUTLS_CLIENT); + gnutls_handshake_set_timeout(session, get_timeout()); + assert(gnutls_priority_set_direct(session, prio, NULL) >= 0); + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + gnutls_transport_set_int(session, fd); + if (ret < 0) + fail("client: error in enabling KTLS: %s\n", gnutls_strerror(ret)); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) { + fail("client: Handshake failed\n"); + close(fd); + gnutls_deinit(session); + exit(1); + } + if (debug) + success("client: Handshake was completed\n"); + + /* server send message via gnutls_record_send */ + int i = 0; + do{ + memset(buffer, 0, MAX_BUF + 1); + do{ + ret = gnutls_record_recv(session, buffer, sizeof(buffer)); + } + while(ret == GNUTLS_E_AGAIN); + + if(strncmp(buffer, MSG+i*MAX_BUF, MAX_BUF)) + fail("client: Message doesn't match\n"); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (debug) + success ("client: messages received\n"); + + if (ret == 0) { + success + ("client: Peer has closed the TLS connection\n"); + goto end; + } else if (ret < 0) { + fail("client: Error: %s\n", gnutls_strerror(ret)); + exit(1); + } + + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + if (ret < 0) { + fail("client: error in closing session: %s\n", gnutls_strerror(ret)); + } + end: + + close(fd); + + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); +} + +pid_t child; +static void terminate(void) +{ + kill(child, SIGTERM); + exit(1); +} + +static void server(int fd, const char *prio) +{ + int ret; + gnutls_certificate_credentials_t x509_cred; + gnutls_session_t session; + + global_init(); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(7); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + ret = gnutls_certificate_set_x509_key_mem(x509_cred, &server_cert, + &server_key, + GNUTLS_X509_FMT_PEM); + if (ret < 0) + exit(1); + + gnutls_init(&session, GNUTLS_SERVER); + gnutls_handshake_set_timeout(session, get_timeout()); + + assert(gnutls_priority_set_direct(session, prio, NULL)>=0); + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) { + close(fd); + gnutls_deinit(session); + fail("server: Handshake has failed (%s)\n\n", + gnutls_strerror(ret)); + terminate(); + } + if (debug) + success("server: Handshake was completed\n"); + + do { + ret = gnutls_record_send(session, MSG, strlen(MSG)); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) { + close(fd); + gnutls_deinit(session); + gnutls_certificate_free_credentials(x509_cred); + gnutls_global_deinit(); + fail("server: data sending has failed (%s)\n\n", + gnutls_strerror(ret)); + terminate(); + } + + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + if (ret < 0) { + fail("server: error in closing session: %s\n", gnutls_strerror(ret)); + + close(fd); + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); + + if (debug) + success("server: finished\n"); + } +} + +static void run(const char *prio) +{ + int ret; + struct sockaddr_in saddr; + socklen_t addrlen; + int listener; + int fd; + + success("running ktls test with %s\n", prio); + + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == -1){ + fail("error in listener(): %s\n", strerror(errno)); + } + + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + saddr.sin_port = 0; + + ret = bind(listener, (struct sockaddr*)&saddr, sizeof(saddr)); + if (ret == -1){ + fail("error in bind(): %s\n", strerror(errno)); + } + + addrlen = sizeof(saddr); + ret = getsockname(listener, (struct sockaddr*)&saddr, &addrlen); + if (ret == -1){ + fail("error in getsockname(): %s\n", strerror(errno)); + } + + child = fork(); + if (child < 0) { + fail("error in fork(): %s\n", strerror(errno)); + exit(1); + } + + if (child) { + /* parent */ + ret = listen(listener, 1); + if (ret == -1) { + fail("error in listen(): %s\n", strerror(errno)); + } + + fd = accept(listener, NULL, NULL); + if (fd == -1) { + fail("error in accept(): %s\n", strerror(errno)); + } + server(fd, prio); + kill(child, SIGTERM); + } else { + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1){ + fail("error in socket(): %s\n", strerror(errno)); + exit(1); + } + usleep(1000000); + connect(fd, (struct sockaddr*)&saddr, addrlen); + client(fd, prio); + exit(0); + } +} + +void doit(void) +{ + run("NORMAL:-VERS-ALL:+VERS-TLS1.2:+AES-128-GCM"); + run("NORMAL:-VERS-ALL:+VERS-TLS1.2:+AES-256-GCM"); + run("NORMAL:-VERS-ALL:+VERS-TLS1.3:+AES-128-GCM"); + run("NORMAL:-VERS-ALL:+VERS-TLS1.3:+AES-256-GCM"); +} + +#endif /* _WIN32 */ -- cgit v1.2.1