*
* @param[in] request the current (real) request.
* @param[in] eap_session representing the outer eap method.
- * @param[in] server_cs The virtual server to send the request to.
+ * @param[in] virtual_server The virtual server to send the request to.
* @return
* - UNLANG_ACTION_PUSHED_CHILD on success
* - UNLANG_ACTION_FAIL on error
*/
-unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, CONF_SECTION *server_cs)
+unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, virtual_server_t *virtual_server)
{
fr_pair_t *vp;
fr_assert(request->parent);
+ fr_assert(virtual_server);
- RDEBUG2("Running request through virtual server \"%s\"", cf_section_name(server_cs));
+ RDEBUG2("Running request through virtual server \"%s\"", cf_section_name2(virtual_server_cs(virtual_server)));
/*
* Re-present the previously stored child's session state if there is one
UNLANG_SUB_FRAME,
eap_session) < 0) return UNLANG_ACTION_FAIL;
- if (unlang_call_push(NULL, request, server_cs, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
+ if (unlang_call_push(NULL, request, virtual_server_cs(virtual_server), UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
return UNLANG_ACTION_PUSHED_CHILD;
}
#include <freeradius-devel/eap/types.h>
#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/server/virtual_servers.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/eap/base.h>
eap_packet_raw_t *eap_packet_from_vp(TALLOC_CTX *ctx, fr_pair_list_t *vps);
void eap_add_reply(request_t *request, fr_dict_attr_t const *da, uint8_t const *value, int len);
-unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, CONF_SECTION *server_cs);
+unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, virtual_server_t *virtual_server);
int eap_base_init(void);
void eap_base_free(void);
-
RCSIDH(lib_eap_aka_sim_module_h, "$Id$")
#include <freeradius-devel/server/cf_util.h>
+#include <freeradius-devel/server/virtual_servers.h>
#include <freeradius-devel/eap/types.h>
#include <freeradius-devel/tls/openssl_user_macros.h>
#endif
typedef struct {
- CONF_SECTION *virtual_server; //!< Virtual server.
+ virtual_server_t *virtual_server; //!< Virtual server.
/** Whether we should include a bid-down prevention attribute by default
*
*
* Short-term hack
*/
-unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, CONF_SECTION *server_cs, bool top_frame)
+unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, virtual_server_t const *vs, bool top_frame)
{
- virtual_server_t *vs;
-
- vs = cf_data_value(cf_data_find(server_cs, virtual_server_t, NULL));
- if (!vs) {
- cf_log_err(server_cs, "server_cs does not contain virtual server data");
- return UNLANG_ACTION_FAIL;
- }
-
/*
* Add a log destination specific to this virtual server.
*
server_remove_log_destination, /* but when we pop the frame */
server_signal_remove_log_destination, ~(FR_SIGNAL_CANCEL),
top_frame,
- vs);
+ UNCONST(void *, vs));
if (action != UNLANG_ACTION_PUSHED_CHILD) return action;
top_frame = UNLANG_SUB_FRAME; /* switch to SUB_FRAME after the first instruction */
return vs->server_cs;
}
+/** Resolve a CONF_SECTION to a virtual server
+ *
+ */
+virtual_server_t const *virtual_server_from_cs(CONF_SECTION *server_cs)
+{
+ virtual_server_t *vs;
+
+ vs = cf_data_value(cf_data_find(server_cs, virtual_server_t, NULL));
+ if (!vs) {
+ cf_log_err(server_cs, "server_cs does not contain virtual server data");
+ return NULL;
+ }
+
+ return vs;
+}
+
/** Return virtual server matching the specified name
*
* @note May be called in bootstrap or instantiate as all servers should be present.
}
done:
- *((CONF_SECTION **)out) = vs->server_cs;
+ *((virtual_server_t const **)out) = vs;
return 0;
}
*/
CONF_SECTION *virtual_server_cs(virtual_server_t const *vs) CC_HINT(nonnull);
+virtual_server_t const *virtual_server_from_cs(CONF_SECTION *server_cs);
+
virtual_server_t const *virtual_server_find(char const *name) CC_HINT(nonnull);
virtual_server_t const *virtual_server_by_child(CONF_ITEM const *ci) CC_HINT(nonnull);
section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, section_name_t const *section) CC_HINT(nonnull);
-unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, CONF_SECTION *server_cs, bool top_frame) CC_HINT(nonnull(2,3));
+unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, virtual_server_t const *vs, bool top_frame) CC_HINT(nonnull(2,3));
/** @name Parsing, bootstrap and instantiation
*
frame_repeat(frame, unlang_call_children);
}
- if (virtual_server_push(NULL, request, gext->server_cs, UNLANG_SUB_FRAME) < 0) goto error;
+ if (virtual_server_push(NULL, request, virtual_server_from_cs(gext->server_cs), UNLANG_SUB_FRAME) < 0) goto error;
return UNLANG_ACTION_PUSHED_CHILD;
}
TARGET := $(TARGETNAME)$(L)
endif
-SOURCES := $(TARGETNAME).c eap_fast.c eap_fast_crypto.c
+SOURCES := $(TARGETNAME).c eap_fast_crypto.c
SRC_INCDIRS := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
+++ /dev/null
-/*
- * 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
- */
-
-/**
- * $Id$
- * @file eap_fast.c
- * @brief Contains the interfaces that are called from the main handler
- *
- * @author Alexander Clouter (alex@digriz.org.uk)
-
- * @copyright 2016 Alan DeKok (aland@freeradius.org)
- * @copyright 2016 The FreeRADIUS server project
- */
-RCSID("$Id$")
-
-#include "eap_fast.h"
-#include "eap_fast_crypto.h"
-#include <freeradius-devel/tls/utils.h>
-#include <freeradius-devel/util/sha1.h>
-#include <freeradius-devel/tls/openssl_user_macros.h>
-#include <openssl/rand.h>
-#include <openssl/ssl.h>
-
-#define RANDFILL(x) do { fr_assert(sizeof(x) % sizeof(uint32_t) == 0); for (size_t i = 0; i < sizeof(x); i += sizeof(uint32_t)) *((uint32_t *)&x[i]) = fr_rand(); } while(0)
-
-/**
- * RFC 4851 section 5.1 - EAP-FAST Authentication Phase 1: Key Derivations
- */
-static void eap_fast_init_keys(request_t *request, fr_tls_session_t *tls_session)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- uint8_t *buf;
- uint8_t *scratch;
- size_t ksize;
-
- RDEBUG2("Deriving EAP-FAST keys");
-
- fr_assert(t->s_imck == NULL);
-
- ksize = fr_tls_utils_keyblock_size_get(request, tls_session->ssl);
- fr_assert(ksize > 0);
- buf = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
- scratch = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
-
- t->keyblock = talloc(t, eap_fast_keyblock_t);
-
- eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
- memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
- memset(buf, 0, ksize + sizeof(*t->keyblock));
-
- t->s_imck = talloc_array(t, uint8_t, EAP_FAST_SIMCK_LEN);
- memcpy(t->s_imck, t->keyblock, EAP_FAST_SKS_LEN); /* S-IMCK[0] = session_key_seed */
-
- t->cmk = talloc_array(t, uint8_t, EAP_FAST_CMK_LEN); /* note that CMK[0] is not defined */
- t->imck_count = 0;
-
- talloc_free(buf);
- talloc_free(scratch);
-}
-
-/**
- * RFC 4851 section 5.2 - Intermediate Compound Key Derivations
- */
-static void eap_fast_update_icmk(request_t *request, fr_tls_session_t *tls_session, uint8_t *msk)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- uint8_t imck[EAP_FAST_SIMCK_LEN + EAP_FAST_CMK_LEN];
-
- RDEBUG2("Updating ICMK");
-
- T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Inner Methods Compound Keys", msk, 32, imck, sizeof(imck)); //-V512
-
- memcpy(t->s_imck, imck, EAP_FAST_SIMCK_LEN);
- RHEXDUMP3(t->s_imck, EAP_FAST_SIMCK_LEN, "S-IMCK[j]");
-
- memcpy(t->cmk, &imck[EAP_FAST_SIMCK_LEN], EAP_FAST_CMK_LEN);
- RHEXDUMP3(t->cmk, EAP_FAST_CMK_LEN, "CMK[j]");
-
- t->imck_count++;
-
- /*
- * Calculate MSK/EMSK at the same time as they are coupled to ICMK
- *
- * RFC 4851 section 5.4 - EAP Master Session Key Generation
- */
- t->msk = talloc_array(t, uint8_t, EAP_FAST_KEY_LEN);
- T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Session Key Generating Function", NULL, 0, t->msk, EAP_FAST_KEY_LEN);
- RHEXDUMP3(t->msk, EAP_FAST_KEY_LEN, "MSK");
-
- t->emsk = talloc_array(t, uint8_t, EAP_EMSK_LEN);
- T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Extended Session Key Generating Function", NULL, 0, t->emsk, EAP_EMSK_LEN);
- RHEXDUMP3(t->emsk, EAP_EMSK_LEN, "EMSK");
-}
-
-void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *tlv, bool mandatory, int length, void const *data)
-{
- uint16_t hdr[2];
-
- hdr[0] = (mandatory) ? htons(tlv->attr | EAP_FAST_TLV_MANDATORY) : htons(tlv->attr);
- hdr[1] = htons(length);
-
- tls_session->record_from_buff(&tls_session->clean_in, &hdr, 4);
- tls_session->record_from_buff(&tls_session->clean_in, data, length);
-}
-
-static void eap_fast_send_error(fr_tls_session_t *tls_session, int error)
-{
- uint32_t value;
- value = htonl(error);
-
- eap_fast_tlv_append(tls_session, attr_eap_fast_error, true, sizeof(value), &value);
-}
-
-static void eap_fast_append_result(fr_tls_session_t *tls_session, fr_radius_packet_code_t code)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- uint16_t state;
- fr_dict_attr_t const *da;
-
-
- da = (t->result_final) ? attr_eap_fast_result : attr_eap_fast_intermediate_result;
- state = htons((code == FR_RADIUS_CODE_ACCESS_REJECT) ? EAP_FAST_TLV_RESULT_FAILURE : EAP_FAST_TLV_RESULT_SUCCESS);
-
- eap_fast_tlv_append(tls_session, da, true, sizeof(state), &state);
-}
-
-static void eap_fast_send_identity_request(request_t *request, fr_tls_session_t *tls_session, eap_session_t *eap_session)
-{
- eap_packet_raw_t eap_packet;
-
- RDEBUG2("Sending EAP-Identity");
-
- eap_packet.code = FR_EAP_CODE_REQUEST;
- eap_packet.id = eap_session->this_round->response->id + 1;
- eap_packet.length[0] = 0;
- eap_packet.length[1] = EAP_HEADER_LEN + 1;
- eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
-
- eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, sizeof(eap_packet), &eap_packet);
-}
-
-static void eap_fast_send_pac_tunnel(request_t *request, fr_tls_session_t *tls_session)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- eap_fast_pac_t pac;
- eap_fast_attr_pac_opaque_plaintext_t opaque_plaintext;
- int alen, dlen;
-
- memset(&pac, 0, sizeof(pac));
- memset(&opaque_plaintext, 0, sizeof(opaque_plaintext));
-
- RDEBUG2("Sending Tunnel PAC");
-
- pac.key.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_key->attr);
- pac.key.hdr.length = htons(sizeof(pac.key.data));
- fr_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0);
- RANDFILL(pac.key.data);
-
- pac.info.lifetime.hdr.type = htons(attr_eap_fast_pac_info_pac_lifetime->attr);
- pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data));
- pac.info.lifetime.data = htonl(fr_time_to_sec(fr_time_add(request->packet->timestamp, t->pac_lifetime)));
-
- pac.info.a_id.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_a_id->attr);
- pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data));
- memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data));
-
- pac.info.a_id_info.hdr.type = htons(attr_eap_fast_pac_a_id->attr);
- pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data));
-
-#define MIN(a,b) (((a)>(b)) ? (b) : (a))
- alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data));
- memcpy(pac.info.a_id_info.data, t->authority_identity, alen);
-
- pac.info.type.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_pac_type->attr);
- pac.info.type.hdr.length = htons(sizeof(pac.info.type.data));
- pac.info.type.data = htons(PAC_TYPE_TUNNEL);
-
- pac.info.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_tlv->attr);
- pac.info.hdr.length = htons(sizeof(pac.info.lifetime)
- + sizeof(pac.info.a_id)
- + sizeof(pac.info.a_id_info)
- + sizeof(pac.info.type));
-
- memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type));
- memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime));
- memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key));
-
- RHEXDUMP3((uint8_t const *)&opaque_plaintext, sizeof(opaque_plaintext), "PAC-Opaque plaintext data section");
-
- fr_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN);
- memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH);
- fr_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0);
- dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
- t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
- pac.opaque.data, pac.opaque.tag);
-
- pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_opaque_tlv->attr);
- pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
- RHEXDUMP3((uint8_t const *)&pac.opaque, sizeof(pac.opaque) - sizeof(pac.opaque.data) + dlen, "PAC-Opaque");
-
- eap_fast_tlv_append(tls_session, attr_eap_fast_pac_tlv, true, sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac);
-}
-
-static void eap_fast_append_crypto_binding(request_t *request, fr_tls_session_t *tls_session)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- eap_tlv_crypto_binding_tlv_t binding = {0};
- int const len = sizeof(binding) - (&binding.reserved - (uint8_t *)&binding);
-
- RDEBUG2("Sending Cryptobinding");
-
- binding.tlv_type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_crypto_binding->attr);
- binding.length = htons(len);
- binding.version = EAP_FAST_VERSION;
- binding.received_version = EAP_FAST_VERSION; /* FIXME use the clients value */
- binding.subtype = EAP_FAST_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST;
-
- fr_assert(sizeof(binding.nonce) % sizeof(uint32_t) == 0);
- RANDFILL(binding.nonce);
- binding.nonce[sizeof(binding.nonce) - 1] &= ~0x01; /* RFC 4851 section 4.2.8 */
- RHEXDUMP3(binding.nonce, sizeof(binding.nonce), "NONCE");
-
- RHEXDUMP3((uint8_t const *) &binding, sizeof(binding), "Crypto-Binding TLV for Compound MAC calculation");
-
- fr_hmac_sha1(binding.compound_mac, (uint8_t *)&binding, sizeof(binding), t->cmk, EAP_FAST_CMK_LEN);
- RHEXDUMP3(binding.compound_mac, sizeof(binding.compound_mac), "Compound MAC");
-
- eap_fast_tlv_append(tls_session, attr_eap_fast_crypto_binding, true, len, &binding.reserved);
-}
-
-#define EAP_FAST_TLV_MAX 11
-
-static int eap_fast_verify(request_t *request, fr_tls_session_t *tls_session, uint8_t const *data, unsigned int data_len)
-{
- uint16_t attr;
- uint16_t length;
- unsigned int remaining = data_len;
- int total = 0;
- int num[EAP_FAST_TLV_MAX] = {0};
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- uint32_t present = 0;
-
- fr_assert(sizeof(present) * 8 > EAP_FAST_TLV_MAX);
-
- while (remaining > 0) {
- if (remaining < 4) {
- RDEBUG2("EAP-FAST TLV is too small (%u) to contain a EAP-FAST TLV header", remaining);
- return 0;
- }
-
- memcpy(&attr, data, sizeof(attr));
- attr = ntohs(attr) & EAP_FAST_TLV_TYPE;
-
- if ((attr == attr_eap_fast_result->attr) ||
- (attr == attr_eap_fast_nak->attr) ||
- (attr == attr_eap_fast_error->attr) ||
- (attr == attr_eap_fast_vendor_specific->attr) ||
- (attr == attr_eap_fast_eap_payload->attr) ||
- (attr == attr_eap_fast_intermediate_result->attr) ||
- (attr == attr_eap_fast_pac_tlv->attr) ||
- (attr == attr_eap_fast_crypto_binding->attr)) {
- num[attr]++;
- present |= 1 << attr;
-
- if (num[attr_eap_fast_eap_payload->attr] > 1) {
- REDEBUG("Too many EAP-Payload TLVs");
-unexpected:
- for (int i = 0; i < EAP_FAST_TLV_MAX; i++) {
- if (present & (1 << i)) RDEBUG2(" - attribute %d is present", i);
- }
- eap_fast_send_error(tls_session, EAP_FAST_ERR_UNEXPECTED_TLV);
- return 0;
- }
-
- if (num[attr_eap_fast_intermediate_result->attr] > 1) {
- REDEBUG("Too many Intermediate-Result TLVs");
- goto unexpected;
- }
- } else {
- if ((data[0] & 0x80) != 0) {
- REDEBUG("Unknown mandatory TLV %02x", attr);
- goto unexpected;
- }
-
- num[0]++;
- }
-
- total++;
-
- memcpy(&length, data + 2, sizeof(length));
- length = ntohs(length);
-
- data += 4;
- remaining -= 4;
-
- if (length > remaining) {
- RDEBUG2("EAP-FAST TLV %u is longer than room remaining in the packet (%u > %u).", attr,
- length, remaining);
- return 0;
- }
-
- /*
- * If the rest of the TLVs are larger than
- * this attribute, continue.
- *
- * Otherwise, if the attribute over-flows the end
- * of the TLCs, die.
- */
- if (remaining < length) {
- RDEBUG2("EAP-FAST TLV overflows packet!");
- return 0;
- }
-
- /*
- * If there's an error, we bail out of the
- * authentication process before allocating
- * memory.
- */
- if ((attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_result->attr)) {
- uint16_t status;
-
- if (length < 2) {
- REDEBUG("EAP-FAST TLV %u is too short. Expected 2, got %d", attr, length);
- return 0;
- }
-
- memcpy(&status, data, 2);
- status = ntohs(status);
-
- if (status == EAP_FAST_TLV_RESULT_FAILURE) {
- REDEBUG("EAP-FAST TLV %u indicates failure. Rejecting request", attr);
- return 0;
- }
-
- if (status != EAP_FAST_TLV_RESULT_SUCCESS) {
- REDEBUG("EAP-FAST TLV %u contains unknown value. Rejecting request", attr);
- goto unexpected;
- }
- }
-
- /*
- * remaining > length, continue.
- */
- remaining -= length;
- data += length;
- }
-
- /*
- * Check if the peer mixed & matched TLVs.
- */
- if ((num[attr_eap_fast_nak->attr] > 0) && (num[attr_eap_fast_nak->attr] != total)) {
- REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request");
- goto unexpected;
- }
-
- if (num[attr_eap_fast_intermediate_result->attr] > 0) {
- REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request");
- goto unexpected;
- }
-
- /*
- * Check mandatory or not mandatory TLVs.
- */
- switch (t->stage) {
- case EAP_FAST_TLS_SESSION_HANDSHAKE:
- if (present) {
- REDEBUG("Unexpected TLVs in TLS Session Handshake stage");
- goto unexpected;
- }
- break;
- case EAP_FAST_AUTHENTICATION:
- if (present != (uint32_t)(1 << attr_eap_fast_eap_payload->attr)) {
- REDEBUG("Unexpected TLVs in authentication stage");
- goto unexpected;
- }
- break;
- case EAP_FAST_CRYPTOBIND_CHECK:
- {
- uint32_t bits = (t->result_final)
- ? 1 << attr_eap_fast_result->attr
- : 1 << attr_eap_fast_intermediate_result->attr;
- if (present & ~(bits | (1 << attr_eap_fast_crypto_binding->attr) | (1 << attr_eap_fast_pac_tlv->attr))) {
- REDEBUG("Unexpected TLVs in cryptobind checking stage");
- goto unexpected;
- }
- break;
- }
- case EAP_FAST_PROVISIONING:
- if (present & ~((1 << attr_eap_fast_pac_tlv->attr) | (1 << attr_eap_fast_result->attr))) {
- REDEBUG("Unexpected TLVs in provisioning stage");
- goto unexpected;
- }
- break;
- case EAP_FAST_COMPLETE:
- if (present) {
- REDEBUG("Unexpected TLVs in complete stage");
- goto unexpected;
- }
- break;
- default:
- REDEBUG("Unexpected stage %d", t->stage);
- return 0;
- }
-
- /*
- * We got this far. It looks OK.
- */
- return 1;
-}
-
-/**
- *
- * FIXME do something with mandatory
- */
-ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
- uint8_t const *data, size_t data_len,
- void *decode_ctx)
-{
- fr_dict_attr_t const *da;
- uint8_t const *p = data, *end = p + data_len;
-
- /*
- * Decode the TLVs
- */
- while (p < end) {
- ssize_t ret;
- uint16_t attr;
- uint16_t len;
- fr_pair_t *vp;
-
- attr = fr_nbo_to_uint16(p) & EAP_FAST_TLV_TYPE;
- p += 2;
- len = fr_nbo_to_uint16(p);
- p += 2;
-
- da = fr_dict_attr_child_by_num(parent, attr);
- if (!da) {
- MEM(vp = fr_pair_afrom_child_num(ctx, parent, attr));
-
- } else if (da->type == FR_TYPE_TLV) {
- p += (size_t) eap_fast_decode_pair(ctx, out, parent, p, len, decode_ctx);
- continue;
-
- } else {
- MEM(vp = fr_pair_afrom_da(ctx, da));
- }
-
- ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
- &FR_DBUFF_TMP(p, (size_t)len), len, true);
- if (ret != len) {
- fr_pair_raw_afrom_pair(vp, p, len);
- }
- fr_pair_append(out, vp);
- p += len;
- }
-
- return p - data;
-}
-
-
-/*
- * Use a reply packet to determine what to do.
- */
-static rlm_rcode_t CC_HINT(nonnull) process_reply(UNUSED eap_session_t *eap_session,
- fr_tls_session_t *tls_session,
- request_t *request,
- fr_packet_t *reply, fr_pair_list_t *reply_list)
-{
- rlm_rcode_t rcode = RLM_MODULE_REJECT;
- fr_pair_t *vp;
- fr_dcursor_t cursor;
-
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
- /*
- * If the response packet was Access-Accept, then
- * we're OK. If not, die horribly.
- *
- * FIXME: EAP-Messages can only start with 'identity',
- * NOT 'eap start', so we should check for that....
- */
- switch (reply->code) {
- case FR_RADIUS_CODE_ACCESS_ACCEPT:
- RDEBUG2("Got tunneled Access-Accept");
-
- rcode = RLM_MODULE_OK;
-
- /*
- * Copy what we need into the TTLS tunnel and leave
- * the rest to be cleaned up.
- */
- for (vp = fr_pair_list_head(reply_list); vp; vp = fr_pair_list_next(reply_list, vp)) {
- if (fr_dict_vendor_num_by_da(vp->da) != VENDORPEC_MICROSOFT) continue;
-
- /* FIXME must be a better way to capture/re-derive this later for ISK */
- switch (vp->da->attr) {
- case FR_MSCHAP_MPPE_SEND_KEY:
- if (vp->vp_length != MD5_DIGEST_LENGTH) {
- wrong_length:
- REDEBUG("Found %s with incorrect length. Expected %u, got %zu",
- vp->da->name, MD5_DIGEST_LENGTH, vp->vp_length);
- rcode = RLM_MODULE_INVALID;
- break;
- }
-
- memcpy(t->isk.mppe_send, vp->vp_octets, MD5_DIGEST_LENGTH);
- break;
-
- case FR_MSCHAP_MPPE_RECV_KEY:
- if (vp->vp_length != MD5_DIGEST_LENGTH) goto wrong_length;
- memcpy(t->isk.mppe_recv, vp->vp_octets, MD5_DIGEST_LENGTH);
- break;
-
- case FR_MSCHAP2_SUCCESS:
- RDEBUG2("Got %s, tunneling it to the client in a challenge", vp->da->name);
- rcode = RLM_MODULE_HANDLED;
- t->authenticated = true;
- break;
-
- default:
- break;
- }
- }
- RHEXDUMP3((uint8_t *)&t->isk, 2 * MD5_DIGEST_LENGTH, "ISK[j]"); /* FIXME (part of above) */
- break;
-
- case FR_RADIUS_CODE_ACCESS_REJECT:
- REDEBUG("Got tunneled Access-Reject");
- rcode = RLM_MODULE_REJECT;
- break;
-
- case FR_RADIUS_CODE_ACCESS_CHALLENGE:
- RDEBUG2("Got tunneled Access-Challenge");
-
- /*
- * Copy the EAP-Message back to the tunnel.
- */
-
- for (vp = fr_pair_dcursor_by_da_init(&cursor, reply_list, attr_eap_message);
- vp;
- vp = fr_dcursor_next(&cursor)) {
- eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, vp->vp_length, vp->vp_octets);
- }
-
- rcode = RLM_MODULE_HANDLED;
- break;
-
- default:
- REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
- rcode = RLM_MODULE_INVALID;
- break;
- }
-
- return rcode;
-}
-
-static fr_radius_packet_code_t eap_fast_eap_payload(request_t *request, eap_session_t *eap_session,
- fr_tls_session_t *tls_session, fr_pair_t *tlv_eap_payload)
-{
- fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
- rlm_rcode_t rcode;
- fr_pair_t *vp;
- eap_fast_tunnel_t *t;
- request_t *fake;
-
- RDEBUG2("Processing received EAP Payload");
-
- /*
- * Allocate a fake request_t structure.
- */
- fake = request_local_alloc_internal(request, &(request_init_args_t){ .parent = request });
- fr_assert(fr_pair_list_empty(&fake->request_pairs));
-
- t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
- /*
- * Add the tunneled attributes to the fake request.
- */
-
- MEM(vp = fr_pair_afrom_da(fake->request_ctx, attr_eap_message));
- fr_pair_append(&fake->request_pairs, vp);
- fr_pair_value_memdup(vp, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false);
-
- RDEBUG2("Got tunneled request");
- log_request_pair_list(L_DBG_LVL_1, fake, NULL, &fake->request_pairs, NULL);
-
- /*
- * Tell the request that it's a fake one.
- */
- MEM(fr_pair_prepend_by_da(fake->request_ctx, &vp, &fake->request_pairs, attr_freeradius_proxied_to) >= 0);
- (void)fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1") - 1, NULL, false);
-
- /*
- * If there's no User-Name in the stored data, look for
- * an EAP-Identity, and pull it out of there.
- */
- if (!t->username) {
- fr_assert(vp->da == attr_eap_message); /* cached from above */
-
- if ((vp->vp_length >= EAP_HEADER_LEN + 2) &&
- (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
- (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
- (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
- /*
- * Create and remember a User-Name
- */
- MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
- t->username->vp_tainted = true;
- fr_pair_value_bstrndup(t->username, (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
-
- RDEBUG2("Got tunneled identity of %pV", &t->username->data);
- } else {
- /*
- * Don't reject the request outright,
- * as it's permitted to do EAP without
- * user-name.
- */
- RWDEBUG2("No EAP-Identity found to start EAP conversation");
- }
- } /* else there WAS a t->username */
-
- if (t->username) {
- vp = fr_pair_copy(fake->request_ctx, t->username);
- fr_pair_append(&fake->request_pairs, vp);
- }
-
- if (t->stage == EAP_FAST_AUTHENTICATION) { /* FIXME do this only for MSCHAPv2 */
- fr_pair_t *tvp;
-
- MEM(tvp = fr_pair_afrom_da(fake, attr_eap_type));
- tvp->vp_uint32 = t->default_provisioning_method;
- fr_pair_append(&fake->control_pairs, tvp);
-
- /*
- * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2
- */
- if (t->mode == EAP_FAST_PROVISIONING_ANON) {
- MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge));
- fr_pair_value_memdup(tvp, t->keyblock->server_challenge, MD5_DIGEST_LENGTH, false);
- fr_pair_append(&fake->control_pairs, tvp);
- RHEXDUMP3(t->keyblock->server_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 auth_challenge");
-
- MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge));
- fr_pair_value_memdup(tvp, t->keyblock->client_challenge, MD5_DIGEST_LENGTH, false);
- fr_pair_append(&fake->control_pairs, tvp);
- RHEXDUMP3(t->keyblock->client_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 peer_challenge");
- }
- }
-
- /*
- * Call authentication recursively, which will
- * do PAP, CHAP, MS-CHAP, etc.
- */
- eap_virtual_server(request, eap_session, t->server_cs);
-
- /*
- * Decide what to do with the reply.
- */
- switch (fake->reply->code) {
- case 0: /* No reply code, must be proxied... */
-#ifdef WITH_PROXY
- vp = fr_pair_find_by_da(&fake->control, NULL, attr_proxy_to_realm);
- if (vp) {
- int ret;
- eap_tunnel_data_t *tunnel;
-
- RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data);
-
- /*
- * Tell the original request that it's going to be proxied.
- */
- fr_pair_list_copy_by_da(request->control_ctx, &request->control_pairs,
- &fake->control_pairs, attr_proxy_to_realm, 0);
-
- /*
- * Seed the proxy packet with the tunneled request.
- */
- fr_assert(!request->proxy);
-
- /*
- * FIXME: Actually proxy stuff
- */
- request->proxy = request_alloc_internal(request, &(request_init_args_t){ .parent = request });
-
- request->proxy->packet = talloc_steal(request->proxy, fake->packet);
- memset(&request->proxy->packet->src_ipaddr, 0,
- sizeof(request->proxy->packet->src_ipaddr));
- memset(&request->proxy->packet->src_ipaddr, 0,
- sizeof(request->proxy->packet->src_ipaddr));
- request->proxy->packet->src_port = 0;
- request->proxy->packet->dst_port = 0;
- fake->packet = NULL;
- fr_packet_free(&fake->reply);
- fake->reply = NULL;
-
- /*
- * Set up the callbacks for the tunnel
- */
- tunnel = talloc_zero(request, eap_tunnel_data_t);
- tunnel->tls_session = tls_session;
-
- /*
- * Associate the callback with the request.
- */
- ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK,
- tunnel, false, false, false);
- fr_cond_assert(ret == 0);
-
- /*
- * rlm_eap.c has taken care of associating the eap_session
- * with the fake request.
- *
- * So we associate the fake request with this request.
- */
- ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK,
- fake, true, false, false);
- fr_cond_assert(ret == 0);
-
- fake = NULL;
-
- /*
- * Didn't authenticate the packet, but we're proxying it.
- */
- code = FR_RADIUS_CODE_STATUS_CLIENT;
-
- } else
-#endif /* WITH_PROXY */
- {
- REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user");
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- }
- break;
-
- default:
- /*
- * Returns RLM_MODULE_FOO, and we want to return FR_FOO
- */
- rcode = process_reply(eap_session, tls_session, request, fake->reply, &fake->reply_pairs);
- switch (rcode) {
- case RLM_MODULE_REJECT:
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- break;
-
- case RLM_MODULE_HANDLED:
- code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
- break;
-
- case RLM_MODULE_OK:
- code = FR_RADIUS_CODE_ACCESS_ACCEPT;
- break;
-
- default:
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- break;
- }
- break;
- }
-
- talloc_free(fake);
-
- return code;
-}
-
-static fr_radius_packet_code_t eap_fast_crypto_binding(request_t *request, UNUSED eap_session_t *eap_session,
- fr_tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t *binding)
-{
- uint8_t cmac[sizeof(binding->compound_mac)];
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
- memcpy(cmac, binding->compound_mac, sizeof(cmac));
- memset(binding->compound_mac, 0, sizeof(binding->compound_mac));
-
- RHEXDUMP3((uint8_t const *) binding, sizeof(*binding), "Crypto-Binding TLV for Compound MAC calculation");
- RHEXDUMP3(cmac, sizeof(cmac), "Received Compound MAC");
-
- fr_hmac_sha1(binding->compound_mac, (uint8_t *)binding, sizeof(*binding), t->cmk, EAP_FAST_CMK_LEN);
- if (memcmp(binding->compound_mac, cmac, sizeof(cmac))) {
- RDEBUG2("Crypto-Binding TLV mismatch");
- RHEXDUMP3((uint8_t const *) binding->compound_mac,
- sizeof(binding->compound_mac), "Calculated Compound MAC");
- return FR_RADIUS_CODE_ACCESS_REJECT;
- }
-
- return FR_RADIUS_CODE_ACCESS_ACCEPT;
-}
-
-static fr_radius_packet_code_t eap_fast_process_tlvs(request_t *request, eap_session_t *eap_session,
- fr_tls_session_t *tls_session, fr_pair_list_t *fast_vps)
-{
- eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
- fr_pair_t *vp;
- eap_tlv_crypto_binding_tlv_t my_binding, *binding = NULL;
-
- memset(&my_binding, 0, sizeof(my_binding));
-
- for (vp = fr_pair_list_head(fast_vps);
- vp;
- vp = fr_pair_list_next(fast_vps, vp)) {
- fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
- if (vp->da->parent == fr_dict_root(dict_eap_fast)) {
- if (vp->da == attr_eap_fast_eap_payload) {
- code = eap_fast_eap_payload(request, eap_session, tls_session, vp);
- if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) t->stage = EAP_FAST_CRYPTOBIND_CHECK;
- } else if ((vp->da == attr_eap_fast_result) ||
- (vp->da == attr_eap_fast_intermediate_result)) {
- code = FR_RADIUS_CODE_ACCESS_ACCEPT;
- t->stage = EAP_FAST_PROVISIONING;
- } else {
- RDEBUG2("ignoring unknown %pP", vp);
- continue;
- }
- } else if (vp->da->parent == attr_eap_fast_crypto_binding) {
- binding = &my_binding;
-
- /*
- * fr_radius_encode_pair() does not work for structures
- */
- switch (vp->da->attr) {
- case 1: /* FR_EAP_FAST_CRYPTO_BINDING_RESERVED */
- binding->reserved = vp->vp_uint8;
- break;
- case 2: /* FR_EAP_FAST_CRYPTO_BINDING_VERSION */
- binding->version = vp->vp_uint8;
- break;
- case 3: /* FR_EAP_FAST_CRYPTO_BINDING_RECV_VERSION */
- binding->received_version = vp->vp_uint8;
- break;
- case 4: /* FR_EAP_FAST_CRYPTO_BINDING_SUB_TYPE */
- binding->subtype = vp->vp_uint8;
- break;
- case 5: /* FR_EAP_FAST_CRYPTO_BINDING_NONCE */
- if (vp->vp_length >= sizeof(binding->nonce)) {
- memcpy(binding->nonce, vp->vp_octets, vp->vp_length);
- }
- break;
- case 6: /* FR_EAP_FAST_CRYPTO_BINDING_COMPOUND_MAC */
- if (vp->vp_length >= sizeof(binding->compound_mac)) {
- memcpy(binding->compound_mac, vp->vp_octets, sizeof(binding->compound_mac));
- }
- break;
- }
- continue;
- } else if (vp->da->parent == attr_eap_fast_pac_tlv) {
- if (vp->da == attr_eap_fast_pac_acknowledge) {
- if (vp->vp_uint32 == EAP_FAST_TLV_RESULT_SUCCESS) {
- code = FR_RADIUS_CODE_ACCESS_ACCEPT;
- t->pac.expires = fr_time_max();
- t->pac.expired = false;
- t->stage = EAP_FAST_COMPLETE;
- }
- } else if (vp->da == attr_eap_fast_pac_info_pac_type) {
- if (vp->vp_uint32 != PAC_TYPE_TUNNEL) {
- RDEBUG2("only able to serve Tunnel PAC's, ignoring request");
- continue;
- }
- t->pac.send = true;
- continue;
- } else {
- RDEBUG2("ignoring unknown EAP-FAST-PAC-TLV %pP", vp);
- continue;
- }
- } else {
- RDEBUG2("ignoring non-EAP-FAST TLV %pP", vp);
- continue;
- }
-
- if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
- }
-
- if (binding) {
- fr_radius_packet_code_t code = eap_fast_crypto_binding(request, eap_session, tls_session, binding);
- if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) {
- t->stage = EAP_FAST_PROVISIONING;
- }
- return code;
- }
-
- return FR_RADIUS_CODE_ACCESS_ACCEPT;
-}
-
-
-/*
- * Process the inner tunnel data
- */
-fr_radius_packet_code_t eap_fast_process(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- fr_radius_packet_code_t code;
- fr_pair_list_t fast_vps;
- uint8_t const *data;
- size_t data_len;
- eap_fast_tunnel_t *t;
-
- fr_pair_list_init(&fast_vps);
- /*
- * Just look at the buffer directly, without doing
- * record_to_buff.
- */
- data_len = tls_session->clean_out.used;
- tls_session->clean_out.used = 0;
- data = tls_session->clean_out.data;
-
- t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
- /*
- * See if the tunneled data is well formed.
- */
- if (!eap_fast_verify(request, tls_session, data, data_len)) return FR_RADIUS_CODE_ACCESS_REJECT;
-
- if (t->stage == EAP_FAST_TLS_SESSION_HANDSHAKE) {
- char buf[256];
-
- fr_assert(t->mode == EAP_FAST_UNKNOWN);
-
- if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl),
- buf, sizeof(buf)), "Au=None")) {
- /* FIXME enforce MSCHAPv2 - RFC 5422 section 3.2.2 */
- RDEBUG2("Using anonymous provisioning");
- t->mode = EAP_FAST_PROVISIONING_ANON;
- t->pac.send = true;
- } else {
- fr_time_t renew;
-
- if (SSL_session_reused(tls_session->ssl)) {
- RDEBUG2("Session Resumed from PAC");
- t->mode = EAP_FAST_NORMAL_AUTH;
- } else {
- RDEBUG2("Using authenticated provisioning");
- t->mode = EAP_FAST_PROVISIONING_AUTH;
- }
-
- /*
- * Send a new pac at 60% of the lifetime,
- * or if the PAC has expired, or if no lifetime was set.
- */
- renew = fr_time_add(request->packet->timestamp,
- fr_time_delta_wrap((fr_time_delta_unwrap(t->pac_lifetime) * 3) / 5));
-
- if (t->pac.expired || fr_time_eq(t->pac.expires, fr_time_wrap(0)) ||
- fr_time_lteq(t->pac.expires, renew)) {
- t->pac.send = true;
- }
- }
-
- eap_fast_init_keys(request, tls_session);
-
- eap_fast_send_identity_request(request, tls_session, eap_session);
-
- t->stage = EAP_FAST_AUTHENTICATION;
- return FR_RADIUS_CODE_ACCESS_CHALLENGE;
- }
-
- if (eap_fast_decode_pair(request, &fast_vps, fr_dict_root(dict_eap_fast),
- data, data_len, NULL) < 0) return FR_RADIUS_CODE_ACCESS_REJECT;
-
- RDEBUG2("Got Tunneled FAST TLVs");
- log_request_pair_list(L_DBG_LVL_1, request, NULL, &fast_vps, NULL);
- code = eap_fast_process_tlvs(request, eap_session, tls_session, &fast_vps);
- fr_pair_list_free(&fast_vps);
-
- if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
-
- switch (t->stage) {
- case EAP_FAST_AUTHENTICATION:
- code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
- break;
-
- case EAP_FAST_CRYPTOBIND_CHECK:
- {
- if (t->mode != EAP_FAST_PROVISIONING_ANON && !t->pac.send)
- t->result_final = true;
-
- eap_fast_append_result(tls_session, code);
-
- eap_fast_update_icmk(request, tls_session, (uint8_t *)&t->isk);
- eap_fast_append_crypto_binding(request, tls_session);
-
- code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
- break;
- }
- case EAP_FAST_PROVISIONING:
- t->result_final = true;
-
- eap_fast_append_result(tls_session, code);
-
- if (t->pac.send) {
- RDEBUG2("Peer requires new PAC");
- eap_fast_send_pac_tunnel(request, tls_session);
- code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
- break;
- }
-
- t->stage = EAP_FAST_COMPLETE;
- FALL_THROUGH;
-
- case EAP_FAST_COMPLETE:
- /*
- * RFC 5422 section 3.5 - Network Access after EAP-FAST Provisioning
- */
- if (t->pac.type && t->pac.expired) {
- REDEBUG("Rejecting expired PAC.");
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- break;
- }
-
- if (t->mode == EAP_FAST_PROVISIONING_ANON) {
- REDEBUG("Rejecting unauthenticated provisioning");
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- break;
- }
-
- /*
- * eap_crypto_mppe_keys() is unsuitable for EAP-FAST as Cisco decided
- * it would be a great idea to flip the recv/send keys around
- */
- #define EAPTLS_MPPE_KEY_LEN 32
- eap_add_reply(request, attr_ms_mppe_recv_key, t->msk, EAPTLS_MPPE_KEY_LEN);
- eap_add_reply(request, attr_ms_mppe_send_key, &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN);
- eap_add_reply(request, attr_eap_msk, t->msk, EAP_FAST_KEY_LEN);
- eap_add_reply(request, attr_eap_emsk, t->emsk, EAP_EMSK_LEN);
-
- break;
-
- default:
- RERROR("Internal sanity check failed in EAP-FAST at %d", t->stage);
- code = FR_RADIUS_CODE_ACCESS_REJECT;
- }
-
- return code;
-}
#endif
CONF_SECTION *server_cs;
} eap_fast_tunnel_t;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-extern HIDDEN fr_dict_attr_t const *attr_eap_type;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_peer_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_proxy_to_realm;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_eap_msk;
-extern HIDDEN fr_dict_attr_t const *attr_eap_emsk;
-extern HIDDEN fr_dict_attr_t const *attr_freeradius_proxied_to;
-extern HIDDEN fr_dict_attr_t const *attr_ms_mppe_send_key;
-extern HIDDEN fr_dict_attr_t const *attr_ms_mppe_recv_key;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-extern HIDDEN fr_dict_attr_t const *attr_user_password;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_crypto_binding;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_eap_payload;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_error;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_intermediate_result;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_nak;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_a_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_key;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_result;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_vendor_specific;
-extern HIDDEN fr_dict_t const *dict_eap_fast;
-
-/*
- * Process the FAST portion of an EAP-FAST request.
- */
-void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *da, bool mandatory,
- int length, const void *data) CC_HINT(nonnull);
-fr_radius_packet_code_t eap_fast_process(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
-
-/*
- * A bunch of EAP-FAST helper functions.
- */
-ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
- uint8_t const *data, size_t data_len,
- UNUSED void *decode_ctx);
USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
#include <freeradius-devel/util/md5.h>
-
+#include <freeradius-devel/tls/utils.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
#include "eap_fast.h"
#include "eap_fast_crypto.h"
virtual_server_t *virtual_server; //!< Virtual server to use for processing
//!< inner EAP method.
- CONF_SECTION *server_cs;
char const *cipher_list; //!< cipher list specific to EAP-FAST
bool req_client_cert; //!< Whether we require a client cert
//!< in the outer tunnel.
{ NULL }
};
-fr_dict_attr_t const *attr_eap_emsk;
-fr_dict_attr_t const *attr_eap_msk;
-fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-fr_dict_attr_t const *attr_eap_type;
-fr_dict_attr_t const *attr_ms_chap_challenge;
-fr_dict_attr_t const *attr_ms_chap_peer_challenge;
-fr_dict_attr_t const *attr_proxy_to_realm;
-
-fr_dict_attr_t const *attr_eap_message;
-fr_dict_attr_t const *attr_freeradius_proxied_to;
-fr_dict_attr_t const *attr_ms_mppe_send_key;
-fr_dict_attr_t const *attr_ms_mppe_recv_key;
-fr_dict_attr_t const *attr_user_name;
-fr_dict_attr_t const *attr_user_password;
-
-fr_dict_attr_t const *attr_eap_fast_crypto_binding;
-fr_dict_attr_t const *attr_eap_fast_eap_payload;
-fr_dict_attr_t const *attr_eap_fast_error;
-fr_dict_attr_t const *attr_eap_fast_intermediate_result;
-fr_dict_attr_t const *attr_eap_fast_nak;
-fr_dict_attr_t const *attr_eap_fast_pac_a_id;
-fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
-fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
-fr_dict_attr_t const *attr_eap_fast_pac_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
-fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
-fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_key;
-fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_type;
-fr_dict_attr_t const *attr_eap_fast_result;
-fr_dict_attr_t const *attr_eap_fast_vendor_specific;
+static fr_dict_attr_t const *attr_eap_emsk;
+static fr_dict_attr_t const *attr_eap_msk;
+static fr_dict_attr_t const *attr_eap_tls_require_client_cert;
+static fr_dict_attr_t const *attr_eap_type;
+static fr_dict_attr_t const *attr_ms_chap_challenge;
+static fr_dict_attr_t const *attr_ms_chap_peer_challenge;
+static fr_dict_attr_t const *attr_proxy_to_realm;
+
+static fr_dict_attr_t const *attr_eap_message;
+static fr_dict_attr_t const *attr_freeradius_proxied_to;
+static fr_dict_attr_t const *attr_ms_mppe_send_key;
+static fr_dict_attr_t const *attr_ms_mppe_recv_key;
+static fr_dict_attr_t const *attr_user_name;
+static fr_dict_attr_t const *attr_user_password;
+
+static fr_dict_attr_t const *attr_eap_fast_crypto_binding;
+static fr_dict_attr_t const *attr_eap_fast_eap_payload;
+static fr_dict_attr_t const *attr_eap_fast_error;
+static fr_dict_attr_t const *attr_eap_fast_intermediate_result;
+static fr_dict_attr_t const *attr_eap_fast_nak;
+static fr_dict_attr_t const *attr_eap_fast_pac_a_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
+static fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
+static fr_dict_attr_t const *attr_eap_fast_pac_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_key;
+static fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_result;
+static fr_dict_attr_t const *attr_eap_fast_vendor_specific;
extern fr_dict_attr_autoload_t rlm_eap_fast_dict_attr[];
fr_dict_attr_autoload_t rlm_eap_fast_dict_attr[] = {
{ NULL }
};
+#define RANDFILL(x) do { fr_assert(sizeof(x) % sizeof(uint32_t) == 0); for (size_t i = 0; i < sizeof(x); i += sizeof(uint32_t)) *((uint32_t *)&x[i]) = fr_rand(); } while(0)
+
+/**
+ * RFC 4851 section 5.1 - EAP-FAST Authentication Phase 1: Key Derivations
+ */
+static void eap_fast_init_keys(request_t *request, fr_tls_session_t *tls_session)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ uint8_t *buf;
+ uint8_t *scratch;
+ size_t ksize;
+
+ RDEBUG2("Deriving EAP-FAST keys");
+
+ fr_assert(t->s_imck == NULL);
+
+ ksize = fr_tls_utils_keyblock_size_get(request, tls_session->ssl);
+ fr_assert(ksize > 0);
+ buf = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
+ scratch = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
+
+ t->keyblock = talloc(t, eap_fast_keyblock_t);
+
+ eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
+ memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
+ memset(buf, 0, ksize + sizeof(*t->keyblock));
+
+ t->s_imck = talloc_array(t, uint8_t, EAP_FAST_SIMCK_LEN);
+ memcpy(t->s_imck, t->keyblock, EAP_FAST_SKS_LEN); /* S-IMCK[0] = session_key_seed */
+
+ t->cmk = talloc_array(t, uint8_t, EAP_FAST_CMK_LEN); /* note that CMK[0] is not defined */
+ t->imck_count = 0;
+
+ talloc_free(buf);
+ talloc_free(scratch);
+}
+
+/**
+ * RFC 4851 section 5.2 - Intermediate Compound Key Derivations
+ */
+static void eap_fast_update_icmk(request_t *request, fr_tls_session_t *tls_session, uint8_t *msk)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ uint8_t imck[EAP_FAST_SIMCK_LEN + EAP_FAST_CMK_LEN];
+
+ RDEBUG2("Updating ICMK");
+
+ T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Inner Methods Compound Keys", msk, 32, imck, sizeof(imck)); //-V512
+
+ memcpy(t->s_imck, imck, EAP_FAST_SIMCK_LEN);
+ RHEXDUMP3(t->s_imck, EAP_FAST_SIMCK_LEN, "S-IMCK[j]");
+
+ memcpy(t->cmk, &imck[EAP_FAST_SIMCK_LEN], EAP_FAST_CMK_LEN);
+ RHEXDUMP3(t->cmk, EAP_FAST_CMK_LEN, "CMK[j]");
+
+ t->imck_count++;
+
+ /*
+ * Calculate MSK/EMSK at the same time as they are coupled to ICMK
+ *
+ * RFC 4851 section 5.4 - EAP Master Session Key Generation
+ */
+ t->msk = talloc_array(t, uint8_t, EAP_FAST_KEY_LEN);
+ T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Session Key Generating Function", NULL, 0, t->msk, EAP_FAST_KEY_LEN);
+ RHEXDUMP3(t->msk, EAP_FAST_KEY_LEN, "MSK");
+
+ t->emsk = talloc_array(t, uint8_t, EAP_EMSK_LEN);
+ T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Extended Session Key Generating Function", NULL, 0, t->emsk, EAP_EMSK_LEN);
+ RHEXDUMP3(t->emsk, EAP_EMSK_LEN, "EMSK");
+}
+
+static void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *tlv, bool mandatory, int length, void const *data)
+{
+ uint16_t hdr[2];
+
+ hdr[0] = (mandatory) ? htons(tlv->attr | EAP_FAST_TLV_MANDATORY) : htons(tlv->attr);
+ hdr[1] = htons(length);
+
+ tls_session->record_from_buff(&tls_session->clean_in, &hdr, 4);
+ tls_session->record_from_buff(&tls_session->clean_in, data, length);
+}
+
+static void eap_fast_send_error(fr_tls_session_t *tls_session, int error)
+{
+ uint32_t value;
+ value = htonl(error);
+
+ eap_fast_tlv_append(tls_session, attr_eap_fast_error, true, sizeof(value), &value);
+}
+
+static void eap_fast_append_result(fr_tls_session_t *tls_session, fr_radius_packet_code_t code)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ uint16_t state;
+ fr_dict_attr_t const *da;
+
+
+ da = (t->result_final) ? attr_eap_fast_result : attr_eap_fast_intermediate_result;
+ state = htons((code == FR_RADIUS_CODE_ACCESS_REJECT) ? EAP_FAST_TLV_RESULT_FAILURE : EAP_FAST_TLV_RESULT_SUCCESS);
+
+ eap_fast_tlv_append(tls_session, da, true, sizeof(state), &state);
+}
+
+static void eap_fast_send_identity_request(request_t *request, fr_tls_session_t *tls_session, eap_session_t *eap_session)
+{
+ eap_packet_raw_t eap_packet;
+
+ RDEBUG2("Sending EAP-Identity");
+
+ eap_packet.code = FR_EAP_CODE_REQUEST;
+ eap_packet.id = eap_session->this_round->response->id + 1;
+ eap_packet.length[0] = 0;
+ eap_packet.length[1] = EAP_HEADER_LEN + 1;
+ eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
+
+ eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, sizeof(eap_packet), &eap_packet);
+}
+
+static void eap_fast_send_pac_tunnel(request_t *request, fr_tls_session_t *tls_session)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ eap_fast_pac_t pac;
+ eap_fast_attr_pac_opaque_plaintext_t opaque_plaintext;
+ int alen, dlen;
+
+ memset(&pac, 0, sizeof(pac));
+ memset(&opaque_plaintext, 0, sizeof(opaque_plaintext));
+
+ RDEBUG2("Sending Tunnel PAC");
+
+ pac.key.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_key->attr);
+ pac.key.hdr.length = htons(sizeof(pac.key.data));
+ fr_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0);
+ RANDFILL(pac.key.data);
+
+ pac.info.lifetime.hdr.type = htons(attr_eap_fast_pac_info_pac_lifetime->attr);
+ pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data));
+ pac.info.lifetime.data = htonl(fr_time_to_sec(fr_time_add(request->packet->timestamp, t->pac_lifetime)));
+
+ pac.info.a_id.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_a_id->attr);
+ pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data));
+ memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data));
+
+ pac.info.a_id_info.hdr.type = htons(attr_eap_fast_pac_a_id->attr);
+ pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data));
+
+#define MIN(a,b) (((a)>(b)) ? (b) : (a))
+ alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data));
+ memcpy(pac.info.a_id_info.data, t->authority_identity, alen);
+
+ pac.info.type.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_pac_type->attr);
+ pac.info.type.hdr.length = htons(sizeof(pac.info.type.data));
+ pac.info.type.data = htons(PAC_TYPE_TUNNEL);
+
+ pac.info.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_tlv->attr);
+ pac.info.hdr.length = htons(sizeof(pac.info.lifetime)
+ + sizeof(pac.info.a_id)
+ + sizeof(pac.info.a_id_info)
+ + sizeof(pac.info.type));
+
+ memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type));
+ memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime));
+ memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key));
+
+ RHEXDUMP3((uint8_t const *)&opaque_plaintext, sizeof(opaque_plaintext), "PAC-Opaque plaintext data section");
+
+ fr_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN);
+ memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH);
+ fr_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0);
+ dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
+ t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
+ pac.opaque.data, pac.opaque.tag);
+
+ pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_opaque_tlv->attr);
+ pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
+ RHEXDUMP3((uint8_t const *)&pac.opaque, sizeof(pac.opaque) - sizeof(pac.opaque.data) + dlen, "PAC-Opaque");
+
+ eap_fast_tlv_append(tls_session, attr_eap_fast_pac_tlv, true, sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac);
+}
+
+static void eap_fast_append_crypto_binding(request_t *request, fr_tls_session_t *tls_session)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ eap_tlv_crypto_binding_tlv_t binding = {0};
+ int const len = sizeof(binding) - (&binding.reserved - (uint8_t *)&binding);
+
+ RDEBUG2("Sending Cryptobinding");
+
+ binding.tlv_type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_crypto_binding->attr);
+ binding.length = htons(len);
+ binding.version = EAP_FAST_VERSION;
+ binding.received_version = EAP_FAST_VERSION; /* FIXME use the clients value */
+ binding.subtype = EAP_FAST_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST;
+
+ fr_assert(sizeof(binding.nonce) % sizeof(uint32_t) == 0);
+ RANDFILL(binding.nonce);
+ binding.nonce[sizeof(binding.nonce) - 1] &= ~0x01; /* RFC 4851 section 4.2.8 */
+ RHEXDUMP3(binding.nonce, sizeof(binding.nonce), "NONCE");
+
+ RHEXDUMP3((uint8_t const *) &binding, sizeof(binding), "Crypto-Binding TLV for Compound MAC calculation");
+
+ fr_hmac_sha1(binding.compound_mac, (uint8_t *)&binding, sizeof(binding), t->cmk, EAP_FAST_CMK_LEN);
+ RHEXDUMP3(binding.compound_mac, sizeof(binding.compound_mac), "Compound MAC");
+
+ eap_fast_tlv_append(tls_session, attr_eap_fast_crypto_binding, true, len, &binding.reserved);
+}
+
+#define EAP_FAST_TLV_MAX 11
+
+static int eap_fast_verify(request_t *request, fr_tls_session_t *tls_session, uint8_t const *data, unsigned int data_len)
+{
+ uint16_t attr;
+ uint16_t length;
+ unsigned int remaining = data_len;
+ int total = 0;
+ int num[EAP_FAST_TLV_MAX] = {0};
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ uint32_t present = 0;
+
+ fr_assert(sizeof(present) * 8 > EAP_FAST_TLV_MAX);
+
+ while (remaining > 0) {
+ if (remaining < 4) {
+ RDEBUG2("EAP-FAST TLV is too small (%u) to contain a EAP-FAST TLV header", remaining);
+ return 0;
+ }
+
+ memcpy(&attr, data, sizeof(attr));
+ attr = ntohs(attr) & EAP_FAST_TLV_TYPE;
+
+ if ((attr == attr_eap_fast_result->attr) ||
+ (attr == attr_eap_fast_nak->attr) ||
+ (attr == attr_eap_fast_error->attr) ||
+ (attr == attr_eap_fast_vendor_specific->attr) ||
+ (attr == attr_eap_fast_eap_payload->attr) ||
+ (attr == attr_eap_fast_intermediate_result->attr) ||
+ (attr == attr_eap_fast_pac_tlv->attr) ||
+ (attr == attr_eap_fast_crypto_binding->attr)) {
+ num[attr]++;
+ present |= 1 << attr;
+
+ if (num[attr_eap_fast_eap_payload->attr] > 1) {
+ REDEBUG("Too many EAP-Payload TLVs");
+unexpected:
+ for (int i = 0; i < EAP_FAST_TLV_MAX; i++) {
+ if (present & (1 << i)) RDEBUG2(" - attribute %d is present", i);
+ }
+ eap_fast_send_error(tls_session, EAP_FAST_ERR_UNEXPECTED_TLV);
+ return 0;
+ }
+
+ if (num[attr_eap_fast_intermediate_result->attr] > 1) {
+ REDEBUG("Too many Intermediate-Result TLVs");
+ goto unexpected;
+ }
+ } else {
+ if ((data[0] & 0x80) != 0) {
+ REDEBUG("Unknown mandatory TLV %02x", attr);
+ goto unexpected;
+ }
+
+ num[0]++;
+ }
+
+ total++;
+
+ memcpy(&length, data + 2, sizeof(length));
+ length = ntohs(length);
+
+ data += 4;
+ remaining -= 4;
+
+ if (length > remaining) {
+ RDEBUG2("EAP-FAST TLV %u is longer than room remaining in the packet (%u > %u).", attr,
+ length, remaining);
+ return 0;
+ }
+
+ /*
+ * If the rest of the TLVs are larger than
+ * this attribute, continue.
+ *
+ * Otherwise, if the attribute over-flows the end
+ * of the TLCs, die.
+ */
+ if (remaining < length) {
+ RDEBUG2("EAP-FAST TLV overflows packet!");
+ return 0;
+ }
+
+ /*
+ * If there's an error, we bail out of the
+ * authentication process before allocating
+ * memory.
+ */
+ if ((attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_result->attr)) {
+ uint16_t status;
+
+ if (length < 2) {
+ REDEBUG("EAP-FAST TLV %u is too short. Expected 2, got %d", attr, length);
+ return 0;
+ }
+
+ memcpy(&status, data, 2);
+ status = ntohs(status);
+
+ if (status == EAP_FAST_TLV_RESULT_FAILURE) {
+ REDEBUG("EAP-FAST TLV %u indicates failure. Rejecting request", attr);
+ return 0;
+ }
+
+ if (status != EAP_FAST_TLV_RESULT_SUCCESS) {
+ REDEBUG("EAP-FAST TLV %u contains unknown value. Rejecting request", attr);
+ goto unexpected;
+ }
+ }
+
+ /*
+ * remaining > length, continue.
+ */
+ remaining -= length;
+ data += length;
+ }
+
+ /*
+ * Check if the peer mixed & matched TLVs.
+ */
+ if ((num[attr_eap_fast_nak->attr] > 0) && (num[attr_eap_fast_nak->attr] != total)) {
+ REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request");
+ goto unexpected;
+ }
+
+ if (num[attr_eap_fast_intermediate_result->attr] > 0) {
+ REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request");
+ goto unexpected;
+ }
+
+ /*
+ * Check mandatory or not mandatory TLVs.
+ */
+ switch (t->stage) {
+ case EAP_FAST_TLS_SESSION_HANDSHAKE:
+ if (present) {
+ REDEBUG("Unexpected TLVs in TLS Session Handshake stage");
+ goto unexpected;
+ }
+ break;
+ case EAP_FAST_AUTHENTICATION:
+ if (present != (uint32_t)(1 << attr_eap_fast_eap_payload->attr)) {
+ REDEBUG("Unexpected TLVs in authentication stage");
+ goto unexpected;
+ }
+ break;
+ case EAP_FAST_CRYPTOBIND_CHECK:
+ {
+ uint32_t bits = (t->result_final)
+ ? 1 << attr_eap_fast_result->attr
+ : 1 << attr_eap_fast_intermediate_result->attr;
+ if (present & ~(bits | (1 << attr_eap_fast_crypto_binding->attr) | (1 << attr_eap_fast_pac_tlv->attr))) {
+ REDEBUG("Unexpected TLVs in cryptobind checking stage");
+ goto unexpected;
+ }
+ break;
+ }
+ case EAP_FAST_PROVISIONING:
+ if (present & ~((1 << attr_eap_fast_pac_tlv->attr) | (1 << attr_eap_fast_result->attr))) {
+ REDEBUG("Unexpected TLVs in provisioning stage");
+ goto unexpected;
+ }
+ break;
+ case EAP_FAST_COMPLETE:
+ if (present) {
+ REDEBUG("Unexpected TLVs in complete stage");
+ goto unexpected;
+ }
+ break;
+ default:
+ REDEBUG("Unexpected stage %d", t->stage);
+ return 0;
+ }
+
+ /*
+ * We got this far. It looks OK.
+ */
+ return 1;
+}
+
+/**
+ *
+ * FIXME do something with mandatory
+ */
+static ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t data_len,
+ void *decode_ctx)
+{
+ fr_dict_attr_t const *da;
+ uint8_t const *p = data, *end = p + data_len;
+
+ /*
+ * Decode the TLVs
+ */
+ while (p < end) {
+ ssize_t ret;
+ uint16_t attr;
+ uint16_t len;
+ fr_pair_t *vp;
+
+ attr = fr_nbo_to_uint16(p) & EAP_FAST_TLV_TYPE;
+ p += 2;
+ len = fr_nbo_to_uint16(p);
+ p += 2;
+
+ da = fr_dict_attr_child_by_num(parent, attr);
+ if (!da) {
+ MEM(vp = fr_pair_afrom_child_num(ctx, parent, attr));
+
+ } else if (da->type == FR_TYPE_TLV) {
+ p += (size_t) eap_fast_decode_pair(ctx, out, parent, p, len, decode_ctx);
+ continue;
+
+ } else {
+ MEM(vp = fr_pair_afrom_da(ctx, da));
+ }
+
+ ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
+ &FR_DBUFF_TMP(p, (size_t)len), len, true);
+ if (ret != len) {
+ fr_pair_raw_afrom_pair(vp, p, len);
+ }
+ fr_pair_append(out, vp);
+ p += len;
+ }
+
+ return p - data;
+}
+
+
+/*
+ * Use a reply packet to determine what to do.
+ */
+static rlm_rcode_t CC_HINT(nonnull) process_reply(UNUSED eap_session_t *eap_session,
+ fr_tls_session_t *tls_session,
+ request_t *request,
+ fr_packet_t *reply, fr_pair_list_t *reply_list)
+{
+ rlm_rcode_t rcode = RLM_MODULE_REJECT;
+ fr_pair_t *vp;
+ fr_dcursor_t cursor;
+
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+ /*
+ * If the response packet was Access-Accept, then
+ * we're OK. If not, die horribly.
+ *
+ * FIXME: EAP-Messages can only start with 'identity',
+ * NOT 'eap start', so we should check for that....
+ */
+ switch (reply->code) {
+ case FR_RADIUS_CODE_ACCESS_ACCEPT:
+ RDEBUG2("Got tunneled Access-Accept");
+
+ rcode = RLM_MODULE_OK;
+
+ /*
+ * Copy what we need into the TTLS tunnel and leave
+ * the rest to be cleaned up.
+ */
+ for (vp = fr_pair_list_head(reply_list); vp; vp = fr_pair_list_next(reply_list, vp)) {
+ if (fr_dict_vendor_num_by_da(vp->da) != VENDORPEC_MICROSOFT) continue;
+
+ /* FIXME must be a better way to capture/re-derive this later for ISK */
+ switch (vp->da->attr) {
+ case FR_MSCHAP_MPPE_SEND_KEY:
+ if (vp->vp_length != MD5_DIGEST_LENGTH) {
+ wrong_length:
+ REDEBUG("Found %s with incorrect length. Expected %u, got %zu",
+ vp->da->name, MD5_DIGEST_LENGTH, vp->vp_length);
+ rcode = RLM_MODULE_INVALID;
+ break;
+ }
+
+ memcpy(t->isk.mppe_send, vp->vp_octets, MD5_DIGEST_LENGTH);
+ break;
+
+ case FR_MSCHAP_MPPE_RECV_KEY:
+ if (vp->vp_length != MD5_DIGEST_LENGTH) goto wrong_length;
+ memcpy(t->isk.mppe_recv, vp->vp_octets, MD5_DIGEST_LENGTH);
+ break;
+
+ case FR_MSCHAP2_SUCCESS:
+ RDEBUG2("Got %s, tunneling it to the client in a challenge", vp->da->name);
+ rcode = RLM_MODULE_HANDLED;
+ t->authenticated = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ RHEXDUMP3((uint8_t *)&t->isk, 2 * MD5_DIGEST_LENGTH, "ISK[j]"); /* FIXME (part of above) */
+ break;
+
+ case FR_RADIUS_CODE_ACCESS_REJECT:
+ REDEBUG("Got tunneled Access-Reject");
+ rcode = RLM_MODULE_REJECT;
+ break;
+
+ case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+ RDEBUG2("Got tunneled Access-Challenge");
+
+ /*
+ * Copy the EAP-Message back to the tunnel.
+ */
+
+ for (vp = fr_pair_dcursor_by_da_init(&cursor, reply_list, attr_eap_message);
+ vp;
+ vp = fr_dcursor_next(&cursor)) {
+ eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, vp->vp_length, vp->vp_octets);
+ }
+
+ rcode = RLM_MODULE_HANDLED;
+ break;
+
+ default:
+ REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+ rcode = RLM_MODULE_INVALID;
+ break;
+ }
+
+ return rcode;
+}
+
+static fr_radius_packet_code_t eap_fast_eap_payload(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session,
+ fr_tls_session_t *tls_session, fr_pair_t *tlv_eap_payload)
+{
+ fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
+ rlm_rcode_t rcode;
+ fr_pair_t *vp;
+ eap_fast_tunnel_t *t;
+ request_t *fake;
+ rlm_eap_fast_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_fast_t);
+
+ RDEBUG2("Processing received EAP Payload");
+
+ /*
+ * Allocate a fake request_t structure.
+ */
+ fake = request_local_alloc_internal(request, &(request_init_args_t){ .parent = request });
+ fr_assert(fr_pair_list_empty(&fake->request_pairs));
+
+ t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+ /*
+ * Add the tunneled attributes to the fake request.
+ */
+
+ MEM(vp = fr_pair_afrom_da(fake->request_ctx, attr_eap_message));
+ fr_pair_append(&fake->request_pairs, vp);
+ fr_pair_value_memdup(vp, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false);
+
+ RDEBUG2("Got tunneled request");
+ log_request_pair_list(L_DBG_LVL_1, fake, NULL, &fake->request_pairs, NULL);
+
+ /*
+ * Tell the request that it's a fake one.
+ */
+ MEM(fr_pair_prepend_by_da(fake->request_ctx, &vp, &fake->request_pairs, attr_freeradius_proxied_to) >= 0);
+ (void)fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1") - 1, NULL, false);
+
+ /*
+ * If there's no User-Name in the stored data, look for
+ * an EAP-Identity, and pull it out of there.
+ */
+ if (!t->username) {
+ fr_assert(vp->da == attr_eap_message); /* cached from above */
+
+ if ((vp->vp_length >= EAP_HEADER_LEN + 2) &&
+ (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
+ (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
+ (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
+ /*
+ * Create and remember a User-Name
+ */
+ MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+ t->username->vp_tainted = true;
+ fr_pair_value_bstrndup(t->username, (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
+
+ RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+ } else {
+ /*
+ * Don't reject the request outright,
+ * as it's permitted to do EAP without
+ * user-name.
+ */
+ RWDEBUG2("No EAP-Identity found to start EAP conversation");
+ }
+ } /* else there WAS a t->username */
+
+ if (t->username) {
+ vp = fr_pair_copy(fake->request_ctx, t->username);
+ fr_pair_append(&fake->request_pairs, vp);
+ }
+
+ if (t->stage == EAP_FAST_AUTHENTICATION) { /* FIXME do this only for MSCHAPv2 */
+ fr_pair_t *tvp;
+
+ MEM(tvp = fr_pair_afrom_da(fake, attr_eap_type));
+ tvp->vp_uint32 = t->default_provisioning_method;
+ fr_pair_append(&fake->control_pairs, tvp);
+
+ /*
+ * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2
+ */
+ if (t->mode == EAP_FAST_PROVISIONING_ANON) {
+ MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge));
+ fr_pair_value_memdup(tvp, t->keyblock->server_challenge, MD5_DIGEST_LENGTH, false);
+ fr_pair_append(&fake->control_pairs, tvp);
+ RHEXDUMP3(t->keyblock->server_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 auth_challenge");
+
+ MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge));
+ fr_pair_value_memdup(tvp, t->keyblock->client_challenge, MD5_DIGEST_LENGTH, false);
+ fr_pair_append(&fake->control_pairs, tvp);
+ RHEXDUMP3(t->keyblock->client_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 peer_challenge");
+ }
+ }
+
+ /*
+ * Call authentication recursively, which will
+ * do PAP, CHAP, MS-CHAP, etc.
+ */
+ eap_virtual_server(request, eap_session, inst->virtual_server);
+
+ /*
+ * Decide what to do with the reply.
+ */
+ switch (fake->reply->code) {
+ case 0: /* No reply code, must be proxied... */
+#ifdef WITH_PROXY
+ vp = fr_pair_find_by_da(&fake->control, NULL, attr_proxy_to_realm);
+ if (vp) {
+ int ret;
+ eap_tunnel_data_t *tunnel;
+
+ RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data);
+
+ /*
+ * Tell the original request that it's going to be proxied.
+ */
+ fr_pair_list_copy_by_da(request->control_ctx, &request->control_pairs,
+ &fake->control_pairs, attr_proxy_to_realm, 0);
+
+ /*
+ * Seed the proxy packet with the tunneled request.
+ */
+ fr_assert(!request->proxy);
+
+ /*
+ * FIXME: Actually proxy stuff
+ */
+ request->proxy = request_alloc_internal(request, &(request_init_args_t){ .parent = request });
+
+ request->proxy->packet = talloc_steal(request->proxy, fake->packet);
+ memset(&request->proxy->packet->src_ipaddr, 0,
+ sizeof(request->proxy->packet->src_ipaddr));
+ memset(&request->proxy->packet->src_ipaddr, 0,
+ sizeof(request->proxy->packet->src_ipaddr));
+ request->proxy->packet->src_port = 0;
+ request->proxy->packet->dst_port = 0;
+ fake->packet = NULL;
+ fr_packet_free(&fake->reply);
+ fake->reply = NULL;
+
+ /*
+ * Set up the callbacks for the tunnel
+ */
+ tunnel = talloc_zero(request, eap_tunnel_data_t);
+ tunnel->tls_session = tls_session;
+
+ /*
+ * Associate the callback with the request.
+ */
+ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK,
+ tunnel, false, false, false);
+ fr_cond_assert(ret == 0);
+
+ /*
+ * rlm_eap.c has taken care of associating the eap_session
+ * with the fake request.
+ *
+ * So we associate the fake request with this request.
+ */
+ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK,
+ fake, true, false, false);
+ fr_cond_assert(ret == 0);
+
+ fake = NULL;
+
+ /*
+ * Didn't authenticate the packet, but we're proxying it.
+ */
+ code = FR_RADIUS_CODE_STATUS_CLIENT;
+
+ } else
+#endif /* WITH_PROXY */
+ {
+ REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user");
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ }
+ break;
+
+ default:
+ /*
+ * Returns RLM_MODULE_FOO, and we want to return FR_FOO
+ */
+ rcode = process_reply(eap_session, tls_session, request, fake->reply, &fake->reply_pairs);
+ switch (rcode) {
+ case RLM_MODULE_REJECT:
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ break;
+
+ case RLM_MODULE_HANDLED:
+ code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+
+ case RLM_MODULE_OK:
+ code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+ break;
+
+ default:
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ break;
+ }
+ break;
+ }
+
+ talloc_free(fake);
+
+ return code;
+}
+
+static fr_radius_packet_code_t eap_fast_crypto_binding(request_t *request, UNUSED eap_session_t *eap_session,
+ fr_tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t *binding)
+{
+ uint8_t cmac[sizeof(binding->compound_mac)];
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+ memcpy(cmac, binding->compound_mac, sizeof(cmac));
+ memset(binding->compound_mac, 0, sizeof(binding->compound_mac));
+
+ RHEXDUMP3((uint8_t const *) binding, sizeof(*binding), "Crypto-Binding TLV for Compound MAC calculation");
+ RHEXDUMP3(cmac, sizeof(cmac), "Received Compound MAC");
+
+ fr_hmac_sha1(binding->compound_mac, (uint8_t *)binding, sizeof(*binding), t->cmk, EAP_FAST_CMK_LEN);
+ if (memcmp(binding->compound_mac, cmac, sizeof(cmac))) {
+ RDEBUG2("Crypto-Binding TLV mismatch");
+ RHEXDUMP3((uint8_t const *) binding->compound_mac,
+ sizeof(binding->compound_mac), "Calculated Compound MAC");
+ return FR_RADIUS_CODE_ACCESS_REJECT;
+ }
+
+ return FR_RADIUS_CODE_ACCESS_ACCEPT;
+}
+
+static fr_radius_packet_code_t eap_fast_process_tlvs(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session,
+ fr_tls_session_t *tls_session, fr_pair_list_t *fast_vps)
+{
+ eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+ fr_pair_t *vp;
+ eap_tlv_crypto_binding_tlv_t my_binding, *binding = NULL;
+
+ memset(&my_binding, 0, sizeof(my_binding));
+
+ for (vp = fr_pair_list_head(fast_vps);
+ vp;
+ vp = fr_pair_list_next(fast_vps, vp)) {
+ fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
+ if (vp->da->parent == fr_dict_root(dict_eap_fast)) {
+ if (vp->da == attr_eap_fast_eap_payload) {
+ code = eap_fast_eap_payload(request, mctx, eap_session, tls_session, vp);
+ if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) t->stage = EAP_FAST_CRYPTOBIND_CHECK;
+ } else if ((vp->da == attr_eap_fast_result) ||
+ (vp->da == attr_eap_fast_intermediate_result)) {
+ code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+ t->stage = EAP_FAST_PROVISIONING;
+ } else {
+ RDEBUG2("ignoring unknown %pP", vp);
+ continue;
+ }
+ } else if (vp->da->parent == attr_eap_fast_crypto_binding) {
+ binding = &my_binding;
+
+ /*
+ * fr_radius_encode_pair() does not work for structures
+ */
+ switch (vp->da->attr) {
+ case 1: /* FR_EAP_FAST_CRYPTO_BINDING_RESERVED */
+ binding->reserved = vp->vp_uint8;
+ break;
+ case 2: /* FR_EAP_FAST_CRYPTO_BINDING_VERSION */
+ binding->version = vp->vp_uint8;
+ break;
+ case 3: /* FR_EAP_FAST_CRYPTO_BINDING_RECV_VERSION */
+ binding->received_version = vp->vp_uint8;
+ break;
+ case 4: /* FR_EAP_FAST_CRYPTO_BINDING_SUB_TYPE */
+ binding->subtype = vp->vp_uint8;
+ break;
+ case 5: /* FR_EAP_FAST_CRYPTO_BINDING_NONCE */
+ if (vp->vp_length >= sizeof(binding->nonce)) {
+ memcpy(binding->nonce, vp->vp_octets, vp->vp_length);
+ }
+ break;
+ case 6: /* FR_EAP_FAST_CRYPTO_BINDING_COMPOUND_MAC */
+ if (vp->vp_length >= sizeof(binding->compound_mac)) {
+ memcpy(binding->compound_mac, vp->vp_octets, sizeof(binding->compound_mac));
+ }
+ break;
+ }
+ continue;
+ } else if (vp->da->parent == attr_eap_fast_pac_tlv) {
+ if (vp->da == attr_eap_fast_pac_acknowledge) {
+ if (vp->vp_uint32 == EAP_FAST_TLV_RESULT_SUCCESS) {
+ code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+ t->pac.expires = fr_time_max();
+ t->pac.expired = false;
+ t->stage = EAP_FAST_COMPLETE;
+ }
+ } else if (vp->da == attr_eap_fast_pac_info_pac_type) {
+ if (vp->vp_uint32 != PAC_TYPE_TUNNEL) {
+ RDEBUG2("only able to serve Tunnel PAC's, ignoring request");
+ continue;
+ }
+ t->pac.send = true;
+ continue;
+ } else {
+ RDEBUG2("ignoring unknown EAP-FAST-PAC-TLV %pP", vp);
+ continue;
+ }
+ } else {
+ RDEBUG2("ignoring non-EAP-FAST TLV %pP", vp);
+ continue;
+ }
+
+ if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
+ }
+
+ if (binding) {
+ fr_radius_packet_code_t code = eap_fast_crypto_binding(request, eap_session, tls_session, binding);
+ if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) {
+ t->stage = EAP_FAST_PROVISIONING;
+ }
+ return code;
+ }
+
+ return FR_RADIUS_CODE_ACCESS_ACCEPT;
+}
+
+
+/*
+ * Process the inner tunnel data
+ */
+static fr_radius_packet_code_t eap_fast_process(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ fr_radius_packet_code_t code;
+ fr_pair_list_t fast_vps;
+ uint8_t const *data;
+ size_t data_len;
+ eap_fast_tunnel_t *t;
+
+ fr_pair_list_init(&fast_vps);
+ /*
+ * Just look at the buffer directly, without doing
+ * record_to_buff.
+ */
+ data_len = tls_session->clean_out.used;
+ tls_session->clean_out.used = 0;
+ data = tls_session->clean_out.data;
+
+ t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+ /*
+ * See if the tunneled data is well formed.
+ */
+ if (!eap_fast_verify(request, tls_session, data, data_len)) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+ if (t->stage == EAP_FAST_TLS_SESSION_HANDSHAKE) {
+ char buf[256];
+
+ fr_assert(t->mode == EAP_FAST_UNKNOWN);
+
+ if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl),
+ buf, sizeof(buf)), "Au=None")) {
+ /* FIXME enforce MSCHAPv2 - RFC 5422 section 3.2.2 */
+ RDEBUG2("Using anonymous provisioning");
+ t->mode = EAP_FAST_PROVISIONING_ANON;
+ t->pac.send = true;
+ } else {
+ fr_time_t renew;
+
+ if (SSL_session_reused(tls_session->ssl)) {
+ RDEBUG2("Session Resumed from PAC");
+ t->mode = EAP_FAST_NORMAL_AUTH;
+ } else {
+ RDEBUG2("Using authenticated provisioning");
+ t->mode = EAP_FAST_PROVISIONING_AUTH;
+ }
+
+ /*
+ * Send a new pac at 60% of the lifetime,
+ * or if the PAC has expired, or if no lifetime was set.
+ */
+ renew = fr_time_add(request->packet->timestamp,
+ fr_time_delta_wrap((fr_time_delta_unwrap(t->pac_lifetime) * 3) / 5));
+
+ if (t->pac.expired || fr_time_eq(t->pac.expires, fr_time_wrap(0)) ||
+ fr_time_lteq(t->pac.expires, renew)) {
+ t->pac.send = true;
+ }
+ }
+
+ eap_fast_init_keys(request, tls_session);
+
+ eap_fast_send_identity_request(request, tls_session, eap_session);
+
+ t->stage = EAP_FAST_AUTHENTICATION;
+ return FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ }
+
+ if (eap_fast_decode_pair(request, &fast_vps, fr_dict_root(dict_eap_fast),
+ data, data_len, NULL) < 0) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+ RDEBUG2("Got Tunneled FAST TLVs");
+ log_request_pair_list(L_DBG_LVL_1, request, NULL, &fast_vps, NULL);
+ code = eap_fast_process_tlvs(request, mctx, eap_session, tls_session, &fast_vps);
+ fr_pair_list_free(&fast_vps);
+
+ if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+ switch (t->stage) {
+ case EAP_FAST_AUTHENTICATION:
+ code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+
+ case EAP_FAST_CRYPTOBIND_CHECK:
+ {
+ if (t->mode != EAP_FAST_PROVISIONING_ANON && !t->pac.send)
+ t->result_final = true;
+
+ eap_fast_append_result(tls_session, code);
+
+ eap_fast_update_icmk(request, tls_session, (uint8_t *)&t->isk);
+ eap_fast_append_crypto_binding(request, tls_session);
+
+ code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+ }
+ case EAP_FAST_PROVISIONING:
+ t->result_final = true;
+
+ eap_fast_append_result(tls_session, code);
+
+ if (t->pac.send) {
+ RDEBUG2("Peer requires new PAC");
+ eap_fast_send_pac_tunnel(request, tls_session);
+ code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+ }
+
+ t->stage = EAP_FAST_COMPLETE;
+ FALL_THROUGH;
+
+ case EAP_FAST_COMPLETE:
+ /*
+ * RFC 5422 section 3.5 - Network Access after EAP-FAST Provisioning
+ */
+ if (t->pac.type && t->pac.expired) {
+ REDEBUG("Rejecting expired PAC.");
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ break;
+ }
+
+ if (t->mode == EAP_FAST_PROVISIONING_ANON) {
+ REDEBUG("Rejecting unauthenticated provisioning");
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ break;
+ }
+
+ /*
+ * eap_crypto_mppe_keys() is unsuitable for EAP-FAST as Cisco decided
+ * it would be a great idea to flip the recv/send keys around
+ */
+ #define EAPTLS_MPPE_KEY_LEN 32
+ eap_add_reply(request, attr_ms_mppe_recv_key, t->msk, EAPTLS_MPPE_KEY_LEN);
+ eap_add_reply(request, attr_ms_mppe_send_key, &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN);
+ eap_add_reply(request, attr_eap_msk, t->msk, EAP_FAST_KEY_LEN);
+ eap_add_reply(request, attr_eap_emsk, t->emsk, EAP_EMSK_LEN);
+
+ break;
+
+ default:
+ RERROR("Internal sanity check failed in EAP-FAST at %d", t->stage);
+ code = FR_RADIUS_CODE_ACCESS_REJECT;
+ }
+
+ return code;
+}
+
/** Allocate the FAST per-session data
*
*/
t->a_id = inst->a_id;
t->pac_opaque_key = (uint8_t const *)inst->pac_opaque_key;
- t->server_cs = inst->server_cs;
-
return t;
}
/*
* Process the FAST portion of the request.
*/
- switch (eap_fast_process(request, eap_session, tls_session)) {
+ switch (eap_fast_process(request, mctx, eap_session, tls_session)) {
case FR_RADIUS_CODE_ACCESS_REJECT:
eap_tls_fail(request, eap_session);
RETURN_UNLANG_FAIL;
rlm_eap_fast_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_fast_t);
CONF_SECTION *conf = mctx->mi->conf;
- inst->server_cs = virtual_server_cs(inst->virtual_server);
-
inst->default_provisioning_method = eap_name2type(inst->default_provisioning_method_name);
if (!inst->default_provisioning_method) {
cf_log_err_by_child(conf, "default_provisioning_eap_type", "Unknown EAP type %s",
TARGET := $(TARGETNAME)$(L)
endif
-SOURCES := $(TARGETNAME).c peap.c
+SOURCES := $(TARGETNAME).c
SRC_INCDIRS := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
+++ /dev/null
-#pragma once
-/*
- * eap_peap.h
- *
- * Version: $Id$
- *
- * 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 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-RCSIDH(eap_peap_h, "$Id$")
-
-#include <freeradius-devel/eap/tls.h>
-
-typedef enum {
- PEAP_STATUS_INVALID,
- PEAP_STATUS_SENT_TLV_SUCCESS,
- PEAP_STATUS_SENT_TLV_FAILURE,
- PEAP_STATUS_TUNNEL_ESTABLISHED,
- PEAP_STATUS_INNER_IDENTITY_REQ_SENT,
- PEAP_STATUS_PHASE2_INIT,
- PEAP_STATUS_PHASE2,
-} peap_status;
-
-typedef enum {
- PEAP_RESUMPTION_NO,
- PEAP_RESUMPTION_YES,
- PEAP_RESUMPTION_MAYBE
-} peap_resumption;
-
-typedef struct {
- fr_pair_t *username;
- peap_status status;
- bool home_access_accept;
- int default_method;
- CONF_SECTION *server_cs;
- peap_resumption session_resumption_state;
-} peap_tunnel_t;
-
-extern HIDDEN fr_dict_attr_t const *attr_auth_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-
-
-#define EAP_TLV_SUCCESS (1)
-#define EAP_TLV_FAILURE (2)
-#define EAP_TLV_ACK_RESULT (3)
-
-#define FR_PEAP_EXTENSIONS_TYPE 33
-
-/*
- * Process the PEAP portion of an EAP-PEAP request.
- */
-unlang_action_t eap_peap_process(unlang_result_t *p_result, request_t *request,
- eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
+++ /dev/null
-/*
- * peap.c contains the interfaces that are called from eap
- *
- * Version: $Id$
- *
- * 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 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-
-RCSID("$Id$")
-USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
-
-#include <freeradius-devel/radius/radius.h>
-#include <freeradius-devel/radius/defs.h>
-
-#include "eap_peap.h"
-
-/*
- * Send protected EAP-Failure
- *
- * Result-TLV = Failure
- */
-static int eap_peap_failure(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- uint8_t tlv_packet[11];
-
- RDEBUG2("FAILURE");
-
- tlv_packet[0] = FR_EAP_CODE_REQUEST;
- tlv_packet[1] = eap_session->this_round->response->id +1;
- tlv_packet[2] = 0;
- tlv_packet[3] = 11; /* length of this packet */
- tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
- tlv_packet[5] = 0x80;
- tlv_packet[6] = EAP_TLV_ACK_RESULT;
- tlv_packet[7] = 0;
- tlv_packet[8] = 2; /* length of the data portion */
- tlv_packet[9] = 0;
- tlv_packet[10] = EAP_TLV_FAILURE;
-
- (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
-
- /*
- * FIXME: Check the return code.
- */
- fr_tls_session_send(request, tls_session);
-
- return 1;
-}
-
-
-/*
- * Send protected EAP-Success
- *
- * Result-TLV = Success
- */
-static int eap_peap_success(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- uint8_t tlv_packet[11];
-
- RDEBUG2("SUCCESS");
-
- tlv_packet[0] = FR_EAP_CODE_REQUEST;
- tlv_packet[1] = eap_session->this_round->response->id +1;
- tlv_packet[2] = 0;
- tlv_packet[3] = 11; /* length of this packet */
- tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
- tlv_packet[5] = 0x80; /* mandatory AVP */
- tlv_packet[6] = EAP_TLV_ACK_RESULT;
- tlv_packet[7] = 0;
- tlv_packet[8] = 2; /* length of the data portion */
- tlv_packet[9] = 0;
- tlv_packet[10] = EAP_TLV_SUCCESS;
-
- (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
-
- /*
- * FIXME: Check the return code.
- */
- fr_tls_session_send(request, tls_session);
-
- return 1;
-}
-
-
-static int eap_peap_identity(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- eap_packet_raw_t eap_packet;
-
- eap_packet.code = FR_EAP_CODE_REQUEST;
- eap_packet.id = eap_session->this_round->response->id + 1;
- eap_packet.length[0] = 0;
- eap_packet.length[1] = EAP_HEADER_LEN + 1;
- eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
-
- (tls_session->record_from_buff)(&tls_session->clean_in, &eap_packet, sizeof(eap_packet));
- fr_tls_session_send(request, tls_session);
- (tls_session->record_init)(&tls_session->clean_in);
-
- return 1;
-}
-
-/*
- * Verify the tunneled EAP message.
- */
-static int eap_peap_verify(request_t *request, peap_tunnel_t *peap_tunnel,
- uint8_t const *data, size_t data_len)
-{
- eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
- eap_type_t eap_method;
-
- /*
- * No data, OR only 1 byte of EAP type.
- */
- if (!data || (data_len == 0) || ((data_len <= 1) && (data[0] != FR_EAP_METHOD_IDENTITY))) return 0;
-
- /*
- * Since the full EAP header is sent for the EAP Extensions type (Type 33),
- * but not for other Types, it is difficult for the implementation to distinguish
- * an Extensions Request (Code 1) from an EAP Type 1 (Identity) Request packet.
- *
- * i.e. The only way to validate PEAP inner method packets properly is to know
- * we just send a protected success/failure.
- */
- switch (peap_tunnel->status) {
- case PEAP_STATUS_SENT_TLV_SUCCESS:
- case PEAP_STATUS_SENT_TLV_FAILURE:
- if (eap_packet->data[0] != FR_PEAP_EXTENSIONS_TYPE) {
- REDEBUG("Invalid inner tunnel data, expected method (%u), got (%u)",
- FR_PEAP_EXTENSIONS_TYPE, eap_packet->data[0]);
- return -1;
- }
- return 0;
-
- default:
- break;
- }
-
- eap_method = data[0]; /* Inner EAP header misses off code and identifier */
- switch (eap_method) {
- case FR_EAP_METHOD_IDENTITY:
- RDEBUG2("Received EAP-Identity-Response");
- return 0;
-
- /*
- * We normally do Microsoft MS-CHAPv2 (26), versus
- * Cisco MS-CHAPv2 (29).
- */
- case FR_EAP_METHOD_MSCHAPV2:
- default:
- RDEBUG2("EAP method %s (%d)", eap_type2name(eap_method), eap_method);
- return 0;
- }
-
-}
-
-/*
- * Convert a pseudo-EAP packet to a list of fr_pair_t's.
- */
-static void eap_peap_inner_to_pairs(TALLOC_CTX *ctx, fr_pair_list_t *pairs,
- eap_round_t *eap_round,
- uint8_t const *data, size_t data_len)
-{
- size_t total;
- uint8_t *p;
- fr_pair_t *vp = NULL;
-
- if (data_len > 65535) return; /* paranoia */
-
- MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
- total = data_len;
- if (total > 249) total = 249;
-
- /*
- * Hand-build an EAP packet from the crap in PEAP version 0.
- */
- MEM(fr_pair_value_mem_alloc(vp, &p, EAP_HEADER_LEN + total, false) == 0);
- p[0] = FR_EAP_CODE_RESPONSE;
- p[1] = eap_round->response->id;
- p[2] = (data_len + EAP_HEADER_LEN) >> 8;
- p[3] = (data_len + EAP_HEADER_LEN) & 0xff;
- memcpy(p + EAP_HEADER_LEN, data, total);
-
- fr_pair_append(pairs, vp);
- while (total < data_len) {
- MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
- fr_pair_value_memdup(vp, data + total, (data_len - total), false);
-
- total += vp->vp_length;
-
- fr_pair_append(pairs, vp);
- }
-}
-
-
-/*
- * Convert a list of fr_pair_t's to an EAP packet, through the
- * simple expedient of dumping the EAP message
- */
-static int eap_peap_inner_from_pairs(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *vps)
-{
- fr_pair_t *this;
-
- fr_assert(!fr_pair_list_empty(vps));
-
- /*
- * Send the EAP data in the first attribute, WITHOUT the
- * header.
- */
- this = fr_pair_list_head(vps);
- (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets + EAP_HEADER_LEN,
- this->vp_length - EAP_HEADER_LEN);
-
- /*
- * Send the rest of the EAP data, but skipping the first VP.
- */
- for (this = fr_pair_list_next(vps, this);
- this;
- this = fr_pair_list_next(vps, this)) {
- (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets, this->vp_length);
- }
-
- fr_tls_session_send(request, tls_session);
-
- return 1;
-}
-
-
-/*
- * See if there's a TLV in the response.
- */
-static int eap_peap_check_tlv(request_t *request, uint8_t const *data, size_t data_len)
-{
- eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
-
- if (data_len < 11) return 0;
-
- /*
- * Look for success or failure.
- */
- if ((eap_packet->code == FR_EAP_CODE_RESPONSE) &&
- (eap_packet->data[0] == FR_PEAP_EXTENSIONS_TYPE)) {
- if (data[10] == EAP_TLV_SUCCESS) {
- return 1;
- }
-
- if (data[10] == EAP_TLV_FAILURE) {
- RDEBUG2("Client rejected our response. The password is probably incorrect");
- return 0;
- }
- }
-
- RDEBUG2("Unknown TLV %02x", data[10]);
-
- return 0;
-}
-
-
-/*
- * Use a reply packet to determine what to do.
- */
-static unlang_action_t process_reply(unlang_result_t *p_result, request_t *request, UNUSED void *uctx)
-{
- eap_session_t *eap_session = talloc_get_type_abort(uctx, eap_session_t);
- eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
- fr_tls_session_t *tls_session = eap_tls_session->tls_session;
- fr_pair_list_t vps;
- peap_tunnel_t *t = tls_session->opaque;
- request_t *parent = request->parent;
- fr_packet_t *reply = request->reply;
-
- p_result->priority = MOD_PRIORITY_MAX;
-
- if (RDEBUG_ENABLED2) {
-
- /*
- * Note that we don't do *anything* with the reply
- * attributes.
- */
- if (FR_RADIUS_PACKET_CODE_VALID(reply->code)) {
- RDEBUG2("Got tunneled reply %s", fr_radius_packet_name[reply->code]);
- } else {
- RDEBUG2("Got tunneled reply code %i", reply->code);
- }
- log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL);
- }
-
- switch (reply->code) {
- case FR_RADIUS_CODE_ACCESS_ACCEPT:
- RDEBUG2("Tunneled authentication was successful");
- t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
- eap_peap_success(request, eap_session, tls_session);
- RETURN_UNLANG_HANDLED;
-
- case FR_RADIUS_CODE_ACCESS_REJECT:
- RDEBUG2("Tunneled authentication was rejected");
- t->status = PEAP_STATUS_SENT_TLV_FAILURE;
- eap_peap_failure(request, eap_session, tls_session);
- RETURN_UNLANG_HANDLED;
-
- case FR_RADIUS_CODE_ACCESS_CHALLENGE:
- RDEBUG2("Got tunneled Access-Challenge");
-
- /*
- * PEAP takes only EAP-Message attributes inside
- * of the tunnel. Any Reply-Message in the
- * Access-Challenge is ignored.
- */
- fr_pair_list_init(&vps);
- MEM(fr_pair_list_copy_by_da(t, &vps, &request->reply_pairs, attr_eap_message, 0) >= 0);
-
- /*
- * Handle the ACK, by tunneling any necessary reply
- * VP's back to the client.
- */
- if (!fr_pair_list_empty(&vps)) {
- eap_peap_inner_from_pairs(parent, tls_session, &vps);
- fr_pair_list_free(&vps);
- }
- RETURN_UNLANG_HANDLED;
-
- default:
- RDEBUG2("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
- RETURN_UNLANG_REJECT;
- }
-}
-
-
-static char const *peap_state(peap_tunnel_t *t)
-{
- switch (t->status) {
- case PEAP_STATUS_TUNNEL_ESTABLISHED:
- return "TUNNEL ESTABLISHED";
-
- case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
- return "WAITING FOR INNER IDENTITY";
-
- case PEAP_STATUS_SENT_TLV_SUCCESS:
- return "send tlv success";
-
- case PEAP_STATUS_SENT_TLV_FAILURE:
- return "send tlv failure";
-
- case PEAP_STATUS_PHASE2_INIT:
- return "phase2_init";
-
- case PEAP_STATUS_PHASE2:
- return "phase2";
-
- default:
- break;
- }
- return "?";
-}
-
-/*
- * Process the pseudo-EAP contents of the tunneled data.
- */
-unlang_action_t eap_peap_process(unlang_result_t *p_result, request_t *request,
- eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- peap_tunnel_t *t = tls_session->opaque;
- request_t *child = NULL;
- fr_pair_t *vp;
- rlm_rcode_t rcode = RLM_MODULE_REJECT;
- uint8_t const *data;
- size_t data_len;
- eap_round_t *eap_round = eap_session->this_round;
-
- /*
- * Just look at the buffer directly, without doing
- * record_to_buff. This lets us avoid another data copy.
- */
- data_len = tls_session->clean_out.used;
- tls_session->clean_out.used = 0;
- data = tls_session->clean_out.data;
-
- RDEBUG2("PEAP state %s", peap_state(t));
-
- if ((t->status != PEAP_STATUS_TUNNEL_ESTABLISHED) && (eap_peap_verify(request, t, data, data_len) < 0)) {
- REDEBUG("Tunneled data is invalid");
- RETURN_UNLANG_REJECT;
- }
-
- switch (t->status) {
- case PEAP_STATUS_TUNNEL_ESTABLISHED:
- /* FIXME: should be no data in the buffer here, check & assert? */
-
- if (SSL_session_reused(tls_session->ssl)) {
- RDEBUG2("Skipping Phase2 because of session resumption");
- t->session_resumption_state = PEAP_RESUMPTION_YES;
- /* we're good, send success TLV */
- t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
- eap_peap_success(request, eap_session, tls_session);
-
- } else {
- /* send an identity request */
- t->session_resumption_state = PEAP_RESUMPTION_NO;
- t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
- eap_peap_identity(request, eap_session, tls_session);
- }
- rcode = RLM_MODULE_HANDLED;
- goto finish;
-
- case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
- /* we're expecting an identity response */
- if (data[0] != FR_EAP_METHOD_IDENTITY) {
- REDEBUG("Expected EAP-Identity, got something else");
- rcode = RLM_MODULE_REJECT;
- goto finish;
- }
-
- /*
- * Save it for later.
- */
- MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
- t->username->vp_tainted = true;
-
- fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
-
- RDEBUG2("Got inner identity \"%pV\"", &t->username->data);
- t->status = PEAP_STATUS_PHASE2_INIT;
- break;
-
- /*
- * If we authenticated the user, then it's OK.
- */
- case PEAP_STATUS_SENT_TLV_SUCCESS:
- if (eap_peap_check_tlv(request, data, data_len)) {
- RDEBUG2("Success");
- rcode = RLM_MODULE_OK;
- goto finish;
- }
-
- /*
- * Otherwise, the client rejected the session
- * resumption. If the session is being re-used,
- * we need to do a full authentication.
- *
- * We do this by sending an EAP-Identity request
- * inside of the PEAP tunnel.
- */
- if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
- RDEBUG2("Client rejected session resumption. Re-starting full authentication");
-
- /*
- * Mark session resumption status.
- */
- t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
- t->session_resumption_state = PEAP_RESUMPTION_NO;
-
- eap_peap_identity(request, eap_session, tls_session);
- rcode = RLM_MODULE_HANDLED;
- goto finish;
- }
-
- REDEBUG("Sent a success, but received something weird in return");
- rcode = RLM_MODULE_REJECT;
- goto finish;
-
- /*
- * Supplicant ACKs our failure.
- */
- case PEAP_STATUS_SENT_TLV_FAILURE:
- RINDENT();
- REDEBUG("The users session was previously rejected: returning reject (again.)");
- RIDEBUG("This means you need to read the PREVIOUS messages in the debug output");
- RIDEBUG("to find out the reason why the user was rejected");
- RIDEBUG("Look for \"reject\" or \"fail\". Those earlier messages will tell you");
- RIDEBUG("what went wrong, and how to fix the problem");
- REXDENT();
-
- RETURN_UNLANG_REJECT;
-
- case PEAP_STATUS_PHASE2_INIT:
- RDEBUG2("In state machine in phase2 init?");
- break;
-
- case PEAP_STATUS_PHASE2:
- break;
-
- default:
- REDEBUG("Unhandled state in peap");
- rcode = RLM_MODULE_REJECT;
- goto finish;
- }
-
- MEM(child = unlang_subrequest_alloc(request, request->proto_dict));
- fr_assert(fr_pair_list_empty(&child->request_pairs));
-
- switch (t->status) {
- /*
- * If we're in PHASE2_INIT, the phase2 method hasn't been
- * sent an Identity packet yet; do so from the stored
- * username and this will kick off the phase2 eap method
- */
- case PEAP_STATUS_PHASE2_INIT:
- {
- size_t len;
- uint8_t *q;
-
- fr_assert(t->username);
-
- len = t->username->vp_length + EAP_HEADER_LEN + 1;
- t->status = PEAP_STATUS_PHASE2;
-
- MEM(vp = fr_pair_afrom_da(child->request_ctx, attr_eap_message));
- MEM(fr_pair_value_mem_alloc(vp, &q, len, false) == 0);
- q[0] = FR_EAP_CODE_RESPONSE;
- q[1] = eap_round->response->id;
- q[2] = (len >> 8) & 0xff;
- q[3] = len & 0xff;
- q[4] = FR_EAP_METHOD_IDENTITY;
- memcpy(q + EAP_HEADER_LEN + 1,
- t->username->vp_strvalue, t->username->vp_length);
- fr_pair_append(&child->request_pairs, vp);
- }
- break;
-
- case PEAP_STATUS_PHASE2:
- eap_peap_inner_to_pairs(child->request_ctx, &child->request_pairs,
- eap_round, data, data_len);
- if (fr_pair_list_empty(&child->request_pairs)) {
- TALLOC_FREE(child);
- RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
- rcode = RLM_MODULE_REJECT;
- goto finish;
- }
- break;
-
- default:
- REDEBUG("Invalid state change in PEAP");
- rcode = RLM_MODULE_REJECT;
- goto finish;
- }
-
- RDEBUG2("Got tunneled request");
- log_request_pair_list(L_DBG_LVL_2, request, NULL, &child->request_pairs, NULL);
-
- /*
- * Update other items in the request_t data structure.
- */
- if (!t->username) {
- /*
- * There's no User-Name in the tunneled session,
- * so we add one here, by pulling it out of the
- * EAP-Identity packet.
- */
- if ((data[0] == FR_EAP_METHOD_IDENTITY) && (data_len > 1)) {
- MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
- fr_assert(t->username != NULL);
- t->username->vp_tainted = true;
-
- fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
-
- RDEBUG2("Got tunneled identity of %pV", &t->username->data);
- }
- } /* else there WAS a t->username */
-
- if (t->username) {
- vp = fr_pair_copy(child->request_ctx, t->username);
- fr_pair_append(&child->request_pairs, vp);
- RDEBUG2("Setting request.User-Name from tunneled (inner) identity \"%s\"",
- vp->vp_strvalue);
- } else {
- RDEBUG2("No tunnel username (SSL resumption?)");
- }
-
- /*
- * Set the child up for execution. This represents
- * a pseudo protocol inside of PEAPs inner EAP method.
- */
- if (unlang_subrequest_child_push(&eap_session->submodule_result, child,
- child,
- false, UNLANG_SUB_FRAME) < 0) goto finish;
-
- /*
- * Setup a function in thie child to process the
- * result of the subrequest.
- */
- if (unlang_function_push_with_result(NULL,
- child,
- NULL,
- /*
- * Run in the child after the virtual sever executes.
- * This sets the rcode for the subrequest, which is
- * written to eap_session->submodule_result.
- */
- process_reply,
- NULL, 0,
- UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
-
- /*
- * Run inner tunnel in the context of the child
- */
- if (unlikely(eap_virtual_server(child, eap_session, t->server_cs) == UNLANG_ACTION_FAIL)) {
- rcode = RLM_MODULE_FAIL;
- goto finish;
- }
-
- /*
- * We now yield to the subrequest. unlang_subrequest_child_push
- * pushed a new frame in the context of the parent which'll start
- * the subrequest.
- */
- return UNLANG_ACTION_PUSHED_CHILD;
-
-finish:
- if (child) {
- /*
- * We can't just free the child, we need to detach it
- * and then let the interpreter to unwind and eventually
- * free the request.
- */
- request_detach(child);
- unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
- }
-
- RETURN_UNLANG_RCODE(rcode);
-}
RCSID("$Id$")
#include <freeradius-devel/eap/tls.h>
-#include "eap_peap.h"
typedef struct {
SSL_CTX *ssl_ctx; //!< Thread local SSL_CTX.
bool req_client_cert; //!< Do we do require a client cert?
} rlm_eap_peap_t;
+typedef enum {
+ PEAP_STATUS_INVALID,
+ PEAP_STATUS_SENT_TLV_SUCCESS,
+ PEAP_STATUS_SENT_TLV_FAILURE,
+ PEAP_STATUS_TUNNEL_ESTABLISHED,
+ PEAP_STATUS_INNER_IDENTITY_REQ_SENT,
+ PEAP_STATUS_PHASE2_INIT,
+ PEAP_STATUS_PHASE2,
+} peap_status;
+
+typedef enum {
+ PEAP_RESUMPTION_NO,
+ PEAP_RESUMPTION_YES,
+ PEAP_RESUMPTION_MAYBE
+} peap_resumption;
+
+typedef struct {
+ fr_pair_t *username;
+ peap_status status;
+ bool home_access_accept;
+ int default_method;
+ peap_resumption session_resumption_state;
+} peap_tunnel_t;
+
+#define EAP_TLV_SUCCESS (1)
+#define EAP_TLV_FAILURE (2)
+#define EAP_TLV_ACK_RESULT (3)
+
+#define FR_PEAP_EXTENSIONS_TYPE 33
+
static conf_parser_t submodule_config[] = {
{ FR_CONF_OFFSET("tls", rlm_eap_peap_t, tls_conf_name) },
{ FR_CONF_OFFSET_TYPE_FLAGS("virtual_server", FR_TYPE_VOID, CONF_FLAG_REQUIRED | CONF_FLAG_NOT_EMPTY, rlm_eap_peap_t, virtual_server),
.func = virtual_server_cf_parse,
- .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "eap_peap"} },
+ .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "radius"} },
{ FR_CONF_OFFSET("require_client_cert", rlm_eap_peap_t, req_client_cert), .dflt = "no" },
CONF_PARSER_TERMINATOR
};
+/*
+ * Send protected EAP-Failure
+ *
+ * Result-TLV = Failure
+ */
+static int eap_peap_failure(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ uint8_t tlv_packet[11];
+
+ RDEBUG2("FAILURE");
+
+ tlv_packet[0] = FR_EAP_CODE_REQUEST;
+ tlv_packet[1] = eap_session->this_round->response->id +1;
+ tlv_packet[2] = 0;
+ tlv_packet[3] = 11; /* length of this packet */
+ tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
+ tlv_packet[5] = 0x80;
+ tlv_packet[6] = EAP_TLV_ACK_RESULT;
+ tlv_packet[7] = 0;
+ tlv_packet[8] = 2; /* length of the data portion */
+ tlv_packet[9] = 0;
+ tlv_packet[10] = EAP_TLV_FAILURE;
+
+ (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
+
+ /*
+ * FIXME: Check the return code.
+ */
+ fr_tls_session_send(request, tls_session);
+
+ return 1;
+}
+
+/*
+ * Send protected EAP-Success
+ *
+ * Result-TLV = Success
+ */
+static int eap_peap_success(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ uint8_t tlv_packet[11];
+
+ RDEBUG2("SUCCESS");
+
+ tlv_packet[0] = FR_EAP_CODE_REQUEST;
+ tlv_packet[1] = eap_session->this_round->response->id +1;
+ tlv_packet[2] = 0;
+ tlv_packet[3] = 11; /* length of this packet */
+ tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
+ tlv_packet[5] = 0x80; /* mandatory AVP */
+ tlv_packet[6] = EAP_TLV_ACK_RESULT;
+ tlv_packet[7] = 0;
+ tlv_packet[8] = 2; /* length of the data portion */
+ tlv_packet[9] = 0;
+ tlv_packet[10] = EAP_TLV_SUCCESS;
+
+ (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
+
+ /*
+ * FIXME: Check the return code.
+ */
+ fr_tls_session_send(request, tls_session);
+
+ return 1;
+}
+
+
+static int eap_peap_identity(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ eap_packet_raw_t eap_packet;
+
+ eap_packet.code = FR_EAP_CODE_REQUEST;
+ eap_packet.id = eap_session->this_round->response->id + 1;
+ eap_packet.length[0] = 0;
+ eap_packet.length[1] = EAP_HEADER_LEN + 1;
+ eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
+
+ (tls_session->record_from_buff)(&tls_session->clean_in, &eap_packet, sizeof(eap_packet));
+ fr_tls_session_send(request, tls_session);
+ (tls_session->record_init)(&tls_session->clean_in);
+
+ return 1;
+}
+
+/*
+ * Verify the tunneled EAP message.
+ */
+static int eap_peap_verify(request_t *request, peap_tunnel_t *peap_tunnel,
+ uint8_t const *data, size_t data_len)
+{
+ eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
+ eap_type_t eap_method;
+
+ /*
+ * No data, OR only 1 byte of EAP type.
+ */
+ if (!data || (data_len == 0) || ((data_len <= 1) && (data[0] != FR_EAP_METHOD_IDENTITY))) return 0;
+
+ /*
+ * Since the full EAP header is sent for the EAP Extensions type (Type 33),
+ * but not for other Types, it is difficult for the implementation to distinguish
+ * an Extensions Request (Code 1) from an EAP Type 1 (Identity) Request packet.
+ *
+ * i.e. The only way to validate PEAP inner method packets properly is to know
+ * we just send a protected success/failure.
+ */
+ switch (peap_tunnel->status) {
+ case PEAP_STATUS_SENT_TLV_SUCCESS:
+ case PEAP_STATUS_SENT_TLV_FAILURE:
+ if (eap_packet->data[0] != FR_PEAP_EXTENSIONS_TYPE) {
+ REDEBUG("Invalid inner tunnel data, expected method (%u), got (%u)",
+ FR_PEAP_EXTENSIONS_TYPE, eap_packet->data[0]);
+ return -1;
+ }
+ return 0;
+
+ default:
+ break;
+ }
+
+ eap_method = data[0]; /* Inner EAP header misses off code and identifier */
+ switch (eap_method) {
+ case FR_EAP_METHOD_IDENTITY:
+ RDEBUG2("Received EAP-Identity-Response");
+ return 0;
+
+ /*
+ * We normally do Microsoft MS-CHAPv2 (26), versus
+ * Cisco MS-CHAPv2 (29).
+ */
+ case FR_EAP_METHOD_MSCHAPV2:
+ default:
+ RDEBUG2("EAP method %s (%d)", eap_type2name(eap_method), eap_method);
+ return 0;
+ }
+
+}
+
+/*
+ * Convert a pseudo-EAP packet to a list of fr_pair_t's.
+ */
+static void eap_peap_inner_to_pairs(TALLOC_CTX *ctx, fr_pair_list_t *pairs,
+ eap_round_t *eap_round,
+ uint8_t const *data, size_t data_len)
+{
+ size_t total;
+ uint8_t *p;
+ fr_pair_t *vp = NULL;
+
+ if (data_len > 65535) return; /* paranoia */
+
+ MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
+ total = data_len;
+ if (total > 249) total = 249;
+
+ /*
+ * Hand-build an EAP packet from the crap in PEAP version 0.
+ */
+ MEM(fr_pair_value_mem_alloc(vp, &p, EAP_HEADER_LEN + total, false) == 0);
+ p[0] = FR_EAP_CODE_RESPONSE;
+ p[1] = eap_round->response->id;
+ p[2] = (data_len + EAP_HEADER_LEN) >> 8;
+ p[3] = (data_len + EAP_HEADER_LEN) & 0xff;
+ memcpy(p + EAP_HEADER_LEN, data, total);
+
+ fr_pair_append(pairs, vp);
+ while (total < data_len) {
+ MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
+ fr_pair_value_memdup(vp, data + total, (data_len - total), false);
+
+ total += vp->vp_length;
+
+ fr_pair_append(pairs, vp);
+ }
+}
+
+
+/*
+ * Convert a list of fr_pair_t's to an EAP packet, through the
+ * simple expedient of dumping the EAP message
+ */
+static int eap_peap_inner_from_pairs(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *vps)
+{
+ fr_pair_t *this;
+
+ fr_assert(!fr_pair_list_empty(vps));
+
+ /*
+ * Send the EAP data in the first attribute, WITHOUT the
+ * header.
+ */
+ this = fr_pair_list_head(vps);
+ (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets + EAP_HEADER_LEN,
+ this->vp_length - EAP_HEADER_LEN);
+
+ /*
+ * Send the rest of the EAP data, but skipping the first VP.
+ */
+ for (this = fr_pair_list_next(vps, this);
+ this;
+ this = fr_pair_list_next(vps, this)) {
+ (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets, this->vp_length);
+ }
+
+ fr_tls_session_send(request, tls_session);
+
+ return 1;
+}
+
+
+/*
+ * See if there's a TLV in the response.
+ */
+static int eap_peap_check_tlv(request_t *request, uint8_t const *data, size_t data_len)
+{
+ eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
+
+ if (data_len < 11) return 0;
+
+ /*
+ * Look for success or failure.
+ */
+ if ((eap_packet->code == FR_EAP_CODE_RESPONSE) &&
+ (eap_packet->data[0] == FR_PEAP_EXTENSIONS_TYPE)) {
+ if (data[10] == EAP_TLV_SUCCESS) {
+ return 1;
+ }
+
+ if (data[10] == EAP_TLV_FAILURE) {
+ RDEBUG2("Client rejected our response. The password is probably incorrect");
+ return 0;
+ }
+ }
+
+ RDEBUG2("Unknown TLV %02x", data[10]);
+
+ return 0;
+}
+
+
+/*
+ * Use a reply packet to determine what to do.
+ */
+static unlang_action_t process_reply(unlang_result_t *p_result, request_t *request, UNUSED void *uctx)
+{
+ eap_session_t *eap_session = talloc_get_type_abort(uctx, eap_session_t);
+ eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+ fr_tls_session_t *tls_session = eap_tls_session->tls_session;
+ fr_pair_list_t vps;
+ peap_tunnel_t *t = tls_session->opaque;
+ request_t *parent = request->parent;
+ fr_packet_t *reply = request->reply;
+
+ p_result->priority = MOD_PRIORITY_MAX;
+
+ if (RDEBUG_ENABLED2) {
+
+ /*
+ * Note that we don't do *anything* with the reply
+ * attributes.
+ */
+ if (FR_RADIUS_PACKET_CODE_VALID(reply->code)) {
+ RDEBUG2("Got tunneled reply %s", fr_radius_packet_name[reply->code]);
+ } else {
+ RDEBUG2("Got tunneled reply code %i", reply->code);
+ }
+ log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL);
+ }
+
+ switch (reply->code) {
+ case FR_RADIUS_CODE_ACCESS_ACCEPT:
+ RDEBUG2("Tunneled authentication was successful");
+ t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+ eap_peap_success(request, eap_session, tls_session);
+ RETURN_UNLANG_HANDLED;
+
+ case FR_RADIUS_CODE_ACCESS_REJECT:
+ RDEBUG2("Tunneled authentication was rejected");
+ t->status = PEAP_STATUS_SENT_TLV_FAILURE;
+ eap_peap_failure(request, eap_session, tls_session);
+ RETURN_UNLANG_HANDLED;
+
+ case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+ RDEBUG2("Got tunneled Access-Challenge");
+
+ /*
+ * PEAP takes only EAP-Message attributes inside
+ * of the tunnel. Any Reply-Message in the
+ * Access-Challenge is ignored.
+ */
+ fr_pair_list_init(&vps);
+ MEM(fr_pair_list_copy_by_da(t, &vps, &request->reply_pairs, attr_eap_message, 0) >= 0);
+
+ /*
+ * Handle the ACK, by tunneling any necessary reply
+ * VP's back to the client.
+ */
+ if (!fr_pair_list_empty(&vps)) {
+ eap_peap_inner_from_pairs(parent, tls_session, &vps);
+ fr_pair_list_free(&vps);
+ }
+ RETURN_UNLANG_HANDLED;
+
+ default:
+ RDEBUG2("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+ RETURN_UNLANG_REJECT;
+ }
+}
+
+
+static char const *peap_state(peap_tunnel_t *t)
+{
+ switch (t->status) {
+ case PEAP_STATUS_TUNNEL_ESTABLISHED:
+ return "TUNNEL ESTABLISHED";
+
+ case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
+ return "WAITING FOR INNER IDENTITY";
+
+ case PEAP_STATUS_SENT_TLV_SUCCESS:
+ return "send tlv success";
+
+ case PEAP_STATUS_SENT_TLV_FAILURE:
+ return "send tlv failure";
+
+ case PEAP_STATUS_PHASE2_INIT:
+ return "phase2_init";
+
+ case PEAP_STATUS_PHASE2:
+ return "phase2";
+
+ default:
+ break;
+ }
+ return "?";
+}
+
+/*
+ * Process the pseudo-EAP contents of the tunneled data.
+ */
+static unlang_action_t eap_peap_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request,
+ eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ peap_tunnel_t *t = tls_session->opaque;
+ request_t *child = NULL;
+ fr_pair_t *vp;
+ rlm_rcode_t rcode = RLM_MODULE_REJECT;
+ uint8_t const *data;
+ size_t data_len;
+ eap_round_t *eap_round = eap_session->this_round;
+ rlm_eap_peap_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_peap_t);
+
+ /*
+ * Just look at the buffer directly, without doing
+ * record_to_buff. This lets us avoid another data copy.
+ */
+ data_len = tls_session->clean_out.used;
+ tls_session->clean_out.used = 0;
+ data = tls_session->clean_out.data;
+
+ RDEBUG2("PEAP state %s", peap_state(t));
+
+ if ((t->status != PEAP_STATUS_TUNNEL_ESTABLISHED) && (eap_peap_verify(request, t, data, data_len) < 0)) {
+ REDEBUG("Tunneled data is invalid");
+ RETURN_UNLANG_REJECT;
+ }
+
+ switch (t->status) {
+ case PEAP_STATUS_TUNNEL_ESTABLISHED:
+ /* FIXME: should be no data in the buffer here, check & assert? */
+
+ if (SSL_session_reused(tls_session->ssl)) {
+ RDEBUG2("Skipping Phase2 because of session resumption");
+ t->session_resumption_state = PEAP_RESUMPTION_YES;
+ /* we're good, send success TLV */
+ t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+ eap_peap_success(request, eap_session, tls_session);
+
+ } else {
+ /* send an identity request */
+ t->session_resumption_state = PEAP_RESUMPTION_NO;
+ t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+ eap_peap_identity(request, eap_session, tls_session);
+ }
+ rcode = RLM_MODULE_HANDLED;
+ goto finish;
+
+ case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
+ /* we're expecting an identity response */
+ if (data[0] != FR_EAP_METHOD_IDENTITY) {
+ REDEBUG("Expected EAP-Identity, got something else");
+ rcode = RLM_MODULE_REJECT;
+ goto finish;
+ }
+
+ /*
+ * Save it for later.
+ */
+ MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+ t->username->vp_tainted = true;
+
+ fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
+
+ RDEBUG2("Got inner identity \"%pV\"", &t->username->data);
+ t->status = PEAP_STATUS_PHASE2_INIT;
+ break;
+
+ /*
+ * If we authenticated the user, then it's OK.
+ */
+ case PEAP_STATUS_SENT_TLV_SUCCESS:
+ if (eap_peap_check_tlv(request, data, data_len)) {
+ RDEBUG2("Success");
+ rcode = RLM_MODULE_OK;
+ goto finish;
+ }
+
+ /*
+ * Otherwise, the client rejected the session
+ * resumption. If the session is being re-used,
+ * we need to do a full authentication.
+ *
+ * We do this by sending an EAP-Identity request
+ * inside of the PEAP tunnel.
+ */
+ if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
+ RDEBUG2("Client rejected session resumption. Re-starting full authentication");
+
+ /*
+ * Mark session resumption status.
+ */
+ t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+ t->session_resumption_state = PEAP_RESUMPTION_NO;
+
+ eap_peap_identity(request, eap_session, tls_session);
+ rcode = RLM_MODULE_HANDLED;
+ goto finish;
+ }
+
+ REDEBUG("Sent a success, but received something weird in return");
+ rcode = RLM_MODULE_REJECT;
+ goto finish;
+
+ /*
+ * Supplicant ACKs our failure.
+ */
+ case PEAP_STATUS_SENT_TLV_FAILURE:
+ RINDENT();
+ REDEBUG("The users session was previously rejected: returning reject (again.)");
+ RIDEBUG("This means you need to read the PREVIOUS messages in the debug output");
+ RIDEBUG("to find out the reason why the user was rejected");
+ RIDEBUG("Look for \"reject\" or \"fail\". Those earlier messages will tell you");
+ RIDEBUG("what went wrong, and how to fix the problem");
+ REXDENT();
+
+ RETURN_UNLANG_REJECT;
+
+ case PEAP_STATUS_PHASE2_INIT:
+ RDEBUG2("In state machine in phase2 init?");
+ break;
+
+ case PEAP_STATUS_PHASE2:
+ break;
+
+ default:
+ REDEBUG("Unhandled state in peap");
+ rcode = RLM_MODULE_REJECT;
+ goto finish;
+ }
+
+ MEM(child = unlang_subrequest_alloc(request, request->proto_dict));
+ fr_assert(fr_pair_list_empty(&child->request_pairs));
+
+ switch (t->status) {
+ /*
+ * If we're in PHASE2_INIT, the phase2 method hasn't been
+ * sent an Identity packet yet; do so from the stored
+ * username and this will kick off the phase2 eap method
+ */
+ case PEAP_STATUS_PHASE2_INIT:
+ {
+ size_t len;
+ uint8_t *q;
+
+ fr_assert(t->username);
+
+ len = t->username->vp_length + EAP_HEADER_LEN + 1;
+ t->status = PEAP_STATUS_PHASE2;
+
+ MEM(vp = fr_pair_afrom_da(child->request_ctx, attr_eap_message));
+ MEM(fr_pair_value_mem_alloc(vp, &q, len, false) == 0);
+ q[0] = FR_EAP_CODE_RESPONSE;
+ q[1] = eap_round->response->id;
+ q[2] = (len >> 8) & 0xff;
+ q[3] = len & 0xff;
+ q[4] = FR_EAP_METHOD_IDENTITY;
+ memcpy(q + EAP_HEADER_LEN + 1,
+ t->username->vp_strvalue, t->username->vp_length);
+ fr_pair_append(&child->request_pairs, vp);
+ }
+ break;
+
+ case PEAP_STATUS_PHASE2:
+ eap_peap_inner_to_pairs(child->request_ctx, &child->request_pairs,
+ eap_round, data, data_len);
+ if (fr_pair_list_empty(&child->request_pairs)) {
+ TALLOC_FREE(child);
+ RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
+ rcode = RLM_MODULE_REJECT;
+ goto finish;
+ }
+ break;
+
+ default:
+ REDEBUG("Invalid state change in PEAP");
+ rcode = RLM_MODULE_REJECT;
+ goto finish;
+ }
+
+ RDEBUG2("Got tunneled request");
+ log_request_pair_list(L_DBG_LVL_2, request, NULL, &child->request_pairs, NULL);
+
+ /*
+ * Update other items in the request_t data structure.
+ */
+ if (!t->username) {
+ /*
+ * There's no User-Name in the tunneled session,
+ * so we add one here, by pulling it out of the
+ * EAP-Identity packet.
+ */
+ if ((data[0] == FR_EAP_METHOD_IDENTITY) && (data_len > 1)) {
+ MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+ fr_assert(t->username != NULL);
+ t->username->vp_tainted = true;
+
+ fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
+
+ RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+ }
+ } /* else there WAS a t->username */
+
+ if (t->username) {
+ vp = fr_pair_copy(child->request_ctx, t->username);
+ fr_pair_append(&child->request_pairs, vp);
+ RDEBUG2("Setting request.User-Name from tunneled (inner) identity \"%s\"",
+ vp->vp_strvalue);
+ } else {
+ RDEBUG2("No tunnel username (SSL resumption?)");
+ }
+
+ /*
+ * Set the child up for execution. This represents
+ * a pseudo protocol inside of PEAPs inner EAP method.
+ */
+ if (unlang_subrequest_child_push(&eap_session->submodule_result, child,
+ child,
+ false, UNLANG_SUB_FRAME) < 0) goto finish;
+
+ /*
+ * Setup a function in thie child to process the
+ * result of the subrequest.
+ */
+ if (unlang_function_push_with_result(NULL,
+ child,
+ NULL,
+ /*
+ * Run in the child after the virtual sever executes.
+ * This sets the rcode for the subrequest, which is
+ * written to eap_session->submodule_result.
+ */
+ process_reply,
+ NULL, 0,
+ UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
+
+ /*
+ * Run inner tunnel in the context of the child
+ */
+ if (unlikely(eap_virtual_server(child, eap_session, inst->virtual_server) == UNLANG_ACTION_FAIL)) {
+ rcode = RLM_MODULE_FAIL;
+ goto finish;
+ }
+
+ /*
+ * We now yield to the subrequest. unlang_subrequest_child_push
+ * pushed a new frame in the context of the parent which'll start
+ * the subrequest.
+ */
+ return UNLANG_ACTION_PUSHED_CHILD;
+
+finish:
+ if (child) {
+ /*
+ * We can't just free the child, we need to detach it
+ * and then let the interpreter to unwind and eventually
+ * free the request.
+ */
+ request_detach(child);
+ unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
+ }
+
+ RETURN_UNLANG_RCODE(rcode);
+}
+
/*
* Allocate the PEAP per-session data
*/
-static peap_tunnel_t *peap_alloc(TALLOC_CTX *ctx, rlm_eap_peap_t *inst)
+static peap_tunnel_t *peap_alloc(TALLOC_CTX *ctx)
{
peap_tunnel_t *t;
t = talloc_zero(ctx, peap_tunnel_t);
-
- t->server_cs = inst->server_cs;
t->session_resumption_state = PEAP_RESUMPTION_MAYBE;
return t;
static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
{
- rlm_eap_peap_t *inst = talloc_get_type(mctx->mi->data, rlm_eap_peap_t);
eap_session_t *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
fr_tls_session_t *tls_session = eap_tls_session->tls_session;
* We may need PEAP data associated with the session, so
* allocate it here, if it wasn't already alloacted.
*/
- if (!tls_session->opaque) tls_session->opaque = peap_alloc(tls_session, inst);
+ if (!tls_session->opaque) tls_session->opaque = peap_alloc(tls_session);
/*
* Setup the resume point to prepare the correct reply based on
/*
* Process the PEAP portion of the request.
*/
- return eap_peap_process(&eap_session->submodule_result, request, eap_session, tls_session);
+ return eap_peap_process(&eap_session->submodule_result, mctx, request, eap_session, tls_session);
}
/*
* Session resumption requires the storage of data, so
* allocate it if it doesn't already exist.
*/
- tls_session->opaque = peap_alloc(tls_session, inst);
+ tls_session->opaque = peap_alloc(tls_session);
eap_session->process = mod_handshake_process;
rlm_eap_peap_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_peap_t);
CONF_SECTION *conf = mctx->mi->conf;
+ fr_assert(inst->virtual_server);
+
inst->server_cs = virtual_server_cs(inst->virtual_server);
/*
TARGET := $(TARGETNAME)$(L)
endif
-SOURCES := $(TARGETNAME).c ttls.c
+SOURCES := $(TARGETNAME).c
SRC_INCDIRS := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
+++ /dev/null
-#pragma once
-/*
- * 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
- */
-
-/**
- * $Id$
- * @file eap_ttls.h
- * @brief Declarations for EAP-TTLS as defined by RFC 5281
- *
- * @copyright 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-RCSIDH(eap_ttls_h, "$Id$")
-
-#include <freeradius-devel/eap/tls.h>
-#include <freeradius-devel/radius/radius.h>
-#include <freeradius-devel/radius/defs.h>
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-extern HIDDEN fr_dict_attr_t const *attr_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap2_success;
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_reply_message;
-extern HIDDEN fr_dict_attr_t const *attr_eap_channel_binding_message;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-extern HIDDEN fr_dict_attr_t const *attr_user_password;
-extern HIDDEN fr_dict_attr_t const *attr_vendor_specific;
-
-typedef struct {
- fr_pair_t *username;
- bool authenticated;
- CONF_SECTION *server_cs;
-} ttls_tunnel_t;
-
-/*
- * Process the TTLS portion of an EAP-TTLS request.
- */
-unlang_action_t eap_ttls_process(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
-unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session);
USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
#include <freeradius-devel/eap/tls.h>
-#include "eap_ttls.h"
+#include <freeradius-devel/eap/chbind.h>
+#include <freeradius-devel/tls/strerror.h>
typedef struct {
SSL_CTX *ssl_ctx; //!< Thread local SSL_CTX.
bool req_client_cert;
} rlm_eap_ttls_t;
+typedef struct {
+ fr_pair_t *username;
+ bool authenticated;
+} ttls_tunnel_t;
static conf_parser_t submodule_config[] = {
{ FR_CONF_OFFSET("tls", rlm_eap_ttls_t, tls_conf_name) },
{ FR_CONF_DEPRECATED("use_tunneled_reply", rlm_eap_ttls_t, NULL), .dflt = "no" },
{ FR_CONF_OFFSET_TYPE_FLAGS("virtual_server", FR_TYPE_VOID, CONF_FLAG_REQUIRED | CONF_FLAG_NOT_EMPTY, rlm_eap_ttls_t, virtual_server),
.func = virtual_server_cf_parse,
- .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "eap_ttls"} },
+ .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "radius"} },
{ FR_CONF_OFFSET("include_length", rlm_eap_ttls_t, include_length), .dflt = "yes" },
{ FR_CONF_OFFSET("require_client_cert", rlm_eap_ttls_t, req_client_cert), .dflt = "no" },
CONF_PARSER_TERMINATOR
{ NULL }
};
-fr_dict_attr_t const *attr_eap_tls_require_client_cert;
+static fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-fr_dict_attr_t const *attr_chap_challenge;
-fr_dict_attr_t const *attr_ms_chap2_success;
-fr_dict_attr_t const *attr_eap_message;
-fr_dict_attr_t const *attr_ms_chap_challenge;
-fr_dict_attr_t const *attr_reply_message;
-fr_dict_attr_t const *attr_eap_channel_binding_message;
-fr_dict_attr_t const *attr_user_name;
-fr_dict_attr_t const *attr_user_password;
-fr_dict_attr_t const *attr_vendor_specific;
+static fr_dict_attr_t const *attr_chap_challenge;
+static fr_dict_attr_t const *attr_ms_chap2_success;
+static fr_dict_attr_t const *attr_eap_message;
+static fr_dict_attr_t const *attr_ms_chap_challenge;
+static fr_dict_attr_t const *attr_reply_message;
+static fr_dict_attr_t const *attr_eap_channel_binding_message;
+static fr_dict_attr_t const *attr_user_name;
+static fr_dict_attr_t const *attr_user_password;
+static fr_dict_attr_t const *attr_vendor_specific;
extern fr_dict_attr_autoload_t rlm_eap_ttls_dict_attr[];
fr_dict_attr_autoload_t rlm_eap_ttls_dict_attr[] = {
{ NULL }
};
+
+#define FR_DIAMETER_AVP_FLAG_VENDOR 0x80
+#define FR_DIAMETER_AVP_FLAG_MANDATORY 0x40
+/*
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | AVP Code |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |V M r r r r r r| AVP Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Vendor-ID (opt) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data ...
+ * +-+-+-+-+-+-+-+-+
+ */
+
+/*
+ * Verify that the diameter packet is valid.
+ */
+static int diameter_verify(request_t *request, uint8_t const *data, unsigned int data_len)
+{
+ uint32_t attr;
+ uint32_t length;
+ unsigned int hdr_len;
+ unsigned int remaining = data_len;
+
+ while (remaining > 0) {
+ hdr_len = 12;
+
+ if (remaining < hdr_len) {
+ RDEBUG2("Diameter attribute is too small (%u) to contain a Diameter header", remaining);
+ return 0;
+ }
+
+ memcpy(&attr, data, sizeof(attr));
+ attr = ntohl(attr);
+ memcpy(&length, data + 4, sizeof(length));
+ length = ntohl(length);
+
+ if ((data[4] & 0x80) != 0) {
+ if (remaining < 16) {
+ RDEBUG2("Diameter attribute is too small to contain a Diameter header with Vendor-Id");
+ return 0;
+ }
+
+ hdr_len = 16;
+ }
+
+ /*
+ * Get the length. If it's too big, die.
+ */
+ length &= 0x00ffffff;
+
+ /*
+ * Too short or too long is bad.
+ */
+ if (length <= (hdr_len - 4)) {
+ RDEBUG2("Tunneled attribute %u is too short (%u < %u) to contain anything useful.", attr,
+ length, hdr_len);
+ return 0;
+ }
+
+ if (length > remaining) {
+ RDEBUG2("Tunneled attribute %u is longer than room remaining in the packet (%u > %u).", attr,
+ length, remaining);
+ return 0;
+ }
+
+ /*
+ * Check for broken implementations, which don't
+ * pad the AVP to a 4-octet boundary.
+ */
+ if (remaining == length) break;
+
+ /*
+ * The length does NOT include the padding, so
+ * we've got to account for it here by rounding up
+ * to the nearest 4-byte boundary.
+ */
+ length += 0x03;
+ length &= ~0x03;
+
+ /*
+ * If the rest of the diameter packet is larger than
+ * this attribute, continue.
+ *
+ * Otherwise, if the attribute over-flows the end
+ * of the packet, die.
+ */
+ if (remaining < length) {
+ REDEBUG2("Diameter attribute overflows packet!");
+ return 0;
+ }
+
+ /*
+ * remaining > length, continue.
+ */
+ remaining -= length;
+ data += length;
+ }
+
+ /*
+ * We got this far. It looks OK.
+ */
+ return 1;
+}
+
+/*
+ * Convert diameter attributes to our fr_pair_t's
+ */
+static ssize_t eap_ttls_decode_pair(request_t *request, TALLOC_CTX *ctx, fr_pair_list_t *out,
+ fr_dict_attr_t const *parent,
+ uint8_t const *data, size_t data_len,
+ void *decode_ctx)
+{
+ uint8_t const *p = data, *end = p + data_len;
+
+ fr_pair_t *vp = NULL;
+ SSL *ssl = decode_ctx;
+ fr_dict_attr_t const *attr_radius;
+ fr_dict_attr_t const *da;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ attr_radius = fr_dict_root(dict_radius);
+
+ while (p < end) {
+ ssize_t ret;
+ uint32_t attr, vendor;
+ uint64_t value_len;
+ uint8_t flags;
+ fr_dict_attr_t const *our_parent = parent;
+
+ if ((end - p) < 8) {
+ fr_strerror_printf("Malformed diameter attribute at offset %zu. Needed at least 8 bytes, got %zu bytes",
+ p - data, end - p);
+ error:
+ talloc_free(tmp_ctx);
+ fr_pair_list_free(out);
+ return -1;
+ }
+
+ RDEBUG3("%04zu %02x%02x%02x%02x %02x%02x%02x%02x ...", p - data,
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+
+ attr = fr_nbo_to_uint32(p);
+ p += 4;
+
+ flags = p[0];
+ p++;
+
+ value_len = fr_nbo_to_uint64v(p, 3); /* Yes, that is a 24 bit length field */
+ p += 3;
+
+ if (value_len < 8) {
+ fr_strerror_printf("Malformed diameter attribute at offset %zu. Needed at least length of 8, got %u",
+ p - data, (unsigned int) value_len);
+ goto error;
+ }
+
+ /*
+ * Account for the 8 bytes we've already read from the packet.
+ */
+ if ((p + ((value_len + 0x03) & ~0x03)) - 8 > end) {
+ fr_strerror_printf("Malformed diameter attribute at offset %zu. Value length %u overflows input",
+ p - data, (unsigned int) value_len);
+ goto error;
+ }
+
+ value_len -= 8; /* -= 8 for AVP code (4), flags (1), AVP length (3) */
+
+ /*
+ * Do we have a vendor field?
+ */
+ if (flags & FR_DIAMETER_AVP_FLAG_VENDOR) {
+ vendor = fr_nbo_to_uint32(p);
+ p += 4;
+ value_len -= 4; /* -= 4 for the vendor ID field */
+
+ our_parent = fr_dict_vendor_da_by_num(attr_vendor_specific, vendor);
+ if (!our_parent) {
+ if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+ fr_strerror_printf("Mandatory bit set and no vendor %u found", vendor);
+ goto error;
+ }
+
+ if (!tmp_ctx) {
+ fr_dict_attr_t *n;
+
+ MEM(our_parent = n = fr_dict_attr_unknown_vendor_afrom_num(ctx, parent, vendor));
+ tmp_ctx = n;
+ } else {
+ MEM(our_parent = fr_dict_attr_unknown_vendor_afrom_num(tmp_ctx, parent, vendor));
+ }
+ }
+ } else {
+ our_parent = attr_radius;
+ }
+
+ /*
+ * Is the attribute known?
+ */
+ da = fr_dict_attr_child_by_num(our_parent, attr);
+ if (!da) {
+ if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+ fr_strerror_printf("Mandatory bit set and no attribute %u defined for parent %s", attr, parent->name);
+ goto error;
+ }
+
+ MEM(da = fr_dict_attr_unknown_raw_afrom_num(vp, our_parent, attr));
+ }
+
+ MEM(vp =fr_pair_afrom_da_nested(ctx, out, da));
+
+ ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
+ &FR_DBUFF_TMP(p, (size_t)value_len), value_len, true);
+ if (ret < 0) {
+ /*
+ * Mandatory bit is set, and the attribute
+ * is malformed. Fail.
+ */
+ if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+ fr_strerror_const("Mandatory bit is set and attribute is malformed");
+ goto error;
+ }
+
+ fr_pair_raw_afrom_pair(vp, p, value_len);
+ }
+
+ /*
+ * The length does NOT include the padding, so
+ * we've got to account for it here by rounding up
+ * to the nearest 4-byte boundary.
+ */
+ p += (value_len + 0x03) & ~0x03;
+
+ if (vp->da->flags.is_unknown) continue;
+
+ /*
+ * Ensure that the client is using the correct challenge.
+ *
+ * This weirdness is to protect against against replay
+ * attacks, where anyone observing the CHAP exchange could
+ * pose as that user, by simply choosing to use the same
+ * challenge.
+ * By using a challenge based on information from the
+ * current session, we can guarantee that the client is
+ * not *choosing* a challenge. We're a little forgiving in
+ * that we have loose checks on the length, and we do NOT
+ * check the Id (first octet of the response to the
+ * challenge) But if the client gets the challenge correct,
+ * we're not too worried about the Id.
+ */
+ if ((vp->da == attr_chap_challenge) || (vp->da == attr_ms_chap_challenge)) {
+ uint8_t challenge[17];
+ static const char label[] = "ttls challenge";
+
+ if ((vp->vp_length < 8) || (vp->vp_length > 16)) {
+ fr_strerror_const("Tunneled challenge has invalid length");
+ goto error;
+ }
+
+ /*
+ * TLSv1.3 exports a different key depending on the length
+ * requested so ask for *exactly* what the spec requires
+ */
+ if (SSL_export_keying_material(ssl, challenge, vp->vp_length + 1,
+ label, sizeof(label) - 1, NULL, 0, 0) != 1) {
+ fr_tls_strerror_printf("Failed generating phase2 challenge");
+ goto error;
+ }
+
+ if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) {
+ fr_strerror_const("Tunneled challenge is incorrect");
+ goto error;
+ }
+ }
+
+ /*
+ * Diameter pads strings (i.e. User-Password) with trailing zeros.
+ */
+ if (vp->vp_type == FR_TYPE_STRING) fr_pair_value_strtrim(vp);
+ }
+
+ /*
+ * We got this far. It looks OK.
+ */
+ talloc_free(tmp_ctx);
+ return p - data;
+}
+
+/*
+ * Convert fr_pair_t's to diameter attributes, and write them
+ * to an SSL session.
+ *
+ * The ONLY fr_pair_t's which may be passed to this function
+ * are ones which can go inside of a RADIUS (i.e. diameter)
+ * packet. So no server-configuration attributes, or the like.
+ */
+static int vp2diameter(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *list)
+{
+ /*
+ * RADIUS packets are no more than 4k in size, so if
+ * we've got more than 4k of data to write, it's very
+ * bad.
+ */
+ uint8_t buffer[4096];
+ uint8_t *p;
+ uint32_t attr;
+ uint32_t length;
+ uint32_t vendor;
+ size_t total;
+ uint64_t attr64;
+ fr_pair_t *vp;
+
+ p = buffer;
+ total = 0;
+
+ for (vp = fr_pair_list_head(list);
+ vp;
+ vp = fr_pair_list_next(list, vp)) {
+ /*
+ * Too much data: die.
+ */
+ if ((total + vp->vp_length + 12) >= sizeof(buffer)) {
+ RDEBUG2("output buffer is full!");
+ return 0;
+ }
+
+ /*
+ * Hmm... we don't group multiple EAP-Messages
+ * together. Maybe we should...
+ */
+
+ length = vp->vp_length;
+ vendor = fr_dict_vendor_num_by_da(vp->da);
+ if (vendor != 0) {
+ attr = vp->da->attr & 0xffff;
+ length |= ((uint32_t)1 << 31);
+ } else {
+ attr = vp->da->attr;
+ }
+
+ /*
+ * Hmm... set the M bit for all attributes?
+ */
+ length |= (1 << 30);
+
+ attr = ntohl(attr);
+
+ memcpy(p, &attr, sizeof(attr));
+ p += 4;
+ total += 4;
+
+ length += 8; /* includes 8 bytes of attr & length */
+
+ if (vendor != 0) {
+ length += 4; /* include 4 bytes of vendor */
+
+ length = ntohl(length);
+ memcpy(p, &length, sizeof(length));
+ p += 4;
+ total += 4;
+
+ vendor = ntohl(vendor);
+ memcpy(p, &vendor, sizeof(vendor));
+ p += 4;
+ total += 4;
+ } else {
+ length = ntohl(length);
+ memcpy(p, &length, sizeof(length));
+ p += 4;
+ total += 4;
+ }
+
+ switch (vp->vp_type) {
+ case FR_TYPE_DATE:
+ attr = htonl(fr_unix_time_to_sec(vp->vp_date)); /* stored in host order */
+ memcpy(p, &attr, sizeof(attr));
+ length = 4;
+ break;
+
+ case FR_TYPE_UINT32:
+ attr = htonl(vp->vp_uint32); /* stored in host order */
+ memcpy(p, &attr, sizeof(attr));
+ length = 4;
+ break;
+
+ case FR_TYPE_UINT64:
+ attr64 = htonll(vp->vp_uint64); /* stored in host order */
+ memcpy(p, &attr64, sizeof(attr64));
+ length = 8;
+ break;
+
+ case FR_TYPE_IPV4_ADDR:
+ memcpy(p, &vp->vp_ipv4addr, 4); /* network order */
+ length = 4;
+ break;
+
+ case FR_TYPE_STRING:
+ case FR_TYPE_OCTETS:
+ default:
+ memcpy(p, vp->vp_strvalue, vp->vp_length);
+ length = vp->vp_length;
+ break;
+ }
+
+ /*
+ * Skip to the end of the data.
+ */
+ p += length;
+ total += length;
+
+ /*
+ * Align the data to a multiple of 4 bytes.
+ */
+ if ((total & 0x03) != 0) {
+ size_t i;
+
+ length = 4 - (total & 0x03);
+ for (i = 0; i < length; i++) {
+ *p = '\0';
+ p++;
+ total++;
+ }
+ }
+ } /* loop over the VP's to write. */
+
+ /*
+ * Write the data in the buffer to the SSL session.
+ */
+ if (total > 0) {
+ (tls_session->record_from_buff)(&tls_session->clean_in, buffer, total);
+
+ /*
+ * FIXME: Check the return code.
+ */
+ fr_tls_session_send(request, tls_session);
+ }
+
+ /*
+ * Everything's OK.
+ */
+ return 1;
+}
+
+
+static unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session)
+{
+ eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+ fr_tls_session_t *tls_session = eap_tls_session->tls_session;
+ eap_tls_prf_label_t prf_label;
+
+ eap_crypto_prf_label_init(&prf_label, eap_session,
+ "ttls keying material",
+ sizeof("ttls keying material") - 1);
+ /*
+ * Success: Automatically return MPPE keys.
+ */
+ if (eap_tls_success(request, eap_session, &prf_label) < 0) RETURN_UNLANG_FAIL;
+
+ /*
+ * Result is always OK, even if we fail to persist the
+ * session data.
+ */
+ p_result->rcode = RLM_MODULE_OK;
+
+ /*
+ * Write the session to the session cache
+ *
+ * We do this here (instead of relying on OpenSSL to call the
+ * session caching callback), because we only want to write
+ * session data to the cache if all phases were successful.
+ *
+ * If we wrote out the cache data earlier, and the server
+ * exited whilst the session was in progress, the supplicant
+ * could resume the session (and get access) even if phase2
+ * never completed.
+ */
+ return fr_tls_cache_pending_push(request, tls_session);
+}
+
+
+/*
+ * Use a reply packet to determine what to do.
+ */
+static unlang_action_t process_reply(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+ eap_session_t *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
+ eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+ fr_tls_session_t *tls_session = eap_tls_session->tls_session;
+ fr_pair_t *vp = NULL;
+ fr_pair_list_t tunnel_vps;
+ ttls_tunnel_t *t = tls_session->opaque;
+ fr_packet_t *reply = request->reply;
+
+ fr_pair_list_init(&tunnel_vps);
+ fr_assert(eap_session->request == request->parent);
+
+ /*
+ * If the response packet was Access-Accept, then
+ * we're OK. If not, die horribly.
+ *
+ * FIXME: Take MS-CHAP2-Success attribute, and
+ * tunnel it back to the client, to authenticate
+ * ourselves to the client.
+ *
+ * FIXME: If we have an Access-Challenge, then
+ * the Reply-Message is tunneled back to the client.
+ *
+ * FIXME: If we have an EAP-Message, then that message
+ * must be tunneled back to the client.
+ *
+ * FIXME: If we have an Access-Challenge with a State
+ * attribute, then do we tunnel that to the client, or
+ * keep track of it ourselves?
+ *
+ * FIXME: EAP-Messages can only start with 'identity',
+ * NOT 'eap start', so we should check for that....
+ */
+ switch (reply->code) {
+ case FR_RADIUS_CODE_ACCESS_ACCEPT:
+ RDEBUG2("Got tunneled Access-Accept");
+
+ /*
+ * Copy what we need into the TTLS tunnel and leave
+ * the rest to be cleaned up.
+ */
+ if ((vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_ms_chap2_success))) {
+ RDEBUG2("Got MS-CHAP2-Success, tunneling it to the client in a challenge");
+ } else {
+ vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_eap_channel_binding_message);
+ }
+ if (vp) {
+ t->authenticated = true;
+ fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+ reply->code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+ break;
+ }
+
+ /*
+ * Success: Automatically return MPPE keys.
+ */
+ return eap_ttls_success(p_result, request, eap_session);
+
+ case FR_RADIUS_CODE_ACCESS_REJECT:
+ REDEBUG("Got tunneled Access-Reject");
+ eap_tls_fail(request, eap_session);
+ RETURN_UNLANG_REJECT;
+
+ /*
+ * Handle Access-Challenge, but only if we
+ * send tunneled reply data. This is because
+ * an Access-Challenge means that we MUST tunnel
+ * a Reply-Message to the client.
+ */
+ case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+ RDEBUG2("Got tunneled Access-Challenge");
+
+ /*
+ * Copy what we need into the TTLS tunnel and leave
+ * the rest to be cleaned up.
+ */
+ vp = NULL;
+ while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
+ if ((vp->da == attr_eap_message) || (vp->da == attr_reply_message)) {
+ fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+ } else if (vp->da == attr_eap_channel_binding_message) {
+ fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+ }
+ }
+ break;
+
+ default:
+ REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+ eap_tls_fail(request, eap_session);
+ RETURN_UNLANG_INVALID;
+ }
+
+
+ /*
+ * Pack any tunneled VPs and send them back
+ * to the supplicant.
+ */
+ if (!fr_pair_list_empty(&tunnel_vps)) {
+ RDEBUG2("Sending tunneled reply attributes");
+ log_request_pair_list(L_DBG_LVL_2, request, NULL, &tunnel_vps, NULL);
+
+ vp2diameter(request, tls_session, &tunnel_vps);
+ fr_pair_list_free(&tunnel_vps);
+ }
+
+ eap_tls_request(request, eap_session);
+ RETURN_UNLANG_OK;
+}
+
+/*
+ * Process the "diameter" contents of the tunneled data.
+ */
+static unlang_action_t eap_ttls_process(unlang_result_t *p_result, module_ctx_t const *mctx,
+ request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+ fr_pair_t *vp = NULL;
+ ttls_tunnel_t *t;
+ uint8_t const *data;
+ size_t data_len;
+ chbind_packet_t *chbind;
+ fr_pair_t *username;
+ rlm_eap_ttls_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_ttls_t);
+
+ /*
+ * Just look at the buffer directly, without doing
+ * record_to_buff.
+ */
+ data_len = tls_session->clean_out.used;
+ tls_session->clean_out.used = 0;
+ data = tls_session->clean_out.data;
+
+ t = (ttls_tunnel_t *) tls_session->opaque;
+
+ /*
+ * If there's no data, maybe this is an ACK to an
+ * MS-CHAP2-Success.
+ */
+ if (data_len == 0) {
+ if (t->authenticated) {
+ RDEBUG2("Got ACK, and the user was already authenticated");
+ return eap_ttls_success(p_result, request, eap_session);
+ } /* else no session, no data, die. */
+
+ /*
+ * FIXME: Call SSL_get_error() to see what went
+ * wrong.
+ */
+ RDEBUG2("SSL_read Error");
+ return UNLANG_ACTION_FAIL;
+ }
+
+ if (!diameter_verify(request, data, data_len)) return UNLANG_ACTION_FAIL;
+
+ /*
+ * Add the tunneled attributes to the request request.
+ */
+ if (eap_ttls_decode_pair(request, request->request_ctx, &request->request_pairs, fr_dict_root(fr_dict_internal()),
+ data, data_len, tls_session->ssl) < 0) {
+ RPEDEBUG("Decoding TTLS TLVs failed");
+ return UNLANG_ACTION_FAIL;
+ }
+
+ /*
+ * Update other items in the request_t data structure.
+ */
+
+ /*
+ * No User-Name, try to create one from stored data.
+ */
+ username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
+ if (!username) {
+ /*
+ * No User-Name in the stored data, look for
+ * an EAP-Identity, and pull it out of there.
+ */
+ if (!t->username) {
+ vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_eap_message);
+ if (vp &&
+ (vp->vp_length >= EAP_HEADER_LEN + 2) &&
+ (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
+ (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
+ (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
+ /*
+ * Create & remember a User-Name
+ */
+ MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+ t->username->vp_tainted = true;
+
+ fr_pair_value_bstrndup(t->username,
+ (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
+
+ RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+ } else {
+ /*
+ * Don't reject the request outright,
+ * as it's permitted to do EAP without
+ * user-name.
+ */
+ RWDEBUG2("No EAP-Identity found to start EAP conversation");
+ }
+ } /* else there WAS a t->username */
+
+ if (t->username) {
+ vp = fr_pair_copy(request->request_ctx, t->username);
+ fr_pair_append(&request->request_pairs, vp);
+ }
+ } /* else the request ALREADY had a User-Name */
+
+ /*
+ * Process channel binding.
+ */
+ chbind = eap_chbind_vp2packet(request, &request->request_pairs);
+ if (chbind) {
+ fr_radius_packet_code_t chbind_code;
+ CHBIND_REQ *req = talloc_zero(request, CHBIND_REQ);
+
+ RDEBUG2("received chbind request");
+ req->request = chbind;
+ if (username) {
+ req->username = username;
+ } else {
+ req->username = NULL;
+ }
+ chbind_code = chbind_process(request, req);
+
+ /* encapsulate response here */
+ if (req->response) {
+ RDEBUG2("sending chbind response");
+ fr_pair_append(&request->reply_pairs,
+ eap_chbind_packet2vp(request->reply_ctx, req->response));
+ } else {
+ RDEBUG2("no chbind response");
+ }
+
+ /* clean up chbind req */
+ talloc_free(req);
+
+ if (chbind_code != FR_RADIUS_CODE_ACCESS_ACCEPT) return UNLANG_ACTION_FAIL;
+ }
+
+ /*
+ * For this round, when the virtual server returns
+ * we run the process reply function.
+ */
+ if (unlikely(unlang_module_yield(request, process_reply, NULL, 0, eap_session) != UNLANG_ACTION_YIELD)) {
+ return UNLANG_ACTION_FAIL;
+ }
+
+ /*
+ * Call authentication recursively, which will
+ * do PAP, CHAP, MS-CHAP, etc.
+ */
+ return eap_virtual_server(request, eap_session, inst->virtual_server);
+}
+
/*
* Allocate the TTLS per-session data
*/
-static ttls_tunnel_t *ttls_alloc(TALLOC_CTX *ctx, rlm_eap_ttls_t *inst)
+static ttls_tunnel_t *ttls_alloc(TALLOC_CTX *ctx)
{
ttls_tunnel_t *t;
t = talloc_zero(ctx, ttls_tunnel_t);
- t->server_cs = inst->server_cs;
return t;
}
/*
* Process the TTLS portion of the request.
*/
- return eap_ttls_process(p_result, request, eap_session, tls_session);
+ return eap_ttls_process(p_result, mctx, request, eap_session, tls_session);
}
/*
RETURN_UNLANG_FAIL;
}
- tls_session->opaque = ttls_alloc(tls_session, inst);
+ tls_session->opaque = ttls_alloc(tls_session);
eap_session->process = mod_handshake_process;
+++ /dev/null
-/*
- * This program is 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
- */
-
-/**
- * $Id$
- * @file ttls.c
- * @brief Library functions for EAP-TTLS as defined by RFC 5281
- *
- * @copyright 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-
-RCSID("$Id$")
-
-#include <freeradius-devel/eap/chbind.h>
-#include <freeradius-devel/tls/log.h>
-#include <freeradius-devel/tls/strerror.h>
-#include "eap_ttls.h"
-
-#define FR_DIAMETER_AVP_FLAG_VENDOR 0x80
-#define FR_DIAMETER_AVP_FLAG_MANDATORY 0x40
-/*
- * 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
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | AVP Code |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |V M r r r r r r| AVP Length |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Vendor-ID (opt) |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Data ...
- * +-+-+-+-+-+-+-+-+
- */
-
-/*
- * Verify that the diameter packet is valid.
- */
-static int diameter_verify(request_t *request, uint8_t const *data, unsigned int data_len)
-{
- uint32_t attr;
- uint32_t length;
- unsigned int hdr_len;
- unsigned int remaining = data_len;
-
- while (remaining > 0) {
- hdr_len = 12;
-
- if (remaining < hdr_len) {
- RDEBUG2("Diameter attribute is too small (%u) to contain a Diameter header", remaining);
- return 0;
- }
-
- memcpy(&attr, data, sizeof(attr));
- attr = ntohl(attr);
- memcpy(&length, data + 4, sizeof(length));
- length = ntohl(length);
-
- if ((data[4] & 0x80) != 0) {
- if (remaining < 16) {
- RDEBUG2("Diameter attribute is too small to contain a Diameter header with Vendor-Id");
- return 0;
- }
-
- hdr_len = 16;
- }
-
- /*
- * Get the length. If it's too big, die.
- */
- length &= 0x00ffffff;
-
- /*
- * Too short or too long is bad.
- */
- if (length <= (hdr_len - 4)) {
- RDEBUG2("Tunneled attribute %u is too short (%u < %u) to contain anything useful.", attr,
- length, hdr_len);
- return 0;
- }
-
- if (length > remaining) {
- RDEBUG2("Tunneled attribute %u is longer than room remaining in the packet (%u > %u).", attr,
- length, remaining);
- return 0;
- }
-
- /*
- * Check for broken implementations, which don't
- * pad the AVP to a 4-octet boundary.
- */
- if (remaining == length) break;
-
- /*
- * The length does NOT include the padding, so
- * we've got to account for it here by rounding up
- * to the nearest 4-byte boundary.
- */
- length += 0x03;
- length &= ~0x03;
-
- /*
- * If the rest of the diameter packet is larger than
- * this attribute, continue.
- *
- * Otherwise, if the attribute over-flows the end
- * of the packet, die.
- */
- if (remaining < length) {
- REDEBUG2("Diameter attribute overflows packet!");
- return 0;
- }
-
- /*
- * remaining > length, continue.
- */
- remaining -= length;
- data += length;
- }
-
- /*
- * We got this far. It looks OK.
- */
- return 1;
-}
-
-
-/*
- * Convert diameter attributes to our fr_pair_t's
- */
-static ssize_t eap_ttls_decode_pair(request_t *request, TALLOC_CTX *ctx, fr_pair_list_t *out,
- fr_dict_attr_t const *parent,
- uint8_t const *data, size_t data_len,
- void *decode_ctx)
-{
- uint8_t const *p = data, *end = p + data_len;
-
- fr_pair_t *vp = NULL;
- SSL *ssl = decode_ctx;
- fr_dict_t const *dict_radius;
- fr_dict_attr_t const *attr_radius;
- fr_dict_attr_t const *da;
- TALLOC_CTX *tmp_ctx = NULL;
-
- dict_radius = fr_dict_by_protocol_name("radius");
- fr_assert(dict_radius != NULL);
- attr_radius = fr_dict_root(dict_radius);
-
- while (p < end) {
- ssize_t ret;
- uint32_t attr, vendor;
- uint64_t value_len;
- uint8_t flags;
- fr_dict_attr_t const *our_parent = parent;
-
- if ((end - p) < 8) {
- fr_strerror_printf("Malformed diameter attribute at offset %zu. Needed at least 8 bytes, got %zu bytes",
- p - data, end - p);
- error:
- talloc_free(tmp_ctx);
- fr_pair_list_free(out);
- return -1;
- }
-
- RDEBUG3("%04zu %02x%02x%02x%02x %02x%02x%02x%02x ...", p - data,
- p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
-
- attr = fr_nbo_to_uint32(p);
- p += 4;
-
- flags = p[0];
- p++;
-
- value_len = fr_nbo_to_uint64v(p, 3); /* Yes, that is a 24 bit length field */
- p += 3;
-
- if (value_len < 8) {
- fr_strerror_printf("Malformed diameter attribute at offset %zu. Needed at least length of 8, got %u",
- p - data, (unsigned int) value_len);
- goto error;
- }
-
- /*
- * Account for the 8 bytes we've already read from the packet.
- */
- if ((p + ((value_len + 0x03) & ~0x03)) - 8 > end) {
- fr_strerror_printf("Malformed diameter attribute at offset %zu. Value length %u overflows input",
- p - data, (unsigned int) value_len);
- goto error;
- }
-
- value_len -= 8; /* -= 8 for AVP code (4), flags (1), AVP length (3) */
-
- /*
- * Do we have a vendor field?
- */
- if (flags & FR_DIAMETER_AVP_FLAG_VENDOR) {
- vendor = fr_nbo_to_uint32(p);
- p += 4;
- value_len -= 4; /* -= 4 for the vendor ID field */
-
- our_parent = fr_dict_vendor_da_by_num(attr_vendor_specific, vendor);
- if (!our_parent) {
- if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
- fr_strerror_printf("Mandatory bit set and no vendor %u found", vendor);
- goto error;
- }
-
- if (!tmp_ctx) {
- fr_dict_attr_t *n;
-
- MEM(our_parent = n = fr_dict_attr_unknown_vendor_afrom_num(ctx, parent, vendor));
- tmp_ctx = n;
- } else {
- MEM(our_parent = fr_dict_attr_unknown_vendor_afrom_num(tmp_ctx, parent, vendor));
- }
- }
- } else {
- our_parent = attr_radius;
- }
-
- /*
- * Is the attribute known?
- */
- da = fr_dict_attr_child_by_num(our_parent, attr);
- if (!da) {
- if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
- fr_strerror_printf("Mandatory bit set and no attribute %u defined for parent %s", attr, parent->name);
- goto error;
- }
-
- MEM(da = fr_dict_attr_unknown_raw_afrom_num(vp, our_parent, attr));
- }
-
- MEM(vp =fr_pair_afrom_da_nested(ctx, out, da));
-
- ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
- &FR_DBUFF_TMP(p, (size_t)value_len), value_len, true);
- if (ret < 0) {
- /*
- * Mandatory bit is set, and the attribute
- * is malformed. Fail.
- */
- if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
- fr_strerror_const("Mandatory bit is set and attribute is malformed");
- goto error;
- }
-
- fr_pair_raw_afrom_pair(vp, p, value_len);
- }
-
- /*
- * The length does NOT include the padding, so
- * we've got to account for it here by rounding up
- * to the nearest 4-byte boundary.
- */
- p += (value_len + 0x03) & ~0x03;
-
- if (vp->da->flags.is_unknown) continue;
-
- /*
- * Ensure that the client is using the correct challenge.
- *
- * This weirdness is to protect against against replay
- * attacks, where anyone observing the CHAP exchange could
- * pose as that user, by simply choosing to use the same
- * challenge.
- * By using a challenge based on information from the
- * current session, we can guarantee that the client is
- * not *choosing* a challenge. We're a little forgiving in
- * that we have loose checks on the length, and we do NOT
- * check the Id (first octet of the response to the
- * challenge) But if the client gets the challenge correct,
- * we're not too worried about the Id.
- */
- if ((vp->da == attr_chap_challenge) || (vp->da == attr_ms_chap_challenge)) {
- uint8_t challenge[17];
- static const char label[] = "ttls challenge";
-
- if ((vp->vp_length < 8) || (vp->vp_length > 16)) {
- fr_strerror_const("Tunneled challenge has invalid length");
- goto error;
- }
-
- /*
- * TLSv1.3 exports a different key depending on the length
- * requested so ask for *exactly* what the spec requires
- */
- if (SSL_export_keying_material(ssl, challenge, vp->vp_length + 1,
- label, sizeof(label) - 1, NULL, 0, 0) != 1) {
- fr_tls_strerror_printf("Failed generating phase2 challenge");
- goto error;
- }
-
- if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) {
- fr_strerror_const("Tunneled challenge is incorrect");
- goto error;
- }
- }
-
- /*
- * Diameter pads strings (i.e. User-Password) with trailing zeros.
- */
- if (vp->vp_type == FR_TYPE_STRING) fr_pair_value_strtrim(vp);
- }
-
- /*
- * We got this far. It looks OK.
- */
- talloc_free(tmp_ctx);
- return p - data;
-}
-
-/*
- * Convert fr_pair_t's to diameter attributes, and write them
- * to an SSL session.
- *
- * The ONLY fr_pair_t's which may be passed to this function
- * are ones which can go inside of a RADIUS (i.e. diameter)
- * packet. So no server-configuration attributes, or the like.
- */
-static int vp2diameter(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *list)
-{
- /*
- * RADIUS packets are no more than 4k in size, so if
- * we've got more than 4k of data to write, it's very
- * bad.
- */
- uint8_t buffer[4096];
- uint8_t *p;
- uint32_t attr;
- uint32_t length;
- uint32_t vendor;
- size_t total;
- uint64_t attr64;
- fr_pair_t *vp;
-
- p = buffer;
- total = 0;
-
- for (vp = fr_pair_list_head(list);
- vp;
- vp = fr_pair_list_next(list, vp)) {
- /*
- * Too much data: die.
- */
- if ((total + vp->vp_length + 12) >= sizeof(buffer)) {
- RDEBUG2("output buffer is full!");
- return 0;
- }
-
- /*
- * Hmm... we don't group multiple EAP-Messages
- * together. Maybe we should...
- */
-
- length = vp->vp_length;
- vendor = fr_dict_vendor_num_by_da(vp->da);
- if (vendor != 0) {
- attr = vp->da->attr & 0xffff;
- length |= ((uint32_t)1 << 31);
- } else {
- attr = vp->da->attr;
- }
-
- /*
- * Hmm... set the M bit for all attributes?
- */
- length |= (1 << 30);
-
- attr = ntohl(attr);
-
- memcpy(p, &attr, sizeof(attr));
- p += 4;
- total += 4;
-
- length += 8; /* includes 8 bytes of attr & length */
-
- if (vendor != 0) {
- length += 4; /* include 4 bytes of vendor */
-
- length = ntohl(length);
- memcpy(p, &length, sizeof(length));
- p += 4;
- total += 4;
-
- vendor = ntohl(vendor);
- memcpy(p, &vendor, sizeof(vendor));
- p += 4;
- total += 4;
- } else {
- length = ntohl(length);
- memcpy(p, &length, sizeof(length));
- p += 4;
- total += 4;
- }
-
- switch (vp->vp_type) {
- case FR_TYPE_DATE:
- attr = htonl(fr_unix_time_to_sec(vp->vp_date)); /* stored in host order */
- memcpy(p, &attr, sizeof(attr));
- length = 4;
- break;
-
- case FR_TYPE_UINT32:
- attr = htonl(vp->vp_uint32); /* stored in host order */
- memcpy(p, &attr, sizeof(attr));
- length = 4;
- break;
-
- case FR_TYPE_UINT64:
- attr64 = htonll(vp->vp_uint64); /* stored in host order */
- memcpy(p, &attr64, sizeof(attr64));
- length = 8;
- break;
-
- case FR_TYPE_IPV4_ADDR:
- memcpy(p, &vp->vp_ipv4addr, 4); /* network order */
- length = 4;
- break;
-
- case FR_TYPE_STRING:
- case FR_TYPE_OCTETS:
- default:
- memcpy(p, vp->vp_strvalue, vp->vp_length);
- length = vp->vp_length;
- break;
- }
-
- /*
- * Skip to the end of the data.
- */
- p += length;
- total += length;
-
- /*
- * Align the data to a multiple of 4 bytes.
- */
- if ((total & 0x03) != 0) {
- size_t i;
-
- length = 4 - (total & 0x03);
- for (i = 0; i < length; i++) {
- *p = '\0';
- p++;
- total++;
- }
- }
- } /* loop over the VP's to write. */
-
- /*
- * Write the data in the buffer to the SSL session.
- */
- if (total > 0) {
- (tls_session->record_from_buff)(&tls_session->clean_in, buffer, total);
-
- /*
- * FIXME: Check the return code.
- */
- fr_tls_session_send(request, tls_session);
- }
-
- /*
- * Everything's OK.
- */
- return 1;
-}
-
-/*
- * Use a reply packet to determine what to do.
- */
-static unlang_action_t process_reply(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
- eap_session_t *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
- eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
- fr_tls_session_t *tls_session = eap_tls_session->tls_session;
- fr_pair_t *vp = NULL;
- fr_pair_list_t tunnel_vps;
- ttls_tunnel_t *t = tls_session->opaque;
- fr_packet_t *reply = request->reply;
-
- fr_pair_list_init(&tunnel_vps);
- fr_assert(eap_session->request == request->parent);
-
- /*
- * If the response packet was Access-Accept, then
- * we're OK. If not, die horribly.
- *
- * FIXME: Take MS-CHAP2-Success attribute, and
- * tunnel it back to the client, to authenticate
- * ourselves to the client.
- *
- * FIXME: If we have an Access-Challenge, then
- * the Reply-Message is tunneled back to the client.
- *
- * FIXME: If we have an EAP-Message, then that message
- * must be tunneled back to the client.
- *
- * FIXME: If we have an Access-Challenge with a State
- * attribute, then do we tunnel that to the client, or
- * keep track of it ourselves?
- *
- * FIXME: EAP-Messages can only start with 'identity',
- * NOT 'eap start', so we should check for that....
- */
- switch (reply->code) {
- case FR_RADIUS_CODE_ACCESS_ACCEPT:
- RDEBUG2("Got tunneled Access-Accept");
-
- /*
- * Copy what we need into the TTLS tunnel and leave
- * the rest to be cleaned up.
- */
- if ((vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_ms_chap2_success))) {
- RDEBUG2("Got MS-CHAP2-Success, tunneling it to the client in a challenge");
- } else {
- vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_eap_channel_binding_message);
- }
- if (vp) {
- t->authenticated = true;
- fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
- reply->code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
- break;
- }
-
- /*
- * Success: Automatically return MPPE keys.
- */
- return eap_ttls_success(p_result, request, eap_session);
-
- case FR_RADIUS_CODE_ACCESS_REJECT:
- REDEBUG("Got tunneled Access-Reject");
- eap_tls_fail(request, eap_session);
- RETURN_UNLANG_REJECT;
-
- /*
- * Handle Access-Challenge, but only if we
- * send tunneled reply data. This is because
- * an Access-Challenge means that we MUST tunnel
- * a Reply-Message to the client.
- */
- case FR_RADIUS_CODE_ACCESS_CHALLENGE:
- RDEBUG2("Got tunneled Access-Challenge");
-
- /*
- * Copy what we need into the TTLS tunnel and leave
- * the rest to be cleaned up.
- */
- vp = NULL;
- while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
- if ((vp->da == attr_eap_message) || (vp->da == attr_reply_message)) {
- fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
- } else if (vp->da == attr_eap_channel_binding_message) {
- fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
- }
- }
- break;
-
- default:
- REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
- eap_tls_fail(request, eap_session);
- RETURN_UNLANG_INVALID;
- }
-
-
- /*
- * Pack any tunneled VPs and send them back
- * to the supplicant.
- */
- if (!fr_pair_list_empty(&tunnel_vps)) {
- RDEBUG2("Sending tunneled reply attributes");
- log_request_pair_list(L_DBG_LVL_2, request, NULL, &tunnel_vps, NULL);
-
- vp2diameter(request, tls_session, &tunnel_vps);
- fr_pair_list_free(&tunnel_vps);
- }
-
- eap_tls_request(request, eap_session);
- RETURN_UNLANG_OK;
-}
-
-unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session)
-{
- eap_tls_session_t *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
- fr_tls_session_t *tls_session = eap_tls_session->tls_session;
- eap_tls_prf_label_t prf_label;
-
- eap_crypto_prf_label_init(&prf_label, eap_session,
- "ttls keying material",
- sizeof("ttls keying material") - 1);
- /*
- * Success: Automatically return MPPE keys.
- */
- if (eap_tls_success(request, eap_session, &prf_label) < 0) RETURN_UNLANG_FAIL;
-
- /*
- * Result is always OK, even if we fail to persist the
- * session data.
- */
- p_result->rcode = RLM_MODULE_OK;
-
- /*
- * Write the session to the session cache
- *
- * We do this here (instead of relying on OpenSSL to call the
- * session caching callback), because we only want to write
- * session data to the cache if all phases were successful.
- *
- * If we wrote out the cache data earlier, and the server
- * exited whilst the session was in progress, the supplicant
- * could resume the session (and get access) even if phase2
- * never completed.
- */
- return fr_tls_cache_pending_push(request, tls_session);
-}
-
-/*
- * Process the "diameter" contents of the tunneled data.
- */
-unlang_action_t eap_ttls_process(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
- fr_pair_t *vp = NULL;
- ttls_tunnel_t *t;
- uint8_t const *data;
- size_t data_len;
- chbind_packet_t *chbind;
- fr_pair_t *username;
-
- /*
- * Just look at the buffer directly, without doing
- * record_to_buff.
- */
- data_len = tls_session->clean_out.used;
- tls_session->clean_out.used = 0;
- data = tls_session->clean_out.data;
-
- t = (ttls_tunnel_t *) tls_session->opaque;
-
- /*
- * If there's no data, maybe this is an ACK to an
- * MS-CHAP2-Success.
- */
- if (data_len == 0) {
- if (t->authenticated) {
- RDEBUG2("Got ACK, and the user was already authenticated");
- return eap_ttls_success(p_result, request, eap_session);
- } /* else no session, no data, die. */
-
- /*
- * FIXME: Call SSL_get_error() to see what went
- * wrong.
- */
- RDEBUG2("SSL_read Error");
- return UNLANG_ACTION_FAIL;
- }
-
- if (!diameter_verify(request, data, data_len)) return UNLANG_ACTION_FAIL;
-
- /*
- * Add the tunneled attributes to the request request.
- */
- if (eap_ttls_decode_pair(request, request->request_ctx, &request->request_pairs, fr_dict_root(fr_dict_internal()),
- data, data_len, tls_session->ssl) < 0) {
- RPEDEBUG("Decoding TTLS TLVs failed");
- return UNLANG_ACTION_FAIL;
- }
-
- /*
- * Update other items in the request_t data structure.
- */
-
- /*
- * No User-Name, try to create one from stored data.
- */
- username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
- if (!username) {
- /*
- * No User-Name in the stored data, look for
- * an EAP-Identity, and pull it out of there.
- */
- if (!t->username) {
- vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_eap_message);
- if (vp &&
- (vp->vp_length >= EAP_HEADER_LEN + 2) &&
- (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
- (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
- (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
- /*
- * Create & remember a User-Name
- */
- MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
- t->username->vp_tainted = true;
-
- fr_pair_value_bstrndup(t->username,
- (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
-
- RDEBUG2("Got tunneled identity of %pV", &t->username->data);
- } else {
- /*
- * Don't reject the request outright,
- * as it's permitted to do EAP without
- * user-name.
- */
- RWDEBUG2("No EAP-Identity found to start EAP conversation");
- }
- } /* else there WAS a t->username */
-
- if (t->username) {
- vp = fr_pair_copy(request->request_ctx, t->username);
- fr_pair_append(&request->request_pairs, vp);
- }
- } /* else the request ALREADY had a User-Name */
-
- /*
- * Process channel binding.
- */
- chbind = eap_chbind_vp2packet(request, &request->request_pairs);
- if (chbind) {
- fr_radius_packet_code_t chbind_code;
- CHBIND_REQ *req = talloc_zero(request, CHBIND_REQ);
-
- RDEBUG2("received chbind request");
- req->request = chbind;
- if (username) {
- req->username = username;
- } else {
- req->username = NULL;
- }
- chbind_code = chbind_process(request, req);
-
- /* encapsulate response here */
- if (req->response) {
- RDEBUG2("sending chbind response");
- fr_pair_append(&request->reply_pairs,
- eap_chbind_packet2vp(request->reply_ctx, req->response));
- } else {
- RDEBUG2("no chbind response");
- }
-
- /* clean up chbind req */
- talloc_free(req);
-
- if (chbind_code != FR_RADIUS_CODE_ACCESS_ACCEPT) return UNLANG_ACTION_FAIL;
- }
-
- /*
- * For this round, when the virtual server returns
- * we run the process reply function.
- */
- if (unlikely(unlang_module_yield(request, process_reply, NULL, 0, eap_session) != UNLANG_ACTION_YIELD)) {
- return UNLANG_ACTION_FAIL;
- }
-
- /*
- * Call authentication recursively, which will
- * do PAP, CHAP, MS-CHAP, etc.
- */
- return eap_virtual_server(request, eap_session, t->server_cs);
-}