diff options
author | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2017-10-19 14:52:03 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2018-02-19 15:29:36 +0100 |
commit | d98473341430849984ff7354cee811e1d9b7842c (patch) | |
tree | 2eb355a8ec13eeba0feb2585366bc4b2c001423a | |
parent | 507fa1d35b3c6713745d9cd2e079eb2d8931466c (diff) | |
download | gnutls-d98473341430849984ff7354cee811e1d9b7842c.tar.gz |
handshake: added TLS1.3 passive key update
Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
-rw-r--r-- | lib/Makefile.am | 1 | ||||
-rw-r--r-- | lib/constate.c | 142 | ||||
-rw-r--r-- | lib/debug.c | 2 | ||||
-rw-r--r-- | lib/gnutls_int.h | 10 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 48 | ||||
-rw-r--r-- | lib/handshake.h | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 2 | ||||
-rw-r--r-- | lib/record.c | 101 | ||||
-rw-r--r-- | lib/tls13/key_update.c | 137 | ||||
-rw-r--r-- | lib/tls13/key_update.h | 24 |
10 files changed, 412 insertions, 57 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index ed229e47ab..52686bf25d 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 fcc8c0bd4c..88d0e17e09 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 = ¶ms->write; + session_epoch = &session->security_parameters.epoch_write; + } else { + state = ¶ms->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 = ¶ms->write; + session_epoch = &session->security_parameters.epoch_write; + } else { + state = ¶ms->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, ¶ms->read); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _tls13_init_record_state(params->cipher->id, ¶ms->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); @@ -850,30 +961,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(¶ms->read.ctx.aead, - params->cipher->id, ¶ms->read.key); - if (ret < 0) - return gnutls_assert_val(ret); - - ret = _gnutls_aead_cipher_init(¶ms->write.ctx.aead, - params->cipher->id, ¶ms->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 3e26ddb8fe..47bdaf8a3c 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); |