summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@redhat.com>2017-10-19 14:52:03 +0200
committerNikos Mavrogiannopoulos <nmav@gnutls.org>2017-12-03 20:15:47 +0100
commit5356ae3debe35d13584491afab20e11aa3b86d35 (patch)
tree5490a24aa24c9e1629cce54bf7c5603e70b137a4
parent508c662a9f85f0237d42aaad7ba042458271f3d2 (diff)
downloadgnutls-5356ae3debe35d13584491afab20e11aa3b86d35.tar.gz
handshake: added TLS1.3 passive key update
Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
-rw-r--r--lib/Makefile.am1
-rw-r--r--lib/constate.c142
-rw-r--r--lib/debug.c2
-rw-r--r--lib/gnutls_int.h10
-rw-r--r--lib/handshake-tls13.c48
-rw-r--r--lib/handshake.h2
-rw-r--r--lib/includes/gnutls/gnutls.h.in2
-rw-r--r--lib/record.c101
-rw-r--r--lib/tls13/key_update.c137
-rw-r--r--lib/tls13/key_update.h24
10 files changed, 412 insertions, 57 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index a0ac3b4c11..bac7ead29e 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -93,6 +93,7 @@ COBJECTS += tls13/encrypted_extensions.c tls13/encrypted_extensions.h \
tls13/certificate_verify.c tls13/certificate_verify.h \
tls13-sig.c tls13-sig.h \
tls13/finished.c tls13/finished.h \
+ tls13/key_update.c tls13/key_update.h \
tls13/hello_retry.c tls13/hello_retry.h \
tls13/session_ticket.c tls13/session_ticket.h \
tls13/certificate.c tls13/certificate.h
diff --git a/lib/constate.c b/lib/constate.c
index fee9361d36..6753f3c55f 100644
--- a/lib/constate.c
+++ b/lib/constate.c
@@ -44,7 +44,7 @@ static const char keyexp[] = "key expansion";
static const int keyexp_length = sizeof(keyexp) - 1;
static int
-_tls13_init_record_state(record_parameters_st * params);
+_tls13_init_record_state(gnutls_cipher_algorithm_t algo, record_state_st *state);
/* This function is to be called after handshake, when master_secret,
* client_random and server_random have been initialized.
@@ -200,7 +200,106 @@ _gnutls_set_keys(gnutls_session_t session, record_parameters_st * params,
}
static int
-_tls13_set_keys(gnutls_session_t session, hs_stage_t stage, record_parameters_st * params,
+_tls13_update_keys(gnutls_session_t session, hs_stage_t stage,
+ uint16_t epoch, record_parameters_st *params,
+ unsigned iv_size, unsigned key_size)
+{
+ uint8_t key_block[MAX_CIPHER_KEY_SIZE];
+ uint8_t iv_block[MAX_CIPHER_IV_SIZE];
+ char buf[65];
+ record_state_st *state;
+ uint16_t *session_epoch;
+ int ret;
+
+ if ((session->security_parameters.entity == GNUTLS_CLIENT && stage == STAGE_UPD_OURS) ||
+ (session->security_parameters.entity == GNUTLS_SERVER && stage == STAGE_UPD_PEERS)) {
+ /* client keys */
+ ret = _tls13_derive_secret(session, APPLICATION_TRAFFIC_UPDATE,
+ sizeof(APPLICATION_TRAFFIC_UPDATE)-1,
+ NULL, 0,
+ session->key.temp_secret,
+ session->key.hs_ckey);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_expand_secret(session, "key", 3, NULL, 0, session->key.hs_ckey, key_size, key_block);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.hs_ckey, iv_size, iv_block);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ if (stage == STAGE_UPD_OURS) {
+ state = &params->write;
+ session_epoch = &session->security_parameters.epoch_write;
+ } else {
+ state = &params->read;
+ session_epoch = &session->security_parameters.epoch_read;
+ }
+
+ } else {
+ ret = _tls13_derive_secret(session, APPLICATION_TRAFFIC_UPDATE,
+ sizeof(APPLICATION_TRAFFIC_UPDATE)-1,
+ NULL, 0,
+ session->key.temp_secret,
+ session->key.hs_skey);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_expand_secret(session, "key", 3, NULL, 0, session->key.hs_skey, key_size, key_block);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.hs_skey, iv_size, iv_block);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ if (stage == STAGE_UPD_OURS) {
+ state = &params->write;
+ session_epoch = &session->security_parameters.epoch_write;
+ } else {
+ state = &params->read;
+ session_epoch = &session->security_parameters.epoch_read;
+ }
+ }
+
+ state->mac_secret.data = NULL;
+ state->mac_secret.size = 0;
+
+ ret = _gnutls_set_datum(&state->key, key_block, key_size);
+ if (ret < 0)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ _gnutls_hard_log("INT: NEW KEY [%d]: %s\n",
+ key_size,
+ _gnutls_bin2hex(key_block, key_size,
+ buf, sizeof(buf), NULL));
+
+ if (iv_size > 0) {
+ ret = _gnutls_set_datum(&state->IV, iv_block, iv_size);
+ if (ret < 0)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ _gnutls_hard_log("INT: NEW WRITE IV [%d]: %s\n",
+ iv_size,
+ _gnutls_bin2hex(iv_block, iv_size,
+ buf, sizeof(buf), NULL));
+ }
+
+ ret = _tls13_init_record_state(params->cipher->id, state);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ *session_epoch = epoch;
+
+ return 0;
+}
+
+static int
+_tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
+ uint16_t epoch,
+ record_parameters_st * params,
unsigned iv_size, unsigned key_size)
{
uint8_t ckey_block[MAX_CIPHER_KEY_SIZE];
@@ -214,6 +313,10 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, record_parameters_st
const char *keylog_label;
int ret;
+ if (stage == STAGE_UPD_OURS || stage == STAGE_UPD_PEERS)
+ return _tls13_update_keys(session, stage, epoch,
+ params, iv_size, key_size);
+
if (stage == STAGE_HS) {
label = HANDSHAKE_CLIENT_TRAFFIC_LABEL;
label_size = sizeof(HANDSHAKE_CLIENT_TRAFFIC_LABEL)-1;
@@ -331,6 +434,17 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, record_parameters_st
buf, sizeof(buf), NULL));
}
+ ret = _tls13_init_record_state(params->cipher->id, &params->read);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_init_record_state(params->cipher->id, &params->write);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->security_parameters.epoch_read = epoch;
+ session->security_parameters.epoch_write = epoch;
+
return 0;
}
@@ -478,13 +592,10 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch, hs_stage_t
if (ver->tls13_sem) {
ret = _tls13_set_keys
- (session, stage, params, IV_size, key_size);
+ (session, stage, epoch, params, IV_size, key_size);
if (ret < 0)
return gnutls_assert_val(ret);
- ret = _tls13_init_record_state(params);
- if (ret < 0)
- return gnutls_assert_val(ret);
} else {
ret = _gnutls_set_keys
(session, params, hash_size, IV_size, key_size);
@@ -849,30 +960,21 @@ int _tls13_connection_state_init(gnutls_session_t session, hs_stage_t stage)
session,
session->security_parameters.cs->name);
- session->security_parameters.epoch_read = epoch_next;
- session->security_parameters.epoch_write = epoch_next;
-
return 0;
}
static int
-_tls13_init_record_state(record_parameters_st * params)
+_tls13_init_record_state(gnutls_cipher_algorithm_t algo, record_state_st *state)
{
int ret;
- ret = _gnutls_aead_cipher_init(&params->read.ctx.aead,
- params->cipher->id, &params->read.key);
- if (ret < 0)
- return gnutls_assert_val(ret);
-
- ret = _gnutls_aead_cipher_init(&params->write.ctx.aead,
- params->cipher->id, &params->write.key);
+ ret = _gnutls_aead_cipher_init(&state->ctx.aead,
+ algo, &state->key);
if (ret < 0)
return gnutls_assert_val(ret);
- params->read.aead_tag_size = params->write.aead_tag_size = gnutls_cipher_get_tag_size(params->cipher->id);
- params->read.is_aead = 1;
- params->write.is_aead = 1;
+ state->aead_tag_size = gnutls_cipher_get_tag_size(algo);
+ state->is_aead = 1;
return 0;
}
diff --git a/lib/debug.c b/lib/debug.c
index 6a6aa1c94c..19e316d9db 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -116,6 +116,8 @@ const char
return "CLIENT KEY EXCHANGE";
case GNUTLS_HANDSHAKE_FINISHED:
return "FINISHED";
+ case GNUTLS_HANDSHAKE_KEY_UPDATE:
+ return "KEY_UPDATE";
case GNUTLS_HANDSHAKE_SUPPLEMENTAL:
return "SUPPLEMENTAL";
case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS:
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index bbd777c6b5..4dccef2beb 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -162,7 +162,9 @@ typedef enum transport_t {
/* The TLS 1.3 stage of handshake */
typedef enum hs_stage_t {
STAGE_HS,
- STAGE_APP
+ STAGE_APP,
+ STAGE_UPD_OURS,
+ STAGE_UPD_PEERS
} hs_stage_t;
typedef enum record_flush_t {
@@ -1117,6 +1119,12 @@ typedef struct {
#define HSK_CRT_REQ_SENT (1<<5)
#define HSK_CRT_REQ_GOT_SIG_ALGO (1<<6)
unsigned hsk_flags; /* TLS1.3 only */
+#define KEY_UPDATE_INACTIVE 0
+#define KEY_UPDATE_SCHEDULED 1
+#define KEY_UPDATE_SENT 2
+#define KEY_UPDATE_COMPLETED 3
+ unsigned key_update_state; /* TLS1.3 only */
+ time_t last_key_update;
unsigned crt_requested; /* 1 if client auth was requested (i.e., client cert).
* In case of a server this holds 1 if we should wait
diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c
index 9a36bacc40..dee7d65f40 100644
--- a/lib/handshake-tls13.c
+++ b/lib/handshake-tls13.c
@@ -53,6 +53,7 @@
#include "tls13/certificate_verify.h"
#include "tls13/certificate.h"
#include "tls13/finished.h"
+#include "tls13/key_update.h"
#include "tls13/session_ticket.h"
static int generate_hs_traffic_keys(gnutls_session_t session);
@@ -326,29 +327,36 @@ _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf)
return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
}
- if (session->security_parameters.entity == GNUTLS_CLIENT) {
- ret = _gnutls_buffer_pop_prefix8(buf, &type, 0);
- if (ret < 0)
- return gnutls_assert_val(ret);
+ /* The following messages are expected asynchronously after
+ * the handshake process is complete */
+ if (unlikely(session->internals.handshake_in_progress))
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
- ret = _gnutls_buffer_pop_prefix24(buf, &length, 1);
- if (ret < 0)
- return gnutls_assert_val(ret);
+ ret = _gnutls_buffer_pop_prefix8(buf, &type, 0);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
- switch(type) {
- case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET:
- ret = _gnutls13_recv_session_ticket(session, buf);
- if (ret < 0)
- return gnutls_assert_val(ret);
- break;
- default:
- gnutls_assert();
- return GNUTLS_E_UNEXPECTED_PACKET;
- }
+ ret = _gnutls_buffer_pop_prefix24(buf, &length, 1);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
- } else {
- gnutls_assert();
- return GNUTLS_E_UNEXPECTED_PACKET;
+ switch(type) {
+ case GNUTLS_HANDSHAKE_KEY_UPDATE:
+ ret = _gnutls13_recv_key_update(session, buf);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ break;
+ case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET:
+ if (session->security_parameters.entity != GNUTLS_CLIENT)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
+
+ ret = _gnutls13_recv_session_ticket(session, buf);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ break;
+ default:
+ gnutls_assert();
+ return GNUTLS_E_UNEXPECTED_PACKET;
}
return 0;
diff --git a/lib/handshake.h b/lib/handshake.h
index 6c84631839..109f1247c8 100644
--- a/lib/handshake.h
+++ b/lib/handshake.h
@@ -119,7 +119,9 @@ int _gnutls_check_if_cert_hash_is_same(gnutls_session_t session, gnutls_certific
#define DERIVED_LABEL "derived"
#define APPLICATION_CLIENT_TRAFFIC_LABEL "c ap traffic"
#define APPLICATION_SERVER_TRAFFIC_LABEL "s ap traffic"
+#define APPLICATION_TRAFFIC_UPDATE "traffic upd"
#define EXPORTER_MASTER_LABEL "exp master"
+#define EXPORTER_LABEL "exp master"
#define RES_LABEL "res master"
int _gnutls_run_verify_callback(gnutls_session_t session, unsigned int side);
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index ef37573265..5299431d42 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -513,6 +513,7 @@ typedef enum {
* @GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE: Client key exchange.
* @GNUTLS_HANDSHAKE_FINISHED: Finished.
* @GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: Certificate status (OCSP).
+ * @GNUTLS_HANDSHAKE_KEY_UPDATE: TLS1.3 key update message.
* @GNUTLS_HANDSHAKE_SUPPLEMENTAL: Supplemental.
* @GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC: Change Cipher Spec.
* @GNUTLS_HANDSHAKE_CLIENT_HELLO_V2: SSLv2 Client Hello.
@@ -537,6 +538,7 @@ typedef enum {
GNUTLS_HANDSHAKE_FINISHED = 20,
GNUTLS_HANDSHAKE_CERTIFICATE_STATUS = 22,
GNUTLS_HANDSHAKE_SUPPLEMENTAL = 23,
+ GNUTLS_HANDSHAKE_KEY_UPDATE = 24,
GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC = 254,
GNUTLS_HANDSHAKE_CLIENT_HELLO_V2 = 1024
} gnutls_handshake_description_t;
diff --git a/lib/record.c b/lib/record.c
index 83c8427c41..66d56eb27a 100644
--- a/lib/record.c
+++ b/lib/record.c
@@ -46,6 +46,7 @@
#include "datum.h"
#include "constate.h"
#include "ext/max_record.h"
+#include "tls13/key_update.h"
#include <ext/heartbeat.h>
#include <state.h>
#include <dtls.h>
@@ -1635,6 +1636,80 @@ gnutls_record_recv_packet(gnutls_session_t session,
return get_packet_from_buffers(session, GNUTLS_APPLICATION_DATA, packet);
}
+static
+ssize_t append_data_to_corked(gnutls_session_t session, const void *data, size_t data_size)
+{
+ int ret;
+
+ if (IS_DTLS(session)) {
+ if (data_size + session->internals.record_presend_buffer.length >
+ gnutls_dtls_get_data_mtu(session)) {
+ return gnutls_assert_val(GNUTLS_E_LARGE_PACKET);
+ }
+ }
+
+ ret =
+ _gnutls_buffer_append_data(&session->internals.
+ record_presend_buffer, data,
+ data_size);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return data_size;
+}
+
+static
+ssize_t handle_key_update(gnutls_session_t session, const void *data, size_t data_size)
+{
+ ssize_t ret;
+
+ /* do nothing, if we are in corked mode. Otherwise
+ * switch to corked mode, cache the data and send
+ * the key update */
+
+ if (session->internals.record_flush_mode == RECORD_FLUSH) {
+ gnutls_record_cork(session); /* we are not in flush mode after that */
+
+ ret = append_data_to_corked(session, data, data_size);
+ if (ret < 0)
+ return ret;
+
+ ret = _gnutls13_send_key_update(session, 0);
+
+ session->internals.key_update_state = KEY_UPDATE_SENT;
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->internals.key_update_state = KEY_UPDATE_COMPLETED;
+
+ ret = gnutls_record_uncork(session, 0);
+ if (ret == 0)
+ session->internals.key_update_state = KEY_UPDATE_INACTIVE;
+ return ret;
+ } else {
+ switch(session->internals.key_update_state) {
+ case KEY_UPDATE_SCHEDULED:
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ case KEY_UPDATE_SENT:
+ ret = _gnutls13_send_key_update(session, 1);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->internals.key_update_state = KEY_UPDATE_COMPLETED;
+
+ FALLTHROUGH;
+ case KEY_UPDATE_COMPLETED:
+ ret = gnutls_record_uncork(session, 0);
+ if (ret == 0)
+ session->internals.key_update_state = KEY_UPDATE_INACTIVE;
+ return ret;
+ default:
+ /* no state */
+ return GNUTLS_E_INT_RET_0; /* notify fall through */
+ }
+ }
+}
/**
* gnutls_record_send:
* @session: is a #gnutls_session_t type.
@@ -1682,29 +1757,23 @@ gnutls_record_send(gnutls_session_t session, const void *data,
return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE);
}
+ if (session->internals.key_update_state > KEY_UPDATE_INACTIVE) {
+ ssize_t ret;
+
+ ret = handle_key_update(session, data, data_size);
+ if (ret != GNUTLS_E_INT_RET_0)
+ return ret;
+ /* otherwise fall through */
+ }
+
if (session->internals.record_flush_mode == RECORD_FLUSH) {
return _gnutls_send_int(session, GNUTLS_APPLICATION_DATA,
-1, EPOCH_WRITE_CURRENT, data,
data_size, MBUFFER_FLUSH);
} else { /* GNUTLS_CORKED */
+ return append_data_to_corked(session, data, data_size);
- int ret;
-
- if (IS_DTLS(session)) {
- if (data_size + session->internals.record_presend_buffer.length >
- gnutls_dtls_get_data_mtu(session)) {
- return gnutls_assert_val(GNUTLS_E_LARGE_PACKET);
- }
- }
-
- ret =
- _gnutls_buffer_append_data(&session->internals.
- record_presend_buffer, data,
- data_size);
- if (ret < 0)
- return gnutls_assert_val(ret);
- return data_size;
}
}
diff --git a/lib/tls13/key_update.c b/lib/tls13/key_update.c
new file mode 100644
index 0000000000..59db784e5b
--- /dev/null
+++ b/lib/tls13/key_update.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "gnutls_int.h"
+#include "errors.h"
+#include "handshake.h"
+#include "tls13/key_update.h"
+#include "mem.h"
+#include "mbuffers.h"
+#include "secrets.h"
+
+#define KEY_UPDATES_PER_SEC 1
+
+static int update_keys(gnutls_session_t session, hs_stage_t stage)
+{
+ int ret;
+
+ ret = _tls13_update_secret(session, session->key.temp_secret,
+ session->key.temp_secret_size);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ _gnutls_epoch_bump(session);
+ ret = _gnutls_epoch_dup(session);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _tls13_connection_state_init(session, stage);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return 0;
+}
+
+int _gnutls13_recv_key_update(gnutls_session_t session, gnutls_buffer_st *buf)
+{
+ int ret;
+ time_t now = gnutls_time(0);
+
+ if (buf->length != 1)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ if (unlikely(now - session->internals.last_key_update < KEY_UPDATES_PER_SEC)) {
+ _gnutls_debug_log("reached maximum number of key updates per second (%d)\n",
+ KEY_UPDATES_PER_SEC);
+ return gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS);
+ }
+
+ session->internals.last_key_update = now;
+
+ _gnutls_epoch_gc(session);
+
+ _gnutls_handshake_log("HSK[%p]: requested TLS 1.3 key update (%u)\n",
+ session, (unsigned)buf->data[0]);
+
+ switch(buf->data[0]) {
+ case 0:
+ /* peer updated its key, not requested our key update */
+ ret = update_keys(session, STAGE_UPD_PEERS);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ break;
+ case 1:
+ /* peer updated its key, requested our key update */
+ ret = update_keys(session, STAGE_UPD_PEERS);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ /* we mark that a key update is schedule, and it
+ * will be performed prior to sending the next application
+ * message.
+ */
+ session->internals.key_update_state = KEY_UPDATE_SCHEDULED;
+
+ break;
+ default:
+ return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
+ }
+
+ return 0;
+}
+
+int _gnutls13_send_key_update(gnutls_session_t session, unsigned again)
+{
+ int ret, ret2;
+ mbuffer_st *bufel = NULL;
+ const uint8_t val = 0;
+
+ if (again == 0) {
+ _gnutls_handshake_log("HSK[%p]: sending key update\n", session);
+
+ bufel = _gnutls_handshake_alloc(session, 1);
+ if (bufel == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ _mbuffer_set_udata_size(bufel, 0);
+ ret = _mbuffer_append_data(bufel, (void*)&val, 1);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ }
+
+ ret = _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_KEY_UPDATE);
+ if (ret == 0) {
+ /* it was completely sent, update the keys */
+ ret2 = update_keys(session, STAGE_UPD_OURS);
+ if (ret2 < 0)
+ return gnutls_assert_val(ret2);
+ }
+
+ return ret;
+
+cleanup:
+ _mbuffer_xfree(&bufel);
+ return ret;
+}
diff --git a/lib/tls13/key_update.h b/lib/tls13/key_update.h
new file mode 100644
index 0000000000..0b313581bb
--- /dev/null
+++ b/lib/tls13/key_update.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+int _gnutls13_recv_key_update(gnutls_session_t session, gnutls_buffer_st *buf);
+int _gnutls13_send_key_update(gnutls_session_t session, unsigned again);