From: Arran Cudbard-Bell Date: Fri, 25 Jul 2025 19:33:55 +0000 (-0700) Subject: Merge state machines with EAP modules, use virtual_server_t in more places X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9145c216f6e7c08461ea7a8b56813d7675c0489b;p=thirdparty%2Ffreeradius-server.git Merge state machines with EAP modules, use virtual_server_t in more places --- diff --git a/src/lib/eap/base.c b/src/lib/eap/base.c index dad15a1767..a25f1c38ce 100644 --- a/src/lib/eap/base.c +++ b/src/lib/eap/base.c @@ -419,18 +419,19 @@ static unlang_action_t eap_virtual_server_resume(UNUSED unlang_result_t *p_resul * * @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 @@ -449,7 +450,7 @@ unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_sessio 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; } diff --git a/src/lib/eap/base.h b/src/lib/eap/base.h index 9896ce8994..2c9a413f8d 100644 --- a/src/lib/eap/base.h +++ b/src/lib/eap/base.h @@ -27,6 +27,7 @@ RCSIDH(lib_eap_base_h, "$Id$") #include #include +#include #include #include @@ -62,9 +63,8 @@ void eap_packet_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *list, eap_packet_raw_t 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); - diff --git a/src/lib/eap_aka_sim/module.h b/src/lib/eap_aka_sim/module.h index 4c28a5a7cf..8a1ca32e86 100644 --- a/src/lib/eap_aka_sim/module.h +++ b/src/lib/eap_aka_sim/module.h @@ -27,6 +27,7 @@ RCSIDH(lib_eap_aka_sim_module_h, "$Id$") #include +#include #include #include @@ -37,7 +38,7 @@ extern "C" { #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 * diff --git a/src/lib/server/virtual_servers.c b/src/lib/server/virtual_servers.c index 298ed57852..4c1f8290e4 100644 --- a/src/lib/server/virtual_servers.c +++ b/src/lib/server/virtual_servers.c @@ -752,16 +752,8 @@ check_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. * @@ -787,7 +779,7 @@ unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *reques 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 */ @@ -951,6 +943,22 @@ CONF_SECTION *virtual_server_cs(virtual_server_t const *vs) 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. @@ -1061,7 +1069,7 @@ int virtual_server_cf_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *pare } done: - *((CONF_SECTION **)out) = vs->server_cs; + *((virtual_server_t const **)out) = vs; return 0; } diff --git a/src/lib/server/virtual_servers.h b/src/lib/server/virtual_servers.h index 34e439b284..11f219b110 100644 --- a/src/lib/server/virtual_servers.h +++ b/src/lib/server/virtual_servers.h @@ -85,6 +85,8 @@ int virtual_server_has_namespace(CONF_SECTION **out, */ 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); @@ -144,7 +146,7 @@ int virtual_server_section_register(virtual_server_t *vs, virtual_server_compil 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 * diff --git a/src/lib/unlang/call.c b/src/lib/unlang/call.c index 756e959fa3..f6267ea259 100644 --- a/src/lib/unlang/call.c +++ b/src/lib/unlang/call.c @@ -135,7 +135,7 @@ static unlang_action_t unlang_call_frame_init(unlang_result_t *p_result, request 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; } diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/all.mk b/src/modules/rlm_eap/types/rlm_eap_fast/all.mk index c79dd63b99..82064e05f4 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/all.mk +++ b/src/modules/rlm_eap/types/rlm_eap_fast/all.mk @@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" "" 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/ diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c deleted file mode 100644 index 05c73666ee..0000000000 --- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#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; -} diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.h b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.h index 8cdb16bc45..c0b9157e62 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.h +++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.h @@ -205,60 +205,3 @@ typedef struct { #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); diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c index 797f9bb22d..756481694e 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c @@ -28,7 +28,9 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include - +#include +#include +#include #include "eap_fast.h" #include "eap_fast_crypto.h" @@ -48,7 +50,6 @@ typedef struct { 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. @@ -94,47 +95,47 @@ fr_dict_autoload_t rlm_eap_fast_dict[] = { { 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[] = { @@ -183,6 +184,1014 @@ 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 * */ @@ -200,8 +1209,6 @@ static eap_fast_tunnel_t *eap_fast_alloc(TALLOC_CTX *ctx, rlm_eap_fast_t const * 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; } @@ -441,7 +1448,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct /* * 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; @@ -633,8 +1640,6 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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", diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/all.mk b/src/modules/rlm_eap/types/rlm_eap_peap/all.mk index f7be0b51c1..f0af56b146 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/all.mk +++ b/src/modules/rlm_eap/types/rlm_eap_peap/all.mk @@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" "" 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/ diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h b/src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h deleted file mode 100644 index 107ba67bde..0000000000 --- a/src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h +++ /dev/null @@ -1,70 +0,0 @@ -#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 - -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); diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c deleted file mode 100644 index 71be8b3aeb..0000000000 --- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +++ /dev/null @@ -1,634 +0,0 @@ -/* - * 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 -#include - -#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); -} diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c index b1ba5fe27c..09eed869b3 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c @@ -23,7 +23,6 @@ RCSID("$Id$") #include -#include "eap_peap.h" typedef struct { SSL_CTX *ssl_ctx; //!< Thread local SSL_CTX. @@ -45,6 +44,36 @@ typedef struct { 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) }, @@ -54,7 +83,7 @@ static conf_parser_t submodule_config[] = { { 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 @@ -87,16 +116,618 @@ fr_dict_attr_autoload_t rlm_eap_peap_dict_attr[] = { }; +/* + * 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; @@ -170,7 +801,6 @@ static unlang_action_t process_rcode(unlang_result_t *p_result, module_ctx_t con 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; @@ -238,7 +868,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct * 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 @@ -249,7 +879,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct /* * 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); } /* @@ -329,7 +959,7 @@ static unlang_action_t mod_session_init_resume(unlang_result_t *p_result, module * 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; @@ -382,6 +1012,8 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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); /* diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/all.mk b/src/modules/rlm_eap/types/rlm_eap_ttls/all.mk index 76e914617b..d8056b5a19 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/all.mk +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/all.mk @@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" "" 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/ diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h b/src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h deleted file mode 100644 index cd3907f444..0000000000 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h +++ /dev/null @@ -1,53 +0,0 @@ -#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 -#include -#include - -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); diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c index ae1613eabf..2df37f0e54 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c @@ -27,7 +27,8 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include -#include "eap_ttls.h" +#include +#include typedef struct { SSL_CTX *ssl_ctx; //!< Thread local SSL_CTX. @@ -63,6 +64,10 @@ typedef struct { 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) }, @@ -70,7 +75,7 @@ static conf_parser_t submodule_config[] = { { 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 @@ -86,17 +91,17 @@ fr_dict_autoload_t rlm_eap_ttls_dict[] = { { 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[] = { @@ -114,15 +119,756 @@ 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; } @@ -191,7 +937,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct /* * 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); } /* @@ -250,7 +996,7 @@ static unlang_action_t mod_session_init_resume(unlang_result_t *p_result, module RETURN_UNLANG_FAIL; } - tls_session->opaque = ttls_alloc(tls_session, inst); + tls_session->opaque = ttls_alloc(tls_session); eap_session->process = mod_handshake_process; diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c deleted file mode 100644 index fd4963ef93..0000000000 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +++ /dev/null @@ -1,772 +0,0 @@ -/* - * 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 -#include -#include -#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); -}