/*
 * Copyright (C) 2016 - 2018 ARPA2 project
 *
 * Author: Tom Vrancken (dev@tomvrancken.nl)
 *
 * 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 is part of the client_certificate_type extension as
 * defined in RFC7250 (https://tools.ietf.org/html/rfc7250).
 *
 * The client_certificate_type extension in the client hello indicates
 * the certificate types the client is able to provide to the server,
 * when requested using a certificate_request message.
 */

#include "gnutls_int.h"
#include <gnutls/gnutls.h>
#include "ext/cert_types.h"
#include "ext/client_cert_type.h"
#include "hello_ext.h"
#include "hello_ext_lib.h"
#include "errors.h"
#include "state.h"
#include "datum.h"

static int _gnutls_client_cert_type_recv_params(gnutls_session_t session,
						const uint8_t *data,
						size_t data_size);
static int _gnutls_client_cert_type_send_params(gnutls_session_t session,
						gnutls_buffer_st *data);

const hello_ext_entry_st ext_mod_client_cert_type = {
	.name = "Client Certificate Type",
	.tls_id = 19,
	.gid = GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
	.client_parse_point = GNUTLS_EXT_TLS,
	.server_parse_point = GNUTLS_EXT_TLS,
	.validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS |
		    GNUTLS_EXT_FLAG_CLIENT_HELLO |
		    GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO | GNUTLS_EXT_FLAG_EE,
	.recv_func = _gnutls_client_cert_type_recv_params,
	.send_func = _gnutls_client_cert_type_send_params,
	.pack_func = _gnutls_hello_ext_default_pack,
	.unpack_func = _gnutls_hello_ext_default_unpack,
	.deinit_func = _gnutls_hello_ext_default_deinit,
	.cannot_be_overriden = 1
};

static int _gnutls_client_cert_type_recv_params(gnutls_session_t session,
						const uint8_t *data,
						size_t data_size)
{
	int ret;
	gnutls_certificate_type_t cert_type;
	size_t i;
	bool found = false;
	const uint8_t *pdata = data;

	/* Only activate this extension if we have cert credentials set
	 * and alternative cert types are allowed */
	if (!are_alternative_cert_types_allowed(session) ||
	    (_gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE) == NULL))
		return 0;

	if (!IS_SERVER(session)) { // client mode
		gnutls_datum_t
			sent_cert_types; // Holds the previously sent cert types

		/* Compare packet length with expected packet length. For the
		 * client this is a single byte. */
		if (data_size != 1) {
			return gnutls_assert_val(
				GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
		}

		/* The server picked one of the offered cert types if he supports
		 * at least one of them and decided to do a client certificate
		 * request. If both parties play by the rules then we may only
		 * receive a cert type that we offered, i.e. one that we support.
		 * Because the world isn't as beautiful as it may seem, we're going
		 * to check it nevertheless. */
		cert_type = IANA2cert_type(pdata[0]);

		_gnutls_handshake_log(
			"EXT[%p]: Received a %s client certificate type confirmation from the server.\n",
			session, gnutls_certificate_type_get_name(cert_type));

		// Check validity of cert type
		if (cert_type == GNUTLS_CRT_UNKNOWN) {
			return gnutls_assert_val(
				GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE);
		}

		/* Get the cert types that we sent to the server (they were stored
		 * in IANA representation.
		 */
		ret = _gnutls_hello_ext_get_datum(
			session, GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
			&sent_cert_types);
		if (ret < 0) {
			/* This should not happen and indicate a memory corruption!
			 * Assertion are always on in production code so execution
			 * will halt here. */
			assert(false);
		}
		// Check whether what we got back is actually offered by us
		for (i = 0; i < sent_cert_types.size; i++) {
			if (IANA2cert_type(sent_cert_types.data[i]) ==
			    cert_type)
				found = true;
		}

		if (found) {
			// Everything OK, now set the client certificate type
			_gnutls_session_client_cert_type_set(session,
							     cert_type);
			ret = GNUTLS_E_SUCCESS;
		} else {
			// No valid cert type found
			ret = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
		}

		return ret;

	} else { // server mode
		gnutls_datum_t cert_types; // Holds the received cert types

		// Compare packet length with expected packet length.
		DECR_LEN(data_size, 1);
		if (data[0] != data_size) {
			return gnutls_assert_val(
				GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
		}
		pdata += 1;

		// Assign the contents of our data buffer to a gnutls_datum_t
		cert_types.data = (uint8_t *)
			pdata; // Need casting to get rid of 'discards const qualifier' warning
		cert_types.size = data_size;

		// Store the client certificate types in our session
		_gnutls_hello_ext_set_datum(session,
					    GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
					    &cert_types);

		/* We receive a list of supported certificate types that the client
		 * is able to provide when requested via a client certificate
		 * request. This list is sorted by order of preference. We now check
		 * in this order of preference whether we support any of these
		 * certificate types.
		 */
		for (i = 0; i < cert_types.size; i++) {
			// Convert to internal representation
			cert_type = IANA2cert_type(cert_types.data[i]);

			// If we have an invalid cert id then continue to the next
			if (cert_type == GNUTLS_CRT_UNKNOWN)
				continue;

			_gnutls_handshake_log(
				"EXT[%p]: Checking compatibility of a %s client certificate type that was received from the client.\n",
				session,
				gnutls_certificate_type_get_name(cert_type));

			// Check for support of this cert type
			if (_gnutls_session_is_cert_type_supported(
				    session, cert_type, false,
				    GNUTLS_CTYPE_CLIENT) == 0) {
				found = true;
				break;
			}
		}

		// We found a matching ctype, we pick this one
		if (found) {
			_gnutls_session_client_cert_type_set(session,
							     cert_type);
			ret = GNUTLS_E_SUCCESS;
		} else {
			/* If no supported certificate type can be found we terminate
			 * with a fatal alert of type "unsupported_certificate"
			 * (according to specification rfc7250).
			 */
			_gnutls_handshake_log(
				"EXT[%p]: No supported client certificate type was found. "
				"Aborting connection.\n",
				session);
			ret = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
		}

		return ret;
	}
}

static int _gnutls_client_cert_type_send_params(gnutls_session_t session,
						gnutls_buffer_st *data)
{
	int ret;
	uint8_t cert_type_IANA; // Holds an IANA cert type ID
	gnutls_certificate_type_t cert_type;

	/* Only activate this extension if we have cert credentials set
	 * and alternative cert types are allowed */
	if (!are_alternative_cert_types_allowed(session) ||
	    (_gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE) == NULL))
		return 0;

	if (!IS_SERVER(session)) { // Client mode
		uint8_t cert_types
			[GNUTLS_CRT_MAX]; // The list with supported (IANA) cert types. Inv: 0 <= cert type Id < 256
		size_t i, num_cert_types = 0;
		priority_st *cert_priorities;
		gnutls_datum_t tmp_cert_types; // For type conversion

		// For brevity
		cert_priorities = &session->internals.priorities->client_ctype;

		/* Retrieve client certificate type priorities if any. If no
		 * priorities are set then the default client certificate type
		 * initialization values apply. This default is currently set to
		 * x.509 in which case we don't enable this extension.
		 */
		if (cert_priorities->num_priorities >
		    0) { // Priorities are explicitly set
			/* If the certificate priority is explicitly set to only
			 * X.509 (default) then, according to spec we don't send
			 * this extension. We check this here to avoid further work in
			 * this routine. We also check it below after pruning supported
			 * types.
			 */
			if (cert_priorities->num_priorities == 1 &&
			    cert_priorities->priorities[0] ==
				    DEFAULT_CERT_TYPE) {
				_gnutls_handshake_log(
					"EXT[%p]: Client certificate type was set to default cert type (%s). "
					"We therefore do not send this extension.\n",
					session,
					gnutls_certificate_type_get_name(
						DEFAULT_CERT_TYPE));

				// Explicitly set but default ctype, so don't send anything
				return 0;
			}

			/* We are only allowed to send certificate types that we support,
			 * i.e. have credentials for. Therefore we check this here and
			 * prune our original list.
			 */
			for (i = 0; i < cert_priorities->num_priorities; i++) {
				cert_type = cert_priorities->priorities[i];

				if (_gnutls_session_is_cert_type_supported(
					    session, cert_type, true,
					    GNUTLS_CTYPE_CLIENT) == 0) {
					/* Check whether we are allowed to store another cert type
					 * in our buffer. In other words, prevent a possible buffer
					 * overflow. This situation can occur when a user sets
					 * duplicate cert types in the priority strings. */
					if (num_cert_types >= GNUTLS_CRT_MAX)
						return gnutls_assert_val(
							GNUTLS_E_SHORT_MEMORY_BUFFER);

					// Convert to IANA representation
					ret = cert_type2IANA(cert_type);

					if (ret < 0)
						return gnutls_assert_val(ret);

					cert_type_IANA = ret; // For readability

					// Add this cert type to our list with supported types
					cert_types[num_cert_types] =
						cert_type_IANA;
					num_cert_types++;

					_gnutls_handshake_log(
						"EXT[%p]: Client certificate type %s (%d) was queued.\n",
						session,
						gnutls_certificate_type_get_name(
							cert_type),
						cert_type_IANA);
				}
			}

			/* Check whether there are any supported certificate types left
			 * after the previous pruning step. If not, we do not send this
			 * extension. Also, if the only supported type is the default type
			 * we do not send this extension (according to RFC7250).
			 */
			if (num_cert_types == 0) {
				_gnutls_handshake_log(
					"EXT[%p]: Client certificate types were set but none of them is supported. "
					"You might want to check your credentials or your priorities. "
					"We do not send this extension.\n",
					session);

				return 0;
			} else if (num_cert_types == 1 &&
				   IANA2cert_type(cert_types[0]) ==
					   DEFAULT_CERT_TYPE) {
				_gnutls_handshake_log(
					"EXT[%p]: The only supported client certificate type is (%s) which is the default. "
					"We therefore do not send this extension.\n",
					session,
					gnutls_certificate_type_get_name(
						DEFAULT_CERT_TYPE));

				return 0;
			}

			/* We have data to send and store a copy internally. We convert
			 * our list with supported cert types to a datum_t in order to
			 * be able to make the ..._set_datum call.
			 */
			tmp_cert_types.data = cert_types;
			tmp_cert_types.size = num_cert_types;

			_gnutls_hello_ext_set_datum(
				session, GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
				&tmp_cert_types);

			/* Serialize the certificate types into a sequence of octets
			 * uint8: length of sequence of cert types (1 octet)
			 * uint8: cert types (0 <= #octets <= 255)
			 */
			ret = _gnutls_buffer_append_data_prefix(
				data, 8, cert_types, num_cert_types);

			// Check for errors
			if (ret < 0) {
				return gnutls_assert_val(ret);
			} else {
				// Number of bytes we are sending
				return num_cert_types + 1;
			}
		}
	} else { // Server mode
		const version_entry_st *vers = get_version(session);
		/* TLS 1.2:
		 * Check whether we are going to send a certificate request,
		 * otherwise omit the response. This is conform spec.
		 * (RFC7250, 4.2 case 3.).
		 *
		 * TLS 1.3:
		 * TLS 1.3 supports post-handshake authentication for the client.
		 * It means that a server can ask for a client certificate anytime
		 * after the handshake. In order for this to work we must always
		 * complete the certificate type negotiation and therefore respond
		 * with a cert type message.
		 */
		if (session->internals.send_cert_req != 0 || vers->tls13_sem) {
			/* Retrieve negotiated client certificate type and send it to
			 * the client.
			 * The scenario where we want to send a certificate request but
			 * do not have a matching certificate does not occur because we
			 * already terminate the connection at reception of this extension
			 * when we cannot find a matching client certificate. This is conform
			 * spec (RFC7250, 4.2 case 2.).
			 */
			cert_type = get_certificate_type(session,
							 GNUTLS_CTYPE_CLIENT);
			ret = cert_type2IANA(cert_type);

			if (ret < 0)
				return gnutls_assert_val(ret);

			cert_type_IANA = ret; // For readability

			_gnutls_handshake_log(
				"EXT[%p]: Confirming to use a %s client certificate type.\n",
				session,
				gnutls_certificate_type_get_name(cert_type));

			ret = gnutls_buffer_append_data(data, &cert_type_IANA,
							1);

			if (ret < 0)
				return gnutls_assert_val(ret);

			return 1; // sent one byte
		}
	}

	// In all other cases don't enable this extension
	return 0;
}

/** Extension interface **/

/* The interface is defined in state.c:
 * Public:
 * - gnutls_certificate_type_get2
 *
 * Private:
 * - _gnutls_session_client_cert_type_set
 */
