summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaiki Ueno <dueno@redhat.com>2020-02-19 14:35:04 +0100
committerDaiki Ueno <dueno@redhat.com>2020-02-19 18:41:08 +0100
commitf48d5114d9a3d624ba82a8f38b654ea63dd34094 (patch)
treee5490233ed788b68348fd2b4a056bb76e865e724
parent8ab75b3cf7130ad7594b68c6ecba79b6e0c082d9 (diff)
downloadgnutls-tmp-record-write-callback.tar.gz
handshake: add functions to read/write handshake messages directlytmp-record-write-callback
This adds a couple of functions, gnutls_handshake_set_read_function() and gnutls_handshake_write(), to allow QUIC implementations to directly interact with the TLS state machine. Signed-off-by: Daiki Ueno <dueno@redhat.com>
-rw-r--r--NEWS5
-rw-r--r--doc/Makefile.am5
-rw-r--r--doc/manpages/Makefile.am2
-rw-r--r--lib/buffers.c27
-rw-r--r--lib/constate.c4
-rw-r--r--lib/gnutls_int.h2
-rw-r--r--lib/includes/gnutls/gnutls.h.in47
-rw-r--r--lib/libgnutls.map2
-rw-r--r--lib/record.c61
-rw-r--r--lib/state.c17
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/handshake-write.c164
12 files changed, 334 insertions, 4 deletions
diff --git a/NEWS b/NEWS
index 3e6e7fa83e..913ba18d10 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@ See the end for copying conditions.
* Version 3.6.13 (unreleased)
+** libgnutls: Added functions to intercept and inject unprotected record
+ directly to the TLS state machine, for the use with QUIC (#849, #850).
+
** libgnutls: Added new APIs to access KDF algorithms (#813).
** API and ABI modifications:
@@ -15,6 +18,8 @@ gnutls_hkdf_expand: Added
gnutls_pbkdf2: Added
gnutls_handshake_secret_type_t: New enumeration
gnutls_handshake_set_secret_function: Added
+gnutls_handshake_set_read_function: Added
+gnutls_handshake_write: Added
* Version 3.6.12 (released 2020-02-01)
diff --git a/doc/Makefile.am b/doc/Makefile.am
index ef3c40f76c..e435621f50 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -583,6 +583,7 @@ ENUMS += enums/gnutls_privkey_type_t
ENUMS += enums/gnutls_protocol_t
ENUMS += enums/gnutls_psk_key_flags
ENUMS += enums/gnutls_pubkey_flags_t
+ENUMS += enums/gnutls_record_encryption_level_t
ENUMS += enums/gnutls_rnd_level_t
ENUMS += enums/gnutls_sec_param_t
ENUMS += enums/gnutls_server_name_type_t
@@ -1084,10 +1085,14 @@ FUNCS += functions/gnutls_handshake_set_private_extensions
FUNCS += functions/gnutls_handshake_set_private_extensions.short
FUNCS += functions/gnutls_handshake_set_random
FUNCS += functions/gnutls_handshake_set_random.short
+FUNCS += functions/gnutls_handshake_set_read_function
+FUNCS += functions/gnutls_handshake_set_read_function.short
FUNCS += functions/gnutls_handshake_set_secret_function
FUNCS += functions/gnutls_handshake_set_secret_function.short
FUNCS += functions/gnutls_handshake_set_timeout
FUNCS += functions/gnutls_handshake_set_timeout.short
+FUNCS += functions/gnutls_handshake_write
+FUNCS += functions/gnutls_handshake_write.short
FUNCS += functions/gnutls_hash
FUNCS += functions/gnutls_hash.short
FUNCS += functions/gnutls_hash_copy
diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am
index 14e591e62f..8bd134959b 100644
--- a/doc/manpages/Makefile.am
+++ b/doc/manpages/Makefile.am
@@ -343,8 +343,10 @@ APIMANS += gnutls_handshake_set_max_packet_length.3
APIMANS += gnutls_handshake_set_post_client_hello_function.3
APIMANS += gnutls_handshake_set_private_extensions.3
APIMANS += gnutls_handshake_set_random.3
+APIMANS += gnutls_handshake_set_read_function.3
APIMANS += gnutls_handshake_set_secret_function.3
APIMANS += gnutls_handshake_set_timeout.3
+APIMANS += gnutls_handshake_write.3
APIMANS += gnutls_hash.3
APIMANS += gnutls_hash_copy.3
APIMANS += gnutls_hash_deinit.3
diff --git a/lib/buffers.c b/lib/buffers.c
index 2d0e3d8afc..3a058a2b18 100644
--- a/lib/buffers.c
+++ b/lib/buffers.c
@@ -792,9 +792,26 @@ ssize_t _gnutls_handshake_io_write_flush(gnutls_session_t session)
{
epoch = cur->epoch;
- ret = _gnutls_send_int(session, cur->type,
- cur->htype,
- epoch, msg.data, msg.size, 0);
+ if (session->internals.read_func) {
+ record_parameters_st *params;
+
+ ret = _gnutls_epoch_get(session, epoch, &params);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ ret = session->internals.read_func(session,
+ cur->htype,
+ params->write.level,
+ msg.data,
+ msg.size);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = msg.size;
+ } else {
+ ret = _gnutls_send_int(session, cur->type,
+ cur->htype,
+ epoch, msg.data, msg.size, 0);
+ }
if (ret >= 0) {
total += ret;
@@ -1429,6 +1446,10 @@ _gnutls_handshake_io_recv_int(gnutls_session_t session,
return gnutls_assert_val(ret);
}
+ /* If handshake is handled manually, don't receive records from I/O */
+ if (session->internals.read_func)
+ return GNUTLS_E_AGAIN;
+
if (htype != (gnutls_handshake_description_t) -1) {
ret = handshake_remaining_time(session);
if (ret < 0)
diff --git a/lib/constate.c b/lib/constate.c
index a11577d7ba..b8cac84101 100644
--- a/lib/constate.c
+++ b/lib/constate.c
@@ -528,6 +528,10 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
buf, sizeof(buf), NULL));
}
+ client_write->level = server_write->level = stage == STAGE_HS ?
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE :
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
+
return 0;
}
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index cd2adc103d..d7ca6ed473 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -867,6 +867,7 @@ struct record_state_st {
unsigned aead_tag_size;
unsigned is_aead;
uint64_t sequence_number;
+ gnutls_record_encryption_level_t level;
};
@@ -1244,6 +1245,7 @@ typedef struct {
int16_t h_post; /* whether post-generation/receive */
gnutls_handshake_secret_func secret_func;
+ gnutls_handshake_read_func read_func;
/* holds the selected certificate and key.
* use _gnutls_selected_certs_deinit() and _gnutls_selected_certs_set()
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index 13b6c35659..bb8a36b139 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -2345,6 +2345,53 @@ typedef int (*gnutls_handshake_secret_func) (gnutls_session_t session,
void gnutls_handshake_set_secret_function(gnutls_session_t session,
gnutls_handshake_secret_func func);
+/**
+ * gnutls_record_encryption_level_t:
+ * @GNUTLS_ENCRYPTION_LEVEL_INITIAL: initial level that doesn't involve any
+ * encryption
+ * @GNUTLS_ENCRYPTION_LEVEL_EARLY: early traffic secret is installed
+ * @GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: handshake traffic secret is installed
+ * @GNUTLS_ENCRYPTION_LEVEL_APPLICATION: application traffic secret is installed
+ *
+ * Enumeration of of different levels of record encryption currently in place.
+ * This is used by gnutls_handshake_set_read_function() and
+ * gnutls_handshake_write().
+ *
+ * Since 3.6.13
+ */
+typedef enum {
+ GNUTLS_ENCRYPTION_LEVEL_INITIAL,
+ GNUTLS_ENCRYPTION_LEVEL_EARLY,
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION
+} gnutls_record_encryption_level_t;
+
+ /**
+ * gnutls_handshake_read_func:
+ * @session: the current session
+ * @htype: the type of the handshake message (#gnutls_handshake_description_t)
+ * @level: #gnutls_record_encryption_level_t
+ * @data: the (const) data that was being sent
+ * @data_size: the size of data
+ *
+ * Function prototype for handshake intercepting hooks. It is set using
+ * gnutls_handshake_set_read_function().
+ *
+ * Returns: Non zero on error.
+ * Since: 3.6.13
+ */
+typedef int (*gnutls_handshake_read_func) (gnutls_session_t session,
+ gnutls_handshake_description_t htype,
+ gnutls_record_encryption_level_t level,
+ const void *data, size_t data_size);
+
+void gnutls_handshake_set_read_function(gnutls_session_t session,
+ gnutls_handshake_read_func func);
+
+int gnutls_handshake_write(gnutls_session_t session,
+ gnutls_record_encryption_level_t level,
+ const void *data, size_t data_size);
+
/* Diffie-Hellman parameter handling.
*/
int gnutls_dh_params_init(gnutls_dh_params_t * dh_params);
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index c1aace905e..f093a8825e 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -1316,6 +1316,8 @@ GNUTLS_3_6_13
gnutls_hkdf_expand;
gnutls_pbkdf2;
gnutls_handshake_set_secret_function;
+ gnutls_handshake_set_read_function;
+ gnutls_handshake_write;
} GNUTLS_3_6_12;
GNUTLS_FIPS140_3_4 {
diff --git a/lib/record.c b/lib/record.c
index af993fe6e5..bb69d94fa7 100644
--- a/lib/record.c
+++ b/lib/record.c
@@ -2339,3 +2339,64 @@ void gnutls_record_set_timeout(gnutls_session_t session, unsigned int ms)
{
session->internals.record_timeout_ms = ms;
}
+
+/**
+ * gnutls_handshake_write:
+ * @session: is a #gnutls_session_t type.
+ * @level: the current encryption level for reading a handshake message
+ * @data: the (const) handshake data to be processed
+ * @data_size: the size of data
+ *
+ * This function processes a handshake message in the encryption level
+ * specified with @level. Prior to calling this function, a handshake
+ * read callback must be set on @session. Use
+ * gnutls_handshake_set_read_function() to do this.
+ *
+ * Since: 3.6.13
+ */
+int
+gnutls_handshake_write(gnutls_session_t session,
+ gnutls_record_encryption_level_t level,
+ const void *data, size_t data_size)
+{
+ record_parameters_st *record_params;
+ record_state_st *record_state;
+ mbuffer_st *bufel;
+ uint8_t *p;
+ int ret;
+
+ /* DTLS is not supported */
+ if (IS_DTLS(session))
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+
+ /* When using this, the outgoing handshake messages should
+ * also be handled manually */
+ if (!session->internals.read_func)
+ return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+
+ ret = _gnutls_epoch_get(session, EPOCH_READ_CURRENT, &record_params);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ record_state = &record_params->read;
+ if (record_state->level > level)
+ return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+
+ bufel = _mbuffer_alloc_align16(data_size, 0);
+ if (bufel == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ memcpy(_mbuffer_get_udata_ptr(bufel), data, data_size);
+ _mbuffer_set_udata_size(bufel, data_size);
+ p = _mbuffer_get_udata_ptr(bufel);
+ bufel->htype = p[0];
+
+ if (sequence_increment(session, &record_state->sequence_number) != 0) {
+ _mbuffer_xfree(&bufel);
+ return gnutls_assert_val(GNUTLS_E_RECORD_LIMIT_REACHED);
+ }
+
+ _gnutls_record_buffer_put(session, GNUTLS_HANDSHAKE,
+ record_state->sequence_number, bufel);
+ return 0;
+}
diff --git a/lib/state.c b/lib/state.c
index f33cd5a8bc..a26c1c7a2f 100644
--- a/lib/state.c
+++ b/lib/state.c
@@ -1419,6 +1419,23 @@ gnutls_handshake_set_hook_function(gnutls_session_t session,
}
/**
+ * gnutls_handshake_set_read_function:
+ * @session: is #gnutls_session_t type
+ * @func: is the function to be called
+ *
+ * This function will set a callback to be called when a handshake
+ * message is being sent.
+ *
+ * Since: 3.6.13
+ */
+void
+gnutls_handshake_set_read_function(gnutls_session_t session,
+ gnutls_handshake_read_func func)
+{
+ session->internals.read_func = func;
+}
+
+/**
* gnutls_record_get_state:
* @session: is a #gnutls_session_t type
* @read: if non-zero the read parameters are returned, otherwise the write
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5b9fdb7168..63690e10af 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -217,7 +217,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
tls-record-size-limit-asym dh-compute ecdh-compute sign-verify-data-newapi \
sign-verify-newapi sign-verify-deterministic iov aead-cipher-vec \
tls13-without-timeout-func buffer status-request-revoked \
- set_x509_ocsp_multi_cli kdf-api secret-hook
+ set_x509_ocsp_multi_cli kdf-api secret-hook handshake-write
if HAVE_SECCOMP_TESTS
ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
diff --git a/tests/handshake-write.c b/tests/handshake-write.c
new file mode 100644
index 0000000000..40ac4cb17a
--- /dev/null
+++ b/tests/handshake-write.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 Red Hat, Inc.
+ *
+ * Author: Daiki Ueno
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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
+ * 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/>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <gnutls/gnutls.h>
+#include <assert.h>
+#include "cert-common.h"
+
+#include "utils.h"
+#define RANDOMIZE
+#include "eagain-common.h"
+
+/* This tests gnutls_record_set_write_function() and
+ * gnutls_record_push_data() by short-circuiting the handshake.
+ */
+
+const char *side = "";
+
+static void tls_log_func(int level, const char *str)
+{
+ fprintf(stderr, "%s|<%d>| %s", side, level, str);
+}
+
+#define MAX_BUF 1024
+#define MSG "Hello TLS, and hi and how are you and more data here... and more... and even more and even more more data..."
+
+static ssize_t
+error_push(gnutls_transport_ptr_t tr, const void *data, size_t len)
+{
+ fail("push_func called unexpectedly");
+ return -1;
+}
+
+static ssize_t
+error_pull(gnutls_transport_ptr_t tr, void *data, size_t len)
+{
+ fail("pull_func called unexpectedly");
+ return -1;
+}
+
+static int
+handshake_read_func(gnutls_session_t session,
+ gnutls_handshake_description_t htype,
+ gnutls_record_encryption_level_t level,
+ const void *data, size_t data_size)
+{
+ gnutls_session_t peer = gnutls_session_get_ptr(session);
+
+ if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC)
+ return 0;
+
+ return gnutls_handshake_write(peer, level, data, data_size);
+}
+
+static void run(const char *name, const char *prio)
+{
+ /* Server stuff. */
+ gnutls_certificate_credentials_t scred;
+ gnutls_session_t server;
+ /* Client stuff. */
+ gnutls_certificate_credentials_t ccred;
+ gnutls_session_t client;
+ int sret, cret;
+ char buffer[MAX_BUF + 1];
+ int transferred = 0;
+
+ success("%s\n", name);
+
+ /* General init. */
+ global_init();
+ gnutls_global_set_log_function(tls_log_func);
+ if (debug)
+ gnutls_global_set_log_level(9);
+
+ /* Init server */
+ assert(gnutls_certificate_allocate_credentials(&scred) >= 0);
+ assert(gnutls_certificate_set_x509_key_mem(scred,
+ &server_ca3_localhost_cert,
+ &server_ca3_key,
+ GNUTLS_X509_FMT_PEM) >= 0);
+
+ assert(gnutls_init(&server, GNUTLS_SERVER) >= 0);
+ assert(gnutls_priority_set_direct(server, prio, NULL) >= 0);
+
+ gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE, scred);
+ gnutls_transport_set_push_function(server, error_push);
+ gnutls_transport_set_pull_function(server, error_pull);
+
+ /* Init client */
+ assert(gnutls_certificate_allocate_credentials(&ccred) >= 0);
+ assert(gnutls_certificate_set_x509_trust_mem
+ (ccred, &ca3_cert, GNUTLS_X509_FMT_PEM) >= 0);
+
+ gnutls_init(&client, GNUTLS_CLIENT);
+ assert(gnutls_priority_set_direct(client, prio, NULL) >= 0);
+
+ assert(gnutls_credentials_set(client, GNUTLS_CRD_CERTIFICATE, ccred) >= 0);
+
+ gnutls_transport_set_push_function(client, error_push);
+ gnutls_transport_set_pull_function(client, error_pull);
+
+ gnutls_session_set_ptr(server, client);
+ gnutls_session_set_ptr(client, server);
+
+ gnutls_handshake_set_read_function(server, handshake_read_func);
+ gnutls_handshake_set_read_function(client, handshake_read_func);
+
+ HANDSHAKE(client, server);
+ if (debug)
+ success("Handshake established\n");
+
+ gnutls_transport_set_push_function(server, server_push);
+ gnutls_transport_set_pull_function(server, server_pull);
+ gnutls_transport_set_ptr(server, server);
+
+ gnutls_transport_set_push_function(client, client_push);
+ gnutls_transport_set_pull_function(client, client_pull);
+ gnutls_transport_set_ptr(client, client);
+
+ TRANSFER(client, server, MSG, strlen(MSG), buffer, MAX_BUF);
+ TRANSFER(server, client, MSG, strlen(MSG), buffer, MAX_BUF);
+
+ gnutls_bye(client, GNUTLS_SHUT_WR);
+ gnutls_bye(server, GNUTLS_SHUT_WR);
+
+ gnutls_deinit(client);
+ gnutls_deinit(server);
+
+ gnutls_certificate_free_credentials(scred);
+ gnutls_certificate_free_credentials(ccred);
+
+ gnutls_global_deinit();
+ reset_buffers();
+}
+
+void doit(void)
+{
+ run("TLS 1.3", "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3");
+}