From: Arran Cudbard-Bell Date: Tue, 12 Dec 2017 12:25:38 +0000 (+0000) Subject: Rewrite large chunks of EAP-SIM so it behaves like the RFC says it should X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f2eaaf84676aed095d646ae36c225a3157690ce5;p=thirdparty%2Ffreeradius-server.git Rewrite large chunks of EAP-SIM so it behaves like the RFC says it should I don't know if this was written pre RFC 4186 but large parts of it don't follow the RFC. --- diff --git a/src/modules/rlm_eap/types/rlm_eap_sim/eap_sim.h b/src/modules/rlm_eap/types/rlm_eap_sim/eap_sim.h index 6ddebbc3730..3c5380bdbb7 100644 --- a/src/modules/rlm_eap/types/rlm_eap_sim/eap_sim.h +++ b/src/modules/rlm_eap/types/rlm_eap_sim/eap_sim.h @@ -33,14 +33,33 @@ RCSIDH(rlm_eap_sim_eap_sim_h, "$Id$") * In server_start, we send a EAP-SIM Start message. */ typedef enum { - EAP_SIM_SERVER_START = 0, - EAP_SIM_SERVER_CHALLENGE = 1, - EAP_SIM_SERVER_SUCCESS = 10, + EAP_SIM_SERVER_START = 0, + EAP_SIM_SERVER_CHALLENGE, + EAP_SIM_SERVER_SUCCESS_NOTIFICATION, + EAP_SIM_SERVER_SUCCESS, + EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION, + EAP_SIM_SERVER_FAILURE, EAP_SIM_SERVER_MAX_STATES } eap_sim_server_state_t; typedef struct eap_aka_session { - eap_sim_server_state_t state; //!< Current session state. - fr_sim_keys_t keys; //!< Various EAP-AKA keys. - int sim_id; //!< Packet ID. (replay protection) + eap_sim_server_state_t state; //!< Current session state. + fr_sim_keys_t keys; //!< Various EAP-AKA keys. + fr_sim_id_req_type_t id_req; //!< The type of identity we're requesting + ///< or previously requested. + + bool request_identity; //!< Always send an identity request before a + ///< challenge. + + bool send_result_ind; //!< Say that we would like to use protected result + ///< indications (SIM-Notification-Success). + + int sim_id; //!< Packet ID. (replay protection) } eap_sim_session_t; + + +typedef struct { + char const *virtual_server; //!< Virtual server for HLR integration. + bool request_identity; //!< Whether we always request the identity of + ///< the subscriber. +} rlm_eap_sim_t; diff --git a/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c index a4793d89e97..a4d5b6783e6 100644 --- a/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c +++ b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c @@ -39,6 +39,21 @@ RCSID("$Id$") # define EAP_TLS_MPPE_KEY_LEN 32 #endif +FR_NAME_NUMBER const sim_state_table[] = { + { "START", EAP_SIM_SERVER_START }, + { "CHALLENGE", EAP_SIM_SERVER_CHALLENGE }, + { "SUCCESS-NOTIFICATION", EAP_SIM_SERVER_SUCCESS_NOTIFICATION }, + { "SUCCESS", EAP_SIM_SERVER_SUCCESS }, + { "GENERAL-FAILURE-NOTIFICATION", EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION }, + { "FAILURE", EAP_SIM_SERVER_FAILURE }, + { NULL } +}; + +static CONF_PARSER submodule_config[] = { + { FR_CONF_OFFSET("virtual_server", FR_TYPE_STRING, rlm_eap_sim_t, virtual_server) }, + CONF_PARSER_TERMINATOR +}; + /* * build a reply to be sent. */ @@ -94,7 +109,7 @@ static int eap_sim_compose(eap_session_t *eap_session) return 0; } -static int eap_sim_send_state(eap_session_t *eap_session) +static int eap_sim_send_start(eap_session_t *eap_session) { REQUEST *request = eap_session->request; VALUE_PAIR **vps, *vp; @@ -130,16 +145,41 @@ static int eap_sim_send_state(eap_session_t *eap_session) vp->vp_uint32 = eap_sim_session->sim_id++; fr_pair_replace(vps, vp); - /* the ANY_ID attribute. We do not support re-auth or pseudonym */ - MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_FULLAUTH_ID_REQ)); + /* + * Select the right type of identity request attribute + */ + switch (eap_sim_session->id_req) { + case SIM_ANY_ID_REQ: + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_ANY_ID_REQ); + break; + + case SIM_PERMANENT_ID_REQ: + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_PERMANENT_ID_REQ); + break; + + case SIM_FULLAUTH_ID_REQ: + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_FULLAUTH_ID_REQ); + break; + + default: + rad_assert(0); + } vp->vp_bool = true; - fr_pair_add(vps, vp); + fr_pair_replace(vps, vp); /* the SUBTYPE, set to start. */ vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE); vp->vp_uint32 = EAP_SIM_START; fr_pair_replace(vps, vp); + /* + * Encode the packet + */ + if (eap_sim_compose(eap_session) < 0) { + fr_pair_list_free(&packet->vps); + return -1; + } + return 0; } @@ -164,91 +204,120 @@ static int eap_sim_send_challenge(eap_session_t *eap_session) static uint8_t hmac_zero[16] = { 0x00 }; REQUEST *request = eap_session->request; - eap_sim_session_t *eap_sim_session; - VALUE_PAIR **from_peer, **to_client, *vp; + eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); + VALUE_PAIR **to_peer, *vp; RADIUS_PACKET *packet; + fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; - eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); - RDEBUG2("Sending SIM-Challenge"); - eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + RDEBUG2("Acquiring GSM vector(s)"); + if ((fr_sim_vector_gsm_from_attrs(eap_session, request->control, 0, &eap_sim_session->keys, &src) != 0) || + (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 1, &eap_sim_session->keys, &src) != 0) || + (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 2, &eap_sim_session->keys, &src) != 0)) { + REDEBUG("Failed retrieving SIM vectors"); + return RLM_MODULE_FAIL; + } /* - * from_peer is the data from the client but this is for non-protocol data here. - * We should already have consumed any client originated data. + * All set, calculate keys! */ - from_peer = &eap_session->request->packet->vps; + fr_sim_crypto_kdf_0_gsm(&eap_sim_session->keys); + if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys); + + RDEBUG2("Sending SIM-Challenge"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* - * to_client is the data to the client + * to_peer is the data to the client */ packet = eap_session->request->reply; - to_client = &packet->vps; + to_peer = &packet->vps; /* * Okay, we got the challenges! Put them into attributes. */ MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[0].rand, SIM_VECTOR_GSM_RAND_SIZE); - fr_pair_add(to_client, vp); + fr_pair_add(to_peer, vp); MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[1].rand, SIM_VECTOR_GSM_RAND_SIZE); - fr_pair_add(to_client, vp); + fr_pair_add(to_peer, vp); MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[2].rand, SIM_VECTOR_GSM_RAND_SIZE); - fr_pair_add(to_client, vp); + fr_pair_add(to_peer, vp); /* - * Set the EAP_ID - new value + * Set subtype to challenge. */ - vp = fr_pair_afrom_child_num(packet, fr_dict_root(fr_dict_internal), FR_EAP_ID); - vp->vp_uint32 = eap_sim_session->sim_id++; - fr_pair_replace(to_client, vp); + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE); + vp->vp_uint32 = EAP_SIM_CHALLENGE; + fr_pair_replace(to_peer, vp); /* - * Use the SIM identity, if available + * Indicate we'd like to use protected success messages */ - vp = fr_pair_find_by_child_num(*from_peer, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY); - if (vp) { - MEM(eap_sim_session->keys.identity = (uint8_t *)talloc_bstrndup(eap_sim_session, - vp->vp_strvalue, vp->vp_length)); - eap_sim_session->keys.identity_len = vp->vp_length; - /* - * Make a copy of the identity - */ - } else { - if (eap_sim_session->keys.identity) talloc_const_free(eap_sim_session->keys.identity); - - eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; - MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity, - eap_sim_session->keys.identity_len)); + if (eap_sim_session->send_result_ind) { + MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RESULT_IND)); + vp->vp_bool = true; + fr_pair_replace(to_peer, vp); } - /* - * All set, calculate keys! - */ - fr_sim_crypto_kdf_0_gsm(&eap_sim_session->keys); - - if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys); - /* * Need to include an AT_MAC attribute so that it will get * calculated. */ vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_MAC); fr_pair_value_memcpy(vp, hmac_zero, sizeof(hmac_zero)); - fr_pair_replace(to_client, vp); + fr_pair_replace(to_peer, vp); /* - * Set subtype to challenge. + * Encode the packet + */ + if (eap_sim_compose(eap_session) < 0) { + fr_pair_list_free(&packet->vps); + return -1; + } + + return 0; +} + +/** Send a success notification + * + */ +static int eap_sim_send_eap_success_notification(eap_session_t *eap_session) +{ + REQUEST *request = eap_session->request; + RADIUS_PACKET *packet = eap_session->request->reply; + fr_cursor_t cursor; + VALUE_PAIR *vp; + + RDEBUG2("Sending SIM-Notification (Success)"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + + fr_cursor_init(&cursor, &packet->vps); + + /* + * Set the subtype to notification */ vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE); - vp->vp_uint32 = EAP_SIM_CHALLENGE; - fr_pair_replace(to_client, vp); + vp->vp_uint32 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION; + fr_cursor_append(&cursor, vp); + + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_NOTIFICATION); + vp->vp_uint32 = FR_EAP_SIM_NOTIFICATION_VALUE_SUCCESS; + fr_cursor_append(&cursor, vp); + + /* + * Encode the packet + */ + if (eap_sim_compose(eap_session) < 0) { + fr_pair_list_free(&packet->vps); + return -1; + } return 0; } @@ -258,28 +327,16 @@ static int eap_sim_send_challenge(eap_session_t *eap_session) * The only work to be done is the add the appropriate SEND/RECV * radius attributes derived from the MSK. */ -static int eap_sim_send_success(eap_session_t *eap_session) +static int eap_sim_send_eap_success(eap_session_t *eap_session) { - uint8_t *p; - REQUEST *request = eap_session->request; - eap_sim_session_t *eap_sim_session; - VALUE_PAIR *vp; - RADIUS_PACKET *packet; + uint8_t *p; + eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); RDEBUG2("Sending SIM-Success"); eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS; eap_session->finished = true; - /* to_client is the data to the client. */ - packet = eap_session->request->reply; - eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); - - /* set the EAP_ID - new value */ - vp = fr_pair_afrom_child_num(packet, fr_dict_root(fr_dict_internal), FR_EAP_ID); - vp->vp_uint32 = eap_sim_session->sim_id++; - fr_pair_replace(&eap_session->request->reply->vps, vp); - p = eap_sim_session->keys.msk; eap_add_reply(eap_session->request, "MS-MPPE-Recv-Key", p, EAP_TLS_MPPE_KEY_LEN); p += EAP_TLS_MPPE_KEY_LEN; @@ -288,49 +345,129 @@ static int eap_sim_send_success(eap_session_t *eap_session) return 0; } +/** Send a failure message + * + */ +static int eap_sim_send_eap_failure_notification(eap_session_t *eap_session) +{ + REQUEST *request = eap_session->request; + RADIUS_PACKET *packet = eap_session->request->reply; + fr_cursor_t cursor; + VALUE_PAIR *vp; + + RDEBUG2("Sending SIM-Notification (General-Failure)"); + eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; + + fr_cursor_init(&cursor, &packet->vps); + + /* + * Set the subtype to notification + */ + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE); + vp->vp_uint32 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION; + fr_cursor_append(&cursor, vp); + + vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_NOTIFICATION); + vp->vp_uint32 = FR_EAP_SIM_NOTIFICATION_VALUE_GENERAL_FAILURE; + fr_cursor_append(&cursor, vp); + + /* + * Encode the packet + */ + if (eap_sim_compose(eap_session) < 0) { + fr_pair_list_free(&packet->vps); + return -1; + } + + return 0; +} + +static int eap_sim_send_eap_failure(eap_session_t *eap_session) +{ + REQUEST *request = eap_session->request; + + RDEBUG2("Sending EAP-Failure"); + + eap_session->this_round->request->code = FR_EAP_CODE_FAILURE; + eap_session->finished = true; + + return 0; +} + /** Run the server state machine * */ -static void eap_sim_state_enter(eap_session_t *eap_session, - eap_sim_session_t *eap_sim_session, - eap_sim_server_state_t new_state) +static void eap_sim_state_enter(eap_session_t *eap_session, eap_sim_server_state_t new_state) { + REQUEST *request = eap_session->request; + eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); + + if (new_state != eap_sim_session->state) { + RDEBUG2("Changed state %s -> %s", + fr_int2str(sim_state_table, eap_sim_session->state, ""), + fr_int2str(sim_state_table, new_state, "")); + eap_sim_session->state = new_state; + } else { + RDEBUG2("Reentering state %s", + fr_int2str(sim_state_table, eap_sim_session->state, "")); + } + switch (new_state) { /* - * Send the EAP-SIM Start message, listing the versions that we support. + * Send our version list */ case EAP_SIM_SERVER_START: - eap_sim_send_state(eap_session); - eap_sim_compose(eap_session); /* Encode SIM TLVs */ + if (eap_sim_send_start(eap_session) < 0) { + notify_failure: + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return; + } break; + /* * Send the EAP-SIM Challenge message. */ case EAP_SIM_SERVER_CHALLENGE: - eap_sim_send_challenge(eap_session); - eap_sim_compose(eap_session); /* Encode SIM TLVs */ + if (eap_sim_send_challenge(eap_session) < 0) goto notify_failure; break; /* - * Send the EAP Success message + * Sent a protected success notification + */ + case EAP_SIM_SERVER_SUCCESS_NOTIFICATION: + if (eap_sim_send_eap_success_notification(eap_session) < 0) goto notify_failure; + break; + + /* + * Send the EAP Success message (we're done) */ case EAP_SIM_SERVER_SUCCESS: - eap_sim_send_success(eap_session); + if (eap_sim_send_eap_success(eap_session) < 0) goto notify_failure; + return; - break; /* - * Nothing to do for this transition. + * Send a general failure notification */ + case EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION: + if (eap_sim_send_eap_failure_notification(eap_session) < 0) { /* Fallback to EAP-Failure */ + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); + } + return; + + /* + * Send an EAP-Failure (we're done) + */ + case EAP_SIM_SERVER_FAILURE: + eap_sim_send_eap_failure(eap_session); + return; + default: - eap_sim_compose(eap_session); /* Encode SIM TLVs */ - break; + rad_assert(0); /* Invalid transition */ + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return; } - - eap_sim_session->state = new_state; } -static rlm_rcode_t CC_HINT(nonnull) mod_process(void *instance, eap_session_t *eap_session); - /** Process an EAP-Sim/Response/Start * * Verify that client chose a version, and provided a NONCE_MT, @@ -343,21 +480,27 @@ static int process_eap_sim_start(eap_session_t *eap_session, VALUE_PAIR *vps) VALUE_PAIR *nonce_vp, *selected_version_vp; eap_sim_session_t *eap_sim_session; uint16_t eap_sim_version; + VALUE_PAIR *id; + fr_sim_id_type_t type = SIM_ID_TYPE_UNKNOWN; + fr_sim_method_hint_t method = SIM_METHOD_HINT_UNKNOWN; eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); + /* + * For fullauth We require both the NONCE_MT + * and SELECTED_VERSION from the peer, else + * the packet is invalid. + */ nonce_vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_NONCE_MT, TAG_ANY); selected_version_vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_SELECTED_VERSION, TAG_ANY); if (!nonce_vp || !selected_version_vp) { - RDEBUG2("Client did not select a version and send a NONCE"); - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START); - - return 0; + REDEBUG2("Client did not select a version and send a NONCE"); + return -1; } eap_sim_version = selected_version_vp->vp_uint16; if (eap_sim_version != EAP_SIM_VERSION) { - REDEBUG("EAP-SIM version %i is unknown", eap_sim_version); + REDEBUG2("EAP-SIM-Version %i is unknown", eap_sim_version); return -1; } @@ -378,13 +521,56 @@ static int process_eap_sim_start(eap_session_t *eap_session, VALUE_PAIR *vps) memcpy(eap_sim_session->keys.gsm.nonce_mt, nonce_vp->vp_octets, 16); /* - * Everything looks good, change states + * See if we got an AT_IDENTITY + */ + id = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY); + if (id) { + if (fr_sim_id_type(&type, &method, + eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { + RWDEBUG2("Failed parsing identity: %s", fr_strerror()); + } + + /* + * Update cryptographic identity + */ + talloc_const_free(eap_sim_session->keys.identity); + eap_sim_session->keys.identity_len = id->vp_length; + MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, id->vp_strvalue, id->vp_length)); + } + + /* + * @TODO Run a virtual server to see if we can use the + * identity we just acquired, or whether we need to + * negotiate the next permissive ID. + */ + + /* + * Negotiate the next permissive form + * if identity, or fail. */ - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_CHALLENGE); + switch (eap_sim_session->id_req) { + case SIM_ANY_ID_REQ: + eap_sim_session->id_req = SIM_FULLAUTH_ID_REQ; + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); + break; + + case SIM_FULLAUTH_ID_REQ: + eap_sim_session->id_req = SIM_PERMANENT_ID_REQ; + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); + break; + + case SIM_NO_ID_REQ: + case SIM_PERMANENT_ID_REQ: + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_CHALLENGE); +// REDEBUG2("Failed to negotiate a usable identity"); +// eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + break; + } return 0; } + /** Process an EAP-Sim/Response/Challenge * * Verify that MAC that we received matches what we would have @@ -398,7 +584,7 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps uint8_t sres_cat[SIM_VECTOR_GSM_SRES_SIZE * 3]; uint8_t *p = sres_cat; - uint8_t calc_mac[SIM_MAC_SIZE]; + uint8_t calc_mac[SIM_MAC_DIGEST_SIZE]; ssize_t slen; VALUE_PAIR *mac; @@ -408,26 +594,24 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps p += SIM_VECTOR_GSM_SRES_SIZE; memcpy(p, eap_sim_session->keys.gsm.vector[2].sres, SIM_VECTOR_GSM_SRES_SIZE); - mac = fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_RES, TAG_ANY); + mac = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_MAC, TAG_ANY); if (!mac) { REDEBUG("Missing AT_MAC attribute"); return -1; } - if (mac->vp_length != SIM_MAC_SIZE) { - REDEBUG("AT_MAC incorrect length, expected %u bytes got %zu bytes", - SIM_MAC_SIZE, mac->vp_length); + if (mac->vp_length != SIM_MAC_DIGEST_SIZE) { + REDEBUG("EAP-SIM-MAC has incorrect length, expected %u bytes got %zu bytes", + SIM_MAC_DIGEST_SIZE, mac->vp_length); return -1; } slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, EVP_sha1(), eap_sim_session->keys.k_aut, eap_sim_session->keys.k_aut_len, - NULL, 0); + sres_cat, sizeof(sres_cat)); if (slen < 0) { RPEDEBUG("Failed calculating MAC"); return -1; - } - - if (slen == 0) { + } else if (slen == 0) { REDEBUG("Missing EAP-SIM-MAC attribute in packet buffer"); return -1; } @@ -436,13 +620,21 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps RDEBUG2("EAP-SIM-MAC matches calculated MAC"); } else { REDEBUG("EAP-SIM-MAC does not match calculated MAC"); - RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_SIZE, "Received"); - RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_SIZE, "Expected"); + RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_DIGEST_SIZE, "Received"); + RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_DIGEST_SIZE, "Expected"); return -1; } - /* everything looks good, change states */ - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_SUCCESS); + /* + * If the peer wants a Success notification, then + * send a success notification, otherwise send a + * normal EAP-Success. + */ + if (fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_SIM_RESULT_IND, TAG_ANY)) { + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_SUCCESS_NOTIFICATION); + } else { + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_SUCCESS); + } return 0; } @@ -455,12 +647,11 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); - fr_sim_decode_ctx_t ctx = { .keys = &eap_sim_session->keys, .root = dict_sim_root }; - VALUE_PAIR *vp, *vps; + VALUE_PAIR *subtype_vp, *from_peer, *vp; vp_cursor_t cursor; eap_sim_subtype_t subtype; @@ -472,7 +663,7 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) /* * VPS is the data from the client */ - vps = eap_session->request->packet->vps; + from_peer = eap_session->request->packet->vps; fr_pair_cursor_init(&cursor, &request->packet->vps); fr_pair_cursor_last(&cursor); @@ -482,7 +673,16 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); - if (ret < 0) return 0; + /* + * RFC 4186 says we *MUST* notify, not just + * send an EAP-Failure in this case where + * we cannot decode an EAP-AKA packet. + */ + if (ret < 0) { + RPEDEBUG2("Failed decoding EAP-SIM attributes"); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ + } vp = fr_pair_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { @@ -490,57 +690,148 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) rdebug_pair_list(L_DBG_LVL_2, request, vp, NULL); } - /* - * See what kind of message we have gotten - */ - vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_SUBTYPE, TAG_ANY); - if (!vp) { - REDEBUG2("No subtype attribute was created, message dropped"); - return 0; + subtype_vp = fr_pair_find_by_child_num(from_peer, dict_sim_root, FR_EAP_SIM_SUBTYPE, TAG_ANY); + if (!subtype_vp) { + REDEBUG("Missing EAP-SIM-Subtype"); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ } - subtype = vp->vp_uint32; + subtype = subtype_vp->vp_uint32; + switch (eap_sim_session->state) { /* - * Client error supersedes anything else. + * Response to our advertised versions and request for an ID + * This is very similar to Identity negotiation in EAP-AKA['] */ - if (subtype == EAP_SIM_CLIENT_ERROR) { - REDEBUG("Client encountered an error"); - return RLM_MODULE_FAIL; - } - - switch (eap_sim_session->state) { case EAP_SIM_SERVER_START: switch (subtype) { + case EAP_SIM_START: + if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED; + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ + /* - * Pretty much anything else here is illegal, so we will retransmit the request. + * Case 1 where we're allowed to send an EAP-Failure + * + * This can happen in the case of a conservative + * peer, where it refuses to provide the permanent + * identity. */ + case EAP_SIM_CLIENT_ERROR: + { + char buff[20]; + + vp = fr_pair_find_by_child_num(from_peer, dict_sim_root, FR_EAP_SIM_CLIENT_ERROR_CODE, TAG_ANY); + if (!vp) { + REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but " + "has not supplied a client error code", + fr_int2str(sim_id_request_table, eap_sim_session->id_req, "")); + } else { + REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)", + fr_int2str(sim_id_request_table, eap_sim_session->id_req, ""), + fr_pair_value_enum(vp, buff), vp->vp_uint16); + } + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); + return RLM_MODULE_REJECT; + } + + case EAP_SIM_NOTIFICATION: + notification: + { + char buff[20]; + + vp = fr_pair_afrom_child_num(from_peer, dict_aka_root, FR_EAP_SIM_NOTIFICATION); + if (!vp) { + REDEBUG2("Received SIM-Notification with no notification code"); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ + } + + /* + * Case 2 where we're allowed to send an EAP-Failure + */ + if (!(vp->vp_uint16 & 0x8000)) { + REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), + vp->vp_uint16); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); + return RLM_MODULE_REJECT; + } + + /* + * ...if it's not a failure, then re-enter the + * current state. + */ + REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); + eap_sim_state_enter(eap_session, eap_sim_session->state); + return RLM_MODULE_HANDLED; + default: - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START); - return RLM_MODULE_OK; - /* - * A response to our EAP-Sim/Request/Start! - */ - case EAP_SIM_START: - return process_eap_sim_start(eap_session, vps); + unexpected_subtype: + /* + * RFC 4186 says we *MUST* notify, not just + * send an EAP-Failure in this case. + */ + REDEBUG("Unexpected subtype %pV", &subtype_vp->data); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ + } } + /* + * Process the response to our previous challenge. + */ case EAP_SIM_SERVER_CHALLENGE: switch (subtype) { - /* - * Pretty much anything else here is illegal, so we will retransmit the request. - */ - default: - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_CHALLENGE); - return 1; /* * A response to our EAP-Sim/Request/Challenge! */ case EAP_SIM_CHALLENGE: - return process_eap_sim_challenge(eap_session, vps); + return process_eap_sim_challenge(eap_session, from_peer); + + case EAP_SIM_CLIENT_ERROR: + { + char buff[20]; + + vp = fr_pair_find_by_child_num(from_peer, dict_aka_root, FR_EAP_SIM_CLIENT_ERROR_CODE, TAG_ANY); + if (!vp) { + REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but " + "has not supplied a client error code"); + } else { + REDEBUG("Client rejected SIM-Challenge with error: %s (%i)", + fr_pair_value_enum(vp, buff), vp->vp_uint16); + } + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); + return RLM_MODULE_REJECT; + } + + case EAP_AKA_NOTIFICATION: + goto notification; + + default: + goto unexpected_subtype; } + /* + * Peer acked our failure + */ + case EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION: + switch (subtype) { + case EAP_SIM_NOTIFICATION: + RDEBUG2("SIM-Notification ACKed, sending EAP-Failure"); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); + return RLM_MODULE_REJECT; + + default: + goto unexpected_subtype; + } + + /* + * Something bad happened... + */ default: rad_assert(0); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION); + return RLM_MODULE_HANDLED; /* We need to process more packets */ } return RLM_MODULE_FAIL; @@ -552,78 +843,81 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session) */ static rlm_rcode_t mod_session_init(UNUSED void *instance, eap_session_t *eap_session) { - REQUEST *request = eap_session->request; - eap_sim_session_t *eap_sim_session; - time_t n; - fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; + REQUEST *request = eap_session->request; + eap_sim_session_t *eap_sim_session; +// rlm_eap_sim_t *inst = instance; + fr_sim_id_type_t type; + fr_sim_method_hint_t method; MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t)); eap_session->opaque = eap_sim_session; /* - * Save the keying material, because it could change on a subsequent retrieval. + * Set default configuration, we may allow these + * to be toggled by attributes later. */ - RDEBUG2("New EAP-SIM session. Acquiring SIM vectors"); - if ((fr_sim_vector_gsm_from_attrs(eap_session, request->control, 0, &eap_sim_session->keys, &src) != 0) || - (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 1, &eap_sim_session->keys, &src) != 0) || - (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 2, &eap_sim_session->keys, &src) != 0)) { - REDEBUG("Failed retrieving SIM vectors"); - return RLM_MODULE_FAIL; - } + eap_sim_session->send_result_ind = true; + eap_sim_session->id_req = SIM_ANY_ID_REQ; /* Set the default */ /* - * This value doesn't have be strong, but it is good if it is different now and then. + * This value doesn't have be strong, but it is + * good if it is different now and then. */ - time(&n); - eap_sim_session->sim_id = (n & 0xff); - - eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START); - - eap_session->process = mod_process; - - return RLM_MODULE_OK; -} + eap_sim_session->sim_id = (fr_rand() & 0xff); -/* - * Attach the module. - */ -static int mod_instantiate(UNUSED void *instance, CONF_SECTION *cs) -{ - fr_dict_attr_t const *da; - CONF_SECTION *subcs = NULL; + /* + * Save the keying material, because it could change on a subsequent retrieval. + */ + RDEBUG2("New EAP-SIM session"); - da = fr_dict_attr_child_by_num(dict_sim_root, FR_EAP_SIM_SUBTYPE); - if (!da) { - cf_log_err(cs, "Failed to find EAP-Sim-Subtype attribute"); - return -1; + /* + * Process the identity that we received in the + * EAP-Identity-Response and use it to determine + * the initial request we send to the Supplicant. + */ + if (fr_sim_id_type(&type, &method, + eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { + RWDEBUG2("Failed parsing identity, continuing anyway: %s", fr_strerror()); } - while ((subcs = cf_section_find_next(cs, subcs, "process", NULL))) { - char const *name2; - fr_dict_enum_t *dv; - - name2 = cf_section_name2(subcs); - if (!name2) { - cf_log_err(subcs, "Cannot compile 'process { ... }' section"); - return -1; - } - - dv = fr_dict_enum_by_alias(NULL, da, name2); - if (!dv) { - cf_log_err(subcs, "Unknown EAP-SIM-Subtype %s", name2); - return -1; - } + switch (method) { + default: + RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM", + fr_int2str(sim_id_method_hint_table, method, "")); + break; - cf_log_debug(subcs, "Loading process %s {...}", name2); + case SIM_METHOD_HINT_SIM: + case SIM_METHOD_HINT_UNKNOWN: + break; + } + eap_session->process = mod_process; - if (unlang_compile(subcs, MOD_AUTHORIZE) < 0) { - cf_log_err(subcs, "Failed compiling 'process %s { ... }' section", name2); - return -1; - } + /* + * Figure out what type of identity we have + * and use it to determine the initial + * request we send. + */ + switch (type) { + /* + * These types need to be transformed into something + * usable before we can do anything. + */ + case SIM_ID_TYPE_UNKNOWN: + case SIM_ID_TYPE_PSEUDONYM: + case SIM_ID_TYPE_FASTAUTH: + /* + * Permanent ID means we can just send the challenge + */ + case SIM_ID_TYPE_PERMANENT: + eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; + MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity, + eap_sim_session->keys.identity_len)); + eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); + return RLM_MODULE_OK; } - return 0; + return RLM_MODULE_OK; } static int mod_load(void) @@ -654,9 +948,11 @@ rlm_eap_submodule_t rlm_eap_sim = { .magic = RLM_MODULE_INIT, .provides = { FR_EAP_SIM }, + .inst_size = sizeof(rlm_eap_sim_t), + .config = submodule_config, + .load = mod_load, .unload = mod_unload, - .instantiate = mod_instantiate, /* Create new submodule instance */ .session_init = mod_session_init, /* Initialise a new EAP session */ .process = mod_process, /* Process next round of EAP method */ };