summaryrefslogtreecommitdiffstats
path: root/lib/ext/session_ticket.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ext/session_ticket.c')
-rw-r--r--lib/ext/session_ticket.c843
1 files changed, 843 insertions, 0 deletions
diff --git a/lib/ext/session_ticket.c b/lib/ext/session_ticket.c
new file mode 100644
index 0000000..87d9069
--- /dev/null
+++ b/lib/ext/session_ticket.c
@@ -0,0 +1,843 @@
+/*
+ * Copyright (C) 2009-2018 Free Software Foundation, Inc.
+ *
+ * Author: Daiki Ueno, Ander Juaristi
+ *
+ * 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/>
+ *
+ */
+
+/* This file implements the TLS session ticket extension.
+ *
+ * Note that the extension is only used in TLS 1.2. For TLS 1.3, session
+ * tickets are sent as part of pre_shared_key extension (see pre_shared_key.c).
+ */
+
+#include "gnutls_int.h"
+#include "errors.h"
+#include <fips.h>
+#include <datum.h>
+#include <algorithms.h>
+#include <handshake.h>
+#include <num.h>
+#include <constate.h>
+#include <session_pack.h>
+#include <random.h>
+#include <ext/session_ticket.h>
+#include <mbuffers.h>
+#include <hello_ext.h>
+#include <constate.h>
+#include <dtls.h>
+#include "stek.h"
+#include "db.h"
+
+static int session_ticket_recv_params(gnutls_session_t session,
+ const uint8_t * data,
+ size_t data_size);
+static int session_ticket_send_params(gnutls_session_t session,
+ gnutls_buffer_st * extdata);
+static int session_ticket_unpack(gnutls_buffer_st * ps,
+ gnutls_ext_priv_data_t * _priv);
+static int session_ticket_pack(gnutls_ext_priv_data_t _priv,
+ gnutls_buffer_st * ps);
+static void session_ticket_deinit_data(gnutls_ext_priv_data_t priv);
+
+const hello_ext_entry_st ext_mod_session_ticket = {
+ .name = "Session Ticket",
+ .tls_id = 35,
+ .gid = GNUTLS_EXTENSION_SESSION_TICKET,
+ .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | GNUTLS_EXT_FLAG_CLIENT_HELLO |
+ GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO,
+ /* This extension must be parsed on session resumption as well; see
+ * https://gitlab.com/gnutls/gnutls/issues/841 */
+ .client_parse_point = GNUTLS_EXT_MANDATORY,
+ /* on server side we want this parsed after normal handshake resumption
+ * actions are complete */
+ .server_parse_point = GNUTLS_EXT_TLS,
+ .recv_func = session_ticket_recv_params,
+ .send_func = session_ticket_send_params,
+ .pack_func = session_ticket_pack,
+ .unpack_func = session_ticket_unpack,
+ .deinit_func = session_ticket_deinit_data,
+ .cannot_be_overriden = 1
+};
+
+typedef struct {
+ uint8_t *session_ticket;
+ int session_ticket_len;
+} session_ticket_ext_st;
+
+static void
+deinit_ticket(struct ticket_st *ticket)
+{
+ free(ticket->encrypted_state);
+}
+
+static int
+unpack_ticket(const gnutls_datum_t *ticket_data, struct ticket_st *ticket)
+{
+ const uint8_t * data = ticket_data->data;
+ size_t data_size = ticket_data->size;
+ const uint8_t *encrypted_state;
+
+ /* Format:
+ * Key name
+ * IV
+ * data length
+ * encrypted data
+ * MAC
+ */
+ DECR_LEN(data_size, TICKET_KEY_NAME_SIZE);
+ memcpy(ticket->key_name, data, TICKET_KEY_NAME_SIZE);
+ data += TICKET_KEY_NAME_SIZE;
+
+ DECR_LEN(data_size, TICKET_IV_SIZE);
+ memcpy(ticket->IV, data, TICKET_IV_SIZE);
+ data += TICKET_IV_SIZE;
+
+ DECR_LEN(data_size, 2);
+ ticket->encrypted_state_len = _gnutls_read_uint16(data);
+ data += 2;
+
+ encrypted_state = data;
+
+ DECR_LEN(data_size, ticket->encrypted_state_len);
+ data += ticket->encrypted_state_len;
+
+ DECR_LEN(data_size, TICKET_MAC_SIZE);
+ memcpy(ticket->mac, data, TICKET_MAC_SIZE);
+
+ ticket->encrypted_state =
+ gnutls_malloc(ticket->encrypted_state_len);
+ if (!ticket->encrypted_state) {
+ gnutls_assert();
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+ memcpy(ticket->encrypted_state, encrypted_state,
+ ticket->encrypted_state_len);
+
+ return 0;
+}
+
+static void
+pack_ticket(const struct ticket_st *ticket, gnutls_datum_t *ticket_data)
+{
+ uint8_t *p;
+
+ p = ticket_data->data;
+
+ memcpy(p, ticket->key_name, TICKET_KEY_NAME_SIZE);
+ p += TICKET_KEY_NAME_SIZE;
+
+ memcpy(p, ticket->IV, TICKET_IV_SIZE);
+ p += TICKET_IV_SIZE;
+
+ _gnutls_write_uint16(ticket->encrypted_state_len, p);
+ p += 2;
+
+ /* We use memmove instead of memcpy here because
+ * ticket->encrypted_state is allocated from
+ * ticket_data->data, and thus both memory areas may overlap.
+ */
+ memmove(p, ticket->encrypted_state, ticket->encrypted_state_len);
+ p += ticket->encrypted_state_len;
+
+ memcpy(p, ticket->mac, TICKET_MAC_SIZE);
+}
+
+static
+int digest_ticket(const gnutls_datum_t * key, struct ticket_st *ticket,
+ uint8_t * digest)
+{
+ mac_hd_st digest_hd;
+ uint16_t length16;
+ int ret;
+
+ ret = _gnutls_mac_init(&digest_hd, mac_to_entry(TICKET_MAC_ALGO),
+ key->data, key->size);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+
+ _gnutls_mac(&digest_hd, ticket->key_name, TICKET_KEY_NAME_SIZE);
+ _gnutls_mac(&digest_hd, ticket->IV, TICKET_IV_SIZE);
+ length16 = _gnutls_conv_uint16(ticket->encrypted_state_len);
+ _gnutls_mac(&digest_hd, &length16, 2);
+ _gnutls_mac(&digest_hd, ticket->encrypted_state,
+ ticket->encrypted_state_len);
+ _gnutls_mac_deinit(&digest_hd, digest);
+
+ return 0;
+}
+
+int
+_gnutls_decrypt_session_ticket(gnutls_session_t session,
+ const gnutls_datum_t *ticket_data,
+ gnutls_datum_t *state)
+{
+ cipher_hd_st cipher_hd;
+ gnutls_datum_t IV;
+ gnutls_datum_t stek_key_name, stek_cipher_key, stek_mac_key;
+ uint8_t cmac[TICKET_MAC_SIZE];
+ struct ticket_st ticket;
+ int ret;
+
+ /* Retrieve ticket decryption keys */
+ if (_gnutls_get_session_ticket_decryption_key(session,
+ ticket_data,
+ &stek_key_name,
+ &stek_mac_key,
+ &stek_cipher_key) < 0)
+ return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+
+ ret = unpack_ticket(ticket_data, &ticket);
+ if (ret < 0)
+ return ret;
+
+ /* If the key name of the ticket does not match the one that is currently active,
+ issue a new ticket. */
+ if (memcmp
+ (ticket.key_name, stek_key_name.data,
+ stek_key_name.size)) {
+ ret = GNUTLS_E_DECRYPTION_FAILED;
+ goto cleanup;
+ }
+
+ /* Check the integrity of ticket */
+ ret = digest_ticket(&stek_mac_key, &ticket, cmac);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ if (memcmp(ticket.mac, cmac, TICKET_MAC_SIZE)) {
+ ret = gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+ goto cleanup;
+ }
+
+ if (ticket.encrypted_state_len % TICKET_BLOCK_SIZE != 0) {
+ ret = gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED);
+ goto cleanup;
+ }
+
+ /* Decrypt encrypted_state */
+ IV.data = ticket.IV;
+ IV.size = TICKET_IV_SIZE;
+ ret =
+ _gnutls_cipher_init(&cipher_hd,
+ cipher_to_entry(TICKET_CIPHER),
+ &stek_cipher_key, &IV, 0);
+ if (ret < 0) {
+ _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+ gnutls_assert();
+ goto cleanup;
+ }
+ _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED);
+
+ ret = _gnutls_cipher_decrypt(&cipher_hd, ticket.encrypted_state,
+ ticket.encrypted_state_len);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup2;
+ }
+
+ state->data = ticket.encrypted_state;
+ state->size = ticket.encrypted_state_len;
+
+ ticket.encrypted_state = NULL;
+
+ ret = 0;
+
+cleanup2:
+ _gnutls_cipher_deinit(&cipher_hd);
+
+cleanup:
+ deinit_ticket(&ticket);
+
+ return ret;
+
+}
+
+int
+_gnutls_encrypt_session_ticket(gnutls_session_t session,
+ const gnutls_datum_t *state,
+ gnutls_datum_t *ticket_data)
+{
+ cipher_hd_st cipher_hd;
+ gnutls_datum_t IV;
+ gnutls_datum_t encrypted_state;
+ gnutls_datum_t result = { NULL, 0 };
+ uint8_t iv[TICKET_IV_SIZE];
+ gnutls_datum_t stek_cipher_key, stek_mac_key, stek_key_name;
+ struct ticket_st ticket;
+ int ret;
+
+ encrypted_state.size = ((state->size + TICKET_BLOCK_SIZE - 1) / TICKET_BLOCK_SIZE) * TICKET_BLOCK_SIZE;
+ result.size = TICKET_KEY_NAME_SIZE + TICKET_IV_SIZE + 2 +
+ encrypted_state.size + TICKET_MAC_SIZE;
+ result.data = gnutls_calloc(1, result.size);
+ if (!result.data) {
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+ }
+ encrypted_state.data = result.data + TICKET_KEY_NAME_SIZE + TICKET_IV_SIZE + 2;
+ memcpy(encrypted_state.data, state->data, state->size);
+
+ /* Retrieve ticket encryption keys */
+ if (_gnutls_get_session_ticket_encryption_key(session,
+ &stek_key_name,
+ &stek_mac_key,
+ &stek_cipher_key) < 0) {
+ ret = GNUTLS_E_ENCRYPTION_FAILED;
+ goto cleanup;
+ }
+
+ /* Encrypt state */
+ IV.data = iv;
+ IV.size = TICKET_IV_SIZE;
+
+ ret = gnutls_rnd(GNUTLS_RND_NONCE, iv, TICKET_IV_SIZE);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ ret =
+ _gnutls_cipher_init(&cipher_hd,
+ cipher_to_entry(TICKET_CIPHER),
+ &stek_cipher_key, &IV, 1);
+ if (ret < 0) {
+ _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR);
+ gnutls_assert();
+ goto cleanup;
+ }
+ _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_APPROVED);
+
+ ret = _gnutls_cipher_encrypt(&cipher_hd, encrypted_state.data,
+ encrypted_state.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup2;
+ }
+
+
+ /* Fill the ticket structure to compute MAC. */
+ memcpy(ticket.key_name, stek_key_name.data, stek_key_name.size);
+ memcpy(ticket.IV, IV.data, IV.size);
+ ticket.encrypted_state_len = encrypted_state.size;
+ ticket.encrypted_state = encrypted_state.data;
+
+ ret = digest_ticket(&stek_mac_key, &ticket, ticket.mac);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup2;
+ }
+
+ pack_ticket(&ticket, &result);
+ ticket_data->data = result.data;
+ ticket_data->size = result.size;
+ result.data = NULL;
+
+cleanup2:
+ _gnutls_cipher_deinit(&cipher_hd);
+
+cleanup:
+ _gnutls_free_datum(&result);
+
+ return ret;
+}
+
+static int
+unpack_session(gnutls_session_t session, const gnutls_datum_t *state)
+{
+ int ret;
+
+ if (unlikely(!state))
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ ret = _gnutls_session_unpack(session, state);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _gnutls_check_resumed_params(session);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->internals.resumed = true;
+ return 0;
+}
+
+static int
+session_ticket_recv_params(gnutls_session_t session,
+ const uint8_t * data, size_t data_size)
+{
+ gnutls_datum_t ticket_data;
+ gnutls_datum_t state;
+ int ret;
+
+ if (session->internals.flags & (GNUTLS_NO_TICKETS | GNUTLS_NO_TICKETS_TLS12))
+ return 0;
+
+ if (session->security_parameters.entity == GNUTLS_SERVER) {
+ /* The client requested a new session ticket. */
+ if (data_size == 0) {
+ session->internals.session_ticket_renew = 1;
+ return 0;
+ }
+
+ ticket_data.data = (void *)data;
+ ticket_data.size = data_size;
+ if ((ret = _gnutls_decrypt_session_ticket(session, &ticket_data, &state)) == 0) {
+ ret = unpack_session(session, &state);
+
+ _gnutls_free_datum(&state);
+ }
+
+ if (ret < 0) {
+ session->internals.session_ticket_renew = 1;
+ return 0;
+ }
+ } else { /* Client */
+
+ if (data_size == 0) {
+ session->internals.session_ticket_renew = 1;
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/* returns a positive number if we send the extension data, (0) if we
+ do not want to send it, and a negative number on failure.
+ */
+static int
+session_ticket_send_params(gnutls_session_t session,
+ gnutls_buffer_st * extdata)
+{
+ session_ticket_ext_st *priv = NULL;
+ gnutls_ext_priv_data_t epriv;
+ int ret;
+
+ if (session->internals.flags & (GNUTLS_NO_TICKETS | GNUTLS_NO_TICKETS_TLS12))
+ return 0;
+
+ if (session->security_parameters.entity == GNUTLS_SERVER) {
+ if (session->internals.session_ticket_renew) {
+ return GNUTLS_E_INT_RET_0;
+ }
+ } else {
+ ret =
+ _gnutls_hello_ext_get_resumed_priv(session,
+ GNUTLS_EXTENSION_SESSION_TICKET,
+ &epriv);
+ if (ret >= 0)
+ priv = epriv;
+
+ /* no previous data. Just advertise it */
+ if (ret < 0)
+ return GNUTLS_E_INT_RET_0;
+
+ /* previous data had session tickets disabled. Don't advertise. Ignore. */
+ if (session->internals.flags & GNUTLS_NO_TICKETS)
+ return 0;
+
+ if (priv->session_ticket_len > 0) {
+ ret =
+ _gnutls_buffer_append_data(extdata,
+ priv->
+ session_ticket,
+ priv->
+ session_ticket_len);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return priv->session_ticket_len;
+ }
+ }
+ return 0;
+}
+
+
+static void session_ticket_deinit_data(gnutls_ext_priv_data_t epriv)
+{
+ session_ticket_ext_st *priv = epriv;
+
+ gnutls_free(priv->session_ticket);
+ gnutls_free(priv);
+}
+
+static int
+session_ticket_pack(gnutls_ext_priv_data_t epriv, gnutls_buffer_st * ps)
+{
+ session_ticket_ext_st *priv = epriv;
+ int ret;
+
+ BUFFER_APPEND_PFX4(ps, priv->session_ticket,
+ priv->session_ticket_len);
+
+ return 0;
+}
+
+static int
+session_ticket_unpack(gnutls_buffer_st * ps, gnutls_ext_priv_data_t * _priv)
+{
+ session_ticket_ext_st *priv = NULL;
+ int ret;
+ gnutls_ext_priv_data_t epriv;
+ gnutls_datum_t ticket;
+
+ priv = gnutls_calloc(1, sizeof(*priv));
+ if (priv == NULL) {
+ gnutls_assert();
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+
+ BUFFER_POP_DATUM(ps, &ticket);
+ priv->session_ticket = ticket.data;
+ priv->session_ticket_len = ticket.size;
+
+ epriv = priv;
+ *_priv = epriv;
+
+ return 0;
+
+ error:
+ gnutls_free(priv);
+ return ret;
+}
+
+
+
+/**
+ * gnutls_session_ticket_key_generate:
+ * @key: is a pointer to a #gnutls_datum_t which will contain a newly
+ * created key.
+ *
+ * Generate a random key to encrypt security parameters within
+ * SessionTicket.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or an
+ * error code.
+ *
+ * Since: 2.10.0
+ **/
+int gnutls_session_ticket_key_generate(gnutls_datum_t * key)
+{
+ if (_gnutls_fips_mode_enabled()) {
+ int ret;
+ /* in FIPS140-2 mode gnutls_key_generate imposes
+ * some limits on allowed key size, thus it is not
+ * used. These limits do not affect this function as
+ * it does not generate a "key" but rather key material
+ * that includes nonces and other stuff. */
+ key->data = gnutls_malloc(TICKET_MASTER_KEY_SIZE);
+ if (key->data == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ key->size = TICKET_MASTER_KEY_SIZE;
+ ret = gnutls_rnd(GNUTLS_RND_RANDOM, key->data, key->size);
+ if (ret < 0) {
+ gnutls_free(key->data);
+ return ret;
+ }
+ return 0;
+ } else {
+ return gnutls_key_generate(key, TICKET_MASTER_KEY_SIZE);
+ }
+}
+
+/**
+ * gnutls_session_ticket_enable_client:
+ * @session: is a #gnutls_session_t type.
+ *
+ * Request that the client should attempt session resumption using
+ * SessionTicket. This call is typically unnecessary as session
+ * tickets are enabled by default.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or an
+ * error code.
+ *
+ * Since: 2.10.0
+ **/
+int gnutls_session_ticket_enable_client(gnutls_session_t session)
+{
+ if (!session) {
+ gnutls_assert();
+ return GNUTLS_E_INVALID_REQUEST;
+ }
+
+ session->internals.flags &= ~GNUTLS_NO_TICKETS;
+
+ return 0;
+}
+
+/**
+ * gnutls_session_ticket_enable_server:
+ * @session: is a #gnutls_session_t type.
+ * @key: key to encrypt session parameters.
+ *
+ * Request that the server should attempt session resumption using
+ * session tickets, i.e., by delegating storage to the client.
+ * @key must be initialized using gnutls_session_ticket_key_generate().
+ * To avoid leaking that key, use gnutls_memset() prior to
+ * releasing it.
+ *
+ * The default ticket expiration time can be overridden using
+ * gnutls_db_set_cache_expiration().
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or an
+ * error code.
+ *
+ * Since: 2.10.0
+ **/
+int
+gnutls_session_ticket_enable_server(gnutls_session_t session,
+ const gnutls_datum_t * key)
+{
+ int ret;
+
+ if (!session || !key || key->size != TICKET_MASTER_KEY_SIZE || !key->data) {
+ gnutls_assert();
+ return GNUTLS_E_INVALID_REQUEST;
+ }
+
+ ret = _gnutls_initialize_session_ticket_key_rotation(session, key);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->internals.flags &= ~GNUTLS_NO_TICKETS;
+
+ return 0;
+}
+
+/*
+ * Return zero if session tickets haven't been enabled.
+ */
+int _gnutls_send_new_session_ticket(gnutls_session_t session, int again)
+{
+ mbuffer_st *bufel = NULL;
+ uint8_t *data = NULL, *p;
+ int data_size = 0;
+ int ret;
+ gnutls_datum_t state = { NULL, 0 };
+ uint16_t epoch_saved = session->security_parameters.epoch_write;
+ gnutls_datum_t ticket_data;
+
+ if (again == 0) {
+ if (session->internals.flags & (GNUTLS_NO_TICKETS |
+ GNUTLS_NO_TICKETS_TLS12)) {
+ return 0;
+ }
+ if (!session->key.stek_initialized) {
+ return 0;
+ }
+ if (!session->internals.session_ticket_renew) {
+ return 0;
+ }
+
+ _gnutls_handshake_log
+ ("HSK[%p]: sending session ticket\n", session);
+
+ /* XXX: Temporarily set write algorithms to be used.
+ _gnutls_write_connection_state_init() does this job, but it also
+ triggers encryption, while NewSessionTicket should not be
+ encrypted in the record layer. */
+ ret =
+ _gnutls_epoch_set_keys(session,
+ session->security_parameters.
+ epoch_next, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+
+ /* Under TLS1.2 with session tickets, the session ID is used for different
+ * purposes than the TLS1.0 session ID. Ensure that there is an internally
+ * set value which the server will see on the original and resumed sessions */
+ if (!session->internals.resumed) {
+ ret = _gnutls_generate_session_id(session->security_parameters.
+ session_id,
+ &session->security_parameters.
+ session_id_size);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+ }
+
+ session->security_parameters.epoch_write =
+ session->security_parameters.epoch_next;
+
+ /* Pack security parameters. */
+ ret = _gnutls_session_pack(session, &state);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+
+ /* Generate an encrypted ticket */
+ ret = _gnutls_encrypt_session_ticket(session, &state, &ticket_data);
+ session->security_parameters.epoch_write = epoch_saved;
+ _gnutls_free_datum(&state);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+
+ bufel =
+ _gnutls_handshake_alloc(session,
+ 4 + 2 + ticket_data.size);
+ if (!bufel) {
+ gnutls_assert();
+ _gnutls_free_datum(&ticket_data);
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+
+ data = _mbuffer_get_udata_ptr(bufel);
+ p = data;
+
+ _gnutls_write_uint32(session->internals.expire_time, p);
+ p += 4;
+
+ _gnutls_write_uint16(ticket_data.size, p);
+ p += 2;
+
+ memcpy(p, ticket_data.data, ticket_data.size);
+ p += ticket_data.size;
+
+ _gnutls_free_datum(&ticket_data);
+
+ data_size = p - data;
+
+ session->internals.hsk_flags |= HSK_TLS12_TICKET_SENT;
+ }
+ return _gnutls_send_handshake(session, data_size ? bufel : NULL,
+ GNUTLS_HANDSHAKE_NEW_SESSION_TICKET);
+}
+
+/*
+ * Return zero if session tickets haven't been enabled.
+ */
+int _gnutls_recv_new_session_ticket(gnutls_session_t session)
+{
+ uint8_t *p;
+ int data_size;
+ gnutls_buffer_st buf;
+ uint16_t ticket_len;
+ int ret;
+ session_ticket_ext_st *priv = NULL;
+ gnutls_ext_priv_data_t epriv;
+
+ if (session->internals.flags & (GNUTLS_NO_TICKETS |
+ GNUTLS_NO_TICKETS_TLS12))
+ return 0;
+ if (!session->internals.session_ticket_renew)
+ return 0;
+
+ /* This is the last flight and peer cannot be sure
+ * we have received it unless we notify him. So we
+ * wait for a message and retransmit if needed. */
+ if (IS_DTLS(session) && !_dtls_is_async(session)) {
+ unsigned have;
+ mbuffer_st *bufel = NULL;
+
+ have = gnutls_record_check_pending(session) +
+ record_check_unprocessed(session);
+
+ if (have != 0) {
+ bufel = _mbuffer_head_get_first(&session->internals.record_buffer, NULL);
+ }
+
+ if (have == 0 || (bufel && bufel->type != GNUTLS_HANDSHAKE)) {
+ ret = _dtls_wait_and_retransmit(session);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ }
+ }
+
+ ret = _gnutls_recv_handshake(session,
+ GNUTLS_HANDSHAKE_NEW_SESSION_TICKET,
+ 0, &buf);
+ if (ret < 0)
+ return gnutls_assert_val_fatal(ret);
+
+ p = buf.data;
+ data_size = buf.length;
+
+ DECR_LENGTH_COM(data_size, 4, ret =
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
+ goto error);
+ /* skip over lifetime hint */
+ p += 4;
+
+ DECR_LENGTH_COM(data_size, 2, ret =
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
+ goto error);
+ ticket_len = _gnutls_read_uint16(p);
+ p += 2;
+
+ DECR_LENGTH_COM(data_size, ticket_len, ret =
+ GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
+ goto error);
+
+ priv = gnutls_calloc(1, sizeof(*priv));
+ if (!priv) {
+ gnutls_assert();
+ ret = GNUTLS_E_MEMORY_ERROR;
+ goto error;
+ }
+ if (ticket_len > 0) {
+ priv->session_ticket =
+ gnutls_realloc_fast(priv->session_ticket, ticket_len);
+ if (!priv->session_ticket) {
+ gnutls_free(priv);
+ gnutls_assert();
+ ret = GNUTLS_E_MEMORY_ERROR;
+ goto error;
+ }
+ memcpy(priv->session_ticket, p, ticket_len);
+ }
+ priv->session_ticket_len = ticket_len;
+ epriv = priv;
+
+ /* Discard the current session ID. (RFC5077 3.4) */
+ ret =
+ _gnutls_generate_session_id(session->security_parameters.
+ session_id,
+ &session->security_parameters.
+ session_id_size);
+ if (ret < 0) {
+ gnutls_assert();
+ session_ticket_deinit_data(epriv);
+ ret = GNUTLS_E_INTERNAL_ERROR;
+ goto error;
+ }
+ ret = 0;
+
+ _gnutls_handshake_log
+ ("HSK[%p]: received session ticket\n", session);
+ session->internals.hsk_flags |= HSK_TICKET_RECEIVED;
+
+ _gnutls_hello_ext_set_priv(session,
+ GNUTLS_EXTENSION_SESSION_TICKET,
+ epriv);
+
+ error:
+ _gnutls_buffer_clear(&buf);
+
+ return ret;
+}