
/*
 * eap_tls.c
 *
 * Version:     $Id: 3a915bc624d69b99a180bc4a86bfb2d85704754c $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
 * Copyright 2003  Alan DeKok <aland@freeradius.org>
 * Copyright 2006  The FreeRADIUS server project
 */

/*
 *
 *  TLS Packet Format in EAP
 *  --- ------ ------ -- ---
 * 0		   1		   2		   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Code      |   Identifier  |	    Length	     |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Type      |     Flags     |      TLS Message Length
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     TLS Message Length	|       TLS Data...
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */

RCSID("$Id: 3a915bc624d69b99a180bc4a86bfb2d85704754c $")
USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */

#include <assert.h>

#include "eap_tls.h"
/*
 *	Send an initial eap-tls request to the peer.
 *
 *	Frame eap reply packet.
 *	len = header + type + tls_typedata
 *	tls_typedata = flags(Start (S) bit set, and no data)
 *
 *	Once having received the peer's Identity, the EAP server MUST
 *	respond with an EAP-TLS/Start packet, which is an
 *	EAP-Request packet with EAP-Type=EAP-TLS, the Start (S) bit
 *	set, and no data.  The EAP-TLS conversation will then begin,
 *	with the peer sending an EAP-Response packet with
 *	EAP-Type = EAP-TLS.  The data field of that packet will
 *	be the TLS data.
 *
 *	Fragment length is Framed-MTU - 4.
 */
tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13)
{
	tls_session_t	*ssn;
	REQUEST		*request = handler->request;

	handler->tls = true;

	tls_conf->name = dict_valnamebyattr(PW_EAP_TYPE, 0, handler->type);
	if (!tls_conf->name) tls_conf->name = "???";

	/*
	 *	Every new session is started only from EAP-TLS-START.
	 *	Before Sending EAP-TLS-START, open a new SSL session.
	 *	Create all the required data structures & store them
	 *	in Opaque.  So that we can use these data structures
	 *	when we get the response
	 */
	ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13);
	if (!ssn) {
		return NULL;
	}

	/*
	 *	Create a structure for all the items required to be
	 *	verified for each client and set that as opaque data
	 *	structure.
	 *
	 *	NOTE: If we want to set each item sepearately then
	 *	this index should be global.
	 */
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_HANDLER, (void *)handler);
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)tls_conf);
	SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)&(handler->certs));
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_IDENTITY, (void *)&(handler->identity));
#ifdef HAVE_OPENSSL_OCSP_H
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_STORE, (void *)tls_conf->ocsp_store);
#endif
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn);
	SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_TALLOC, handler);

	return talloc_steal(handler, ssn); /* ssn */
}

/*
   The S flag is set only within the EAP-TLS start message
   sent from the EAP server to the peer.
*/
int eaptls_start(EAP_DS *eap_ds, int peap_flag)
{
       EAPTLS_PACKET   reply;

       reply.code = FR_TLS_START;
       reply.length = TLS_HEADER_LEN + 1/*flags*/;

       reply.flags = peap_flag;
       reply.flags = SET_START(reply.flags);

       reply.data = NULL;
       reply.dlen = 0;

       eaptls_compose(eap_ds, &reply);

       return 1;
}

/** Send an EAP-TLS success
 *
 * Composes an EAP-TLS-Success.  This is a message with code EAP_TLS_ESTABLISHED.
 * It contains no cryptographic material, and is not protected.
 *
 * We add the MPPE keys here.  These are used by the NAS.  The supplicant
 * will derive the same keys separately.
 *
 * @param handler handler of eap session that completed successfully.
 * @param peap_flag to indicate PEAP version
 * @return
 *      - 1 on success.
 */
int eaptls_success(eap_handler_t *handler, int peap_flag)
{
	EAPTLS_PACKET reply;
	REQUEST *request = handler->request;
	tls_session_t *tls_session = handler->opaque;

	handler->finished = true;
	reply.code = FR_TLS_SUCCESS;
	reply.length = TLS_HEADER_LEN;
	reply.flags = peap_flag;
	reply.data = NULL;
	reply.dlen = 0;

	tls_success(tls_session, request);

	/*
	 *	Call compose AFTER checking for cached data.
	 */
	eaptls_compose(handler->eap_ds, &reply);

	/*
	 *	Automatically generate MPPE keying material.
	 */
	if (tls_session->label) {
		uint8_t const *context = NULL;
		size_t context_size = 0;
#ifdef TLS1_3_VERSION
		uint8_t const context_tls13[] = { handler->type };
#endif

		switch (SSL_version(tls_session->ssl)) {
#ifdef TLS1_3_VERSION
		case TLS1_3_VERSION:
			context = context_tls13;
			context_size = sizeof(context_tls13);
			tls_session->label = "EXPORTER_EAP_TLS_Key_Material";
			break;
#endif
		case TLS1_2_VERSION:
		case TLS1_1_VERSION:
		case TLS1_VERSION:
			break;
		case SSL2_VERSION:
		case SSL3_VERSION:
		default:
			/* Should never happen */
			rad_assert(0);
			return 0;
		}
		eaptls_gen_mppe_keys(request,
				     tls_session->ssl, tls_session->label,
				     context, context_size);
	} else if ((handler->type != PW_EAP_FAST) && (handler->type != PW_EAP_TEAP)) {
		RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label");
	}

	eaptls_gen_eap_key(handler);

	return 1;
}

int eaptls_fail(eap_handler_t *handler, int peap_flag)
{
	EAPTLS_PACKET	reply;
	tls_session_t *tls_session = handler->opaque;

	handler->finished = true;
	reply.code = FR_TLS_FAIL;
	reply.length = TLS_HEADER_LEN;
	reply.flags = peap_flag;
	reply.data = NULL;
	reply.dlen = 0;

	tls_fail(tls_session);

	eaptls_compose(handler->eap_ds, &reply);

	return 1;
}

/*
   A single TLS record may be up to 16384 octets in length, but a TLS
   message may span multiple TLS records, and a TLS certificate message
   may in principle be as long as 16MB.
*/

/*
 *	Frame the Dirty data that needs to be send to the client in an
 *	EAP-Request.  We always embed the TLS-length in all EAP-TLS
 *	packets that we send, for easy reference purpose.  Handle
 *	fragmentation and sending the next fragment etc.
 *
 *	FIXME: support fragmented start due to TEAP outer tlvs
 */
int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn, bool start)
{
	EAPTLS_PACKET	reply;
	unsigned int	size;
	unsigned int 	lbit = 0;
	unsigned int 	obit = 0;
	VALUE_PAIR	*vp;
	vp_cursor_t	cursor;
	uint32_t	nlen;
	uint16_t	ohdr[2];
	uint32_t	olen = 0;
	uint32_t	tls_mtu;

	/* This value determines whether we set (L)ength flag for
		EVERY packet we send and add corresponding
		"TLS Message Length" field.

	length_flag = true;
		This means we include L flag and "TLS Msg Len" in EVERY
		packet we send out.

	length_flag = false;
		This means we include L flag and "TLS Msg Len" **ONLY**
		in First packet of a fragment series. We do not use
		it anywhere else.

		Having L flag in every packet is prefered.

	*/
	if (ssn->length_flag) {
		lbit = 4;
	}

	/*
	 *	This is included in the first fragment, and then never
	 *	afterwards.
	 */
	if (start && ssn->outer_tlvs) {
		for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			if (vp->da->type != PW_TYPE_OCTETS) {
				DEBUG("FIXME Outer-TLV %s is of not type octets", vp->da->name);
				continue;
			}
			obit = 4;
			olen += sizeof(ohdr) + vp->vp_length;
			break;
		}
	}

	if (ssn->fragment == 0) {
		ssn->tls_msg_len = ssn->dirty_out.used;
	}

	reply.code = start ? FR_TLS_START : FR_TLS_REQUEST;
	reply.flags = ssn->peap_flag;
	if (start) reply.flags = SET_START(reply.flags);

	/*
	 *	This is only true for TEAP, so only TEAP has to check the return value of this function.
	 */
	if (lbit + obit + olen >= ssn->mtu) {
		ERROR("fragment_size is too small for outer TLVs");
		return -1;
	}

	tls_mtu = ssn->mtu - lbit - obit - olen;

	/* Send data, NOT more than the FRAGMENT size */
	if (ssn->dirty_out.used > tls_mtu) {
		size = tls_mtu;
		reply.flags = SET_MORE_FRAGMENTS(reply.flags);
		/* Length MUST be included if it is the First Fragment */
		if (ssn->fragment == 0) {
			lbit = 4;
		}
		ssn->fragment = 1;
	} else {
		size = ssn->dirty_out.used;
		ssn->fragment = 0;
	}

	reply.dlen = lbit + obit + size + olen;
	reply.length = TLS_HEADER_LEN + 1/*flags*/ + reply.dlen;

	reply.data = talloc_array(eap_ds, uint8_t, reply.length);
	if (!reply.data) return 0;

	if (lbit) {
		nlen = htonl(ssn->tls_msg_len);
		memcpy(reply.data, &nlen, sizeof(nlen));
		reply.flags = SET_LENGTH_INCLUDED(reply.flags);
	}

	if (obit) {
		nlen = 0;
		for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			if (vp->da->type != PW_TYPE_OCTETS) continue;
			nlen += sizeof(ohdr) + vp->vp_length;
		}

		ssn->outer_tlvs_octets = talloc_array(ssn, uint8_t, olen);
		if (!ssn->outer_tlvs_octets) return 0;

		nlen = htonl(nlen);
		memcpy(reply.data + lbit, &nlen, sizeof(nlen));
		reply.flags = SET_OUTER_TLV_INCLUDED(reply.flags);
	}

	(ssn->record_minus)(&ssn->dirty_out, reply.data + lbit + obit, size);

	/*
	 *	Tack on the outer TLVs after the TLS data.
	 */
	if (obit) {
		olen = 0;
		for (vp = fr_cursor_init(&cursor, &ssn->outer_tlvs);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			if (vp->da->type != PW_TYPE_OCTETS) continue;

			/* FIXME duplicates eap_teap_tlv_append */

			/*
			 *	RFC7170, Section 4.3.1 - Outer TLVs must be marked optional
			 */
			ohdr[0] = htons((vp->da->attr >> fr_attr_shift[1]) & fr_attr_mask[1]);
			ohdr[1] = htons(vp->vp_length);

			/* use by Crypto-Binding TLV */
			memcpy(ssn->outer_tlvs_octets + olen, ohdr, sizeof(ohdr));
			olen += sizeof(ohdr);
			memcpy(ssn->outer_tlvs_octets + olen, vp->vp_octets, vp->vp_length);
			olen += vp->vp_length;

			memcpy(reply.data + lbit + obit + size, ohdr, sizeof(ohdr));
			size += sizeof(ohdr);
			memcpy(reply.data + lbit + obit + size, vp->vp_octets, vp->vp_length);
			size += vp->vp_length;
		}
	}

	eaptls_compose(eap_ds, &reply);
	talloc_free(reply.data);
	reply.data = NULL;

	return 1;
}


/*
 *	Similarly, when the EAP server receives an EAP-Response with
 *	the M bit set, it MUST respond with an EAP-Request with
 *	EAP-Type=EAP-TLS and no data. This serves as a fragment ACK.
 *
 *	In order to prevent errors in the processing of fragments, the
 *	EAP server MUST use increment the Identifier value for each
 *	fragment ACK contained within an EAP-Request, and the peer
 *	MUST include this Identifier value in the subsequent fragment
 *	contained within an EAP- Reponse.
 *
 *	EAP server sends an ACK when it determines there are More
 *	fragments to receive to make the complete
 *	TLS-record/TLS-Message
 */
static int eaptls_send_ack(eap_handler_t *handler, int peap_flag)
{
	EAPTLS_PACKET 	reply;
	REQUEST		*request = handler->request;

	RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data.");
	reply.code = FR_TLS_ACK;
	reply.length = TLS_HEADER_LEN + 1/*flags*/;
	reply.flags = peap_flag;
	reply.data = NULL;
	reply.dlen = 0;

	eaptls_compose(handler->eap_ds, &reply);

	return 1;
}

/*
 *	The S flag is set only within the EAP-TLS start message sent
 *	from the EAP server to the peer.
 *
 *	Similarly, when the EAP server receives an EAP-Response with
 *	the M bit set, it MUST respond with an EAP-Request with
 *	EAP-Type=EAP-TLS and no data. This serves as a fragment
 *	ACK. The EAP peer MUST wait.
 */
static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
{
	EAP_DS			*eap_ds = handler->eap_ds;
	tls_session_t		*tls_session = handler->opaque;
	EAP_DS			*prev_eap_ds = handler->prev_eapds;
	eaptls_packet_t		*eaptls_packet, *eaptls_prev = NULL;
	REQUEST			*request = handler->request;
	size_t			frag_len;

	/*
	 *	We don't check ANY of the input parameters.  It's all
	 *	code which works together, so if something is wrong,
	 *	we SHOULD core dump.
	 *
	 *	e.g. if eap_ds is NULL, of if eap_ds->response is
	 *	NULL, of if it's NOT an EAP-Response, or if the packet
	 *	is too short.  See eap_validation()., in ../../eap.c
	 *
	 *	Also, eap_method_select() takes care of selecting the
	 *	appropriate type, so we don't need to check
	 *	eap_ds->response->type.num == PW_EAP_TLS, or anything
	 *	else.
	 */
	eaptls_packet = (eaptls_packet_t *)eap_ds->response->type.data;
	if (prev_eap_ds && prev_eap_ds->response)
		eaptls_prev = (eaptls_packet_t *)prev_eap_ds->response->type.data;

	if (eaptls_packet) {
		/*
		 *	First output the flags (for debugging)
		 */
		RDEBUG3("(TLS) EAP Peer sent flags %c%c%c",
			TLS_START(eaptls_packet->flags) ? 'S' : '-',
			TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-',
			TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-');
	}

	/*
	 *	check for ACK
	 *
	 *	If there's no TLS data, or there's 1 byte of TLS data,
	 *	with the flags set to zero, then it's an ACK.
	 *
	 *	Find if this is a reply to the previous request sent
	 */
	if ((!eaptls_packet) ||
	    ((eap_ds->response->length == EAP_HEADER_LEN + 2) &&
	     ((eaptls_packet->flags & 0xc0) == 0x00))) {

		if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) {
			return tls_ack_handler(handler->opaque, request);
		} else {
			REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection");
			return FR_TLS_INVALID;
		}
	}

	/*
	 *	We send TLS_START, but do not receive it.
	 */
	if (TLS_START(eaptls_packet->flags)) {
		REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)");
		return FR_TLS_INVALID;
	}

	/*
	 *	Calculate this fragment's length
	 */
	frag_len = eap_ds->response->length -
		   (EAP_HEADER_LEN + (TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 6 : 2));

	/*
	 *	The L bit (length included) is set to indicate the
	 *	presence of the four octet TLS Message Length field,
	 *	and MUST be set for the first fragment of a fragmented
	 *	TLS message or set of messages.
	 *
	 *	The M bit (more fragments) is set on all but the last
	 *	fragment.
	 *
	 *	The S bit (EAP-TLS start) is set in an EAP-TLS Start
	 *	message. This differentiates the EAP-TLS Start message
	 *	from a fragment acknowledgement.
	 */
	if (TLS_LENGTH_INCLUDED(eaptls_packet->flags)) {
		/*
		 *	data[0] and data[1] are always zero, vi eap_vp2packet()
		 */
		size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3];

		if (frag_len > total_len) {
			RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len,
				total_len);
		}

		RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len);
		if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
			/*
			 *	The supplicant is free to send fragments of wildly varying
			 *	lengths, but the vast majority won't.
			 *
			 *	In this calculation we take into account the fact that the future
			 *	fragments are likely to be 4 bytes larger than the initial one
			 *	as they won't contain the length field.
			 */
			if (frag_len + 4) {	/* check for wrap, else clang scan gets excited */
				RDEBUG2("(TLS) EAP Expecting %i fragments",
					(int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1));
			}

			/*
			 *	FIRST_FRAGMENT is identified
			 *	1. If there is no previous EAP-response received.
			 *	2. If EAP-response received, then its M bit not set.
			 * 	   (It is because Last fragment will not have M bit set)
			 */
			if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) ||
			    !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) {
			    	RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes).  Peer says more fragments "
			    		"will follow", frag_len);
			    	tls_session->tls_record_in_total_len = total_len;
			    	tls_session->tls_record_in_recvd_len = frag_len;

				return FR_TLS_FIRST_FRAGMENT;
			}

			/*
			 *	The "O" bit is only allowed for the first fragment.
			 */
			if (TLS_OUTER_TLV_INCLUDED(eaptls_packet->flags)) {
				REDEBUG("(TLS) EAP Peer set 'O' bit after initial fragment");
				return FR_TLS_INVALID;
			}

			RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes).  "
				"Peer says more fragments will follow", frag_len);

			/*
			 *	Check we've not exceeded the originally indicated TLS record size.
			 */
			tls_session->tls_record_in_recvd_len += frag_len;
			if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
				RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
					"total data length (%zu bytes)", frag_len, total_len);
			}

			return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH;
		}

		/*
		 *	If it's a complete record, our fragment size should match the
		 *	value of the four octet TLS length field.
		 */
		if (total_len != frag_len) {
			RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) "
				"does not match expected data length (%zu bytes)", total_len, frag_len);
		}

		tls_session->tls_record_in_total_len = total_len;
		tls_session->tls_record_in_recvd_len = frag_len;
		RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len);
		return FR_TLS_LENGTH_INCLUDED;
	}

	/*
	 *	eap_vp2packet() ensures that the 'O' bit is not set here.
	 */

	/*
	 *	The previous packet had the M flags set, but this one doesn't,
	 *	this must be the final record fragment
	 */
	if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
		RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len);
		tls_session->tls_record_in_recvd_len += frag_len;
		if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) {
			RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected "
				"expected data length (%zu bytes)",
				tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
		}
	}

	if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
		RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes).  Peer says more fragments will follow",
			frag_len);
		tls_session->tls_record_in_recvd_len += frag_len;
		if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) {
			RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds "
				"expected length (%zu bytes)",
				tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len);
		}
		return FR_TLS_MORE_FRAGMENTS;
	}

	/*
	 *	None of the flags are set, but it's still a valid EAP-TLS packet.
	 */
	return FR_TLS_OK;
}

/*
 * EAPTLS_PACKET
 * code    = EAP-code
 * id      = EAP-id
 * length  = code + id + length + flags + tlsdata
 *	   =  1   +  1 +   2    +  1    +  X
 * length  = EAP-length - 1(EAP-Type = 1 octet)
 * flags   = EAP-typedata[0] (1 octet)
 * dlen    = EAP-typedata[1-4] (4 octets), if L flag set
 *	   = length - 5(code+id+length+flags), otherwise
 * data    = EAP-typedata[5-n], if L flag set
 *	   = EAP-typedata[1-n], otherwise
 * packet  = EAP-typedata (complete typedata)
 *
 * Points to consider during EAP-TLS data extraction
 * 1. In the received packet, No data will be present incase of ACK-NAK
 * 2. Incase if more fragments need to be received then ACK after retreiving this fragment.
 *
 *  RFC 2716 Section 4.2.  PPP EAP TLS Request Packet
 *
 *  0		   1		   2		   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |     Code      |   Identifier  |	    Length	     |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |     Type      |     Flags     |      TLS Message Length
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |     TLS Message Length	|       TLS Data...
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 *  The Length field is two octets and indicates the length of the EAP
 *  packet including the Code, Identifir, Length, Type, and TLS data
 *  fields.
 */
static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_status_t status)
{
	EAPTLS_PACKET	*tlspacket;
	uint32_t	data_len = 0;
	uint32_t	obit = 0;
	uint8_t		*data = NULL;

	if (status == FR_TLS_INVALID) return NULL;

	/*
	 *	The main EAP code & eaptls_verify() take care of
	 *	ensuring that the packet is OK, and that we can
	 *	extract the various fields we want.
	 *
	 *	e.g. a TLS packet with zero data is allowed as an ACK,
	 *	but we will never see it here, as we will simply
	 *	send another fragment, instead of trying to extract
	 *	the data.
	 *
	 *	MUST have TLS type octet, followed by flags, followed
	 *	by data.
	 */
	assert(eap_ds->response->length > 2);

	tlspacket = talloc(eap_ds, EAPTLS_PACKET);
	if (!tlspacket) return NULL;

	/*
	 *	Code & id for EAPTLS & EAP are same
	 *	but eaptls_length = eap_length - 1(EAP-Type = 1 octet)
	 *
	 *	length = code + id + length + type + tlsdata
	 *	       =  1   +  1 +   2    +  1    +  X
	 */
	tlspacket->code = eap_ds->response->code;
	tlspacket->id = eap_ds->response->id;
	tlspacket->length = eap_ds->response->length - 1; /* EAP type */
	tlspacket->flags = eap_ds->response->type.data[0];

	/*
	 *	eaptls_verify() ensures that all of the flags are correct.
	 */
	switch (status) {
	/*
	 *	The TLS Message Length field is four octets, and
	 *	provides the total length of the TLS message or set of
	 *	messages that is being fragmented; this simplifies
	 *	buffer allocation.
	 *
	 *	Dynamic allocation of buffers as & when we know the
	 *	length should solve the problem.
	 */
	case FR_TLS_FIRST_FRAGMENT:
		obit = TLS_OUTER_TLV_INCLUDED(tlspacket->flags) << 2;

		/*
		 *	@todo - decode outer TLVs, too
		 */

		/* FALL-THROUGH */

	case FR_TLS_LENGTH_INCLUDED:
	case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH:
		eap_ds->response->type.data += 4 + obit;
		eap_ds->response->type.length -= 4 + obit;

		/* FALL-THROUGH */

		/*
		 *	Data length is implicit, from the EAP header.
		 */
	case FR_TLS_MORE_FRAGMENTS:
	case FR_TLS_OK:
		data_len = eap_ds->response->type.length - 1;
		data = eap_ds->response->type.data + 1;
		break;

	default:
		REDEBUG("(TLS) EAP Invalid packet received");
		talloc_free(tlspacket);
		return NULL;
	}

	tlspacket->dlen = data_len;
	if (data_len) {
		tlspacket->data = talloc_array(tlspacket, uint8_t,
					       data_len);
		if (!tlspacket->data) {
			talloc_free(tlspacket);
			return NULL;
		}

		memcpy(tlspacket->data, data, data_len);
	}

	return tlspacket;
}



/*
 * To process the TLS,
 *  INCOMING DATA:
 * 	1. EAP-TLS should get the compelete TLS data from the peer.
 * 	2. Store that data in a data structure with any other required info
 *	3. Handle that data structure to the TLS module.
 *	4. TLS module will perform its operations on the data and
 *	handle back to EAP-TLS
 *
 *  OUTGOING DATA:
 * 	1. EAP-TLS if necessary will fragment it and send it to the
 * 	destination.
 *
 *	During EAP-TLS initialization, TLS Context object will be
 *	initialized and stored.  For every new authentication
 *	requests, TLS will open a new session object and that session
 *	object should be maintained even after the session is
 *	completed for session resumption. (Probably later as a feature
 *	as we donot know who maintains these session objects ie,
 *	SSL_CTX (internally) or TLS module(explicitly). If TLS module,
 *	then how to let SSL API know about these sessions.)
 */
static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *handler)
{
	REQUEST		*request = handler->request;
	tls_session_t	*tls_session = handler->opaque;

	if ((status == FR_TLS_MORE_FRAGMENTS) ||
	    (status == FR_TLS_MORE_FRAGMENTS_WITH_LENGTH) ||
	    (status == FR_TLS_FIRST_FRAGMENT)) {
		/*
		 *	Send the ACK.
		 */
		eaptls_send_ack(handler, tls_session->peap_flag);
		return FR_TLS_HANDLED;

	}

	/*
	 *	We have the complete TLS-data or TLS-message.
	 *
	 *	Clean the dirty message.
	 *
	 *	Authenticate the user and send
	 *	Success/Failure.
	 *
	 *	If more info
	 *	is required then send another request.
	 */
	if (!tls_handshake_recv(handler->request, tls_session)) {
		REDEBUG("(TLS) EAP Receive handshake failed during operation");
		tls_fail(tls_session);
		return FR_TLS_FAIL;
	}

#ifdef TLS1_3_VERSION
	/*
	 *	https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5
	 *
	 *	We need to signal the other end that TLS negotiation
	 *	is done.  We can't send a zero-length application data
	 *	message, so we send application data which is one byte
	 *	of zero.
	 *
	 *	Note this is only done for when there is no application
	 *	data to be sent. So this is done always for EAP-TLS but
	 *	notibly not for PEAP even on resumption.
	 */
	if ((SSL_version(tls_session->ssl) == TLS1_3_VERSION) &&
	    (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) {
		if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) {
			tls_session->authentication_success = true;

			RDEBUG("(TLS) EAP Sending final Commitment Message.");
			tls_session->record_plus(&tls_session->clean_in, "\0", 1);
		}

		tls_handshake_send(request, tls_session);
	}
#endif

	/*
	 *	FIXME: return success/fail.
	 *
	 *	TLS proper can decide what to do, then.
	 */
	if (tls_session->dirty_out.used > 0) {
		eaptls_request(handler->eap_ds, tls_session, false);
		return FR_TLS_HANDLED;
	}

	/*
	 *	If there is no data to send i.e
	 *	dirty_out.used <=0 and if the SSL
	 *	handshake is finished.
	 */
	if (tls_session->is_init_finished) return FR_TLS_SUCCESS;

	/*
	 *	If session is established, skip round-trip and
	 *	try to process any inner tunnel data if present.
	 *
	 *	This occurs for EAP-TTLS/PAP with TLSv1.3.
	 */
	if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) {
		/*
		 *	Don't set is_init_finished, as that causes the
		 *	rest of the code to make too many assumptions.
		 */
		return FR_TLS_OK;
	}

	/*
	 *	Who knows what happened...
	 */
	REDEBUG("(TLS) Cannot continue, as the peer is misbehaving.");
	return FR_TLS_FAIL;
}


/*
 * In the actual authentication first verify the packet and then create the data structure
 */
/*
 * To process the TLS,
 *  INCOMING DATA:
 * 	1. EAP-TLS should get the compelete TLS data from the peer.
 * 	2. Store that data in a data structure with any other required info
 *	3. Hand this data structure to the TLS module.
 *	4. TLS module will perform its operations on the data and hands back to EAP-TLS
 *  OUTGOING DATA:
 * 	1. EAP-TLS if necessary will fragment it and send it to the destination.
 *
 *	During EAP-TLS initialization, TLS Context object will be
 *	initialized and stored.  For every new authentication
 *	requests, TLS will open a new session object and that
 *	session object SHOULD be maintained even after the session
 *	is completed, for session resumption. (Probably later as a
 *	feature, as we do not know who maintains these session
 *	objects ie, SSL_CTX (internally) or TLS module (explicitly). If
 *	TLS module, then how to let SSL API know about these
 *	sessions.)
 */

/*
 *	Process an EAP request
 */
fr_tls_status_t eaptls_process(eap_handler_t *handler)
{
	tls_session_t *tls_session = (tls_session_t *) handler->opaque;
	EAPTLS_PACKET	*tlspacket;
	fr_tls_status_t	status;
	REQUEST *request = handler->request;

	if (!request) return FR_TLS_FAIL;

	RDEBUG3("(TLS) EAP Continuing ...");

	SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request);

	if (handler->certs) fr_pair_add(&request->packet->vps,
				    fr_pair_list_copy(request->packet, handler->certs));

	/*
	 *	This case is when SSL generates Alert then we
	 *	send that alert to the client and then send the EAP-Failure
	 */
	status = eaptls_verify(handler);
	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
		REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
	} else {
		RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
	}

	switch (status) {
	default:
	case FR_TLS_INVALID:
	case FR_TLS_FAIL:

	/*
	 *	Success means that we're done the initial
	 *	handshake.  For TTLS, this means send stuff
	 *	back to the client, and the client sends us
	 *	more tunneled data.
	 */
	case FR_TLS_SUCCESS:
		goto done;

	/*
	 *	Normal TLS request, continue with the "get rest
	 *	of fragments" phase.
	 */
	case FR_TLS_REQUEST:
		eaptls_request(handler->eap_ds, tls_session, false);
		status = FR_TLS_HANDLED;
		goto done;

	/*
	 *	The handshake is done, and we're in the "tunnel
	 *	data" phase.
	 */
	case FR_TLS_OK:
		RDEBUG2("(TLS) EAP Done initial handshake");

	/*
	 *	Get the rest of the fragments.
	 */
	case FR_TLS_FIRST_FRAGMENT:
	case FR_TLS_MORE_FRAGMENTS:
	case FR_TLS_LENGTH_INCLUDED:
	case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH:
		break;
	}

	/*
	 *	Extract the TLS packet from the buffer.
	 */
	if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) {
		REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message");
		status = FR_TLS_FAIL;
		goto done;
	}

	/*
	 *	Get the session struct from the handler
	 *
	 *	update the dirty_in buffer
	 *
	 *	NOTE: This buffer will contain partial data when M bit is set.
	 *
	 * 	CAUTION while reinitializing this buffer, it should be
	 * 	reinitialized only when this M bit is NOT set.
	 */
	if (tlspacket->dlen !=
	    (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
		talloc_free(tlspacket);
		REDEBUG("(TLS) EAP Exceeded maximum record size");
		status = FR_TLS_FAIL;
		goto done;
	}

	/*
	 *	No longer needed.
	 */
	talloc_free(tlspacket);

	/*
	 *	SSL initalization is done.  Return.
	 *
	 *	The TLS data will be in the tls_session structure.
	 */
	if (tls_session->is_init_finished) {
		/*
		 *	The initialization may be finished, but if
		 *	there more fragments coming, then send ACK,
		 *	and get the caller to continue the
		 *	conversation.
		 */
		if ((status == FR_TLS_MORE_FRAGMENTS) ||
		    (status == FR_TLS_MORE_FRAGMENTS_WITH_LENGTH) ||
		    (status == FR_TLS_FIRST_FRAGMENT)) {
			/*
			 *	Send the ACK.
			 */
			eaptls_send_ack(handler, tls_session->peap_flag);
			RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented");
			status = FR_TLS_HANDLED;
			goto done;
		}

		status = tls_application_data(tls_session, request);
		goto done;
	}

	/*
	 *	Continue the handshake.
	 */
	status = eaptls_operation(status, handler);
	if (status == FR_TLS_SUCCESS) {
#define MAX_SESSION_SIZE (256)
		VALUE_PAIR *vps;
		char buffer[2 * MAX_SESSION_SIZE + 1];

		/*
		 *	Restore the cached VPs before processing the
		 *	application data.
		 */
		tls_session_id(tls_session->ssl_session, buffer, MAX_SESSION_SIZE);

		vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps);
		if (!vps) {
			RWDEBUG("(TLS) EAP No information in cached session %s", buffer);
		} else {
			vp_cursor_t cursor;
			VALUE_PAIR *vp;
			fr_tls_server_conf_t *conf;

			RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer);

			conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF);
			rad_assert(conf != NULL);

			/*
			 *	The cbtls_get_session() function doesn't have
			 *	access to sock->certs or handler->certs, which
			 *	is where the certificates normally live.  So
			 *	the certs are all in the VPS list here, and
			 *	have to be manually extracted.
			 */
			RINDENT();
			for (vp = fr_cursor_init(&cursor, &vps);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				if (conf->cache_ht && fr_hash_table_finddata(conf->cache_ht, vp->da)) {
					rdebug_pair(L_DBG_LVL_2, request, vp, "&session-state:");
					fr_pair_add(&request->state, fr_pair_copy(request->state_ctx, vp));
					continue;
				}

				/*
				 *	TLS-* attrs get added back to
				 *	the request list.
				 */
				if ((vp->da->vendor == 0) &&
				    (vp->da->attr >= PW_TLS_CERT_SERIAL) &&
				    (vp->da->attr <= PW_TLS_CLIENT_CERT_SUBJECT_ALT_NAME_UPN)) {
					/*
					 *	Certs already exist.  Don't re-add them.
					 */
					if (!handler->certs) {
						rdebug_pair(L_DBG_LVL_2, request, vp, "&request:");
						fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp));
					}

				} else if ((vp->da->vendor == 0) &&
					   (vp->da->attr == PW_EAP_TYPE)) {
					/*
					 *	EAP-Type gets added to
					 *	the control list, so
					 *	that we can sanity check it.
					 */
					rdebug_pair(L_DBG_LVL_2, request, vp, "&control:");
					fr_pair_add(&request->config, fr_pair_copy(request, vp));

				} else {

					rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:");
					fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
				}
			}
			REXDENT();
		}
	}

 done:
	SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, NULL);

	return status;
}


/*
 *	compose the TLS reply packet in the EAP reply typedata
 */
int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply)
{
	uint8_t *ptr;

	/*
	 *	Don't set eap_ds->request->type.num, as the main EAP
	 *	handler will do that for us.  This allows the TLS
	 *	module to be called from TTLS & PEAP.
	 */

	/*
	 * 	When the EAP server receives an EAP-Response with the
	 * 	M bit set, it MUST respond with an EAP-Request with
	 * 	EAP-Type=EAP-TLS and no data. This serves as a
	 * 	fragment ACK. The EAP peer MUST wait until it receives
	 * 	the EAP-Request before sending another fragment.
	 *
	 *	In order to prevent errors in the processing of
	 *	fragments, the EAP server MUST use increment the
	 *	Identifier value for each fragment ACK contained
	 *	within an EAP-Request, and the peer MUST include this
	 *	Identifier value in the subsequent fragment contained
	 *	within an EAP- Reponse.
	 */
	eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t,
						  reply->length - TLS_HEADER_LEN + 1);
	if (!eap_ds->request->type.data) return 0;

	/* EAPTLS Header length is excluded while computing EAP typelen */
	eap_ds->request->type.length = reply->length - TLS_HEADER_LEN;

	ptr = eap_ds->request->type.data;
	*ptr++ = (uint8_t)(reply->flags & 0xFF);

	if (reply->dlen) memcpy(ptr, reply->data, reply->dlen);

	switch (reply->code) {
	case FR_TLS_ACK:
	case FR_TLS_START:
	case FR_TLS_REQUEST:
		eap_ds->request->code = PW_EAP_REQUEST;
		break;

	case FR_TLS_SUCCESS:
		eap_ds->request->code = PW_EAP_SUCCESS;
		break;

	case FR_TLS_FAIL:
		eap_ds->request->code = PW_EAP_FAILURE;
		break;

	default:
		/* Should never enter here */
		rad_assert(0);
		break;
	}

	return 1;
}

/*
 *	Parse TLS configuration
 *
 *	If the option given by 'attr' is set, we find the config section
 *	of that name and use that for the TLS configuration. If not, we
 *	fall back to compatibility mode and read the TLS options from
 *	the 'tls' section.
 */
fr_tls_server_conf_t *eaptls_conf_parse(CONF_SECTION *cs, char const *attr)
{
	char const 		*tls_conf_name;
	CONF_PAIR		*cp;
	CONF_SECTION		*parent;
	CONF_SECTION		*tls_cs;
	fr_tls_server_conf_t	*tls_conf;

	if (!cs)
		return NULL;

	rad_assert(attr != NULL);

	parent = cf_item_parent(cf_section_to_item(cs));

	cp = cf_pair_find(cs, attr);
	if (cp) {
		tls_conf_name = cf_pair_value(cp);

		tls_cs = cf_section_sub_find_name2(parent, TLS_CONFIG_SECTION, tls_conf_name);

		if (!tls_cs) {
			ERROR("Cannot find tls config \"%s\"", tls_conf_name);
			return NULL;
		}
	} else {
		/*
		 *	If we can't find the section given by the 'attr', we
		 *	fall-back to looking for the "tls" section, as in
		 *	previous versions.
		 *
		 *	We don't fall back if the 'attr' is specified, but we can't
		 *	find the section - that is just a config error.
		 */
		INFO("TLS section \"%s\" missing, trying to use legacy configuration", attr);
		tls_cs = cf_section_sub_find(parent, "tls");
	}

	if (!tls_cs)
		return NULL;

	tls_conf = tls_server_conf_parse(tls_cs);

	if (!tls_conf)
		return NULL;

	/*
	 *	The EAP RFC's say 1020, but we're less picky.
	 */
	if (tls_conf->fragment_size < 100) {
		ERROR("Configured fragment size is too small, must be >= 100");
		return NULL;
	}

	/*
	 *	The maximum size for a RADIUS packet is 4096,
	 *	minus the header (20), Message-Authenticator (18),
	 *	and State (18), etc. results in about 4000 bytes of data
	 *	that can be devoted *solely* to EAP.
	 */
	if (tls_conf->fragment_size > 4000) {
		ERROR("Configured fragment size is too large, must be <= 4000");
		return NULL;
	}

	/*
	 *	Account for the EAP header (4), and the EAP-TLS header
	 *	(6), as per Section 4.2 of RFC 2716.  What's left is
	 *	the maximum amount of data we read from a TLS buffer.
	 */
	tls_conf->fragment_size -= 10;

	return tls_conf;
}

