]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
ktls: basic implementation of SW mode
authorHedgehog5040 <krenzelok.frantisek@gmail.com>
Fri, 14 May 2021 13:56:06 +0000 (15:56 +0200)
committerFrantisek Krenzelok <krenzelok.frantisek@gmail.com>
Tue, 19 Oct 2021 13:17:45 +0000 (15:17 +0200)
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 <krenzelok.frantisek@gmail.com>
12 files changed:
.gitignore
configure.ac
lib/Makefile.am
lib/alert.c
lib/gnutls_int.h
lib/handshake.c
lib/record.c
lib/system/ktls.c [new file with mode: 0644]
lib/system/ktls.h [new file with mode: 0644]
m4/hooks.m4
tests/Makefile.am
tests/gnutls_ktls.c [new file with mode: 0644]

index 0ad607835ab9dfdbc8cb8916309069e2ae351092..22ddfdde66dd81a7659ae16cd38f75fe2859a797 100644 (file)
@@ -923,6 +923,7 @@ tests/x509sign-verify-gost
 tests/x509sign-verify-rsa
 tests/x509sign-verify2
 tests/x509signself
+tests/gnutls_ktls
 *.tmp
 tmp-*
 *.trs
index 10b25e216d0b1885d5789d7768fe196415dbce13..c22acef7c388b22b2a929d4bd39197110798b57d 100644 (file)
@@ -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([
index f213be19a9b89294095cc3e004994f89e184459d..1a6a7f963cc94737b8cfa5637bcc359a5ace06d3 100644 (file)
@@ -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
index c8ca99286cf18bca9458435a9c2da5b249231fb5..eda931a1c5783358f402862d76d8ec90670e321c 100644 (file)
@@ -25,6 +25,7 @@
 #include <record.h>
 #include <debug.h>
 #include "str.h"
+#include <system/ktls.h>
 
 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;
 }
 
 /**
index 62a061e67abe3e6852a0bf73ee10e9ec9d97a054..88f0c28a00c56748dba80a4734944b1e2d0267c7 100644 (file)
@@ -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;
index 565012d499198f04d9fa336ad5746354da385c3e..9d36446e54d0f9db65279ebd1eee5f5cadbd978b 100644 (file)
@@ -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;
 }
 
index 3f16ae65059aa5e5d8a05734fb4c19eb8ef429bd..ebc07d9e1cfcb92f91bf70d971bb55fcb56be045 100644 (file)
@@ -54,6 +54,7 @@
 #include <random.h>
 #include <xsize.h>
 #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 (file)
index 0000000..7ab1d32
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>
+ *
+ */
+
+#include "config.h"
+#include "system/ktls.h"
+
+#ifdef ENABLE_KTLS
+
+#include <linux/tls.h>
+#include <record.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+#include <errno.h>
+#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 (file)
index 0000000..3955052
--- /dev/null
@@ -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 */
index 4d3d22107d90882613b44c2e69a2f94b5bd696a4..f0efe52cf6485957fc1143ca5bc318d97c48df4c 100644 (file)
@@ -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([<linux/tls.h> 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],
index 156f6a6e9716d99b44ecd2aaed6588f4c1915c8a..95a447b997fcf1ee0a8fc0d204a42da88d81f680 100644 (file)
@@ -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 (file)
index 0000000..9482e22
--- /dev/null
@@ -0,0 +1,282 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+int main(void)
+{
+       exit(77);
+}
+
+#else
+
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/dtls.h>
+#include <gnutls/socket.h>
+#include <signal.h>
+#include <assert.h>
+#include <errno.h>
+
+#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 */