* @file src/lib/eap_aka_sim/state_machine.c
* @brief Implement a common state machine for EAP-SIM, EAP-AKA, EAP-AKA'.
*
- * @author Arran Cudbard-Bell \<a.cudbardb@freeradius.org\>
+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
*
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
* @copyright 2019 The FreeRADIUS server project
- * @copyright 2019 Network RADIUS \<legal@networkradius.com\>
+ * @copyright 2019 Network RADIUS (legal@networkradius.com)
*/
RCSID("$Id$")
#include <freeradius-devel/eap/base.h>
#include <freeradius-devel/eap/types.h>
+#include <freeradius-devel/unlang/interpret.h>
#include <freeradius-devel/unlang/module.h>
+#include <freeradius-devel/util/table.h>
#include "base.h"
#include "state_machine.h"
# define EAP_TLS_MPPE_KEY_LEN 32
#endif
-/** A state transition function
- *
- * This is passed to sub-state machines that perform other actions, before
- * fully transitioning to a new state.
- *
- * Examples of these are the sub-state machines that deal with clearing
- * pseudonyms and reauthentication data.
- */
-typedef unlang_action_t(*aka_sim_state_enter_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-
-static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_eap_success(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-
-static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session);
-static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session);
-static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session);
-
-static module_state_func_table_t const aka_sim_stable_table[] = {
- { "FAILURE-NOTIFICATION", common_failure_notification },
- { "EAP-FAILURE", common_eap_failure },
- { "SUCCESS-NOTIFICATION", common_success_notification },
- { "EAP-SUCCESS", common_eap_success },
- { "REAUTHENTICATION", common_reauthentication },
- { "AKA-CHALLENGE", aka_challenge },
- { "SIM-CHALLENGE", sim_challenge },
- { "IDENTITY", aka_identity },
- { "SIM-START", sim_start },
- { "EAP-IDENTITY", aka_sim_state_machine_start },
-
- { NULL }
+#define STATE(_x) static inline unlang_action_t state_ ## _x(rlm_rcode_t *p_result, \
+ module_ctx_t const *mctx, \
+ request_t *request,\
+ eap_aka_sim_session_t *eap_aka_sim_session)
+#define STATE_GUARD(_x) static unlang_action_t guard_ ## _x(rlm_rcode_t *p_result, \
+ module_ctx_t const *mctx, \
+ request_t *request, \
+ eap_aka_sim_session_t *eap_aka_sim_session)
+
+#define RESUME(_x) static inline unlang_action_t resume_ ## _x(rlm_rcode_t *p_result, \
+ module_ctx_t const *mctx, \
+ request_t *request, \
+ void *rctx)
+
+#define STATE_TRANSITION(_x) guard_ ## _x(p_result, mctx, request, eap_aka_sim_session);
+
+#define CALL_SECTION(_x) unlang_module_yield_to_section(p_result, \
+ request, \
+ inst->actions._x, \
+ RLM_MODULE_NOOP, \
+ resume_ ## _x, \
+ mod_signal, \
+ eap_aka_sim_session)
+
+/*
+ * Declare all state and guard functions to
+ * avoid ordering issues.
+ */
+STATE(eap_failure);
+STATE_GUARD(eap_failure);
+STATE(common_failure_notification);
+STATE_GUARD(common_failure_notification);
+STATE(eap_success);
+STATE_GUARD(eap_success);
+STATE(common_success_notification);
+STATE_GUARD(common_success_notification);
+STATE(common_reauthentication);
+STATE_GUARD(common_reauthentication);
+STATE(aka_challenge);
+STATE_GUARD(aka_challenge);
+STATE(sim_challenge);
+STATE_GUARD(sim_challenge);
+STATE_GUARD(common_challenge);
+STATE(aka_identity);
+STATE_GUARD(aka_identity);
+STATE(sim_start);
+STATE_GUARD(sim_start);
+STATE_GUARD(common_identity);
+STATE(init);
+
+static fr_table_ptr_ordered_t const aka_sim_state_table[] = {
+ { L("INIT"), NULL },
+
+ { L("EAP-IDENTITY"), (void *)state_init },
+ { L("SIM-START"), (void *)state_sim_start },
+ { L("AKA-IDENTITY"), (void *)state_aka_identity },
+
+ { L("SIM-CHALLENGE"), (void *)state_sim_challenge },
+ { L("AKA-CHALLENGE"), (void *)state_aka_challenge },
+
+ { L("SUCCESS-NOTIFICATION"), (void *)state_common_success_notification },
+ { L("FAILURE-NOTIFICATION"), (void *)state_common_failure_notification },
+
+ { L("REAUTHENTICATION"), (void *)state_common_reauthentication },
+
+ { L("EAP-SUCCESS"), (void *)state_eap_success },
+ { L("EAP-FAILURE"), (void *)state_eap_failure }
};
+static size_t aka_sim_state_table_len = NUM_ELEMENTS(aka_sim_state_table);
/** Cancel a call to a submodule
*
* @param[in] rctx the eap_session_t
* @param[in] action to perform.
*/
-static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *rctx,
+static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, void *rctx,
fr_state_signal_t action)
{
- eap_session_t *eap_session = eap_session_get(request->parent);
-
if (action != FR_SIGNAL_CANCEL) return;
RDEBUG2("Request cancelled - Destroying session");
- TALLOC_FREE(eap_session->opaque);
+ /*
+ * Remove data from the request to
+ * avoid double free.
+ */
+ if (!fr_cond_assert(request_data_get(request, (void *)eap_aka_sim_state_machine_process, 0) == rctx)) return;
+
+ TALLOC_FREE(rctx);
}
/** Warn the user that the rcode they provided is being ignored in this section
*
*/
-static inline void section_rcode_ignored(request_t *request)
-{
- switch (request->rcode) {
- case RLM_MODULE_USER_SECTION_REJECT:
- RWDEBUG("Ignoring rcode (%s)",
- fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
- break;
-
- default:
- break;
- }
-}
+#define SECTION_RCODE_IGNORED \
+do { \
+ switch (request->rcode) { \
+ case RLM_MODULE_USER_SECTION_REJECT: \
+ RWDEBUG("Ignoring rcode (%s)", \
+ fr_table_str_by_value(rcode_table, request->rcode, "<invalid>")); \
+ break; \
+ default: \
+ break; \
+ } \
+} while(0)
/** Trigger a state transition to FAILURE-NOTIFICATION if the section returned a failure code
*
*/
-#define section_rcode_process(_p_result, _inst, _request, _eap_session, _eap_aka_sim_session) \
-{ \
- if (after_authentication(_eap_aka_sim_session)) { \
- switch ((_request)->rcode) { \
+#define SECTION_RCODE_PROCESS \
+do { \
+ if (after_authentication(eap_aka_sim_session)) { \
+ switch (unlang_interpret_stack_result(request)) { \
case RLM_MODULE_REJECT: \
case RLM_MODULE_DISALLOW: \
eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED; \
- return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+ return STATE_TRANSITION(common_failure_notification); \
case RLM_MODULE_NOTFOUND: \
eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED; \
- return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+ return STATE_TRANSITION(common_failure_notification); \
case RLM_MODULE_INVALID: \
case RLM_MODULE_FAIL: \
eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION;\
- return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+ return STATE_TRANSITION(common_failure_notification); \
default: \
break; \
} \
} else { \
- switch ((_request)->rcode) { \
+ switch (unlang_interpret_stack_result(request)) { \
case RLM_MODULE_USER_SECTION_REJECT: \
REDEBUG("Section rcode (%s) indicates we should reject the user", \
fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>")); \
- return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+ return STATE_TRANSITION(common_failure_notification); \
default: \
break; \
} \
} \
+} while(0)
+
+/** Print debugging information, and write new state to eap_aka_sim_session->state
+ *
+ */
+static inline CC_HINT(always_inline) void state_set(request_t *request,
+ eap_aka_sim_session_t *eap_aka_sim_session,
+ eap_aka_sim_state_t new_state)
+{
+ eap_aka_sim_state_t old_state = eap_aka_sim_session->state;
+
+ if (new_state != old_state) {
+ RDEBUG2("Changed state %s -> %s",
+ fr_table_str_by_value(aka_sim_state_table, (void *)old_state, "<unknown>"),
+ fr_table_str_by_value(aka_sim_state_table, (void *)new_state, "<unknown>"));
+ } else {
+ RDEBUG2("Reentering state %s",
+ fr_table_str_by_value(aka_sim_state_table, (void *)old_state, "<unknown>"));
+ }
+
+ eap_aka_sim_session->state = new_state;
+}
+#define STATE_SET(_new_state) state_set(request, eap_aka_sim_session, state_ ## _new_state)
+
+/** Determine if we're after authentication
+ *
+ */
+static inline CC_HINT(always_inline) bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session)
+{
+ return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success;
+}
+
+/** Print out the error the client returned
+ *
+ */
+static inline CC_HINT(always_inline) void client_error_debug(request_t *request)
+{
+ fr_pair_t *vp;
+
+ vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_client_error_code);
+ if (!vp) {
+ REDEBUG("Peer has not supplied a AT_ERROR_CODE");
+ } else {
+ REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data);
+ }
}
/** Sync up what identity we're requesting with attributes in the reply
if (method_p) *method_p = method;
}
-/** Print out the error the client returned
- *
- */
-static inline void client_error_debug(request_t *request, fr_pair_list_t *from_peer)
-{
- fr_pair_t *vp;
-
- vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_client_error_code);
- if (!vp) {
- REDEBUG("Peer has not supplied a AT_ERROR_CODE");
- } else {
- REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data);
- }
-}
-
/** Add an Identity Request attribute to the reply
*
* Verify the progression of identity requests is valid.
/** Set the crypto identity from a received identity
*
*/
-static void identity_to_crypto_identity(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
- uint8_t const *identity, size_t len)
+static void crypto_identity_set(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
+ uint8_t const *identity, size_t len)
{
RDEBUG3("Setting cryptographic identity to \"%pV\"", fr_box_strvalue_len((char const *)identity, len));
}
-/** Determine if we're after authentication
- *
- */
-static inline bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session)
-{
- return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success;
-}
-
/** Resume after 'store session { ... }'
*
*/
-static unlang_action_t session_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, void *rctx)
+RESUME(store_session)
{
- aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx;
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
switch (request->rcode) {
/*
pair_delete_request(attr_eap_aka_sim_next_reauth_id);
- return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+ return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
}
/** Resume after 'store pseudonym { ... }'
*
+ * Stores session data if required.
*/
-static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, void *rctx)
+RESUME(store_pseudonym)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
fr_pair_t *vp;
fr_pair_t *new;
pair_delete_request(attr_eap_aka_sim_next_pseudonym);
+ /*
+ * Generate fast-reauth data if we
+ * find a next_reauth_id pair in the
+ * reply list.
+ */
vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_next_reauth_id);
if (vp) {
/*
vp->vp_uint16 = 0;
}
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.store_session,
- RLM_MODULE_NOOP,
- session_store_resume,
- mod_signal,
- rctx);
+ return CALL_SECTION(store_session);
}
+ /*
+ * We didn't store any fast-reauth data
+ */
done:
- return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+ return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
}
/** Implements a set of states for storing pseudonym and fastauth identities
*
* Call the appropriate sections to persist those values.
*
- * @param[out] p_result Result of calling the module.
- * @param[in] mctx Module calling ctx.
- * @param[in] request the current request.
- * @param[in] eap_session the EAP session
- * @param[in] state_enter state entry function for the
- * state to transition to *after* the current
- * state.
+ * @param[out] p_result Result of calling the module.
+ * @param[in] mctx Module calling ctx.
+ * @param[in] request the current request.
+ * @param[in] eap_aka_sim_session the EAP session
+ * @param[in] next function to call after storing sessions and pseudonyms.
*/
static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session,
- aka_sim_state_enter_t state_enter)
+ request_t *request,
+ eap_aka_sim_session_t *eap_aka_sim_session,
+ eap_aka_sim_next_t next)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_pair_t *vp;
- fr_pair_t *new;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *vp;
+ fr_pair_t *new;
+
+ if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification);
+
+ eap_aka_sim_session->next = next;
request->rcode = RLM_MODULE_NOOP; /* Needed because we may call resume functions directly */
RWDEBUG("Found empty Pseudonym-Id, and told not to generate one. "
"Skipping store pseudonym { ... } section");
- return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter);
+ return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session);
}
MEM(identity = talloc_array(vp, char, inst->ephemeral_id_length + 2));
MEM(eap_aka_sim_session->pseudonym_sent = talloc_bstrndup(eap_aka_sim_session,
vp->vp_strvalue, vp->vp_length));
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.store_pseudonym,
- RLM_MODULE_NOOP,
- pseudonym_store_resume,
- mod_signal,
- (void *)state_enter);
+ return CALL_SECTION(store_pseudonym);
}
- return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter);
+ return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session);
}
/** Resume after 'clear session { ... }'
*
*/
-static unlang_action_t session_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, void *rctx)
+RESUME(clear_session)
{
- aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx;
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
pair_delete_request(attr_session_id);
- return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+ return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
}
/** Resume after 'clear pseudonym { ... }'
*
*/
-static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, void *rctx)
+RESUME(clear_pseudonym)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
pair_delete_request(attr_eap_aka_sim_next_pseudonym);
/*
- * Clear session
+ * Clear session (if one was stored)
*/
if (eap_aka_sim_session->fastauth_sent) {
fr_pair_t *vp;
talloc_array_length(eap_aka_sim_session->fastauth_sent) - 1, true);
TALLOC_FREE(eap_aka_sim_session->fastauth_sent);
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.clear_session,
- RLM_MODULE_NOOP,
- session_clear_resume,
- mod_signal,
- rctx);
+ return CALL_SECTION(clear_session);
}
- return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+ return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
}
/** Implements a set of states for clearing out pseudonym and fastauth identities
* any identities that were provided during those rounds, as the supplicant
* will have discarded them.
*
- * @param[out] p_result Result of calling the module.
- * @param[in] mctx module calling ctx.
- * @param[in] request the current request.
- * @param[in] eap_session the current EAP session
- * @param[in] state_enter state entry function for the
- * state to transition to *after* the current
- * state.
+ * @param[out] p_result Result of calling the module.
+ * @param[in] mctx module calling ctx.
+ * @param[in] request the current request.
+ * @param[in] eap_aka_sim_session the current EAP session
+ * @param[in] next function to call after clearing sessions and pseudonyms.
*/
static unlang_action_t session_and_pseudonym_clear(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session,
- aka_sim_state_enter_t state_enter)
+ request_t *request,
+ eap_aka_sim_session_t *eap_aka_sim_session,
+ eap_aka_sim_next_t next)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+ if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification);
+
+ eap_aka_sim_session->next = next;
+
+ request->rcode = RLM_MODULE_NOOP; /* Needed because we may call resume functions directly */
/*
- * Clear out pseudonyms
+ * Clear out pseudonyms (if we sent one)
*/
if (eap_aka_sim_session->pseudonym_sent) {
fr_pair_t *vp;
fr_value_box_bstrdup_buffer(vp, &vp->data, NULL, eap_aka_sim_session->pseudonym_sent, true);
TALLOC_FREE(eap_aka_sim_session->pseudonym_sent);
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.clear_pseudonym,
- RLM_MODULE_NOOP,
- session_clear_resume,
- mod_signal,
- (void *)state_enter);
+ return CALL_SECTION(clear_pseudonym);
}
- return pseudonym_clear_resume(p_result, mctx, request, (void *)state_enter);
+ return resume_clear_pseudonym(p_result, mctx, request, eap_aka_sim_session);
}
-/** Encode EAP-SIM/AKA['] attributes
+/** Export EAP-SIM/AKA['] attributes
*
+ * Makes any internal data available as attributes in the response.
+ * This allows test frameworks and the encoder to access any data they need without
+ * needing to look at the eap_aka_session_t.
*/
-static int common_encode(request_t *request, eap_session_t *eap_session, uint16_t subtype,
- uint8_t const *hmac_extra, size_t hmac_extra_len)
+static void common_reply(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, uint16_t subtype)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_dcursor_t cursor;
- fr_dcursor_t to_encode;
- fr_pair_list_t head;
- fr_pair_t *vp, *subtype_vp;
- ssize_t ret;
- fr_aka_sim_encode_ctx_t encode_ctx = {
- .root = fr_dict_root(dict_eap_aka_sim),
- .keys = &eap_aka_sim_session->keys,
-
- .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
- .iv_included = false,
-
- .hmac_md = eap_aka_sim_session->mac_md,
- .eap_packet = eap_session->this_round->request,
- .hmac_extra = hmac_extra,
- .hmac_extra_len = hmac_extra_len
- };
-
- fr_pair_list_init(&head);
+ fr_pair_t *vp = NULL, *subtype_vp;
+
/*
* Set the subtype to identity request
*/
MEM(pair_update_reply(&subtype_vp, attr_eap_aka_sim_subtype) >= 0);
subtype_vp->vp_uint16 = subtype;
- /*
- * State what kind of request we're sending
- */
- if (RDEBUG_ENABLED2) {
- switch (subtype) {
- case FR_SUBTYPE_VALUE_SIM_START:
- case FR_SUBTYPE_VALUE_AKA_IDENTITY:
- RDEBUG2("Sending EAP-Request/%pV (%s)", &subtype_vp->data,
- fr_table_str_by_value(fr_aka_sim_id_request_table,
- eap_aka_sim_session->id_req, "<INVALID>"));
- break;
-
- default:
- RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data);
- break;
+ if (!eap_aka_sim_session->allow_encrypted) {
+ while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
+ if (fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) {
+ RWDEBUG("Silently discarding &reply.%pP: Encrypted attributes not "
+ "allowed in this round", vp);
+ vp = fr_pair_list_free_item(&request->reply_pairs, vp);
+ continue;
+ }
}
}
+}
- fr_dcursor_init(&cursor, &request->reply_pairs);
- fr_dcursor_init(&to_encode, &head);
+static void CC_HINT(nonnull(1,2))
+common_crypto_export(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
+ uint8_t const *hmac_extra_request, size_t hmac_extra_request_len,
+ uint8_t const *hmac_extra_response, size_t hmac_extra_response_len)
+{
+ fr_pair_t *vp;
/*
- * We have to be *SUPER* careful here not to reorder
- * attributes, because for EAP-SIM the RAND values
- * must be inserted into the packet in exactly the
- * same order as they appear in fr_sim_keys_t
- * otherwise the KDF will produce a different
- * result.
+ * Export keying material necessary for
+ * the encoder to encrypt and sign
+ * packets.
*/
- while ((vp = fr_dcursor_current(&cursor))) {
- if (!fr_dict_attr_common_parent(encode_ctx.root, vp->da, true)) {
- fr_dcursor_next(&cursor);
- continue;
- }
- vp = fr_dcursor_remove(&cursor);
-
- /*
- * Silently discard encrypted attributes until
- * the peer should have k_encr. These can be
- * added by policy, and seem to cause
- * wpa_supplicant to fail if sent before the challenge.
- */
- if (!eap_aka_sim_session->allow_encrypted &&
- fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) {
- RWDEBUG("Silently discarding &reply.%s: Encrypted attributes not allowed in this round",
- vp->da->name);
- talloc_free(vp);
- continue;
- }
-
- fr_dcursor_prepend(&to_encode, vp);
+ if (hmac_extra_request && hmac_extra_request_len) {
+ MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_request) >= 0);
+ MEM(fr_pair_value_memdup(vp, hmac_extra_request, hmac_extra_request_len, true) == 0);
}
+ if (hmac_extra_response && hmac_extra_response_len) {
+ MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_response) >= 0);
+ MEM(fr_pair_value_memdup(vp, hmac_extra_response, hmac_extra_response_len, true) == 0);
+ }
- RDEBUG2("Encoding attributes");
- log_request_pair_list(L_DBG_LVL_2, request, NULL, &head, NULL);
-
- eap_session->this_round->request->type.num = eap_aka_sim_session->type;
- eap_session->this_round->request->id = eap_aka_sim_session->id++ & 0xff;
- eap_session->this_round->set_request_id = true;
-
- ret = fr_aka_sim_encode(request, &head, &encode_ctx);
- fr_dcursor_head(&to_encode);
- fr_dcursor_free_list(&to_encode);
+ MEM(pair_update_control(&vp, attr_eap_aka_sim_k_aut) >= 0);
+ MEM(fr_pair_value_memdup(vp,
+ eap_aka_sim_session->keys.k_aut,
+ eap_aka_sim_session->keys.k_aut_len,
+ true) == 0);
- if (ret < 0) {
- RPEDEBUG("Failed encoding attributes");
- return -1;
- }
- return 0;
+ MEM(pair_update_control(&vp, attr_eap_aka_sim_k_encr) >= 0);
+ MEM(fr_pair_value_memdup(vp,
+ eap_aka_sim_session->keys.k_encr,
+ sizeof(eap_aka_sim_session->keys.k_encr),
+ true) == 0);
}
-/** Send a EAP-Failure message to the supplicant
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
*
*/
-static unlang_action_t common_eap_failure_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session)
+static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+ request_t *request,
+ eap_aka_sim_session_t *eap_aka_sim_session)
{
- RDEBUG2("Sending EAP-Failure");
+ /*
+ * Return reply attributes - AT_IV is handled automatically by the encoder
+ */
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION);
- eap_session->this_round->request->code = FR_EAP_CODE_FAILURE;
- eap_session->finished = true;
+ /*
+ * 9.5. EAP-Request/SIM/Re-authentication
+ *
+ * AT_MAC MUST be included. No message-specific data is included in the
+ * MAC calculation. See Section 10.14.
+ *
+ * 9.6. EAP-Response/SIM/Re-authentication
+ *
+ * The AT_MAC attribute MUST be included. For
+ * EAP-Response/SIM/Re-authentication, the MAC code is calculated over
+ * the following data: EAP packet| NONCE_S
+ *
+ * 9.7. EAP-Request/AKA-Reauthentication
+ *
+ * The AT_MAC attribute MUST be included. No message-specific data is
+ * included in the MAC calculation, see Section 10.15.
+ *
+ * 9.8. EAP-Response/AKA-Reauthentication
+ *
+ * The AT_MAC attribute MUST be included. For
+ * EAP-Response/AKA-Reauthentication, the MAC code is calculated over
+ * the following data: EAP packet| NONCE_S.
+ */
+ common_crypto_export(request, eap_aka_sim_session,
+ NULL, 0,
+ eap_aka_sim_session->keys.reauth.nonce_s,
+ sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
- RETURN_MODULE_REJECT;
+ RETURN_MODULE_HANDLED;
}
-/** Send a EAP-Request/(AKA|SIM)-Notification (Failure) message to the supplicant
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
*
*/
-static unlang_action_t common_failure_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+ request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
{
- fr_pair_t *vp, *notification_vp;
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
/*
- * Allow the user to specify specific failure notification
- * types. We assume the user knows what they're doing and
- * only toggle success and phase bits.
- *
- * This allows custom notification schemes to be implemented.
- *
- * If this is prior to authentication, valid values are:
- * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE
- *
- * If this is after authentication, valid values are:
- * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION
- * - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been
- * temporarily denied access to the requested service.
- * - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED
- * User has not subscribed to the requested service.
+ * Encode the packet - AT_IV is handled automatically
+ * by the encoder.
*/
- notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification);
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE);
/*
- * Change the failure notification depending where
- * we are in the eap_aka_state machine.
+ * 9.3. EAP-Request/AKA-Challenge
+ *
+ * AT_MAC MUST be included. In EAP-Request/AKA-Challenge, there is no
+ * message-specific data covered by the MAC, see Section 10.15.
+ *
+ * 9.4. EAP-Response/AKA-Challenge
+ *
+ * The AT_MAC attribute MUST be included. In
+ * EAP-Response/AKA-Challenge, there is no message-specific data covered
+ * by the MAC, see Section 10.15.
*/
- if (after_authentication(eap_aka_sim_session)) {
- if (!notification_vp) {
- MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0);
- notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */
- }
+ common_crypto_export(request, eap_aka_sim_session, NULL, 0, NULL, 0);
- notification_vp->vp_uint16 &= ~0x4000; /* Unset phase bit */
+ RETURN_MODULE_HANDLED;
+}
- /*
- * Include the counter attribute if we're failing
- * after a reauthentication success.
- *
- * RFC 4187 Section #9.10
- *
- * If EAP-Request/AKA-Notification is used on
- * a fast re-authentication exchange, and if
- * the P bit in AT_NOTIFICATION is set to zero,
- * then AT_COUNTER is used for replay protection.
- * In this case, the AT_ENCR_DATA and AT_IV
- * attributes MUST be included, and the
- * encapsulated plaintext attributes MUST include
- * the AT_COUNTER attribute. The counter value
- * included in AT_COUNTER MUST be the same
- * as in the EAP-Request/AKA-Reauthentication
- * packet on the same fast re-authentication
- * exchange.
- *
- * If the counter is used it should never be zero,
- * as it's incremented on first reauthentication
- * request.
- */
- if (eap_aka_sim_session->reauthentication_success) {
- MEM(pair_update_reply(¬ification_vp, attr_eap_aka_sim_counter) >= 0);
- notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
- }
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+ *
+ */
+static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+ request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
+{
+ uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
+ uint8_t *p = sres_cat;
- /*
- * If we're after the challenge phase
- * then we need to include a MAC to
- * protect notifications.
- */
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
- fr_pair_value_memdup(vp, NULL, 0, false);
- } else {
- /*
- * Only valid code is general failure
- */
- if (!notification_vp) {
- MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0);
- notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE;
- /*
- * User supplied failure code
- */
- } else {
- notification_vp->vp_uint16 |= 0x4000; /* Set phase bit */
- }
- }
- notification_vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */
+ /*
+ * Encode the packet - AT_IV is handled automatically
+ * by the encoder.
+ */
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE);
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+ p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+ p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
/*
- * Encode the packet
+ * 9.3. EAP-Request/SIM/Challenge
+ *
+ * The AT_MAC attribute MUST be included. For
+ * EAP-Request/SIM/Challenge, the MAC code is calculated over the
+ * following data: EAP packet| NONCE_MT
+ *
+ * 9.4. EAP-Response/SIM/Challenge
+ *
+ * The AT_MAC attribute MUST be included. For EAP-
+ * Response/SIM/Challenge, the MAC code is calculated over the following
+ * data: EAP packet| n*SRES
*/
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) {
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+ common_crypto_export(request, eap_aka_sim_session,
+ eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt),
+ sres_cat, sizeof(sres_cat));
RETURN_MODULE_HANDLED;
}
-/** Add MPPE keys to the request being sent to the supplicant
+/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure
*
- * The only work to be done is the add the appropriate SEND/RECV
- * attributes derived from the MSK.
+ * Also checks the version matches one of the ones we advertised in our version list,
+ * which is a bit redundant seeing as there's only one version of EAP-SIM.
*/
-static unlang_action_t common_eap_success_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session)
+static int sim_start_selected_version_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
{
- uint8_t *p;
- eap_aka_sim_session_t *eap_aka_sim_session;
-
- RDEBUG2("Sending EAP-Success");
-
- eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS;
- eap_session->finished = true;
+ fr_pair_t *selected_version_vp;
- eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
+ /*
+ * Check that we got an AT_SELECTED_VERSION
+ */
+ selected_version_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version);
+ if (!selected_version_vp) {
+ REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION");
+ return -1;
+ }
- RDEBUG2("Adding attributes for MSK");
- p = eap_aka_sim_session->keys.msk;
- eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN);
- p += EAP_TLS_MPPE_KEY_LEN;
- eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN);
+ /*
+ * See if the selected version was in our list
+ */
+ {
+ uint8_t selected_version[2];
+ uint8_t *p, *end;
+ bool found = false;
- RETURN_MODULE_OK;
+ p = eap_aka_sim_session->keys.gsm.version_list;
+ end = p + eap_aka_sim_session->keys.gsm.version_list_len;
+
+ selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8;
+ selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff);
+
+ while (p < end) {
+ if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) {
+ found = true;
+ /*
+ * Update our keying material
+ */
+ eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0];
+ eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1];
+ break;
+ }
+ }
+
+ if (!found) {
+ REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list",
+ selected_version_vp->vp_uint16);
+ return -1;
+ }
+ }
+
+ return 0;
}
-/** Send a EAP-Request/(AKA|SIM)-Notification (Success) message to the supplicant
+/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure
*
+ * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later.
*/
-static unlang_action_t common_success_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+static int sim_start_nonce_mt_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
- fr_pair_t *vp;
-
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
-
- if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) {
- RETURN_MODULE_FAIL;
- }
+ fr_pair_t *nonce_mt_vp;
/*
- * If we're in this state success bit is
- * high phase bit is low.
+ * Copy nonce_mt to the keying material
*/
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0);
- vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS;
+ nonce_mt_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt);
+ if (!nonce_mt_vp) {
+ REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT");
+ return -1;
+ }
- /*
- * RFC 4187 section #9.10
- *
- * If EAP-Request/AKA-Notification is used on
- * a fast re-authentication exchange, and if
- * the P bit in AT_NOTIFICATION is set to zero,
- * then AT_COUNTER is used for replay protection.
- * In this case, the AT_ENCR_DATA and AT_IV
- * attributes MUST be included, and the
- * encapsulated plaintext attributes MUST include
- * the AT_COUNTER attribute. The counter value
- * included in AT_COUNTER MUST be the same
- * as in the EAP-Request/AKA-Reauthentication
- * packet on the same fast re-authentication
- * exchange.
- *
- * If the counter is used it should never be zero,
- * as it's incremented on first reauthentication
- * request.
- */
- if (eap_aka_sim_session->keys.reauth.counter > 0) {
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
- vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+ if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) {
+ REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes",
+ sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length);
+ return -1;
}
+ memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets,
+ sizeof(eap_aka_sim_session->keys.gsm.nonce_mt));
- /*
- * Need to include an AT_MAC attribute so that
- * it will get calculated.
- */
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
- fr_pair_value_memdup(vp, NULL, 0, false);
+ return 0;
+}
- /*
- * Encode the packet
- */
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) {
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+/** FAILURE state - State machine exit point after sending EAP-Failure
+ *
+ * Should never actually be called. Is just a placeholder function to represent the FAILURE
+ * termination state. Could equally be a NULL pointer, but then on a logic error
+ * we'd get a SEGV instead of a more friendly assert/failure rcode.
+ */
+STATE(eap_failure)
+{
+ if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL; /* unused args */
- RETURN_MODULE_HANDLED;
+ fr_assert(0); /* Should never actually be called */
+
+ RETURN_MODULE_FAIL;
}
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** Resume after 'send EAP-Failure { ... }'
*
*/
-static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+RESUME(send_eap_failure)
{
- /*
- * Encode the packet - AT_IV is handled automatically
- * by the encoder.
- */
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION, NULL, 0) < 0) {
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+ if (!fr_cond_assert(mctx && rctx)) RETURN_MODULE_FAIL; /* unused args */
- RETURN_MODULE_HANDLED;
+ SECTION_RCODE_IGNORED;
+
+ RDEBUG2("Sending EAP-Failure");
+
+ RETURN_MODULE_REJECT;
}
-/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant
+/** Enter EAP-FAILURE state
*
*/
-static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result,
- module_ctx_t const *mctx,
- request_t *request,
- eap_session_t *eap_session)
+STATE_GUARD(eap_failure)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_pair_list_t *to_peer = &request->reply_pairs;
- fr_pair_t *vp;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
- fr_pair_t *kdf_id;
+ /*
+ * Free anything we were going to send out...
+ */
+ fr_pair_list_free(&request->reply_pairs);
/*
- * Allow override of KDF Identity
- *
- * Because certain handset manufacturers don't
- * implement RFC 4187 correctly and use the
- * wrong identity as input the the PRF/KDF.
- *
- * Not seen any doing this for re-authentication
- * but you never know...
+ * If we're failing, then any identities
+ * we sent are now invalid.
*/
- kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
- if (kdf_id) {
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
- fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+ if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+ return session_and_pseudonym_clear(p_result, mctx,
+ request, eap_aka_sim_session, guard_eap_failure);
+ /* come back when we're done */
}
- RDEBUG2("Generating new session keys");
+ STATE_SET(eap_failure);
+
+ return CALL_SECTION(send_eap_failure);
+}
+
+/** Resume after 'recv Failure-Notification-Ack { ... }'
+ *
+ * - Enter the EAP-FAILURE state.
+ */
+RESUME(recv_common_failure_notification_ack)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+ SECTION_RCODE_IGNORED;
- switch (eap_aka_sim_session->type) {
/*
- * The GSM and UMTS KDF_0 mutate their keys using
- * and identical algorithm.
+ * Case 2 where we're allowed to send an EAP-Failure
*/
- case FR_EAP_METHOD_SIM:
- case FR_EAP_METHOD_AKA:
- if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs,
- &eap_aka_sim_session->keys) != 0) {
- request_new_id:
- switch (eap_aka_sim_session->last_id_req) {
- /*
- * Got here processing EAP-Identity-Response
- * If this is the *true* reauth ID, then
- * there's no point in setting AKA_SIM_ANY_ID_REQ.
- */
- case AKA_SIM_NO_ID_REQ:
- case AKA_SIM_ANY_ID_REQ:
- RDEBUG2("Composing EAP-Request/Reauthentication failed. Clearing reply attributes and "
- "requesting additional Identity");
- fr_pair_list_free(&request->reply_pairs);
- eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- return common_identity_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(eap_failure);
+}
- case AKA_SIM_FULLAUTH_ID_REQ:
- case AKA_SIM_PERMANENT_ID_REQ:
- REDEBUG("Last requested fullauth or permanent ID, "
- "but received, or were told we received (by policy), "
- "a fastauth ID. Cannot continue");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- }
- if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
- break;
+/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ *
+ * - Continue based on received AT_SUBTYPE value:
+ * - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }'
+ * - Anything else, enter the FAILURE-NOTIFICATION state.
+ */
+STATE(common_failure_notification)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *subtype_vp = NULL;
- case FR_EAP_METHOD_AKA_PRIME:
- switch (eap_aka_sim_session->kdf) {
- case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
- if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs,
- &eap_aka_sim_session->keys) != 0) {
- goto request_new_id;
- }
- if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
- break;
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+ if (!subtype_vp) goto fail;
- default:
- fr_assert(0);
- break;
- }
- break;
+ switch (subtype_vp->vp_uint16) {
+ case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION:
+ RDEBUG2("Failure-Notification ACKed, sending EAP-Failure");
+ return CALL_SECTION(recv_common_failure_notification_ack);
default:
- fr_assert(0);
- break;
+ fail:
+ RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway");
+ return STATE_TRANSITION(eap_failure)
}
+}
- if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+/** Resume after 'send Failure-Notification { ... }'
+ *
+ * Ignores return code from send Failure-Notification { ... } section.
+ */
+RESUME(send_common_failure_notification)
+{
+ fr_pair_t *vp, *notification_vp;
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx,
+ eap_aka_sim_session_t);
- /*
- * Add AT_IV, AT_COUNTER, AT_NONCE_S, and AT_MAC to to reply
- * The user may have added AT_NEXT_REAUTH_ID, in which case
- * we'll have sent that too.
- */
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+ if (!fr_cond_assert(mctx)) RETURN_MODULE_FAIL; /* quiet unused warning */
- /*
- * Indicate we'd like to use protected success messages
- * with AT_RESULT_IND
- *
- * Use our default, but allow user override too.
- */
- vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
- if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
+ SECTION_RCODE_IGNORED;
/*
- * RFC 5448 says AT_BIDDING is only sent in the challenge
- * not in reauthentication, so don't add that here.
+ * Free anything we were going to send out...
*/
-
- /*
- * Add AT_NONCE_S
- */
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s,
- sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false);
+ fr_pair_list_free(&request->reply_pairs);
/*
- * Add AT_COUNTER
- */
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
- vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
-
- /*
- * need to include an empty AT_MAC attribute so that
- * the mac will get calculated.
+ * Allow the user to specify specific failure notification
+ * types. We assume the user knows what they're doing and
+ * only toggle success and phase bits.
+ *
+ * This allows custom notification schemes to be implemented.
+ *
+ * If this is prior to authentication, valid values are:
+ * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE
+ *
+ * If this is after authentication, valid values are:
+ * - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION
+ * - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been
+ * temporarily denied access to the requested service.
+ * - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED
+ * User has not subscribed to the requested service.
*/
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
- fr_pair_value_memdup(vp, NULL, 0, false);
+ notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification);
/*
- * If there's no checkcode_md we're not doing
- * checkcodes.
+ * Change the failure notification depending where
+ * we are in the eap_aka_state machine.
*/
- if (eap_aka_sim_session->checkcode_md) {
+ if (after_authentication(eap_aka_sim_session)) {
+ if (!notification_vp) {
+ MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0);
+ notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */
+ }
+
+ notification_vp->vp_uint16 &= ~0x4000; /* Unset phase bit */
+
/*
- * If we have checkcode data, send that to the peer
- * in AT_CHECKCODE for validation.
+ * Include the counter attribute if we're failing
+ * after a reauthentication success.
+ *
+ * RFC 4187 Section #9.10
+ *
+ * If EAP-Request/AKA-Notification is used on
+ * a fast re-authentication exchange, and if
+ * the P bit in AT_NOTIFICATION is set to zero,
+ * then AT_COUNTER is used for replay protection.
+ * In this case, the AT_ENCR_DATA and AT_IV
+ * attributes MUST be included, and the
+ * encapsulated plaintext attributes MUST include
+ * the AT_COUNTER attribute. The counter value
+ * included in AT_COUNTER MUST be the same
+ * as in the EAP-Request/AKA-Reauthentication
+ * packet on the same fast re-authentication
+ * exchange.
+ *
+ * If the counter is used it should never be zero,
+ * as it's incremented on first reauthentication
+ * request.
*/
- if (eap_aka_sim_session->checkcode_state) {
- ssize_t slen;
-
- slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
- &eap_aka_sim_session->checkcode_state);
- if (slen < 0) {
- RPEDEBUG("Failed calculating checkcode");
- goto failure;
- }
- eap_aka_sim_session->checkcode_len = slen;
+ if (eap_aka_sim_session->reauthentication_success) {
+ MEM(pair_update_reply(¬ification_vp, attr_eap_aka_sim_counter) >= 0);
+ notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+ }
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
/*
- * If we don't have checkcode data, then we exchanged
- * no identity packets, so checkcode is zero.
+ * If we're after the challenge phase
+ * then we need to include a MAC to
+ * protect notifications.
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+ fr_pair_value_memdup(vp, NULL, 0, false);
+ } else {
+ /*
+ * Only valid code is general failure
+ */
+ if (!notification_vp) {
+ MEM(pair_append_reply(¬ification_vp, attr_eap_aka_sim_notification) >= 0);
+ notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE;
+ /*
+ * User supplied failure code
*/
} else {
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
- fr_pair_value_memdup(vp, NULL, 0, false);
- eap_aka_sim_session->checkcode_len = 0;
+ notification_vp->vp_uint16 |= 0x4000; /* Set phase bit */
}
}
+ notification_vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */
/*
- * We've sent the challenge so the peer should now be able
- * to accept encrypted attributes.
+ * Send a response
*/
- eap_aka_sim_session->allow_encrypted = true;
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION);
- return session_and_pseudonym_store(p_result, mctx,request, eap_session, common_reauthentication_request_send);
+ RETURN_MODULE_HANDLED;
}
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** Enter the FAILURE-NOTIFICATION state
*
*/
-static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+STATE_GUARD(common_failure_notification)
{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
/*
- * Encode the packet - AT_IV is handled automatically
- * by the encoder.
+ * If we're failing, then any identities
+ * we sent are now invalid.
*/
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE, NULL, 0) < 0) {
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+ return session_and_pseudonym_clear(p_result, mctx, request, eap_aka_sim_session,
+ guard_common_failure_notification); /* come back when we're done */
}
- RETURN_MODULE_HANDLED;
+ /*
+ * We've already sent a failure notification
+ * Now we just fail as it means something
+ * went wrong processing the ACK or we got
+ * garbage from the supplicant.
+ */
+ if (eap_aka_sim_session->state == state_common_failure_notification) {
+ return STATE_TRANSITION(eap_failure);
+ }
+
+ /*
+ * Otherwise just transition as normal...
+ */
+ STATE_SET(common_failure_notification);
+
+ return CALL_SECTION(send_common_failure_notification);
}
-/** Send a EAP-Request/AKA-Challenge message to the supplicant
+/** SUCCESS state - State machine exit point after sending EAP-Success
*
+ * Should never actually be called. Is just a placeholder function to represent the SUCCESS
+ * termination state. Could equally be a NULL pointer, but then on a logic error
+ * we'd get a SEGV instead of a more friendly assert/failure rcode.
*/
-static unlang_action_t aka_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+STATE(eap_success)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
- fr_pair_list_t *to_peer = &request->reply_pairs;
- fr_pair_t *vp;
- fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
+ if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL; /* unused args */
- fr_pair_t *kdf_id;
+ fr_assert(0); /* Should never actually be called */
+
+ RETURN_MODULE_FAIL;
+}
+
+/** Resume after 'send EAP-Success { ... }'
+ *
+ * Add MPPE keys to the request being sent to the supplicant
+ *
+ * The only work to be done is the add the appropriate SEND/RECV
+ * attributes derived from the MSK.
+ */
+RESUME(send_eap_success)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ uint8_t *p;
+
+ RDEBUG2("Sending EAP-Success");
/*
- * Allow override of KDF Identity
+ * If this is true we're entering this state
+ * after sending a AKA-Success-Notification
*
- * Because certain handset manufacturers don't
- * implement RFC 4187 correctly and use the
- * wrong identity as input the the PRF/KDF.
+ * Is seems like a really bad idea to allow the
+ * user to send a protected success to the
+ * supplicant and then force a failure using
+ * the send EAP-Success { ... } section.
*/
- kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
- if (kdf_id) {
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
- fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
- }
-
- RDEBUG2("Acquiring UMTS vector(s)");
+ if (eap_aka_sim_session->send_result_ind) {
+ switch (request->rcode) {
+ case RLM_MODULE_USER_SECTION_REJECT:
+ RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } "
+ "as we already sent a Success-Notification",
+ fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
+ RWDEBUG("If you need to force a failure, return an error code from "
+ "send Success-Notification { ... }");
+ break;
- if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) {
- /*
- * Copy the network name the user specified for
- * key derivation purposes.
- */
- vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_kdf_input);
- if (vp) {
- talloc_free(eap_aka_sim_session->keys.network);
- eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session,
- (uint8_t const *)vp->vp_strvalue,
- vp->vp_length);
- eap_aka_sim_session->keys.network_len = vp->vp_length;
- } else {
- REDEBUG("No network name available, can't set AT_KDF_INPUT");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ default:
+ break;
}
- /*
- * We don't allow the user to specify
- * the KDF currently.
- */
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0);
- vp->vp_uint16 = eap_aka_sim_session->kdf;
- }
-
/*
- * Get vectors from attribute or generate
- * them using COMP128-* or Milenage.
+ * But... if we're not working with protected
+ * success indication, this is the only
+ * opportunity the user has to force a failure at
+ * the end of authentication.
*/
- if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs, &eap_aka_sim_session->keys, &src) != 0) {
- REDEBUG("Failed retrieving UMTS vectors");
- goto failure;
+ } else {
+ SECTION_RCODE_PROCESS;
}
- /*
- * Don't leave the AMF hanging around
- */
- if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf);
+ RDEBUG2("Adding attributes for MSK");
+ p = eap_aka_sim_session->keys.msk;
+ eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN);
+ p += EAP_TLS_MPPE_KEY_LEN;
+ eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN);
- /*
- * All set, calculate keys!
- */
- switch (eap_aka_sim_session->type) {
- default:
- case FR_EAP_METHOD_SIM:
- fr_assert(0); /* EAP-SIM has its own Challenge state */
- break;
+ RETURN_MODULE_OK;
+}
- case FR_EAP_METHOD_AKA:
- fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys);
- break;
+/** Enter EAP-SUCCESS state
+ *
+ */
+STATE_GUARD(eap_success)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
- case FR_EAP_METHOD_AKA_PRIME:
- switch (eap_aka_sim_session->kdf) {
- case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
- fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys);
- break;
+ STATE_SET(eap_success);
- default:
- fr_assert(0);
- break;
- }
- }
- if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+ return CALL_SECTION(send_eap_success);
+}
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+/** Resume after 'recv Success-Notification-Ack { ... }'
+ *
+ * - Enter the EAP-SUCCESS state.
+ */
+RESUME(recv_common_success_notification_ack)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
- /*
- * Indicate we'd like to use protected success messages
- * with AT_RESULT_IND
- *
- * Use our default, but allow user override too.
- */
- vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
- if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
+ SECTION_RCODE_IGNORED;
/*
- * See if we're indicating we want EAP-AKA'
- * If so include AT_BIDDING with the correct
- * value.
+ * RFC 4187 says we ignore the contents of the
+ * next packet after we send our success notification
+ * and always send a success.
*/
- vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_bidding);
- if (vp) {
- eap_aka_sim_session->send_at_bidding_prefer_prime =
- (vp->vp_uint16 == FR_BIDDING_VALUE_PREFER_AKA_PRIME);
- }
+ return STATE_TRANSITION(eap_success);
+}
+
+/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ *
+ * - Call 'recv Success-Notification-Ack { ... }'
+ */
+STATE(common_success_notification)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
/*
- * These attributes are only allowed with
- * EAP-AKA', protect users from themselves.
- */
- if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) {
- pair_delete_reply(attr_eap_aka_sim_kdf_input);
- pair_delete_reply(attr_eap_aka_sim_kdf);
- }
+ * Because the server uses the AT_NOTIFICATION code "Success" (32768) to
+ * indicate that the EAP exchange has completed successfully, the EAP
+ * exchange cannot fail when the server processes the EAP-AKA response
+ * to this notification. Hence, the server MUST ignore the contents of
+ * the EAP-AKA response it receives to the EAP-Request/AKA-Notification
+ * with this code. Regardless of the contents of the EAP-AKA response,
+ * the server MUST send EAP-Success as the next packet.
+ */
+ return CALL_SECTION(recv_common_success_notification_ack);
+}
+
+/** Resume after 'send Success-Notification { ... }'
+ *
+ */
+RESUME(send_common_success_notification)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ fr_pair_t *vp;
+
+ SECTION_RCODE_PROCESS;
+
+ if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) RETURN_MODULE_FAIL;
/*
- * Okay, we got the challenge! Put it into an attribute.
+ * If we're in this state success bit is
+ * high phase bit is low.
*/
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false);
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0);
+ vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS;
/*
- * Send the AUTN value to the client, so it can authenticate
- * whoever has knowledge of the Ki.
+ * RFC 4187 section #9.10
+ *
+ * If EAP-Request/AKA-Notification is used on
+ * a fast re-authentication exchange, and if
+ * the P bit in AT_NOTIFICATION is set to zero,
+ * then AT_COUNTER is used for replay protection.
+ * In this case, the AT_ENCR_DATA and AT_IV
+ * attributes MUST be included, and the
+ * encapsulated plaintext attributes MUST include
+ * the AT_COUNTER attribute. The counter value
+ * included in AT_COUNTER MUST be the same
+ * as in the EAP-Request/AKA-Reauthentication
+ * packet on the same fast re-authentication
+ * exchange.
+ *
+ * If the counter is used it should never be zero,
+ * as it's incremented on first reauthentication
+ * request.
*/
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false);
+ if (eap_aka_sim_session->keys.reauth.counter > 0) {
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
+ vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+ }
/*
- * need to include an AT_MAC attribute so that it will get
- * calculated.
+ * Need to include an AT_MAC attribute so that
+ * it will get calculated.
*/
MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
fr_pair_value_memdup(vp, NULL, 0, false);
/*
- * If we have checkcode data, send that to the peer
- * in AT_CHECKCODE for validation.
+ * Return reply attributes
*/
- if (eap_aka_sim_session->checkcode_state) {
- ssize_t slen;
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION);
- slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
- &eap_aka_sim_session->checkcode_state);
- if (slen < 0) {
- RPEDEBUG("Failed calculating checkcode");
+ RETURN_MODULE_HANDLED;
+}
+
+/** Enter the SUCCESS-NOTIFICATION state
+ *
+ */
+STATE_GUARD(common_success_notification)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+ STATE_SET(common_success_notification);
+
+ return CALL_SECTION(send_common_success_notification);
+}
+
+/** Resume after 'recv Client-Error { ... }'
+ *
+ * - Enter the EAP-FAILURE state.
+ */
+RESUME(recv_common_client_error)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+ SECTION_RCODE_IGNORED;
+
+ return STATE_TRANSITION(eap_failure);
+}
+
+/** Resume after 'recv Reauthentication-Response { ... }'
+ *
+ * - If 'recv Reauthentication-Response { ... }' returned a failure
+ * rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call the EAP-Request/Reauthentication-Response function to act on the
+ * contents of the response.
+ */
+RESUME(recv_common_reauthentication_response)
+{
+ eap_session_t *eap_session = eap_session_get(request->parent);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+ ssize_t slen;
+ fr_pair_t *mac;
+
+#if 0
+ *checkcode;
+#endif
+
+ SECTION_RCODE_PROCESS;
+
+ mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+ if (!mac) {
+ REDEBUG("Missing AT_MAC attribute");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+ REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+ AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+ goto failure;
+ }
+
+ // TODO: MAC validation should happen in the decoder not the module
+ slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+ eap_aka_sim_session->mac_md,
+ eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+ eap_aka_sim_session->keys.reauth.nonce_s,
+ sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
+ if (slen < 0) {
+ RPEDEBUG("Failed calculating MAC");
+ goto failure;
+ } else if (slen == 0) {
+ REDEBUG("Zero length AT_MAC attribute");
+ goto failure;
+ }
+
+ if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+ RDEBUG2("Received MAC matches calculated MAC");
+ } else {
+ REDEBUG("Received MAC does not match calculated MAC");
+ RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+ RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+ goto failure;
+ }
+
+#if 0
+ /*
+ * If the peer doesn't include a checkcode then that
+ * means they don't support it, and we can't validate
+ * their view of the identity packets.
+ */
+ checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode);
+ if (checkcode) {
+ if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
+ REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)",
+ checkcode->vp_length, eap_aka_sim_session->checkcode_len);
goto failure;
}
- eap_aka_sim_session->checkcode_len = slen;
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+ if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode,
+ eap_aka_sim_session->checkcode_len) == 0) {
+ RDEBUG2("Received checkcode matches calculated checkcode");
+ } else {
+ REDEBUG("Received checkcode does not match calculated checkcode");
+ RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
+ RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
+ eap_aka_sim_session->checkcode_len, "Expected");
+ goto failure;
+ }
/*
- * If we don't have checkcode data, then we exchanged
- * no identity packets, so AT_CHECKCODE is zero.
+ * Only print something if we calculated a checkcode
*/
- } else {
- MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
- fr_pair_value_memdup(vp, NULL, 0, false);
- eap_aka_sim_session->checkcode_len = 0;
+ } else if (eap_aka_sim_session->checkcode_len > 0){
+ RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
}
+#endif
/*
- * We've sent the challenge so the peer should now be able
- * to accept encrypted attributes.
+ * Check to see if the supplicant sent
+ * AT_COUNTER_TOO_SMALL, if they did then we
+ * clear out reauth information and enter the
+ * challenge state.
*/
- eap_aka_sim_session->allow_encrypted = true;
+ if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_counter_too_small)) {
+ RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)",
+ eap_aka_sim_session->keys.reauth.counter);
+
+ fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys);
+ eap_aka_sim_session->allow_encrypted = false;
+
+ return STATE_TRANSITION(aka_challenge);
+ }
+
+ /*
+ * If the peer wants a Success notification, and
+ * we included AT_RESULT_IND then send a success
+ * notification, otherwise send a normal EAP-Success.
+ *
+ * RFC 4187 Section #6.2. Result Indications
+ */
+ if (eap_aka_sim_session->send_result_ind) {
+ if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("We wanted to use protected result indications, but peer does not");
+ eap_aka_sim_session->send_result_ind = false;
+ } else {
+ return STATE_TRANSITION(common_success_notification);
+ }
+ } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("Peer wanted to use protected result indications, but we do not");
+ }
+
+ eap_aka_sim_session->reauthentication_success = true;
- return session_and_pseudonym_store(p_result, mctx,request, eap_session, aka_challenge_request_send);
+ return STATE_TRANSITION(eap_success);
}
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
*
+ * - Continue based on received AT_SUBTYPE value:
+ * - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }'
+ * - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that
+ * send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure.
+ * - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+STATE(common_reauthentication)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *subtype_vp = NULL;
+
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+ if (!subtype_vp) {
+ REDEBUG("Missing AT_SUBTYPE");
+ goto fail;
+ }
/*
- * Encode the packet - AT_IV is handled automatically
- * by the encoder.
+ * These aren't allowed in Reauthentication responses as they don't apply:
+ *
+ * EAP_AKA_AUTHENTICATION_REJECT - We didn't provide an AUTN value
+ * EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors.
*/
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE,
- eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) < 0) {
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+ switch (subtype_vp->vp_uint16) {
+ case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION:
+ /*
+ * AT_COUNTER_TOO_SMALL is handled
+ * in common_reauthentication_response_process.
+ */
+ return CALL_SECTION(recv_common_reauthentication_response);
- RETURN_MODULE_HANDLED;
+ /*
+ * Case 1 where we're allowed to send an EAP-Failure
+ */
+ case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+ client_error_debug(request);
+
+ eap_aka_sim_session->allow_encrypted = false;
+
+ return CALL_SECTION(recv_common_client_error);
+ /*
+ * RFC 4187 says we *MUST* notify, not just
+ * send an EAP-Failure in this case.
+ */
+ default:
+ REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+ fail:
+ eap_aka_sim_session->allow_encrypted = false;
+
+ return STATE_TRANSITION(common_failure_notification);
+ }
}
-/** Send a EAP-Request/SIM-Challenge message to the supplicant
+
+/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant
*
*/
-static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result,
+ module_ctx_t const *mctx,
+ request_t *request,
+ eap_aka_sim_session_t *eap_aka_sim_session)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_pair_list_t *to_peer = &request->reply_pairs;
fr_pair_t *vp;
- fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
-
fr_pair_t *kdf_id;
/*
* Because certain handset manufacturers don't
* implement RFC 4187 correctly and use the
* wrong identity as input the the PRF/KDF.
+ *
+ * Not seen any doing this for re-authentication
+ * but you never know...
*/
kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
if (kdf_id) {
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
}
- RDEBUG2("Acquiring GSM vector(s)");
- if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0,
- &eap_aka_sim_session->keys, &src) != 0) ||
- (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1,
- &eap_aka_sim_session->keys, &src) != 0) ||
- (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2,
- &eap_aka_sim_session->keys, &src) != 0)) {
- REDEBUG("Failed retrieving SIM vectors");
- RETURN_MODULE_FAIL;
- }
+ RDEBUG2("Generating new session keys");
- fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys);
+ switch (eap_aka_sim_session->type) {
+ /*
+ * The GSM and UMTS KDF_0 mutate their keys using
+ * and identical algorithm.
+ */
+ case FR_EAP_METHOD_SIM:
+ case FR_EAP_METHOD_AKA:
+ if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs,
+ &eap_aka_sim_session->keys) != 0) {
+ request_new_id:
+ switch (eap_aka_sim_session->last_id_req) {
+ /*
+ * Got here processing EAP-Identity-Response
+ * If this is the *true* reauth ID, then
+ * there's no point in setting AKA_SIM_ANY_ID_REQ.
+ */
+ case AKA_SIM_NO_ID_REQ:
+ case AKA_SIM_ANY_ID_REQ:
+ RDEBUG2("Composing EAP-Request/Reauthentication failed. Clearing reply attributes and "
+ "requesting additional Identity");
+ fr_pair_list_free(&request->reply_pairs);
+ eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+ return STATE_TRANSITION(common_identity);
- if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+ case AKA_SIM_FULLAUTH_ID_REQ:
+ case AKA_SIM_PERMANENT_ID_REQ:
+ REDEBUG("Last requested fullauth or permanent ID, "
+ "but received, or were told we received (by policy), "
+ "a fastauth ID. Cannot continue");
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ }
+ if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
+ break;
+
+ case FR_EAP_METHOD_AKA_PRIME:
+ switch (eap_aka_sim_session->kdf) {
+ case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
+ if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs,
+ &eap_aka_sim_session->keys) != 0) {
+ goto request_new_id;
+ }
+ if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
+ break;
+
+ default:
+ fr_assert(0);
+ break;
+ }
+ break;
+
+ default:
+ fr_assert(0);
+ break;
+ }
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+ if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
/*
* Indicate we'd like to use protected success messages
*
* Use our default, but allow user override too.
*/
- vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
+ vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
/*
- * Okay, we got the challenges! Put them into attributes.
+ * RFC 5448 says AT_BIDDING is only sent in the challenge
+ * not in reauthentication, so don't add that here.
*/
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
+ /*
+ * Add AT_NONCE_S
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s,
+ sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false);
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
+ /*
+ * Add AT_COUNTER
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
+ vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
/*
- * need to include an AT_MAC attribute so that it will get
- * calculated.
+ * need to include an empty AT_MAC attribute so that
+ * the mac will get calculated.
*/
MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
fr_pair_value_memdup(vp, NULL, 0, false);
+#if 0
+ /*
+ * If there's no checkcode_md we're not doing
+ * checkcodes.
+ */
+ if (eap_aka_sim_session->checkcode_md) {
+ /*
+ * If we have checkcode data, send that to the peer
+ * in AT_CHECKCODE for validation.
+ */
+ if (eap_aka_sim_session->checkcode_state) {
+ ssize_t slen;
+
+ slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
+ &eap_aka_sim_session->checkcode_state);
+ if (slen < 0) {
+ RPEDEBUG("Failed calculating checkcode");
+ goto failure;
+ }
+ eap_aka_sim_session->checkcode_len = slen;
+
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+ /*
+ * If we don't have checkcode data, then we exchanged
+ * no identity packets, so checkcode is zero.
+ */
+ } else {
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+ fr_pair_value_memdup(vp, NULL, 0, false);
+ eap_aka_sim_session->checkcode_len = 0;
+ }
+ }
+#endif
+
/*
* We've sent the challenge so the peer should now be able
* to accept encrypted attributes.
*/
eap_aka_sim_session->allow_encrypted = true;
- return session_and_pseudonym_store(p_result, mctx,request, eap_session, sim_challenge_request_send);
+ return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session,
+ common_reauthentication_request_send);
}
-/** Send a EAP-Request/AKA-Identity message to the supplicant
- *
- * There are three types of user identities that can be implemented
- * - Permanent identities such as 0123456789098765@myoperator.com
- * Permanent identities can be identified by the leading zero followed by
- * by 15 digits (the IMSI number).
- * - Ephemeral identities (pseudonyms). These are identities assigned for
- * identity privacy so the user can't be tracked. These can identities
- * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
- * document section 14, where a set of up to 16 encryption keys are used
- * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
- * randomised and stored in a datastore.
- * - A fast resumption ID which resolves to data used for fast resumption.
- *
- * In order to perform full authentication the original IMSI is required for
- * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
- * or can't perform fast resumption, we need to request the full identity from
- * the supplicant.
+/** Resume after 'send Reauthentication-Request { ... }'
*
- * @param[out] p_result Result of calling the module, one of:
- * - RLM_MODULE_HANDLED on success.
- * - anything else on failure.
- * @param[in] mctx module calling ctx.
- * @param[in] request The current subrequest.
- * @param[in] eap_session to continue.
*/
-static unlang_action_t aka_identity_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+RESUME(send_common_reauthentication_request)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ switch (request->rcode) {
/*
- * Update eap_aka_sim_session->id_req in case the the
- * user set attributes in `send Identity-Request { ... }`
- * Also removes all existing id_req attributes
- * from the reply.
+ * Failed getting the values we need for resumption
+ * Request a different identity.
*/
- identity_req_set_by_user(request, eap_aka_sim_session);
+ default:
+ switch (eap_aka_sim_session->last_id_req) {
+ /*
+ * Got here processing EAP-Identity-Response
+ * If this is the *true* reauth ID, then
+ * there's no point in setting AKA_SIM_ANY_ID_REQ.
+ */
+ case AKA_SIM_NO_ID_REQ:
+ case AKA_SIM_ANY_ID_REQ:
+ RDEBUG2("Previous section returned (%s), clearing reply attributes and "
+ "requesting additional identity",
+ fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
+ fr_pair_list_free(&request->reply_pairs);
+ eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- /*
- * Select the right type of identity request attribute
- *
- * Implement checks on identity request order described
- * by RFC4187 section #4.1.5.
- *
- * The internal state machine should always handle this
- * correctly, but the user may have other ideas...
- */
- if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+ return STATE_TRANSITION(common_identity);
+
+ case AKA_SIM_FULLAUTH_ID_REQ:
+ case AKA_SIM_PERMANENT_ID_REQ:
+ default:
+ break;
+ }
+ REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
+ "but received a Fast-Auth-Id. Cannot continue");
failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+ return STATE_TRANSITION(common_failure_notification);
/*
- * Encode the packet
+ * Policy rejected the user
*/
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_IDENTITY, NULL, 0) < 0) goto failure;
+ case RLM_MODULE_REJECT:
+ case RLM_MODULE_DISALLOW:
+ goto failure;
/*
- * Digest the packet contents, updating our checkcode.
+ * Everything looks ok, send the EAP-Request/reauthentication message
+ * After storing any new pseudonyms or session information.
*/
- if (eap_aka_sim_session->checkcode_md) {
- if (!eap_aka_sim_session->checkcode_state &&
- fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state,
- eap_aka_sim_session->checkcode_md) < 0) {
- RPEDEBUG("Failed initialising checkcode");
- goto failure;
- }
- if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
- eap_session->this_round->request) < 0) {
- RPEDEBUG("Failed updating checkcode");
- goto failure;
- }
+ case RLM_MODULE_NOOP:
+ case RLM_MODULE_OK:
+ case RLM_MODULE_UPDATED:
+ return common_reauthentication_request_compose(p_result, mctx, request, eap_aka_sim_session);
}
-
- RETURN_MODULE_HANDLED;
}
-/** Send a EAP-Request/SIM-Start message to the supplicant
- *
- * There are three types of user identities that can be implemented
- * - Permanent identities such as 0123456789098765@myoperator.com
- * Permanent identities can be identified by the leading zero followed by
- * by 15 digits (the IMSI number).
- * - Ephemeral identities (pseudonyms). These are identities assigned for
- * identity privacy so the user can't be tracked. These can identities
- * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
- * document section 14, where a set of up to 16 encryption keys are used
- * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
- * randomised and stored in a datastore.
- * - A fast resumption ID which resolves to data used for fast resumption.
- *
- * In order to perform full authentication the original IMSI is required for
- * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
- * or can't perform fast resumption, we need to request the full identity from
- * the supplicant.
+/** Resume after 'load pseudonym { ... }'
*
- * @param[out] p_result Result of calling the module, one of:
- * - RLM_MODULE_HANDLED on success.
- * - anything else on failure.
- * @param[in] mctx module calling context.
- * @param[in] request The current subrequest.
- * @param[in] eap_session to continue.
*/
-static unlang_action_t sim_start_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+RESUME(load_pseudonym)
{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_pair_t *vp;
- uint8_t *p, *end;
-
- eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
- p = eap_aka_sim_session->keys.gsm.version_list;
- end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list);
- eap_aka_sim_session->keys.gsm.version_list_len = 0;
+ pair_delete_request(attr_eap_aka_sim_next_reauth_id);
/*
- * If the user provided no versions, then
- * just add the default (1).
+ * Control attributes required could have been specified
+ * in another section.
*/
- if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) {
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0);
- vp->vp_uint16 = EAP_SIM_VERSION;
+ if (!inst->actions.load_pseudonym) {
+ next_state:
+ return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
}
+ switch (request->rcode) {
/*
- * Iterate over the the versions adding them
- * to the version list we use for keying.
- */
- for (vp = fr_pair_list_head(&request->reply_pairs); vp; vp = fr_pair_list_next(&request->reply_pairs, vp)) {
- if (vp->da != attr_eap_aka_sim_version_list) continue;
-
- if ((end - p) < 2) break;
-
- /*
- * Store as big endian
- */
- *p++ = (vp->vp_uint16 & 0xff00) >> 8;
- *p++ = (vp->vp_uint16 & 0x00ff);
- eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t);
- }
-
- /*
- * Update eap_aka_sim_session->id_req in case the the
- * user set attributes in `send Identity-Request { ... }`
- * Also removes all existing id_req attributes
- * from the reply.
- */
- identity_req_set_by_user(request, eap_aka_sim_session);
-
- /*
- * Select the right type of identity request attribute
- *
- * Implement checks on identity request order described
- * by RFC4186 section #4.2.5.
- *
- * The internal state machine should always handle this
- * correctly, but the user may have other ideas...
- */
- if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
-
- /*
- * Encode the packet
- */
- if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_START, NULL, 0) < 0) goto failure;
-
- RETURN_MODULE_HANDLED;
-}
-
-/** Print debugging information, and write new state to eap_session->process
- *
- */
-static inline void state_transition(request_t *request, eap_session_t *eap_session, module_method_t new_state)
-{
- module_method_t old_state = eap_session->process;
-
- if (new_state != old_state) {
- RDEBUG2("Changed state %s -> %s",
- module_state_method_to_str(aka_sim_stable_table, old_state, "<unknown>"),
- module_state_method_to_str(aka_sim_stable_table, new_state, "<unknown>"));
- } else {
- RDEBUG2("Reentering state %s",
- module_state_method_to_str(aka_sim_stable_table, old_state, "<unknown>"));
- }
-
- eap_session->process = new_state;
-}
-
-/** Resume after 'send EAP-Failure { ... }'
- *
- */
-static unlang_action_t common_eap_failure_enter_resume(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
-
- section_rcode_ignored(request);
-
- return common_eap_failure_send(p_result, request, eap_session);
-}
-
-/** Enter EAP-FAILURE state
- *
- */
-static unlang_action_t common_eap_failure_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-
- /*
- * Free anything we were going to send out...
- */
- fr_pair_list_free(&request->reply_pairs);
-
- /*
- * If we're failing, then any identities
- * we sent are now invalid.
- */
- if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
- return session_and_pseudonym_clear(p_result, mctx,
- request, eap_session, common_eap_failure_enter);
- /* come back when we're done */
- }
-
- state_transition(request, eap_session, common_eap_failure);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_eap_failure,
- RLM_MODULE_NOOP,
- common_eap_failure_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Resume after 'send Failure-Notification { ... }'
- *
- * Ignores return code from send Failure-Notification { ... } section.
- */
-static unlang_action_t common_failure_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
-
- section_rcode_ignored(request);
-
- /*
- * Free anything we were going to send out...
- */
- fr_pair_list_free(&request->reply_pairs);
-
- /*
- * If there's an issue composing the failure
- * message just send an EAP-Failure instead.
- */
- return common_failure_notification_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the FAILURE-NOTIFICATION state
- *
- */
-static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-
- /*
- * If we're failing, then any identities
- * we sent are now invalid.
- */
- if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
- return session_and_pseudonym_clear(p_result, mctx,request, eap_session,
- common_failure_notification_enter); /* come back when we're done */
- }
-
- /*
- * We've already sent a failure notification
- * Now we just fail as it means something
- * went wrong processing the ACK or we got
- * garbage from the supplicant.
- */
- if (eap_session->process == common_failure_notification) {
- return common_eap_failure_enter(p_result, mctx, request, eap_session);
- }
-
- /*
- * Otherwise just transition as normal...
- */
- state_transition(request, eap_session, common_failure_notification);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_failure_notification,
- RLM_MODULE_NOOP,
- common_failure_notification_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Resume after 'send EAP-Success { ... }'
- *
- */
-static unlang_action_t common_eap_success_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
-
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- /*
- * If this is true we're entering this state
- * after sending a AKA-Success-Notification
- *
- * Is seems like a really bad idea to allow the
- * user to send a protected success to the
- * supplicant and then force a failure using
- * the send EAP-Success { ... } section.
- */
- if (eap_aka_sim_session->send_result_ind) {
- switch (request->rcode) {
- case RLM_MODULE_USER_SECTION_REJECT:
- RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } "
- "as we already sent a Success-Notification",
- fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
- RWDEBUG("If you need to force a failure, return an error code from "
- "send Success-Notification { ... }");
- break;
-
- default:
- break;
- }
-
- /*
- * But... if we're not working with protected
- * success indication, this is the only
- * opportunity the user has to force a failure at
- * the end of authentication.
- */
- } else {
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
- }
-
- return common_eap_success_send(p_result, request, eap_session);
-}
-
-/** Enter EAP-SUCCESS state
- *
- */
-static unlang_action_t common_eap_success_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request,
- eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- state_transition(request, eap_session, common_eap_success);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_eap_success,
- RLM_MODULE_NOOP,
- common_eap_success_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Resume after 'send Success-Notification { ... }'
- *
- */
-static unlang_action_t common_success_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return common_success_notification_send(p_result, mctx, request, eap_session);
-}
-
-/** Enter the SUCCESS-NOTIFICATION state
- *
- */
-static unlang_action_t common_success_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- state_transition(request, eap_session, common_success_notification);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_success_notification,
- RLM_MODULE_NOOP,
- common_success_notification_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Resume after 'send Reauthentication-Request { ... }'
- *
- */
-static unlang_action_t common_reauthentication_send_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- switch (request->rcode) {
- /*
- * Failed getting the values we need for resumption
- * Request a different identity.
+ * Failed resolving the pseudonym
+ * request a different identity.
*/
default:
switch (eap_aka_sim_session->last_id_req) {
- /*
- * Got here processing EAP-Identity-Response
- * If this is the *true* reauth ID, then
- * there's no point in setting AKA_SIM_ANY_ID_REQ.
- */
case AKA_SIM_NO_ID_REQ:
case AKA_SIM_ANY_ID_REQ:
+ case AKA_SIM_FULLAUTH_ID_REQ:
RDEBUG2("Previous section returned (%s), clearing reply attributes and "
"requesting additional identity",
fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
fr_pair_list_free(&request->reply_pairs);
eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+ return STATE_TRANSITION(common_identity);
- return common_identity_enter(p_result, mctx, request, eap_session);
-
- case AKA_SIM_FULLAUTH_ID_REQ:
case AKA_SIM_PERMANENT_ID_REQ:
- default:
- break;
+ REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym. Cannot continue");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
}
- REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
- "but received a Fast-Auth-Id. Cannot continue");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ break;
/*
* Policy rejected the user
goto failure;
/*
- * Everything looks ok, send the EAP-Request/reauthentication message
- * After storing any new pseudonyms or session information.
+ * Everything OK
*/
- case RLM_MODULE_NOOP:
case RLM_MODULE_OK:
case RLM_MODULE_UPDATED:
- return common_reauthentication_request_compose(p_result, mctx, request, eap_session);
+ goto next_state;
}
+
+ goto failure;
}
/** Resume after 'load session { ... }'
*
*/
-static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
+RESUME(load_session)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
pair_delete_request(attr_session_id);
fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
fr_pair_list_free(&request->reply_pairs);
eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- return common_identity_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_identity);
case AKA_SIM_FULLAUTH_ID_REQ:
case AKA_SIM_PERMANENT_ID_REQ:
REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
"but received a Fast-Auth-Id. Cannot continue");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
-
+ return STATE_TRANSITION(common_failure_notification);
}
break;
case RLM_MODULE_REJECT:
case RLM_MODULE_DISALLOW:
reject:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_failure_notification);
/*
* Everything OK
case RLM_MODULE_OK:
case RLM_MODULE_UPDATED:
reauthenticate:
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_reauthentication_request,
- RLM_MODULE_NOOP,
- common_reauthentication_send_resume,
- mod_signal,
- NULL);
+ return CALL_SECTION(send_common_reauthentication_request);
}
goto reject;
}
-/** Resume after 'load pseudonym { ... }'
+/** Enter the REAUTHENTICATION state
*
*/
-static unlang_action_t pseudonym_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, void *rctx)
+STATE_GUARD(common_reauthentication)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- aka_sim_state_enter_t state_enter = (aka_sim_state_enter_t)rctx;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *vp = NULL;
- pair_delete_request(attr_eap_aka_sim_next_reauth_id);
+ STATE_SET(common_reauthentication);
/*
- * Control attributes required could have been specified
- * in another section.
+ * Add the current identity as session_id
+ * to make it easier to load/store things from
+ * the cache module.
*/
- if (!inst->actions.load_pseudonym) {
- next_state:
- return state_enter(p_result, mctx, request, eap_session);
- }
+ MEM(pair_update_request(&vp, attr_session_id) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true);
- switch (request->rcode) {
- /*
- * Failed resolving the pseudonym
- * request a different identity.
- */
- default:
- switch (eap_aka_sim_session->last_id_req) {
- case AKA_SIM_NO_ID_REQ:
- case AKA_SIM_ANY_ID_REQ:
- case AKA_SIM_FULLAUTH_ID_REQ:
- RDEBUG2("Previous section returned (%s), clearing reply attributes and "
- "requesting additional identity",
- fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
- fr_pair_list_free(&request->reply_pairs);
- eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- return common_identity_enter(p_result, mctx, request, eap_session);
+ return CALL_SECTION(load_session);
+}
- case AKA_SIM_PERMANENT_ID_REQ:
- REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym. Cannot continue");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- break;
+/** Resume after 'recv Synchronization-Failure { ... }'
+ *
+ * - If 'recv Synchronization-Failure { ... }' returned a failure
+ * rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or if no 'recv Syncronization-Failure { ... }' section was
+ * defined, then enter the FAILURE-NOTIFICATION state.
+ * - ...or if the user didn't provide a new SQN value in &control.SQN
+ * then enter the FAILURE-NOTIFICATION state.
+ * - ...or enter the AKA-CHALLENGE state.
+ */
+RESUME(recv_aka_syncronization_failure)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ fr_pair_t *vp;
+
+ SECTION_RCODE_PROCESS;
/*
- * Policy rejected the user
+ * If there's no section to handle this, then no resynchronisation
+ * can't have occurred and we just send a reject.
+ *
+ * Similarly, if we've already received one synchronisation failure
+ * then it's highly likely whatever user configured action was
+ * configured was unsuccessful, and we should just give up.
*/
- case RLM_MODULE_REJECT:
- case RLM_MODULE_DISALLOW:
- goto failure;
+ if (!inst->actions.recv_aka_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) {
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
/*
- * Everything OK
+ * We couldn't generate an SQN and the user didn't provide one,
+ * so we need to fail.
*/
- case RLM_MODULE_OK:
- case RLM_MODULE_UPDATED:
- goto next_state;
+ vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn);
+ if (!vp) {
+ REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue");
+ goto failure;
}
- goto failure;
+ /*
+ * RFC 4187 Section #6.3.1
+ *
+ * "if the peer detects that the
+ * sequence number in AUTN is not correct, the peer responds with
+ * EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the
+ * server proceeds with a new EAP-Request/AKA-Challenge."
+ */
+ return STATE_TRANSITION(aka_challenge);
}
-/** Enter the REAUTHENTICATION state
+/** Resume after 'recv Authentication-Reject { ... }'
*
+ * - Enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t common_reauthentication_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+RESUME(recv_aka_authentication_reject)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- fr_pair_t *vp = NULL;
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
- state_transition(request, eap_session, common_reauthentication);
+ SECTION_RCODE_IGNORED;
/*
- * Add the current identity as session_id
- * to make it easier to load/store things from
- * the cache module.
+ * Case 2 where we're allowed to send an EAP-Failure
*/
- MEM(pair_update_request(&vp, attr_session_id) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.load_session,
- RLM_MODULE_NOOP,
- session_load_resume,
- mod_signal,
- NULL);
+ return STATE_TRANSITION(eap_failure);
}
-/** Resume after 'send Challenge-Request { ... }'
+/** Resume after 'recv Challenge-Response { ... }'
+ *
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the AKA-Challenge message.
*
+ * Verify that MAC, and RES match what we expect.
*/
-static unlang_action_t aka_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
+RESUME(recv_aka_challenge_response)
{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ eap_session_t *eap_session = eap_session_get(request->parent);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+ ssize_t slen;
+ fr_pair_t *vp = NULL, *mac;
+#if 0
+ *checkcode;
+#endif
+ SECTION_RCODE_PROCESS;
- return aka_challenge_request_compose(p_result, mctx, request, eap_session);
-}
-/** Enter the AKA-CHALLENGE state
- *
- */
-static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
- fr_pair_t *vp;
+ mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+ if (!mac) {
+ REDEBUG("Missing AT_MAC attribute");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+ REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+ AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+ goto failure;
+ }
- /*
- * If we've sent either of these identities it
- * means we've come here form a Reauthentication-Request
- * that failed.
- */
- if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
- return session_and_pseudonym_clear(p_result, mctx,request, eap_session, aka_challenge_enter); /* come back when we're done */
+ // TODO: MAC validation should happen in the decoder not the state machine
+ slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+ eap_aka_sim_session->mac_md,
+ eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+ NULL, 0);
+ if (slen < 0) {
+ RPEDEBUG("Failed calculating MAC");
+ goto failure;
+ } else if (slen == 0) {
+ REDEBUG("Zero length AT_MAC attribute");
+ goto failure;
}
- state_transition(request, eap_session, aka_challenge);
+ if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+ RDEBUG2("Received MAC matches calculated MAC");
+ } else {
+ REDEBUG("Received MAC does not match calculated MAC");
+ RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+ RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+ goto failure;
+ }
/*
- * Set some default attributes, giving the user a
- * chance to modify them.
+ * If the peer doesn't include a checkcode then that
+ * means they don't support it, and we can't validate
+ * their view of the identity packets.
*/
- switch (eap_session->type) {
- case FR_EAP_METHOD_AKA_PRIME:
- {
- uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */
+#if 0
+ if (eap_aka_sim_session->checkcode_md) {
+ checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode);
+ if (checkcode) {
+ if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
+ REDEBUG("Received checkcode's length (%zu) does not match "
+ "calculated checkcode's length (%zu)",
+ checkcode->vp_length, eap_aka_sim_session->checkcode_len);
+ goto failure;
+ }
+ if (memcmp(checkcode->vp_octets,
+ eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) {
+ RDEBUG2("Received checkcode matches calculated checkcode");
+ } else {
+ REDEBUG("Received checkcode does not match calculated checkcode");
+ RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
+ RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
+ eap_aka_sim_session->checkcode_len, "Expected");
+ goto failure;
+ }
/*
- * Toggle the AMF high bit to indicate we're doing AKA'
+ * Only print something if we calculated a checkcode
*/
- MEM(pair_update_control(&vp, attr_sim_amf) >= 0);
- fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false);
-
- /*
- * Use the default network name we have configured
- * and send it to the peer.
- */
- if (inst->network_name &&
- !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) {
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0);
- fr_pair_value_bstrdup_buffer(vp, inst->network_name, false);
+ } else if (eap_aka_sim_session->checkcode_len > 0){
+ RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
}
}
- break;
+#endif
- default:
- /*
- * Use the default bidding value we have configured
- */
- if (eap_aka_sim_session->send_at_bidding_prefer_prime &&
- !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_bidding)) {
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_bidding) >= 0);
- vp->vp_uint16 = FR_BIDDING_VALUE_PREFER_AKA_PRIME;
- }
- break;
+ vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_res);
+ if (!vp) {
+ REDEBUG("AT_RES missing from challenge response");
+ goto failure;
+ }
+ if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) {
+ REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)",
+ vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len);
+ goto failure;
}
- /*
- * Set the defaults for protected result indicator
- */
- if (eap_aka_sim_session->send_result_ind &&
- !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
- vp->vp_bool = true;
+ if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) {
+ REDEBUG("Received RES does not match calculated XRES");
+ RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES :");
+ RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres,
+ eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :");
+ goto failure;
}
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_challenge_request,
- RLM_MODULE_NOOP,
- aka_challenge_enter_resume,
- mod_signal,
- NULL);
-}
+ RDEBUG2("Received RES matches calculated XRES");
-/** Resume after 'send Challenge-Request { ... }'
- *
- */
-static unlang_action_t sim_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_session->challenge_success = true;
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ /*
+ * If the peer wants a Success notification, and
+ * we included AT_RESULT_IND then send a success
+ * notification, otherwise send a normal EAP-Success.
+ *
+ * RFC 4187 Section #6.2. Result Indications
+ */
+ if (eap_aka_sim_session->send_result_ind) {
+ if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("We wanted to use protected result indications, but peer does not");
+ eap_aka_sim_session->send_result_ind = false;
+ } else {
+ return STATE_TRANSITION(common_success_notification);
+ }
+ } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("Peer wanted to use protected result indications, but we do not");
+ }
- return sim_challenge_request_compose(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(eap_success);
}
-/** Enter the SIM-CHALLENGE state
+/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
*
+ * - Continue based on received AT_SUBTYPE value:
+ * - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'.
+ * - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }' and after that
+ * send a EAP-Request/SIM-Notification indicating a General Failure.
+ * - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'.
+ * - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that
+ * send a EAP-Request/AKA-Notification indicating a General Failure.
+ * - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
+STATE(aka_challenge)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *subtype_vp = NULL;
fr_pair_t *vp;
- /*
- * If we've sent either of these identities it
- * means we've come here form a Reauthentication-Request
- * that failed.
- */
- if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
- return session_and_pseudonym_clear(p_result, mctx,request, eap_session, sim_challenge_enter); /* come back when we're done */
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+ if (!subtype_vp) {
+ REDEBUG("Missing AT_SUBTYPE");
+ goto fail;
}
- state_transition(request, eap_session, sim_challenge);
+ switch (subtype_vp->vp_uint16) {
+ case FR_SUBTYPE_VALUE_AKA_CHALLENGE:
+ return CALL_SECTION(recv_aka_challenge_response);
/*
- * Set the defaults for protected result indicator
+ * Case 2 where we're allowed to send an EAP-Failure
*/
- if (eap_aka_sim_session->send_result_ind &&
- !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
- MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
- vp->vp_bool = true;
- }
+ case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT:
+ eap_aka_sim_session->allow_encrypted = false;
+ return CALL_SECTION(recv_aka_authentication_reject);
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.send_challenge_request,
- RLM_MODULE_NOOP,
- sim_challenge_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state
- *
- * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
- * to enter the correct challenge state.
- */
-static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- switch (eap_session->type) {
- case FR_EAP_METHOD_SIM:
- return sim_challenge_enter(p_result, mctx, request, eap_session);
-
- case FR_EAP_METHOD_AKA:
- case FR_EAP_METHOD_AKA_PRIME:
- return aka_challenge_enter(p_result, mctx, request, eap_session);
-
- default:
- break;
- }
-
- fr_assert(0);
- RETURN_MODULE_FAIL;
-}
-
-/** Resume after 'send Identity-Request { ... }'
- *
- */
-static unlang_action_t aka_identity_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return aka_identity_request_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the AKA-IDENTITY state
- *
- */
-static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-
- state_transition(request, eap_session, aka_identity);
-
- /*
- * If we have an send_aka_identity_request section
- * then run that, otherwise just run the normal
- * identity request section.
- */
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.aka.send_aka_identity_request ?
- inst->actions.aka.send_aka_identity_request:
- inst->actions.send_identity_request,
- RLM_MODULE_NOOP,
- aka_identity_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Resume after 'send Start { ... }'
- *
- */
-static unlang_action_t sim_start_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return sim_start_request_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the SIM-START state
- *
- */
-static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-
- state_transition(request, eap_session, sim_start);
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.sim.send_sim_start_request ?
- inst->actions.sim.send_sim_start_request:
- inst->actions.send_identity_request,
- RLM_MODULE_NOOP,
- sim_start_enter_resume,
- mod_signal,
- NULL);
-}
-
-/** Enter the SIM-START or AKA-IDENTITY state
- *
- * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
- * to enter the correct Identity-Request state.
- */
-static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- switch (eap_session->type) {
- case FR_EAP_METHOD_SIM:
- return sim_start_enter(p_result, mctx, request, eap_session);
-
- case FR_EAP_METHOD_AKA:
- case FR_EAP_METHOD_AKA_PRIME:
- return aka_identity_enter(p_result, mctx, request, eap_session);
-
- default:
- break;
- }
-
- fr_assert(0);
- RETURN_MODULE_FAIL;
-}
-
-/** Process a EAP-Response/(AKA|SIM)-Reauthentication message - The response to our EAP-Request/(AKA|SIM)-Reauthentication message
- *
- */
-static unlang_action_t common_reauthentication_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
- ssize_t slen;
- fr_pair_t *mac, *checkcode;
- fr_pair_list_t *from_peer = &request->request_pairs;
-
- mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
- if (!mac) {
- REDEBUG("Missing AT_MAC attribute");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
- REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
- AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
- goto failure;
- }
-
- slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
- eap_aka_sim_session->mac_md,
- eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
- eap_aka_sim_session->keys.reauth.nonce_s,
- sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
- if (slen < 0) {
- RPEDEBUG("Failed calculating MAC");
- goto failure;
- } else if (slen == 0) {
- REDEBUG("Zero length AT_MAC attribute");
- goto failure;
- }
-
- if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
- RDEBUG2("Received MAC matches calculated MAC");
- } else {
- REDEBUG("Received MAC does not match calculated MAC");
- RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
- RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
- goto failure;
- }
-
- /*
- * If the peer doesn't include a checkcode then that
- * means they don't support it, and we can't validate
- * their view of the identity packets.
- */
- checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode);
- if (checkcode) {
- if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
- REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)",
- checkcode->vp_length, eap_aka_sim_session->checkcode_len);
- goto failure;
- }
-
- if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode,
- eap_aka_sim_session->checkcode_len) == 0) {
- RDEBUG2("Received checkcode matches calculated checkcode");
- } else {
- REDEBUG("Received checkcode does not match calculated checkcode");
- RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
- RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
- eap_aka_sim_session->checkcode_len, "Expected");
- goto failure;
- }
- /*
- * Only print something if we calculated a checkcode
- */
- } else if (eap_aka_sim_session->checkcode_len > 0){
- RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
- }
-
- /*
- * Check to see if the supplicant sent
- * AT_COUNTER_TOO_SMALL, if they did then we
- * clear out reauth information and enter the
- * challenge state.
- */
- if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_counter_too_small)) {
- RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)",
- eap_aka_sim_session->keys.reauth.counter);
-
- fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys);
- eap_aka_sim_session->allow_encrypted = false;
-
- return aka_challenge_enter(p_result, mctx, request, eap_session);
- }
-
- /*
- * If the peer wants a Success notification, and
- * we included AT_RESULT_IND then send a success
- * notification, otherwise send a normal EAP-Success.
- *
- * RFC 4187 Section #6.2. Result Indications
- */
- if (eap_aka_sim_session->send_result_ind) {
- if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("We wanted to use protected result indications, but peer does not");
- eap_aka_sim_session->send_result_ind = false;
- } else {
- return common_success_notification_enter(p_result, mctx, request, eap_session);
- }
- } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("Peer wanted to use protected result indications, but we do not");
- }
-
- eap_aka_sim_session->reauthentication_success = true;
-
- return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/AKA-Challenge message - The response to our EAP-Request/AKA-Challenge message
- *
- * Verify that MAC, and RES match what we expect.
- */
-static unlang_action_t aka_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
- ssize_t slen;
- fr_pair_t *vp = NULL, *mac, *checkcode;
- fr_pair_list_t *from_peer = &request->request_pairs;
-
- mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
- if (!mac) {
- REDEBUG("Missing AT_MAC attribute");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
- REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
- AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
- goto failure;
- }
-
- slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
- eap_aka_sim_session->mac_md,
- eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
- NULL, 0);
- if (slen < 0) {
- RPEDEBUG("Failed calculating MAC");
- goto failure;
- } else if (slen == 0) {
- REDEBUG("Zero length AT_MAC attribute");
- goto failure;
- }
-
- if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
- RDEBUG2("Received MAC matches calculated MAC");
- } else {
- REDEBUG("Received MAC does not match calculated MAC");
- RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
- RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
- goto failure;
- }
-
- /*
- * If the peer doesn't include a checkcode then that
- * means they don't support it, and we can't validate
- * their view of the identity packets.
- */
- if (eap_aka_sim_session->checkcode_md) {
- checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode);
- if (checkcode) {
- if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
- REDEBUG("Received checkcode's length (%zu) does not match "
- "calculated checkcode's length (%zu)",
- checkcode->vp_length, eap_aka_sim_session->checkcode_len);
- goto failure;
- }
-
- if (memcmp(checkcode->vp_octets,
- eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) {
- RDEBUG2("Received checkcode matches calculated checkcode");
- } else {
- REDEBUG("Received checkcode does not match calculated checkcode");
- RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
- RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
- eap_aka_sim_session->checkcode_len, "Expected");
- goto failure;
- }
- /*
- * Only print something if we calculated a checkcode
- */
- } else if (eap_aka_sim_session->checkcode_len > 0){
- RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
- }
- }
-
- vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_res);
- if (!vp) {
- REDEBUG("AT_RES missing from challenge response");
- goto failure;
- }
-
- if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) {
- REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)",
- vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len);
- goto failure;
- }
-
- if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) {
- REDEBUG("Received RES does not match calculated XRES");
- RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES :");
- RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres,
- eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :");
- goto failure;
- }
-
- RDEBUG2("Received RES matches calculated XRES");
-
- eap_aka_sim_session->challenge_success = true;
-
- /*
- * If the peer wants a Success notification, and
- * we included AT_RESULT_IND then send a success
- * notification, otherwise send a normal EAP-Success.
- *
- * RFC 4187 Section #6.2. Result Indications
- */
- if (eap_aka_sim_session->send_result_ind) {
- if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("We wanted to use protected result indications, but peer does not");
- eap_aka_sim_session->send_result_ind = false;
- } else {
- return common_success_notification_enter(p_result, mctx, request, eap_session);
- }
- } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("Peer wanted to use protected result indications, but we do not");
- }
-
- return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/SIM-Challenge message - The response to our EAP-Request/SIM-Challenge message
- *
- * Verify that MAC, and RES match what we expect.
- */
-static unlang_action_t sim_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
- uint8_t *p = sres_cat;
-
- uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
- ssize_t slen;
- fr_pair_t *mac;
- fr_pair_list_t *from_peer = &request->request_pairs;
-
- memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
- p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
- memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
- p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
- memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
-
- mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
- if (!mac) {
- REDEBUG("Missing AT_MAC attribute");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
- REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
- AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
- goto failure;
- }
-
- slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
- eap_aka_sim_session->mac_md,
- eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
- sres_cat, sizeof(sres_cat));
- if (slen < 0) {
- RPEDEBUG("Failed calculating MAC");
- goto failure;
- } else if (slen == 0) {
- REDEBUG("Zero length AT_MAC attribute");
- goto failure;
- }
-
- if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
- RDEBUG2("Received MAC matches calculated MAC");
- } else {
- REDEBUG("Received MAC does not match calculated MAC");
- RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
- RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
- goto failure;
- }
-
- eap_aka_sim_session->challenge_success = true;
-
- /*
- * If the peer wants a Success notification, and
- * we included AT_RESULT_IND then send a success
- * notification, otherwise send a normal EAP-Success.
- */
- if (eap_aka_sim_session->send_result_ind) {
- if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("We wanted to use protected result indications, but peer does not");
- eap_aka_sim_session->send_result_ind = false;
- } else {
- return common_success_notification_enter(p_result, mctx, request, eap_session);
- }
- } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
- RDEBUG("Peer wanted to use protected result indications, but we do not");
- }
-
- return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/AKA-Identity message i.e. an EAP-AKA Identity-Response
- *
- * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
- * - If the user requested another identity, re-enter the AKA-Identity sate.
- * - ...or continue based on the value of &Identity-Type which was added by #aka_identity,
- * and possibly modified by the user.
- * - Fastauth - Enter the REAUTHENTICATION state.
- * - Pseudonym - Call 'load pseudonym { ... }'
- * - Permanent - Enter the CHALLENGE state.
- */
-static unlang_action_t aka_identity_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- bool user_set_id_req;
- fr_pair_t *identity_type;
- fr_pair_list_t *from_peer = &request->request_pairs;
- /*
- * Digest the identity response
- */
- if (eap_aka_sim_session->checkcode_md) {
- if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
- eap_session->this_round->response) < 0) {
- RPEDEBUG("Failed updating checkcode");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- }
-
- /*
- * See if the user wants us to request another
- * identity.
- *
- * If they set one themselves don't override
- * what they set.
- */
- user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
- if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
- if (!user_set_id_req) {
- switch (eap_aka_sim_session->last_id_req) {
- case AKA_SIM_ANY_ID_REQ:
- eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- break;
-
- case AKA_SIM_FULLAUTH_ID_REQ:
- eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
- break;
-
- case AKA_SIM_NO_ID_REQ: /* Should not happen */
- fr_assert(0);
- FALL_THROUGH;
-
- case AKA_SIM_PERMANENT_ID_REQ:
- REDEBUG("Peer sent no usable identities");
- goto failure;
-
- }
- RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
- fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
- fr_table_str_by_value(fr_aka_sim_id_request_table,
- eap_aka_sim_session->id_req, "<INVALID>"));
- }
- return aka_identity_enter(p_result, mctx, request, eap_session);
- }
-
- /*
- * If the identity looks like a fast re-auth id
- * run fast re-auth, otherwise do fullauth.
- */
- identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
- if (identity_type) switch (identity_type->vp_uint32) {
- case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
- return common_reauthentication_enter(p_result, mctx, request, eap_session);
-
- /*
- * It's a pseudonym, which now needs resolving.
- * The resume function here calls aka_challenge_enter
- * if pseudonym resolution went ok.
- */
- case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.load_pseudonym,
- RLM_MODULE_NOOP,
- pseudonym_load_resume,
- mod_signal,
- (void *)aka_challenge_enter);
- default:
- break;
- }
-
- return aka_challenge_enter(p_result, mctx, request, eap_session);
-}
-
-/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure
- *
- * Also checks the version matches one of the ones we advertised in our version list,
- * which is a bit redundant seeing as there's only one version of EAP-SIM.
- */
-static int sim_start_selected_version_check(request_t *request, fr_pair_list_t *from_peer,
- eap_aka_sim_session_t *eap_aka_sim_session)
-{
- fr_pair_t *selected_version_vp;
-
- /*
- * Check that we got an AT_SELECTED_VERSION
- */
- selected_version_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version);
- if (!selected_version_vp) {
- REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION");
- return -1;
- }
-
- /*
- * See if the selected version was in our list
- */
- {
- uint8_t selected_version[2];
- uint8_t *p, *end;
- bool found = false;
-
- p = eap_aka_sim_session->keys.gsm.version_list;
- end = p + eap_aka_sim_session->keys.gsm.version_list_len;
-
- selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8;
- selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff);
-
- while (p < end) {
- if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) {
- found = true;
- /*
- * Update our keying material
- */
- eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0];
- eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1];
- break;
- }
- }
-
- if (!found) {
- REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list",
- selected_version_vp->vp_uint16);
- return -1;
- }
- }
-
- return 0;
-}
-
-/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure
- *
- * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later.
- */
-static int sim_start_nonce_mt_check(request_t *request, fr_pair_list_t *from_peer,
- eap_aka_sim_session_t *eap_aka_sim_session)
-{
- fr_pair_t *nonce_mt_vp;
-
- /*
- * Copy nonce_mt to the keying material
- */
- nonce_mt_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt);
- if (!nonce_mt_vp) {
- REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT");
- return -1;
- }
-
- if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) {
- REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes",
- sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length);
- return -1;
- }
- memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets,
- sizeof(eap_aka_sim_session->keys.gsm.nonce_mt));
+ case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE:
+ {
+ uint64_t new_sqn;
- return 0;
-}
+ eap_aka_sim_session->allow_encrypted = false;
-/** Process a EAP-Response/SIM-Start message i.e. an EAP-SIM Identity-Response
- *
- * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
- * - If the user requested another identity, re-enter the SIM-START sate.
- * - ...or continue based on the value of &Identity-Type which was added by #sim_start,
- * and possibly modified by the user.
- * - Fastauth
- * - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state.
- * - ...or enter the REAUTHENTICATION state.
- * - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }'
- * - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state.
- */
-static unlang_action_t sim_start_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, eap_session_t *eap_session)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- bool user_set_id_req;
- fr_pair_t *identity_type;
+ vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_auts);
+ if (!vp) {
+ REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
- fr_pair_list_t *from_peer = &request->request_pairs;
+ switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn,
+ request, vp, &eap_aka_sim_session->keys)) {
+ /*
+ * Add everything back that we'll need in the
+ * next challenge round.
+ */
+ case 0:
+ MEM(pair_append_control(&vp, attr_sim_sqn) >= 0);
+ vp->vp_uint64 = new_sqn;
- /*
- * See if the user wants us to request another
- * identity.
- *
- * If they set one themselves don't override
- * what they set.
- */
- user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
- if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
- if (!user_set_id_req) {
- switch (eap_aka_sim_session->last_id_req) {
- case AKA_SIM_ANY_ID_REQ:
- eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
- break;
+ MEM(pair_append_control(&vp, attr_sim_ki) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki,
+ sizeof(eap_aka_sim_session->keys.auc.ki), false);
- case AKA_SIM_FULLAUTH_ID_REQ:
- eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
- break;
+ MEM(pair_append_control(&vp, attr_sim_opc) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc,
+ sizeof(eap_aka_sim_session->keys.auc.opc), false);
+ break;
- case AKA_SIM_NO_ID_REQ: /* Should not happen */
- fr_assert(0);
- FALL_THROUGH;
+ case 1: /* Don't have Ki or OPc so something else will need to deal with this */
+ break;
- case AKA_SIM_PERMANENT_ID_REQ:
- REDEBUG("Peer sent no usable identities");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
- fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
- fr_table_str_by_value(fr_aka_sim_id_request_table,
- eap_aka_sim_session->id_req, "<INVALID>"));
+ default:
+ case -1:
+ goto failure;
}
- return sim_start_enter(p_result, mctx, request, eap_session);
+
+ return CALL_SECTION(recv_aka_syncronization_failure);
}
/*
- * If the identity looks like a fast re-auth id
- * run fast re-auth, otherwise do fullauth.
+ * Case 1 where we're allowed to send an EAP-Failure
*/
- identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
- if (identity_type) switch (identity_type->vp_uint32) {
- case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
- /*
- * RFC 4186 Section #9.2
- *
- * The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY
- * with a fast re-authentication identity is present for fast
- * re-authentication
- */
- if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt)) {
- REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
-
- /*
- * RFC 4186 Section #9.2
- *
- * The AT_SELECTED_VERSION attribute MUST NOT be included if the
- * AT_IDENTITY attribute with a fast re-authentication identity is
- * present for fast re-authentication.
- */
- if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version)) {
- REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
-
- return common_reauthentication_enter(p_result, mctx, request, eap_session);
+ case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+ client_error_debug(request);
- /*
- * It's a pseudonym, which now needs resolving.
- * The resume function here calls aka_challenge_enter
- * if pseudonym resolution went ok.
- */
- case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
- if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
- if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
+ eap_aka_sim_session->allow_encrypted = false;
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.load_pseudonym,
- RLM_MODULE_NOOP,
- pseudonym_load_resume,
- mod_signal,
- (void *)sim_challenge_enter);
+ return CALL_SECTION(recv_common_client_error);
/*
- * If it's a permanent ID, copy it over to
- * the session state list for use in the
- * store pseudonym/store session sections
- * later.
+ * RFC 4187 says we *MUST* notify, not just
+ * send an EAP-Failure in this case.
*/
- case FR_IDENTITY_TYPE_VALUE_PERMANENT:
- if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
- if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
-
- FALL_THROUGH;
default:
- break;
+ REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+ fail:
+ eap_aka_sim_session->allow_encrypted = false;
+ goto failure;
}
-
- return sim_challenge_enter(p_result, mctx, request, eap_session);
}
-/** Resume after 'recv Failure-Notification-Ack { ... }'
+/** Resume after 'send Challenge-Request { ... }'
*
- * - Enter the EAP-FAILURE state.
*/
-static unlang_action_t common_failure_notification_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
+RESUME(send_aka_challenge_request)
{
- eap_session_t *eap_session = eap_session_get(request->parent);
-
- section_rcode_ignored(request);
-
- /*
- * Case 2 where we're allowed to send an EAP-Failure
- */
- return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ fr_pair_t *vp;
+ fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
-/** Resume after 'recv Success-Notification-Ack { ... }'
- *
- * - Enter the EAP-SUCCESS state.
- */
-static unlang_action_t common_success_notification_ack_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
+ fr_pair_t *kdf_id;
- section_rcode_ignored(request);
+ SECTION_RCODE_PROCESS;
/*
- * RFC 4187 says we ignore the contents of the
- * next packet after we send our success notification
- * and always send a success.
+ * Allow override of KDF Identity
+ *
+ * Because certain handset manufacturers don't
+ * implement RFC 4187 correctly and use the
+ * wrong identity as input the the PRF/KDF.
*/
- return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Resume after 'recv Challenge-Response { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the AKA-Challenge message.
- */
-static unlang_action_t aka_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return aka_challenge_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Challenge-Response { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the SIM-Challenge message.
- */
-static unlang_action_t sim_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return sim_challenge_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value.
- */
-static unlang_action_t aka_identity_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
- return aka_identity_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value.
- */
-static unlang_action_t sim_start_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+ if (kdf_id) {
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+ fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+ }
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ RDEBUG2("Acquiring UMTS vector(s)");
- return sim_start_response_process(p_result, mctx,request, eap_session);
-}
+ if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) {
+ /*
+ * Copy the network name the user specified for
+ * key derivation purposes.
+ */
+ vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input);
+ if (vp) {
+ talloc_free(eap_aka_sim_session->keys.network);
+ eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session,
+ (uint8_t const *)vp->vp_strvalue,
+ vp->vp_length);
+ eap_aka_sim_session->keys.network_len = vp->vp_length;
+ } else {
+ REDEBUG("No network name available, can't set AT_KDF_INPUT");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
-/** Resume after 'recv Authentication-Reject { ... }'
- *
- * - Enter the FAILURE-NOTIFICATION state.
- */
-static unlang_action_t aka_authentication_reject_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
+ /*
+ * We don't allow the user to specify
+ * the KDF currently.
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0);
+ vp->vp_uint16 = eap_aka_sim_session->kdf;
+ }
- section_rcode_ignored(request);
+ /*
+ * Get vectors from attribute or generate
+ * them using COMP128-* or Milenage.
+ */
+ if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs,
+ &eap_aka_sim_session->keys, &src) != 0) {
+ REDEBUG("Failed retrieving UMTS vectors");
+ goto failure;
+ }
/*
- * Case 2 where we're allowed to send an EAP-Failure
+ * Don't leave the AMF hanging around
*/
- return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+ if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf);
-/** Resume after 'recv Synchronization-Failure { ... }'
- *
- * - If 'recv Synchronization-Failure { ... }' returned a failure
- * rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or if no 'recv Syncronization-Failure { ... }' section was
- * defined, then enter the FAILURE-NOTIFICATION state.
- * - ...or if the user didn't provide a new SQN value in &control.SQN
- * then enter the FAILURE-NOTIFICATION state.
- * - ...or enter the AKA-CHALLENGE state.
- */
-static unlang_action_t aka_synchronization_failure_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- fr_pair_t *vp;
+ /*
+ * All set, calculate keys!
+ */
+ switch (eap_aka_sim_session->type) {
+ default:
+ case FR_EAP_METHOD_SIM:
+ fr_assert(0); /* EAP-SIM has its own Challenge state */
+ break;
+
+ case FR_EAP_METHOD_AKA:
+ fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys);
+ break;
+
+ case FR_EAP_METHOD_AKA_PRIME:
+ switch (eap_aka_sim_session->kdf) {
+ case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
+ fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys);
+ break;
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ default:
+ fr_assert(0);
+ break;
+ }
+ }
+ if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
/*
- * If there's no section to handle this, then no resynchronisation
- * can't have occurred and we just send a reject.
+ * Indicate we'd like to use protected success messages
+ * with AT_RESULT_IND
*
- * Similarly, if we've already received one synchronisation failure
- * then it's highly likely whatever user configured action was
- * configured was unsuccessful, and we should just give up.
+ * Use our default, but allow user override too.
*/
- if (!inst->actions.aka.recv_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) {
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+ vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
+ if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
/*
- * We couldn't generate an SQN and the user didn't provide one,
- * so we need to fail.
+ * These attributes are only allowed with
+ * EAP-AKA', protect users from themselves.
*/
- vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn);
- if (!vp) {
- REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue");
- goto failure;
+ if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) {
+ pair_delete_reply(attr_eap_aka_sim_kdf_input);
+ pair_delete_reply(attr_eap_aka_sim_kdf);
}
/*
- * RFC 4187 Section #6.3.1
- *
- * "if the peer detects that the
- * sequence number in AUTN is not correct, the peer responds with
- * EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the
- * server proceeds with a new EAP-Request/AKA-Challenge."
+ * Okay, we got the challenge! Put it into an attribute.
*/
- return aka_challenge_enter(p_result, mctx, request, eap_session);
-}
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false);
-/** Resume after 'recv Client-Error { ... }'
- *
- * - Enter the EAP-FAILURE state.
- */
-static unlang_action_t common_client_error_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
+ /*
+ * Send the AUTN value to the client, so it can authenticate
+ * whoever has knowledge of the Ki.
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false);
+
+ /*
+ * need to include an AT_MAC attribute so that it will get
+ * calculated.
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+ fr_pair_value_memdup(vp, NULL, 0, false);
- section_rcode_ignored(request);
+#if 0
+ /*
+ * If we have checkcode data, send that to the peer
+ * in AT_CHECKCODE for validation.
+ */
+ if (eap_aka_sim_session->checkcode_state) {
+ ssize_t slen;
- return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+ slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
+ &eap_aka_sim_session->checkcode_state);
+ if (slen < 0) {
+ RPEDEBUG("Failed calculating checkcode");
+ goto failure;
+ }
+ eap_aka_sim_session->checkcode_len = slen;
-/** Resume after 'recv Reauthentication-Response { ... }'
- *
- * - If 'recv Reauthentication-Response { ... }' returned a failure
- * rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call the EAP-Request/Reauthentication-Response function to act on the
- * contents of the response.
- */
-static unlang_action_t common_reauthentication_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
-{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+ /*
+ * If we don't have checkcode data, then we exchanged
+ * no identity packets, so AT_CHECKCODE is zero.
+ */
+ } else {
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+ fr_pair_value_memdup(vp, NULL, 0, false);
+ eap_aka_sim_session->checkcode_len = 0;
+ }
+#endif
+
+ /*
+ * We've sent the challenge so the peer should now be able
+ * to accept encrypted attributes.
+ */
+ eap_aka_sim_session->allow_encrypted = true;
- return common_reauthentication_response_process(p_result, mctx,request, eap_session);
+ return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session, aka_challenge_request_send);
}
-/** Decode the peer's response
+/** Enter the AKA-CHALLENGE state
*
- * This is called by the state_* functions to decode the peer's response.
*/
-static unlang_action_t common_decode(fr_pair_t **subtype_vp, fr_pair_list_t *vps,
- rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE_GUARD(aka_challenge)
{
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- fr_aka_sim_decode_ctx_t ctx = {
- .keys = &eap_aka_sim_session->keys,
- };
- fr_pair_t *aka_vp;
- fr_pair_list_t aka_vps;
- fr_dcursor_t cursor;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *vp;
- int ret;
+ /*
+ * If we've sent either of these identities it
+ * means we've come here form a Reauthentication-Request
+ * that failed.
+ */
+ if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+ return session_and_pseudonym_clear(p_result, mctx, request,
+ eap_aka_sim_session, guard_aka_challenge);
+ /* come back when we're done */
+ }
- fr_pair_list_init(&aka_vps);
- fr_dcursor_init(&cursor, &request->request_pairs);
- fr_dcursor_tail(&cursor);
+ STATE_SET(aka_challenge);
- ret = fr_aka_sim_decode(request,
- &cursor,
- dict_eap_aka_sim,
- eap_session->this_round->response->type.data,
- eap_session->this_round->response->type.length,
- &ctx);
/*
- * RFC 4187 says we *MUST* notify, not just
- * send an EAP-Failure in this case where
- * we cannot decode an EAP-AKA packet.
+ * Set some default attributes, giving the user a
+ * chance to modify them.
*/
- if (ret < 0) {
- RPEDEBUG2("Failed decoding attributes");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
- /* vps is the data from the client */
- aka_vp = fr_dcursor_next(&cursor);
- fr_pair_sublist_copy(request, &aka_vps, &request->request_pairs, aka_vp);
- if (!fr_pair_list_empty(&aka_vps) && RDEBUG_ENABLED2) {
- RDEBUG2("Decoded attributes");
- log_request_pair_list(L_DBG_LVL_2, request, NULL, &aka_vps, NULL);
+ switch (eap_aka_sim_session->type) {
+ case FR_EAP_METHOD_AKA_PRIME:
+ {
+ uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */
+
+ /*
+ * Toggle the AMF high bit to indicate we're doing AKA'
+ */
+ MEM(pair_update_control(&vp, attr_sim_amf) >= 0);
+ fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false);
+
+ /*
+ * Use the default network name we have configured
+ * and send it to the peer.
+ */
+ if (inst->network_name &&
+ !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) {
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0);
+ fr_pair_value_bstrdup_buffer(vp, inst->network_name, false);
+ }
}
+ break;
+
+ default:
+ break;
- *subtype_vp = fr_pair_find_by_da(&aka_vps, attr_eap_aka_sim_subtype);
- if (!*subtype_vp) {
- REDEBUG("Missing AT_SUBTYPE");
- goto failure;
}
- fr_tmp_pair_list_move(vps, &aka_vps);
- RDEBUG2("Received EAP-Response/%pV", &(*subtype_vp)->data);
+ /*
+ * Set the defaults for protected result indicator
+ */
+ if (eap_aka_sim_session->send_result_ind &&
+ !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
+ vp->vp_bool = true;
+ }
- RETURN_MODULE_OK;
+ return CALL_SECTION(send_aka_challenge_request);
}
-/** FAILURE state - State machine exit point after sending EAP-Failure
+/** Resume after 'recv Challenge-Response { ... }'
*
- * Should never actually be called. Is just a placeholder function to represent the FAILURE
- * termination state. Could equally be a NULL pointer, but then on a logic error
- * we'd get a SEGV instead of a more friendly assert/failure rcode.
- */
-static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
- UNUSED request_t *request)
-{
- fr_assert(0); /* Should never actually be called */
- RETURN_MODULE_FAIL;
-}
-
-/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the SIM-Challenge message.
*
- * - Continue based on received AT_SUBTYPE value:
- * - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }'
- * - Anything else, enter the FAILURE-NOTIFICATION state.
+ * Verify that MAC, and RES match what we expect.
*/
-static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(recv_sim_challenge_response)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
+ eap_session_t *eap_session = eap_session_get(request->parent);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ uint8_t sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
+ uint8_t *p = sres_cat;
- fr_pair_t *subtype_vp = NULL;
- fr_pair_list_t vps;
+ uint8_t calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+ ssize_t slen;
+ fr_pair_t *mac;
- fr_pair_list_init(&vps);
- common_decode(&subtype_vp, &vps, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+ SECTION_RCODE_PROCESS;
-#ifdef __clang_analyzer__
- if (!subtype_vp) {
- RETURN_MODULE_FAIL;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+ p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+ p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+ memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+
+ mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+ if (!mac) {
+ REDEBUG("Missing AT_MAC attribute");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+ REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+ AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+ goto failure;
}
-#endif
- switch (subtype_vp->vp_uint16) {
- case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION:
- RDEBUG2("Failure-Notification ACKed, sending EAP-Failure");
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_failure_notification_ack,
- RLM_MODULE_NOOP,
- common_failure_notification_recv_resume,
- mod_signal,
- NULL);
+ // TODO: MAC validation should be in the decoder
+ slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+ eap_aka_sim_session->mac_md,
+ eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+ sres_cat, sizeof(sres_cat));
+ if (slen < 0) {
+ RPEDEBUG("Failed calculating MAC");
+ goto failure;
+ } else if (slen == 0) {
+ REDEBUG("Zero length AT_MAC attribute");
+ goto failure;
+ }
- default:
- RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway");
- return common_eap_failure_enter(p_result, mctx, request, eap_session);
+ if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+ RDEBUG2("Received MAC matches calculated MAC");
+ } else {
+ REDEBUG("Received MAC does not match calculated MAC");
+ RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+ RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+ goto failure;
}
-}
-/** SUCCESS state - State machine exit point after sending EAP-Success
- *
- * Should never actually be called. Is just a placeholder function to represent the FAILURE
- * termination state. Could equally be a NULL pointer, but then on a logic error
- * we'd get a SEGV instead of a more friendly assert/failure rcode.
- */
-static unlang_action_t common_eap_success(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
- UNUSED request_t *request)
-{
- fr_assert(0);
- RETURN_MODULE_FAIL;
-}
+ eap_aka_sim_session->challenge_success = true;
-/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
- *
- * - Call 'recv Success-Notification-Ack { ... }'
- */
-static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
+ /*
+ * If the peer wants a Success notification, and
+ * we included AT_RESULT_IND then send a success
+ * notification, otherwise send a normal EAP-Success.
+ */
+ if (eap_aka_sim_session->send_result_ind) {
+ if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("We wanted to use protected result indications, but peer does not");
+ eap_aka_sim_session->send_result_ind = false;
+ } else {
+ return STATE_TRANSITION(common_success_notification);
+ }
+ } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+ RDEBUG("Peer wanted to use protected result indications, but we do not");
+ }
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_success_notification_ack,
- RLM_MODULE_NOOP,
- common_success_notification_ack_recv_resume,
- mod_signal,
- NULL);
+ return STATE_TRANSITION(eap_success);
}
-
-/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
+/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
*
* - Continue based on received AT_SUBTYPE value:
- * - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }'
- * - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that
- * send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure.
+ * - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'.
+ * - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that
+ * send a EAP-Request/SIM-Notification indicating a General Failure.
* - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(sim_challenge)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
- fr_pair_t *subtype_vp = NULL;
- fr_pair_list_t from_peer;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *subtype_vp = NULL;
- fr_pair_list_init(&from_peer);
- common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-
-#ifdef __clang_analyzer__
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
if (!subtype_vp) {
- RETURN_MODULE_FAIL;
+ REDEBUG("Missing AT_SUBTYPE");
+ goto fail;
}
-#endif
- /*
- * These aren't allowed in Reauthentication responses as they don't apply:
- *
- * EAP_AKA_AUTHENTICATION_REJECT - We didn't provide an AUTN value
- * EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors.
- */
+
switch (subtype_vp->vp_uint16) {
- case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION:
- /*
- * AT_COUNTER_TOO_SMALL is handled
- * in common_reauthentication_response_process.
- */
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_reauthentication_response,
- RLM_MODULE_NOOP,
- common_reauthentication_response_recv_resume,
- mod_signal,
- NULL);
+ case FR_SUBTYPE_VALUE_SIM_CHALLENGE:
+ return CALL_SECTION(recv_sim_challenge_response);
/*
* Case 1 where we're allowed to send an EAP-Failure
*/
case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
- client_error_debug(request, &from_peer);
+ client_error_debug(request);
eap_aka_sim_session->allow_encrypted = false;
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_client_error,
- RLM_MODULE_NOOP,
- common_client_error_recv_resume,
- mod_signal,
- NULL);
+ return CALL_SECTION(recv_common_client_error);
/*
- * RFC 4187 says we *MUST* notify, not just
+ * RFC 4186 says we *MUST* notify, not just
* send an EAP-Failure in this case.
*/
default:
REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+ fail:
eap_aka_sim_session->allow_encrypted = false;
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+
+ return STATE_TRANSITION(common_failure_notification);
}
}
-/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
+/** Resume after 'send Challenge-Request { ... }'
*
- * - Continue based on received AT_SUBTYPE value:
- * - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'.
- * - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }' and after that
- * send a EAP-Request/SIM-Notification indicating a General Failure.
- * - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'.
- * - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that
- * send a EAP-Request/AKA-Notification indicating a General Failure.
- * - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(send_sim_challenge_request)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
- fr_pair_t *subtype_vp = NULL;
fr_pair_t *vp;
- fr_pair_list_t from_peer;
+ fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
+
+ fr_pair_t *kdf_id;
- fr_pair_list_init(&from_peer);
- common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+ SECTION_RCODE_PROCESS;
-#ifdef __clang_analyzer__
- if (!subtype_vp) {
+ /*
+ * Allow override of KDF Identity
+ *
+ * Because certain handset manufacturers don't
+ * implement RFC 4187 correctly and use the
+ * wrong identity as input the the PRF/KDF.
+ */
+ kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+ if (kdf_id) {
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+ fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+ }
+
+ RDEBUG2("Acquiring GSM vector(s)");
+ if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0,
+ &eap_aka_sim_session->keys, &src) != 0) ||
+ (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1,
+ &eap_aka_sim_session->keys, &src) != 0) ||
+ (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2,
+ &eap_aka_sim_session->keys, &src) != 0)) {
+ REDEBUG("Failed retrieving SIM vectors");
RETURN_MODULE_FAIL;
}
-#endif
- switch (subtype_vp->vp_uint16) {
- case FR_SUBTYPE_VALUE_AKA_CHALLENGE:
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_challenge_response,
- RLM_MODULE_NOOP,
- aka_challenge_response_recv_resume,
- mod_signal,
- NULL);
+ fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys);
+
+ if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
/*
- * Case 2 where we're allowed to send an EAP-Failure
+ * Indicate we'd like to use protected success messages
+ * with AT_RESULT_IND
+ *
+ * Use our default, but allow user override too.
*/
- case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT:
- eap_aka_sim_session->allow_encrypted = false;
-
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.aka.recv_authentication_reject,
- RLM_MODULE_NOOP,
- aka_authentication_reject_recv_resume,
- mod_signal,
- NULL);
-
- case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE:
- {
- uint64_t new_sqn;
+ vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
+ if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
- eap_aka_sim_session->allow_encrypted = false;
+ /*
+ * Okay, we got the challenges! Put them into attributes.
+ */
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
- vp = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_auts);
- if (!vp) {
- REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS");
- failure:
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
- }
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
- switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn,
- request, vp, &eap_aka_sim_session->keys)) {
- /*
- * Add everything back that we'll need in the
- * next challenge round.
- */
- case 0:
- MEM(pair_append_control(&vp, attr_sim_sqn) >= 0);
- vp->vp_uint64 = new_sqn;
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+ fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
- MEM(pair_append_control(&vp, attr_sim_ki) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki,
- sizeof(eap_aka_sim_session->keys.auc.ki), false);
+ /*
+ * need to include an AT_MAC attribute so that it will get
+ * calculated.
+ */
+ MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+ fr_pair_value_memdup(vp, NULL, 0, false);
- MEM(pair_append_control(&vp, attr_sim_opc) >= 0);
- fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc,
- sizeof(eap_aka_sim_session->keys.auc.opc), false);
- break;
+ /*
+ * We've sent the challenge so the peer should now be able
+ * to accept encrypted attributes.
+ */
+ eap_aka_sim_session->allow_encrypted = true;
- case 1: /* Don't have Ki or OPc so something else will need to deal with this */
- break;
+ return session_and_pseudonym_store(p_result, mctx,request, eap_aka_sim_session, sim_challenge_request_send);
+}
- default:
- case -1:
- goto failure;
- }
+/** Enter the SIM-CHALLENGE state
+ *
+ */
+STATE_GUARD(sim_challenge)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ fr_pair_t *vp;
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.aka.recv_syncronization_failure,
- RLM_MODULE_NOOP,
- aka_synchronization_failure_recv_resume,
- mod_signal,
- NULL);
+ /*
+ * If we've sent either of these identities it
+ * means we've come here form a Reauthentication-Request
+ * that failed.
+ */
+ if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+ return session_and_pseudonym_clear(p_result, mctx, request,
+ eap_aka_sim_session, guard_sim_challenge);
+ /* come back when we're done */
}
+ STATE_SET(sim_challenge);
+
/*
- * Case 1 where we're allowed to send an EAP-Failure
+ * Set the defaults for protected result indicator
*/
- case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
- client_error_debug(request, &from_peer);
+ if (eap_aka_sim_session->send_result_ind &&
+ !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
+ vp->vp_bool = true;
+ }
- eap_aka_sim_session->allow_encrypted = false;
+ return CALL_SECTION(send_sim_challenge_request);
+}
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_client_error,
- RLM_MODULE_NOOP,
- common_client_error_recv_resume,
- mod_signal,
- NULL);
+/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state
+ *
+ * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
+ * to enter the correct challenge state.
+ */
+STATE_GUARD(common_challenge)
+{
+ switch (eap_aka_sim_session->type) {
+ case FR_EAP_METHOD_SIM:
+ return STATE_TRANSITION(sim_challenge);
+
+ case FR_EAP_METHOD_AKA:
+ case FR_EAP_METHOD_AKA_PRIME:
+ return STATE_TRANSITION(aka_challenge);
- /*
- * RFC 4187 says we *MUST* notify, not just
- * send an EAP-Failure in this case.
- */
default:
- REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
- eap_aka_sim_session->allow_encrypted = false;
- goto failure;
+ break;
}
+
+ fr_assert(0);
+ RETURN_MODULE_FAIL;
}
-/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
+/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }'
*
- * - Continue based on received AT_SUBTYPE value:
- * - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'.
- * - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that
- * send a EAP-Request/SIM-Notification indicating a General Failure.
- * - Anything else, enter the FAILURE-NOTIFICATION state.
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value.
+ * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
+ * - If the user requested another identity, re-enter the AKA-Identity sate.
+ * - ...or continue based on the value of &Identity-Type which was added by #aka_identity,
+ * and possibly modified by the user.
+ * - Fastauth - Enter the REAUTHENTICATION state.
+ * - Pseudonym - Call 'load pseudonym { ... }'
+ * - Permanent - Enter the CHALLENGE state.
*/
-static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(recv_aka_identity_response)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
- fr_pair_t *subtype_vp = NULL;
- fr_pair_list_t from_peer;
+ bool user_set_id_req;
+ fr_pair_t *identity_type;
- fr_pair_list_init(&from_peer);
- common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+ SECTION_RCODE_PROCESS;
-#ifdef __clang_analyzer__
- if (!subtype_vp) {
- RETURN_MODULE_FAIL;
+#if 0
+ /*
+ * Digest the identity response
+ */
+ if (eap_aka_sim_session->checkcode_md) {
+ // TODO: Need another way of doing this
+ if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
+ eap_session->this_round->response) < 0) {
+ RPEDEBUG("Failed updating checkcode");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
}
#endif
- switch (subtype_vp->vp_uint16) {
- case FR_SUBTYPE_VALUE_SIM_CHALLENGE:
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_challenge_response,
- RLM_MODULE_NOOP,
- sim_challenge_response_recv_resume,
- mod_signal,
- NULL);
-
/*
- * Case 1 where we're allowed to send an EAP-Failure
+ * See if the user wants us to request another
+ * identity.
+ *
+ * If they set one themselves don't override
+ * what they set.
*/
- case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
- client_error_debug(request, &from_peer);
+ user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
+ if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
+ if (!user_set_id_req) {
+ switch (eap_aka_sim_session->last_id_req) {
+ case AKA_SIM_ANY_ID_REQ:
+ eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+ break;
- eap_aka_sim_session->allow_encrypted = false;
+ case AKA_SIM_FULLAUTH_ID_REQ:
+ eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
+ break;
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_client_error,
- RLM_MODULE_NOOP,
- common_client_error_recv_resume,
- mod_signal,
- NULL);
+ case AKA_SIM_NO_ID_REQ: /* Should not happen */
+ fr_assert(0);
+ FALL_THROUGH;
+
+ case AKA_SIM_PERMANENT_ID_REQ:
+ REDEBUG("Peer sent no usable identities");
+ return STATE_TRANSITION(common_failure_notification);
+
+ }
+ RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
+ fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
+ fr_table_str_by_value(fr_aka_sim_id_request_table,
+ eap_aka_sim_session->id_req, "<INVALID>"));
+ }
+ return STATE_TRANSITION(aka_identity);
+ }
/*
- * RFC 4186 says we *MUST* notify, not just
- * send an EAP-Failure in this case.
+ * If the identity looks like a fast re-auth id
+ * run fast re-auth, otherwise do fullauth.
*/
- default:
- REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+ identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
+ if (identity_type) switch (identity_type->vp_uint32) {
+ case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
+ return STATE_TRANSITION(common_reauthentication);
- eap_aka_sim_session->allow_encrypted = false;
+ /*
+ * It's a pseudonym, which now needs resolving.
+ * The resume function here calls aka_challenge_enter
+ * if pseudonym resolution went ok.
+ */
+ case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
+ eap_aka_sim_session->next = guard_aka_challenge;
+ return CALL_SECTION(load_pseudonym);
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ default:
+ break;
}
+
+ return STATE_TRANSITION(aka_challenge);
}
/** AKA-IDENTITY state - Continue the state machine after receiving a response to our EAP-Request/AKA-Identity
* send a EAP-Request/SIM-Notification indicating a General Failure.
* - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(aka_identity)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
fr_pair_t *subtype_vp = NULL;
- fr_pair_list_t from_peer;
-
- fr_pair_list_init(&from_peer);
- common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-#ifdef __clang_analyzer__
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
if (!subtype_vp) {
- RETURN_MODULE_FAIL;
+ REDEBUG("Missing AT_SUBTYPE");
+ goto fail;
}
-#endif
switch (subtype_vp->vp_uint16) {
/*
fr_pair_t *id;
fr_aka_sim_id_type_t type;
- id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity);
+ id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity);
if (!id) {
/*
* 9.2. EAP-Response/Identity
* AT_IDENTITY is defined in Section 4.1.
*/
REDEBUG("EAP-Response/Identity does not contain AT_IDENTITY");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_failure_notification);
+ }
+
+ /*
+ * Add ID hint attributes to the request to help
+ * the user make policy decisions.
+ */
+ identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue);
+ if (type == AKA_SIM_ID_TYPE_PERMANENT) {
+ identity_to_permanent_identity(request, id,
+ eap_aka_sim_session->type,
+ inst->strip_permanent_identity_hint);
+ }
+
+ /*
+ * Update cryptographic identity
+ */
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)id->vp_strvalue, id->vp_length);
+
+ return unlang_module_yield_to_section(p_result,
+ request,
+ inst->actions.recv_aka_identity_response ?
+ inst->actions.recv_aka_identity_response:
+ inst->actions.recv_common_identity_response,
+ RLM_MODULE_NOOP,
+ resume_recv_aka_identity_response,
+ mod_signal,
+ eap_aka_sim_session);
+ }
+
+ /*
+ * 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 FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+ client_error_debug(request);
+
+ return CALL_SECTION(recv_common_client_error);
+
+ default:
+ /*
+ * RFC 4187 says we *MUST* notify, not just
+ * send an EAP-Failure in this case.
+ */
+ REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+ fail:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+}
+
+/** Resume after 'send Identity-Request { ... }'
+ *
+ * There are three types of user identities that can be implemented
+ * - Permanent identities such as 0123456789098765@myoperator.com
+ * Permanent identities can be identified by the leading zero followed by
+ * by 15 digits (the IMSI number).
+ * - Ephemeral identities (pseudonyms). These are identities assigned for
+ * identity privacy so the user can't be tracked. These can identities
+ * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
+ * document section 14, where a set of up to 16 encryption keys are used
+ * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
+ * randomised and stored in a datastore.
+ * - A fast resumption ID which resolves to data used for fast resumption.
+ *
+ * In order to perform full authentication the original IMSI is required for
+ * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
+ * or can't perform fast resumption, we need to request the full identity from
+ * the supplicant.
+ *
+ */
+RESUME(send_aka_identity_request)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+ SECTION_RCODE_PROCESS;
+
+ /*
+ * Update eap_aka_sim_session->id_req in case the the
+ * user set attributes in `send Identity-Request { ... }`
+ * Also removes all existing id_req attributes
+ * from the reply.
+ */
+ identity_req_set_by_user(request, eap_aka_sim_session);
+
+ /*
+ * Select the right type of identity request attribute
+ *
+ * Implement checks on identity request order described
+ * by RFC4187 section #4.1.5.
+ *
+ * The internal state machine should always handle this
+ * correctly, but the user may have other ideas...
+ */
+ if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+
+ /*
+ * Encode the packet
+ */
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_IDENTITY);
+
+#if 0
+ /*
+ * Digest the packet contents, updating our checkcode.
+ */
+ if (eap_aka_sim_session->checkcode_md) {
+ if (!eap_aka_sim_session->checkcode_state &&
+ fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state,
+ eap_aka_sim_session->checkcode_md) < 0) {
+ RPEDEBUG("Failed initialising checkcode");
+ goto failure;
+ }
+ // TODO: Need another way of doing this
+ if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
+ eap_session->this_round->request) < 0) {
+ RPEDEBUG("Failed updating checkcode");
+ goto failure;
+ }
+ }
+#endif
+
+ RETURN_MODULE_HANDLED;
+}
+
+/** Enter the AKA-IDENTITY state
+ *
+ */
+STATE_GUARD(aka_identity)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+ STATE_SET(aka_identity);
+
+ /*
+ * If we have an send_aka_identity_request section
+ * then run that, otherwise just run the normal
+ * identity request section.
+ */
+ return unlang_module_yield_to_section(p_result,
+ request,
+ inst->actions.send_aka_identity_request ?
+ inst->actions.send_aka_identity_request:
+ inst->actions.send_common_identity_request,
+ RLM_MODULE_NOOP,
+ resume_send_aka_identity_request,
+ mod_signal,
+ eap_aka_sim_session);
+}
+
+/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }'
+ *
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value.
+ * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
+ * - If the user requested another identity, re-enter the SIM-START sate.
+ * - ...or continue based on the value of &Identity-Type which was added by #sim_start,
+ * and possibly modified by the user.
+ * - Fastauth
+ * - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state.
+ * - ...or enter the REAUTHENTICATION state.
+ * - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }'
+ * - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state.
+ */
+RESUME(recv_sim_start_response)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ bool user_set_id_req;
+ fr_pair_t *identity_type;
+
+ SECTION_RCODE_PROCESS;
+
+ /*
+ * See if the user wants us to request another
+ * identity.
+ *
+ * If they set one themselves don't override
+ * what they set.
+ */
+ user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
+ if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
+ if (!user_set_id_req) {
+ switch (eap_aka_sim_session->last_id_req) {
+ case AKA_SIM_ANY_ID_REQ:
+ eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+ break;
+
+ case AKA_SIM_FULLAUTH_ID_REQ:
+ eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
+ break;
+
+ case AKA_SIM_NO_ID_REQ: /* Should not happen */
+ fr_assert(0);
+ FALL_THROUGH;
+
+ case AKA_SIM_PERMANENT_ID_REQ:
+ REDEBUG("Peer sent no usable identities");
+ failure:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
+ fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
+ fr_table_str_by_value(fr_aka_sim_id_request_table,
+ eap_aka_sim_session->id_req, "<INVALID>"));
}
+ return STATE_TRANSITION(sim_start);
+ }
+ /*
+ * If the identity looks like a fast re-auth id
+ * run fast re-auth, otherwise do fullauth.
+ */
+ identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
+ if (identity_type) switch (identity_type->vp_uint32) {
+ case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
/*
- * Add ID hint attributes to the request to help
- * the user make policy decisions.
+ * RFC 4186 Section #9.2
+ *
+ * The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY
+ * with a fast re-authentication identity is present for fast
+ * re-authentication
*/
- identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue);
- if (type == AKA_SIM_ID_TYPE_PERMANENT) {
- identity_to_permanent_identity(request, id,
- eap_aka_sim_session->type,
- inst->strip_permanent_identity_hint);
+ if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt)) {
+ REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages");
+ return STATE_TRANSITION(common_failure_notification);
}
/*
- * Update cryptographic identity
+ * RFC 4186 Section #9.2
+ *
+ * The AT_SELECTED_VERSION attribute MUST NOT be included if the
+ * AT_IDENTITY attribute with a fast re-authentication identity is
+ * present for fast re-authentication.
*/
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)id->vp_strvalue, id->vp_length);
+ if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version)) {
+ REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages");
+ return STATE_TRANSITION(common_failure_notification);
+ }
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.aka.recv_aka_identity_response?
- inst->actions.aka.recv_aka_identity_response:
- inst->actions.recv_identity_response,
- RLM_MODULE_NOOP,
- aka_identity_response_recv_resume,
- mod_signal,
- NULL);
- }
+ return STATE_TRANSITION(common_reauthentication);
/*
- * 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.
+ * It's a pseudonym, which now needs resolving.
+ * The resume function here calls aka_challenge_enter
+ * if pseudonym resolution went ok.
*/
- case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
- client_error_debug(request, &from_peer);
+ case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
+ if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure;
+ if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure;
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_client_error,
- RLM_MODULE_NOOP,
- common_client_error_recv_resume,
- mod_signal,
- NULL);
+ eap_aka_sim_session->next = guard_sim_challenge;
+ return CALL_SECTION(load_pseudonym);
+
+ /*
+ * If it's a permanent ID, copy it over to
+ * the session state list for use in the
+ * store pseudonym/store session sections
+ * later.
+ */
+ case FR_IDENTITY_TYPE_VALUE_PERMANENT:
+ if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure;
+ if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure;
+ FALL_THROUGH;
default:
- /*
- * RFC 4187 says we *MUST* notify, not just
- * send an EAP-Failure in this case.
- */
- REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ break;
}
+
+ return STATE_TRANSITION(sim_challenge);
}
/** SIM-START state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
* send a EAP-Request/SIM-Notification indicating a General Failure.
* - Anything else, enter the FAILURE-NOTIFICATION state.
*/
-static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(sim_start)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
-
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
fr_pair_t *subtype_vp = NULL;
- fr_pair_list_t from_peer;
- fr_pair_list_init(&from_peer);
- common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
- if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-
-#ifdef __clang_analyzer__
+ subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
if (!subtype_vp) {
- RETURN_MODULE_FAIL;
+ REDEBUG("Missing AT_SUBTYPE");
+ goto fail;
}
-#endif
-
switch (subtype_vp->vp_uint16) {
case FR_SUBTYPE_VALUE_SIM_START:
{
fr_pair_t *id;
fr_aka_sim_id_type_t type;
- id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity);
+ id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity);
if (!id) {
/*
* RFC 4186 Section #9.2
* AT_IDENTITY is defined in Section 4.1.
*/
REDEBUG("EAP-Response/SIM/Start does not contain AT_IDENTITY");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_failure_notification);
}
/*
/*
* Update cryptographic identity
*/
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)id->vp_strvalue, id->vp_length);
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)id->vp_strvalue, id->vp_length);
return unlang_module_yield_to_section(p_result,
request,
- inst->actions.sim.recv_sim_start_response?
- inst->actions.sim.recv_sim_start_response:
- inst->actions.recv_identity_response,
+ inst->actions.recv_sim_start_response?
+ inst->actions.recv_sim_start_response:
+ inst->actions.recv_common_identity_response,
RLM_MODULE_NOOP,
- sim_start_response_recv_resume,
+ resume_recv_sim_start_response,
mod_signal,
- NULL);
+ eap_aka_sim_session);
}
/*
* identity.
*/
case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
- client_error_debug(request, &from_peer);
+ client_error_debug(request);
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_client_error,
- RLM_MODULE_NOOP,
- common_client_error_recv_resume,
- mod_signal,
- NULL);
+ return CALL_SECTION(recv_common_client_error);
default:
/*
* send an EAP-Failure in this case.
*/
REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ fail:
+ return STATE_TRANSITION(common_failure_notification);
+ }
+}
+
+/** Resume after 'send Start { ... }'
+ *
+ * Send a EAP-Request/SIM-Start message to the supplicant
+ *
+ * There are three types of user identities that can be implemented
+ * - Permanent identities such as 0123456789098765@myoperator.com
+ * Permanent identities can be identified by the leading zero followed by
+ * by 15 digits (the IMSI number).
+ * - Ephemeral identities (pseudonyms). These are identities assigned for
+ * identity privacy so the user can't be tracked. These can identities
+ * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
+ * document section 14, where a set of up to 16 encryption keys are used
+ * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
+ * randomised and stored in a datastore.
+ * - A fast resumption ID which resolves to data used for fast resumption.
+ *
+ * In order to perform full authentication the original IMSI is required for
+ * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
+ * or can't perform fast resumption, we need to request the full identity from
+ * the supplicant.
+ */
+RESUME(send_sim_start)
+{
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+ fr_pair_t *vp;
+ uint8_t *p, *end;
+
+ SECTION_RCODE_PROCESS;
+
+ p = eap_aka_sim_session->keys.gsm.version_list;
+ end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list);
+ eap_aka_sim_session->keys.gsm.version_list_len = 0;
+
+ /*
+ * If the user provided no versions, then
+ * just add the default (1).
+ */
+ if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) {
+ MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0);
+ vp->vp_uint16 = EAP_SIM_VERSION;
+ }
+
+ /*
+ * Iterate over the the versions adding them
+ * to the version list we use for keying.
+ */
+ for (vp = fr_pair_list_head(&request->reply_pairs);
+ vp;
+ vp = fr_pair_list_next(&request->reply_pairs, vp)) {
+ if (vp->da != attr_eap_aka_sim_version_list) continue;
+
+ if ((end - p) < 2) break;
+
+ /*
+ * Store as big endian
+ */
+ *p++ = (vp->vp_uint16 & 0xff00) >> 8;
+ *p++ = (vp->vp_uint16 & 0x00ff);
+ eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t);
+ }
+
+ /*
+ * Update eap_aka_sim_session->id_req in case the the
+ * user set attributes in `send Identity-Request { ... }`
+ * Also removes all existing id_req attributes
+ * from the reply.
+ */
+ identity_req_set_by_user(request, eap_aka_sim_session);
+
+ /*
+ * Select the right type of identity request attribute
+ *
+ * Implement checks on identity request order described
+ * by RFC4186 section #4.2.5.
+ *
+ * The internal state machine should always handle this
+ * correctly, but the user may have other ideas...
+ */
+ if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+ return STATE_TRANSITION(common_failure_notification);
+ }
+ eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+
+ common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_START);
+
+ RETURN_MODULE_HANDLED;
+}
+
+/** Enter the SIM-START state
+ *
+ */
+STATE_GUARD(sim_start)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+ STATE_SET(sim_start);
+
+ return unlang_module_yield_to_section(p_result,
+ request,
+ inst->actions.send_sim_start_request ?
+ inst->actions.send_sim_start_request:
+ inst->actions.send_common_identity_request,
+ RLM_MODULE_NOOP,
+ resume_send_sim_start,
+ mod_signal,
+ eap_aka_sim_session);
+}
+
+/** Enter the SIM-START or AKA-IDENTITY state
+ *
+ * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
+ * to enter the correct Identity-Request state.
+ */
+STATE_GUARD(common_identity)
+{
+ switch (eap_aka_sim_session->type) {
+ case FR_EAP_METHOD_SIM:
+ return STATE_TRANSITION(sim_start);
+
+ case FR_EAP_METHOD_AKA:
+ case FR_EAP_METHOD_AKA_PRIME:
+ return STATE_TRANSITION(aka_identity);
+
+ default:
+ break;
}
+
+ fr_assert(0);
+ RETURN_MODULE_FAIL;
}
/** Resume after 'recv Identity-Response { ... }'
* - If identity is a fastauth identity, enter the REAUTHENTICATE state.
* - If identity is a permanent identity, enter the CHALLENGE state.
*/
-static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
- request_t *request, UNUSED void *rctx)
+RESUME(recv_common_identity_response)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
- eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
- eap_aka_sim_session_t);
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
fr_pair_t *eap_type, *method, *identity_type;
fr_aka_sim_method_hint_t running, hinted;
- fr_pair_list_t *from_peer = &request->request_pairs;
- section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+ SECTION_RCODE_PROCESS;
/*
* Ignore attempts to change the EAP-Type
eap_type = fr_pair_find_by_da(&request->control_pairs, attr_eap_type);
if (eap_type) RWDEBUG("Ignoring &control.EAP-Type, this must be set *before* the EAP module is called");
- method = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_method_hint);
+ method = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_method_hint);
/*
* Set default configuration, we may allow these
* use it... It has stronger keying, and
* binds authentication to the network.
*/
- switch (eap_session->type) {
+ switch (eap_aka_sim_session->type) {
case FR_EAP_METHOD_SIM:
RDEBUG2("New EAP-SIM session");
eap_aka_sim_session->type = FR_EAP_METHOD_SIM;
eap_aka_sim_session->mac_md = EVP_sha1(); /* no checkcode support, so no checkcode_md */
-
- /*
- * RFC 5448 makes no mention of being
- * able to use this with EAP-SIM, so it's
- * permanently disabled for that EAP method.
- */
- eap_aka_sim_session->send_at_bidding_prefer_prime = false;
break;
case FR_EAP_METHOD_AKA:
eap_aka_sim_session->type = FR_EAP_METHOD_AKA;
eap_aka_sim_session->checkcode_md = eap_aka_sim_session->mac_md = EVP_sha1();
- eap_aka_sim_session->send_at_bidding_prefer_prime = inst->send_at_bidding_prefer_prime;
break;
case FR_EAP_METHOD_AKA_PRIME:
* can 'decorate' the identity in the identity
* response.
*/
- if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) {
- return common_identity_enter(p_result, mctx, request, eap_session);
- }
+ if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) return STATE_TRANSITION(common_identity);
/*
* If the identity looks like a fast re-auth id
* run fast re-auth, otherwise do a fullauth.
*/
- identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
+ identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
if (identity_type) switch (identity_type->vp_uint32) {
case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
- return common_reauthentication_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_reauthentication);
/*
* It's a pseudonym, which now needs resolving.
* if pseudonym resolution went ok.
*/
case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.load_pseudonym,
- RLM_MODULE_NOOP,
- pseudonym_load_resume,
- mod_signal,
- (void *)common_challenge_enter);
+ eap_aka_sim_session->next = guard_common_challenge;
+ return CALL_SECTION(load_pseudonym);
case FR_IDENTITY_TYPE_VALUE_PERMANENT:
default:
break;
}
- return common_challenge_enter(p_result, mctx, request, eap_session);
-}
-
-/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys
- *
- * @param[in] eap_aka_sim_session to free.
- * @return 0
- */
-static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session)
-{
- memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session));
- return 0;
+ return STATE_TRANSITION(common_challenge);
}
-/** Enter the EAP-IDENTITY state - State machine entry point
+/** Enter the EAP-IDENTITY state
*
* - Process the incoming EAP-Identity-Response
* - Start EAP-SIM/EAP-AKA/EAP-AKA' state machine optionally calling 'recv Identity-Response { ... }'
*/
-unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(init)
{
- eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
eap_session_t *eap_session = eap_session_get(request->parent);
- eap_aka_sim_session_t *eap_aka_sim_session;
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
fr_pair_t *vp;
fr_aka_sim_id_type_t type;
-
- MEM(eap_aka_sim_session = talloc_zero(eap_session, eap_aka_sim_session_t));
- talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free);
-
- eap_session->opaque = eap_aka_sim_session;
-
- /*
- * This value doesn't have be strong, but it is
- * good if it is different now and then.
- */
- eap_aka_sim_session->id = (fr_rand() & 0xff);
-
/*
* Verify we received an EAP-Response/Identity
* message before the supplicant started sending
*/
if (!eap_session->identity) {
REDEBUG("All SIM or AKA exchanges must begin with a EAP-Response/Identity message");
- return common_failure_notification_enter(p_result, mctx, request, eap_session);
+ return STATE_TRANSITION(common_failure_notification);
}
/*
- * Add ID hint attributes to the request to help
- * the user make policy decisions.
- */
-
- /*
- * Copy the EAP-Identity into and Identity
+ * Copy the EAP-Identity into our Identity
* attribute to make policies easier.
*/
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >= 0);
inst->strip_permanent_identity_hint);
}
- identity_to_crypto_identity(request, eap_aka_sim_session,
- (uint8_t const *)eap_session->identity,
- talloc_array_length(eap_session->identity) - 1);
+ /*
+ * Set the initial crypto identity from
+ * the EAP-Identity-Response
+ */
+ crypto_identity_set(request, eap_aka_sim_session,
+ (uint8_t const *)eap_session->identity,
+ talloc_array_length(eap_session->identity) - 1);
+
+ return CALL_SECTION(recv_common_identity_response);
+}
+
+/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys
+ *
+ * @param[in] eap_aka_sim_session to free.
+ * @return 0
+ */
+static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session)
+{
+ memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session));
+ return 0;
+}
+/** Resumes the state machine when receiving a new response packet
+ *
+ */
+unlang_action_t eap_aka_sim_state_machine_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+ eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+ eap_aka_sim_session_t *eap_aka_sim_session = request_data_reference(request,
+ (void *)eap_aka_sim_state_machine_process,
+ 0);
/*
- * Running the same section as Identity-Response
- * makes policies significantly easier.
+ * A new EAP-SIM/AKA/AKA' session!
*/
- return unlang_module_yield_to_section(p_result,
- request,
- inst->actions.recv_identity_response,
- RLM_MODULE_NOOP,
- common_eap_identity_resume,
- mod_signal,
- NULL);
+ if (!eap_aka_sim_session) {
+ /*
+ * Must be allocated in the NULL ctx as this will
+ * need to persist over multiple rounds of EAP.
+ */
+ MEM(eap_aka_sim_session = talloc_zero(NULL, eap_aka_sim_session_t));
+ talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free);
+
+ /*
+ * Add new session data to the request
+ * We only ever need to do this once as it's restored
+ * during the next round of EAP automatically.
+ *
+ * It will also be freed automatically if the request
+ * is freed and persistable data hasn't been moved
+ * into the parent.
+ */
+ if (unlikely(request_data_add(request, (void *)eap_aka_sim_state_machine_process, 0,
+ eap_aka_sim_session, true, true, true) < 0)) {
+ RPEDEBUG("Failed creating new EAP-SIM/AKA/AKA' session");
+ RETURN_MODULE_FAIL;
+ }
+ eap_aka_sim_session->type = inst->type;
+
+ return state_init(p_result, mctx, request, eap_aka_sim_session);
+ }
+
+ if (!fr_cond_assert(eap_aka_sim_session->state)) RETURN_MODULE_FAIL;
+
+ return eap_aka_sim_session->state(p_result, mctx, request, eap_aka_sim_session);
}