summaryrefslogtreecommitdiffstats
path: root/lib/tls13/key_update.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tls13/key_update.c')
-rw-r--r--lib/tls13/key_update.c217
1 files changed, 217 insertions, 0 deletions
diff --git a/lib/tls13/key_update.c b/lib/tls13/key_update.c
new file mode 100644
index 0000000..c6f6e0a
--- /dev/null
+++ b/lib/tls13/key_update.c
@@ -0,0 +1,217 @@
+/*
+ * 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 <https://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_WINDOW 1000
+#define KEY_UPDATES_PER_WINDOW 8
+
+static int update_keys(gnutls_session_t session, hs_stage_t stage)
+{
+ int ret;
+
+ ret = _tls13_update_secret(session, session->key.proto.tls13.temp_secret,
+ session->key.proto.tls13.temp_secret_size);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ _gnutls_epoch_bump(session);
+ ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ /* If we send a key update during early start, only update our
+ * write keys */
+ if (session->internals.recv_state == RECV_STATE_EARLY_START) {
+ ret = _tls13_write_connection_state_init(session, stage);
+ } else {
+ 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;
+ struct timespec now;
+
+ if (buf->length != 1)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ gnutls_gettime(&now);
+
+ /* Roll over the counter if the time window has elapsed */
+ if (session->internals.key_update_count == 0 ||
+ timespec_sub_ms(&now, &session->internals.last_key_update) >
+ KEY_UPDATES_WINDOW) {
+ session->internals.last_key_update = now;
+ session->internals.key_update_count = 0;
+ }
+
+ if (unlikely(++session->internals.key_update_count >
+ KEY_UPDATES_PER_WINDOW)) {
+ _gnutls_debug_log("reached maximum number of key updates per %d milliseconds (%d)\n",
+ KEY_UPDATES_WINDOW,
+ KEY_UPDATES_PER_WINDOW);
+ return gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS);
+ }
+
+ _gnutls_epoch_gc(session);
+
+ _gnutls_handshake_log("HSK[%p]: received 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:
+ if (session->internals.hsk_flags & HSK_KEY_UPDATE_ASKED) {
+ /* if we had asked a key update we shouldn't get this
+ * reply */
+ return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
+ }
+
+ /* 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.
+ */
+ if (session->internals.rsend_state == RECORD_SEND_NORMAL)
+ session->internals.rsend_state = RECORD_SEND_KEY_UPDATE_1;
+ else if (session->internals.rsend_state == RECORD_SEND_CORKED)
+ session->internals.rsend_state = RECORD_SEND_CORKED_TO_KU;
+
+ break;
+ default:
+ return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
+ }
+
+ session->internals.hsk_flags &= ~(unsigned)(HSK_KEY_UPDATE_ASKED);
+
+ return 0;
+}
+
+int _gnutls13_send_key_update(gnutls_session_t session, unsigned again, unsigned flags /* GNUTLS_KU_* */)
+{
+ int ret;
+ mbuffer_st *bufel = NULL;
+ uint8_t val;
+
+ if (again == 0) {
+ if (flags & GNUTLS_KU_PEER) {
+ /* mark that we asked a key update to prevent an
+ * infinite ping pong when receiving the reply */
+ session->internals.hsk_flags |= HSK_KEY_UPDATE_ASKED;
+ val = 0x01;
+ } else {
+ val = 0x00;
+ }
+
+ _gnutls_handshake_log("HSK[%p]: sending key update (%u)\n", session, (unsigned)val);
+
+ 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, &val, 1);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ }
+
+ return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_KEY_UPDATE);
+
+cleanup:
+ _mbuffer_xfree(&bufel);
+ return ret;
+}
+
+/**
+ * gnutls_session_key_update:
+ * @session: is a #gnutls_session_t type.
+ * @flags: zero of %GNUTLS_KU_PEER
+ *
+ * This function will update/refresh the session keys when the
+ * TLS protocol is 1.3 or better. The peer is notified of the
+ * update by sending a message, so this function should be
+ * treated similarly to gnutls_record_send() --i.e., it may
+ * return %GNUTLS_E_AGAIN or %GNUTLS_E_INTERRUPTED.
+ *
+ * When this flag %GNUTLS_KU_PEER is specified, this function
+ * in addition to updating the local keys, will ask the peer to
+ * refresh its keys too.
+ *
+ * If the negotiated version is not TLS 1.3 or better this
+ * function will return %GNUTLS_E_INVALID_REQUEST.
+ *
+ * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
+ *
+ * Since: 3.6.3
+ **/
+int gnutls_session_key_update(gnutls_session_t session, unsigned flags)
+{
+ int ret;
+ const version_entry_st *vers = get_version(session);
+
+ if (!vers->tls13_sem)
+ return GNUTLS_E_INVALID_REQUEST;
+
+ ret =
+ _gnutls13_send_key_update(session, AGAIN(STATE150), flags);
+ STATE = STATE150;
+
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+ STATE = STATE0;
+
+ _gnutls_epoch_gc(session);
+
+ /* it was completely sent, update the keys */
+ ret = update_keys(session, STAGE_UPD_OURS);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return 0;
+}