*
* TLS Packet Format in EAP
* --- ------ ------ -- ---
- * 0 1 2 3
+ * 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 |
+ * | Code | Identifier | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Flags | TLS Message Length
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | TLS Message Length | TLS Data...
+ * | TLS Message Length | TLS Data...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
*/
-#include <freeradius-devel/ident.h>
RCSID("$Id$")
+USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
-#include <freeradius-devel/autoconf.h>
#include <assert.h>
-#include "eap_tls.h"
+#include "eap_tls.h"
/*
- * Allocate a new TLS_PACKET
+ * 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.
*/
-EAPTLS_PACKET *eaptls_alloc(void)
+tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert)
{
- EAPTLS_PACKET *rp;
+ tls_session_t *ssn;
+ REQUEST *request = handler->request;
+
+ handler->tls = true;
- if ((rp = malloc(sizeof(EAPTLS_PACKET))) == NULL) {
- radlog(L_ERR, "rlm_eap_tls: out of memory");
+ /*
+ * 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);
+ if (!ssn) {
return NULL;
}
- memset(rp, 0, sizeof(EAPTLS_PACKET));
- return rp;
-}
-
-/*
- * Free EAPTLS_PACKET
- */
-void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr)
-{
- EAPTLS_PACKET *eaptls_packet;
- if (!eaptls_packet_ptr) return;
- eaptls_packet = *eaptls_packet_ptr;
- if (eaptls_packet == NULL) return;
-
- if (eaptls_packet->data) {
- free(eaptls_packet->data);
- eaptls_packet->data = 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, NULL);
- free(eaptls_packet);
- *eaptls_packet_ptr = NULL;
+ return talloc_steal(handler, ssn); /* ssn */
}
/*
{
EAPTLS_PACKET reply;
- reply.code = EAPTLS_START;
+ reply.code = FR_TLS_START;
reply.length = TLS_HEADER_LEN + 1/*flags*/;
reply.flags = peap_flag;
return 1;
}
-int eaptls_success(EAP_HANDLER *handler, int peap_flag)
+int eaptls_success(eap_handler_t *handler, int peap_flag)
{
EAPTLS_PACKET reply;
- VALUE_PAIR *vp, *vps = NULL;
REQUEST *request = handler->request;
tls_session_t *tls_session = handler->opaque;
- reply.code = EAPTLS_SUCCESS;
+ handler->finished = true;
+ reply.code = FR_TLS_SUCCESS;
reply.length = TLS_HEADER_LEN;
reply.flags = peap_flag;
reply.data = NULL;
reply.dlen = 0;
- /*
- * If there's no session resumption, delete the entry
- * from the cache. This means either it's disabled
- * globally for this SSL context, OR we were told to
- * disable it for this user.
- *
- * This also means you can't turn it on just for one
- * user.
- */
- if ((!tls_session->allow_session_resumption) ||
- (((vp = pairfind(request->config_items, 1127)) != NULL) &&
- (vp->vp_integer == 0))) {
- SSL_CTX_remove_session(tls_session->ctx,
- tls_session->ssl->session);
- tls_session->allow_session_resumption = 0;
-
- /*
- * If we're in a resumed session and it's
- * not allowed,
- */
- if (SSL_session_reused(tls_session->ssl)) {
- RDEBUG("FAIL: Forcibly stopping session resumption as it is not allowed.");
- return eaptls_fail(handler, peap_flag);
- }
-
- /*
- * Else resumption IS allowed, so we store the
- * user data in the cache.
- */
- } else if (!SSL_session_reused(tls_session->ssl)) {
- RDEBUG2("Saving response in the cache");
-
- vp = paircopy2(request->reply->vps, PW_USER_NAME);
- pairadd(&vps, vp);
-
- vp = paircopy2(request->packet->vps, PW_STRIPPED_USER_NAME);
- pairadd(&vps, vp);
-
- if (vps) {
- SSL_SESSION_set_ex_data(tls_session->ssl->session,
- eaptls_session_idx, vps);
- }
-
- /*
- * Else the session WAS allowed. Copy the cached
- * reply.
- */
- } else {
-
- vp = SSL_SESSION_get_ex_data(tls_session->ssl->session,
- eaptls_session_idx);
- if (!vp) {
- RDEBUG("WARNING: No information in cached session!");
- } else {
- RDEBUG("Adding cached attributes to the reply:");
- debug_pair_list(vp);
- pairadd(&request->reply->vps, paircopy(vp));
-
- /*
- * Mark the request as resumed.
- */
- vp = pairmake("EAP-Session-Resumed", "0", T_OP_SET);
- if (vp) pairadd(&request->packet->vps, vp);
- }
- }
+ tls_success(tls_session, request);
/*
* Call compose AFTER checking for cached data.
* Automatically generate MPPE keying material.
*/
if (tls_session->prf_label) {
- eaptls_gen_mppe_keys(&handler->request->reply->vps,
+ eaptls_gen_mppe_keys(handler->request,
tls_session->ssl, tls_session->prf_label);
} else {
- RDEBUG("WARNING: Not adding MPPE keys because there is no PRF label");
+ RWDEBUG("Not adding MPPE keys because there is no PRF label");
}
+ eaptls_gen_eap_key(handler->request->reply, tls_session->ssl,
+ handler->type);
return 1;
}
-int eaptls_fail(EAP_HANDLER *handler, int peap_flag)
+int eaptls_fail(eap_handler_t *handler, int peap_flag)
{
EAPTLS_PACKET reply;
tls_session_t *tls_session = handler->opaque;
- reply.code = EAPTLS_FAIL;
+ handler->finished = true;
+ reply.code = FR_TLS_FAIL;
reply.length = TLS_HEADER_LEN;
reply.flags = peap_flag;
reply.data = NULL;
reply.dlen = 0;
- /*
- * Force the session to NOT be cached.
- */
- SSL_CTX_remove_session(tls_session->ctx, tls_session->ssl->session);
+ tls_fail(tls_session);
eaptls_compose(handler->eap_ds, &reply);
EVERY packet we send and add corresponding
"TLS Message Length" field.
- length_flag = TRUE;
+ length_flag = true;
This means we include L flag and "TLS Msg Len" in EVERY
packet we send out.
- length_flag = FALSE;
+ 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.
ssn->tls_msg_len = ssn->dirty_out.used;
}
- reply.code = EAPTLS_REQUEST;
+ reply.code = FR_TLS_REQUEST;
reply.flags = ssn->peap_flag;
/* Send data, NOT more than the FRAGMENT size */
- if (ssn->dirty_out.used > ssn->offset) {
- size = ssn->offset;
+ if (ssn->dirty_out.used > ssn->mtu) {
+ size = ssn->mtu;
reply.flags = SET_MORE_FRAGMENTS(reply.flags);
/* Length MUST be included if it is the First Fragment */
if (ssn->fragment == 0) {
reply.dlen = lbit + size;
reply.length = TLS_HEADER_LEN + 1/*flags*/ + reply.dlen;
- reply.data = malloc(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, lbit);
(ssn->record_minus)(&ssn->dirty_out, reply.data + lbit, size);
eaptls_compose(eap_ds, &reply);
- free(reply.data);
+ talloc_free(reply.data);
reply.data = NULL;
return 1;
}
-/*
- * Acknowledge received is for one of the following messages sent earlier
- * 1. Handshake completed Message, so now send, EAP-Success
- * 2. Alert Message, now send, EAP-Failure
- * 3. Fragment Message, now send, next Fragment
- */
-static eaptls_status_t eaptls_ack_handler(EAP_HANDLER *handler)
-{
- tls_session_t *tls_session;
- REQUEST *request = handler->request;
-
- tls_session = (tls_session_t *)handler->opaque;
- if (tls_session == NULL){
- radlog_request(L_ERR, 0, request, "FAIL: Unexpected ACK received. Could not obtain session information.");
- return EAPTLS_FAIL;
- }
- if (tls_session->info.initialized == 0) {
- RDEBUG("No SSL info available. Waiting for more SSL data.");
- return EAPTLS_REQUEST;
- }
- if ((tls_session->info.content_type == handshake) &&
- (tls_session->info.origin == 0)) {
- radlog_request(L_ERR, 0, request, "FAIL: ACK without earlier message.");
- return EAPTLS_FAIL;
- }
-
- switch (tls_session->info.content_type) {
- case alert:
- RDEBUG2("ACK alert");
- eaptls_fail(handler, tls_session->peap_flag);
- return EAPTLS_FAIL;
-
- case handshake:
- if (tls_session->info.handshake_type == finished) {
- RDEBUG2("ACK handshake is finished");
-
- /*
- * From now on all the content is
- * application data set it here as nobody else
- * sets it.
- */
- tls_session->info.content_type = application_data;
- return EAPTLS_SUCCESS;
- } /* else more data to send */
-
- RDEBUG2("ACK handshake fragment handler");
- /* Fragmentation handler, send next fragment */
- return EAPTLS_REQUEST;
-
- case application_data:
- RDEBUG2("ACK handshake fragment handler in application data");
- return EAPTLS_REQUEST;
-
- /*
- * For the rest of the conditions, switch over
- * to the default section below.
- */
- default:
- RDEBUG2("ACK default");
- radlog_request(L_ERR, 0, request, "Invalid ACK received: %d",
- tls_session->info.content_type);
- return EAPTLS_FAIL;
- }
-}
/*
* Similarly, when the EAP server receives an EAP-Response with
* fragments to receive to make the complete
* TLS-record/TLS-Message
*/
-static int eaptls_send_ack(EAP_DS *eap_ds, int peap_flag)
+static int eaptls_send_ack(eap_handler_t *handler, int peap_flag)
{
EAPTLS_PACKET reply;
+ REQUEST *request = handler->request;
- reply.code = EAPTLS_ACK;
+ RDEBUG2("ACKing Peer's TLS record fragment");
+ reply.code = FR_TLS_ACK;
reply.length = TLS_HEADER_LEN + 1/*flags*/;
reply.flags = peap_flag;
reply.data = NULL;
reply.dlen = 0;
- eaptls_compose(eap_ds, &reply);
+ eaptls_compose(handler->eap_ds, &reply);
return 1;
}
* EAP-Type=EAP-TLS and no data. This serves as a fragment
* ACK. The EAP peer MUST wait.
*/
-static eaptls_status_t eaptls_verify(EAP_HANDLER *handler)
+static fr_tls_status_t eaptls_verify(eap_handler_t *handler)
{
- EAP_DS *eap_ds = handler->eap_ds;
- EAP_DS *prev_eap_ds = handler->prev_eapds;
- eaptls_packet_t *eaptls_packet, *eaptls_prev = NULL;
- REQUEST *request = handler->request;
+ 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
* NULL, of if it's NOT an EAP-Response, or if the packet
* is too short. See eap_validation()., in ../../eap.c
*
- * Also, eaptype_select() takes care of selecting the
+ * Also, eap_method_select() takes care of selecting the
* appropriate type, so we don't need to check
- * eap_ds->response->type.type == PW_EAP_TLS, or anything
+ * 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("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
*
*
* Find if this is a reply to the previous request sent
*/
- if ((eaptls_packet == NULL) ||
+ if ((!eaptls_packet) ||
((eap_ds->response->length == EAP_HEADER_LEN + 2) &&
((eaptls_packet->flags & 0xc0) == 0x00))) {
-#if 0
- /*
- * Un-comment this for TLS inside of TTLS/PEAP
- */
- RDEBUG2("Received EAP-TLS ACK message");
- return eaptls_ack_handler(handler);
-#else
- if (prev_eap_ds->request->id == eap_ds->response->id) {
- /*
- * Run the ACK handler directly from here.
- */
- RDEBUG2("Received TLS ACK");
- return eaptls_ack_handler(handler);
+ if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) {
+ return tls_ack_handler(handler->opaque, request);
} else {
- radlog_request(L_ERR, 0, request, "Received Invalid TLS ACK");
- return EAPTLS_INVALID;
+ REDEBUG("Received Invalid TLS ACK");
+ return FR_TLS_INVALID;
}
-#endif
}
/*
* We send TLS_START, but do not receive it.
*/
if (TLS_START(eaptls_packet->flags)) {
- RDEBUG("Received unexpected EAP-TLS Start message");
- return EAPTLS_INVALID;
+ REDEBUG("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
* from a fragment acknowledgement.
*/
if (TLS_LENGTH_INCLUDED(eaptls_packet->flags)) {
- DEBUG2(" TLS Length %d",
- eaptls_packet->data[2] * 256 | eaptls_packet->data[3]);
+ size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3];
+
+ if (frag_len > total_len) {
+ RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len,
+ total_len);
+ }
+
+ RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len);
if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
/*
- * 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)
+ * 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 (!prev_eap_ds ||
- (prev_eap_ds->response == NULL) ||
- (eaptls_prev == NULL) ||
+ if (frag_len + 4) { /* check for wrap, else clang scan gets excited */
+ RDEBUG2("Expecting %i TLS record 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("Got first TLS record fragment (%zu bytes). Peer indicated more fragments "
+ "to 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;
+ }
- RDEBUG2("Received EAP-TLS First Fragment of the message");
- return EAPTLS_FIRST_FRAGMENT;
- } else {
+ RDEBUG2("Got additional TLS record fragment with length (%zu bytes). "
+ "Peer indicated more fragments to follow", frag_len);
- RDEBUG2("More Fragments with length included");
- return EAPTLS_MORE_FRAGMENTS_WITH_LENGTH;
+ /*
+ * 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("Total received TLS record fragments (%zu bytes), exceeds "
+ "total TLS record length (%zu bytes)", frag_len, total_len);
}
- } else {
- RDEBUG2("Length Included");
- return EAPTLS_LENGTH_INCLUDED;
+
+ 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("Peer indicated no more fragments, but TLS record length (%zu bytes) "
+ "does not match EAP-TLS 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("Got complete TLS record (%zu bytes)", frag_len);
+ return FR_TLS_LENGTH_INCLUDED;
+ }
+
+ /*
+ * 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("Got final TLS record 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("Total received TLS record fragments (%zu bytes), does not equal indicated "
+ "TLS record 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("More fragments to follow");
- return EAPTLS_MORE_FRAGMENTS;
+ RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to 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("Total received TLS record fragments (%zu bytes), exceeds "
+ "indicated TLS record 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
- * EAPTLS packet.
+ * None of the flags are set, but it's still a valid EAP-TLS packet.
*/
- return EAPTLS_OK;
+ 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)
+ * 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
*
* RFC 2716 Section 4.2. PPP EAP TLS Request Packet
*
- * 0 1 2 3
+ * 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 |
+ * | Code | Identifier | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Flags | TLS Message Length
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | TLS Message Length | TLS Data...
+ * | 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, eaptls_status_t status)
+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 len = 0;
uint8_t *data = NULL;
- if (status == EAPTLS_INVALID)
- return NULL;
+ if (status == FR_TLS_INVALID) return NULL;
/*
* The main EAP code & eaptls_verify() take care of
*/
assert(eap_ds->response->length > 2);
- tlspacket = eaptls_alloc();
- if (tlspacket == NULL) return NULL;
+ tlspacket = talloc(eap_ds, EAPTLS_PACKET);
+ if (!tlspacket) return NULL;
/*
* Code & id for EAPTLS & EAP are same
*/
if (TLS_LENGTH_INCLUDED(tlspacket->flags) &&
(tlspacket->length < 5)) { /* flags + TLS message length */
- RDEBUG("Invalid EAP-TLS packet received. (Length bit is set, but no length was found.)");
- eaptls_free(&tlspacket);
+ REDEBUG("Invalid EAP-TLS packet received: Length bit is set, "
+ "but packet too short to contain length field");
+ talloc_free(tlspacket);
return NULL;
}
*
* Likewise, if the EAP packet says N bytes, and the TLS
* packet says there's fewer bytes, it's a problem.
- *
- * FIXME: Try to ensure that the claimed length is
- * consistent across multiple TLS fragments.
*/
if (TLS_LENGTH_INCLUDED(tlspacket->flags)) {
memcpy(&data_len, &eap_ds->response->type.data[1], 4);
data_len = ntohl(data_len);
if (data_len > MAX_RECORD_SIZE) {
- RDEBUG("The EAP-TLS packet will contain more data than we can process.");
- eaptls_free(&tlspacket);
- return NULL;
- }
-
-#if 0
- DEBUG2(" TLS: %d %d\n", data_len, tlspacket->length);
-
- if (data_len < tlspacket->length) {
- RDEBUG("EAP-TLS packet claims to be smaller than the encapsulating EAP packet.");
- eaptls_free(&tlspacket);
+ REDEBUG("Reassembled TLS record will be %u bytes, "
+ "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)",
+ data_len);
+ talloc_free(tlspacket);
return NULL;
}
-#endif
}
switch (status) {
* Dynamic allocation of buffers as & when we know the
* length should solve the problem.
*/
- case EAPTLS_FIRST_FRAGMENT:
- case EAPTLS_LENGTH_INCLUDED:
- case EAPTLS_MORE_FRAGMENTS_WITH_LENGTH:
+ case FR_TLS_FIRST_FRAGMENT:
+ case FR_TLS_LENGTH_INCLUDED:
+ case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH:
if (tlspacket->length < 5) { /* flags + TLS message length */
- RDEBUG("Invalid EAP-TLS packet received. (Expected length, got none.)");
- eaptls_free(&tlspacket);
+ REDEBUG("Invalid EAP-TLS packet received: Expected length, got none");
+ talloc_free(tlspacket);
return NULL;
}
/*
* Data length is implicit, from the EAP header.
*/
- case EAPTLS_MORE_FRAGMENTS:
- case EAPTLS_OK:
+ case FR_TLS_MORE_FRAGMENTS:
+ case FR_TLS_OK:
data_len = eap_ds->response->type.length - 1/*flags*/;
data = eap_ds->response->type.data + 1/*flags*/;
break;
default:
- RDEBUG("Invalid EAP-TLS packet received");
- eaptls_free(&tlspacket);
+ REDEBUG("Invalid EAP-TLS packet received");
+ talloc_free(tlspacket);
return NULL;
}
tlspacket->dlen = data_len;
if (data_len) {
- tlspacket->data = (unsigned char *)malloc(data_len);
- if (tlspacket->data == NULL) {
- RDEBUG("out of memory");
- eaptls_free(&tlspacket);
+ tlspacket->data = talloc_array(tlspacket, uint8_t,
+ data_len);
+ if (!tlspacket->data) {
+ talloc_free(tlspacket);
return NULL;
}
memcpy(tlspacket->data, data, data_len);
* SSL_CTX (internally) or TLS module(explicitly). If TLS module,
* then how to let SSL API know about these sessions.)
*/
-static eaptls_status_t eaptls_operation(eaptls_status_t status,
- EAP_HANDLER *handler)
+static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *handler)
{
- tls_session_t *tls_session;
+ REQUEST *request = handler->request;
+ tls_session_t *tls_session = handler->opaque;
- tls_session = (tls_session_t *)handler->opaque;
-
- if ((status == EAPTLS_MORE_FRAGMENTS) ||
- (status == EAPTLS_MORE_FRAGMENTS_WITH_LENGTH) ||
- (status == EAPTLS_FIRST_FRAGMENT)) {
+ 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->eap_ds, tls_session->peap_flag);
- return EAPTLS_HANDLED;
+ eaptls_send_ack(handler, tls_session->peap_flag);
+ return FR_TLS_HANDLED;
}
* If more info
* is required then send another request.
*/
- if (!tls_handshake_recv(tls_session)) {
- DEBUG2("TLS receive handshake failed during operation");
- eaptls_fail(handler, tls_session->peap_flag);
- return EAPTLS_FAIL;
+ if (!tls_handshake_recv(handler->request, tls_session)) {
+ REDEBUG("TLS receive handshake failed during operation");
+ tls_fail(tls_session);
+ return FR_TLS_FAIL;
}
/*
*/
if (tls_session->dirty_out.used > 0) {
eaptls_request(handler->eap_ds, tls_session);
- return EAPTLS_HANDLED;
+ return FR_TLS_HANDLED;
}
-
- /*
+
+ /*
* If there is no data to send i.e
* dirty_out.used <=0 and if the SSL
* handshake is finished, then return a
* EPTLS_SUCCESS
*/
-
+
if (SSL_is_init_finished(tls_session->ssl)) {
/*
* Init is finished. The rest is
* application data.
*/
- tls_session->info.content_type = application_data;
- return EAPTLS_SUCCESS;
+ tls_session->info.content_type = application_data;
+ return FR_TLS_SUCCESS;
}
-
+
/*
* Who knows what happened...
*/
- DEBUG2("TLS failed during operation");
- return EAPTLS_FAIL;
+ REDEBUG("TLS failed during operation");
+ return FR_TLS_FAIL;
}
/*
* Process an EAP request
*/
-eaptls_status_t eaptls_process(EAP_HANDLER *handler)
+fr_tls_status_t eaptls_process(eap_handler_t *handler)
{
tls_session_t *tls_session = (tls_session_t *) handler->opaque;
EAPTLS_PACKET *tlspacket;
- eaptls_status_t status;
+ fr_tls_status_t status;
REQUEST *request = handler->request;
- RDEBUG2("processing EAP-TLS");
+ if (!request) return FR_TLS_FAIL;
+
+ RDEBUG2("Continuing EAP-TLS");
- /* This case is when SSL generates Alert then we
- * send that alert to the client and then send the EAP-Failure
+ 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);
- RDEBUG2("eaptls_verify returned %d\n", status);
+ if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
+ REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ } else {
+ RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
+ }
switch (status) {
default:
- case EAPTLS_INVALID:
- case EAPTLS_FAIL:
+ 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 EAPTLS_SUCCESS:
- return status;
- break;
+ /*
+ * 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 EAPTLS_REQUEST:
+ /*
+ * Normal TLS request, continue with the "get rest
+ * of fragments" phase.
+ */
+ case FR_TLS_REQUEST:
eaptls_request(handler->eap_ds, tls_session);
- return EAPTLS_HANDLED;
- break;
+ status = FR_TLS_HANDLED;
+ goto done;
- /*
- * The handshake is done, and we're in the "tunnel
- * data" phase.
- */
- case EAPTLS_OK:
+ /*
+ * The handshake is done, and we're in the "tunnel
+ * data" phase.
+ */
+ case FR_TLS_OK:
RDEBUG2("Done initial handshake");
- /*
- * Get the rest of the fragments.
- */
- case EAPTLS_FIRST_FRAGMENT:
- case EAPTLS_MORE_FRAGMENTS:
- case EAPTLS_LENGTH_INCLUDED:
- case EAPTLS_MORE_FRAGMENTS_WITH_LENGTH:
+ /*
+ * 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)
- return EAPTLS_FAIL;
+ if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) {
+ status = FR_TLS_FAIL;
+ goto done;
+ }
/*
* Get the session struct from the handler
*/
if (tlspacket->dlen !=
(tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
- eaptls_free(&tlspacket);
- RDEBUG("Exceeded maximum record size");
- return EAPTLS_FAIL;
+ talloc_free(tlspacket);
+ REDEBUG("Exceeded maximum record size");
+ status = FR_TLS_FAIL;
+ goto done;
}
/*
* No longer needed.
*/
- eaptls_free(&tlspacket);
+ talloc_free(tlspacket);
/*
* SSL initalization is done. Return.
* The TLS data will be in the tls_session structure.
*/
if (SSL_is_init_finished(tls_session->ssl)) {
- int err;
-
/*
* The initialization may be finished, but if
* there more fragments coming, then send ACK,
* and get the caller to continue the
* conversation.
- */
- if ((status == EAPTLS_MORE_FRAGMENTS) ||
- (status == EAPTLS_MORE_FRAGMENTS_WITH_LENGTH) ||
- (status == EAPTLS_FIRST_FRAGMENT)) {
+ */
+ 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->eap_ds,
- tls_session->peap_flag);
+ eaptls_send_ack(handler, tls_session->peap_flag);
RDEBUG2("Init is done, but tunneled data is fragmented");
- return EAPTLS_HANDLED;
+ status = FR_TLS_HANDLED;
+ goto done;
}
- /*
- * Decrypt the complete record.
- */
- BIO_write(tls_session->into_ssl, tls_session->dirty_in.data,
- tls_session->dirty_in.used);
+ status = tls_application_data(tls_session, request);
+ goto done;
+ }
- /*
- * Clear the dirty buffer now that we are done with it
- * and init the clean_out buffer to store decrypted data
- */
- (tls_session->record_init)(&tls_session->dirty_in);
- (tls_session->record_init)(&tls_session->clean_out);
+ /*
+ * 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];
/*
- * Read (and decrypt) the tunneled data from the
- * SSL session, and put it into the decrypted
- * data buffer.
+ * Restore the cached VPs before processing the
+ * application data.
*/
- err = SSL_read(tls_session->ssl, tls_session->clean_out.data,
- sizeof(tls_session->clean_out.data));
-
- if (err < 0) {
- RDEBUG("SSL_read Error");
-
- switch (SSL_get_error(tls_session->ssl, err)) {
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- RDEBUG("Error in fragmentation logic");
- break;
- default:
+ 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("No information in cached session %s", buffer);
+ } else {
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+
+ RDEBUG("Adding cached attributes from session %s", buffer);
+
+ /*
+ * 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)) {
/*
- * FIXME: Call int_ssl_check?
+ * TLS-* attrs get added back to
+ * the request list.
*/
- break;
+ 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 {
+ rdebug_pair(L_DBG_LVL_2, request, vp, "reply:");
+ fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
+ }
}
- return EAPTLS_FAIL;
+ REXDENT();
}
-
- if (err == 0) {
- RDEBUG("WARNING: No data inside of the tunnel.");
- }
-
- /*
- * Passed all checks, successfully decrypted data
- */
- tls_session->clean_out.used = err;
-
- return EAPTLS_OK;
}
- /*
- * Continue the handshake.
- */
- return eaptls_operation(status, handler);
+ done:
+ SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, NULL);
+
+ return status;
}
uint8_t *ptr;
/*
- * Don't set eap_ds->request->type.type, as the main EAP
+ * 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.
*/
* Identifier value in the subsequent fragment contained
* within an EAP- Reponse.
*/
- eap_ds->request->type.data = malloc(reply->length - TLS_HEADER_LEN + 1);
- if (eap_ds->request->type.data == NULL) {
- radlog(L_ERR, "out of memory");
- return 0;
- }
+ 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;
if (reply->dlen) memcpy(ptr, reply->data, reply->dlen);
switch (reply->code) {
- case EAPTLS_ACK:
- case EAPTLS_START:
- case EAPTLS_REQUEST:
+ case FR_TLS_ACK:
+ case FR_TLS_START:
+ case FR_TLS_REQUEST:
eap_ds->request->code = PW_EAP_REQUEST;
break;
- case EAPTLS_SUCCESS:
+
+ case FR_TLS_SUCCESS:
eap_ds->request->code = PW_EAP_SUCCESS;
break;
- case EAPTLS_FAIL:
+
+ case FR_TLS_FAIL:
eap_ds->request->code = PW_EAP_FAILURE;
break;
+
default:
/* Should never enter here */
- eap_ds->request->code = PW_EAP_FAILURE;
+ 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;
+}
+