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

void _gnutls_session_ticket_disable_server(gnutls_session_t session)
{
	session->internals.flags |= GNUTLS_NO_TICKETS;
}

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