diff options
author | Daiki Ueno <dueno@redhat.com> | 2020-02-19 14:35:04 +0100 |
---|---|---|
committer | Daiki Ueno <dueno@redhat.com> | 2020-02-19 18:41:08 +0100 |
commit | f48d5114d9a3d624ba82a8f38b654ea63dd34094 (patch) | |
tree | e5490233ed788b68348fd2b4a056bb76e865e724 | |
parent | 8ab75b3cf7130ad7594b68c6ecba79b6e0c082d9 (diff) | |
download | gnutls-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-- | NEWS | 5 | ||||
-rw-r--r-- | doc/Makefile.am | 5 | ||||
-rw-r--r-- | doc/manpages/Makefile.am | 2 | ||||
-rw-r--r-- | lib/buffers.c | 27 | ||||
-rw-r--r-- | lib/constate.c | 4 | ||||
-rw-r--r-- | lib/gnutls_int.h | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 47 | ||||
-rw-r--r-- | lib/libgnutls.map | 2 | ||||
-rw-r--r-- | lib/record.c | 61 | ||||
-rw-r--r-- | lib/state.c | 17 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/handshake-write.c | 164 |
12 files changed, 334 insertions, 4 deletions
@@ -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, ¶ms); + 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"); +} |