/* * 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 * */ #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; }