/* * hostapd / EAP-AKA (RFC 4187) and EAP-AKA' (RFC 5448) * Copyright (c) 2005-2012, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include "common.h" #include "utils/base64.h" #include "crypto/sha256.h" #include "crypto/crypto.h" #include "crypto/random.h" #include "eap_common/eap_sim_common.h" #include "eap_server/eap_i.h" #include "eap_server/eap_sim_db.h" struct eap_aka_data { u8 mk[EAP_SIM_MK_LEN]; u8 nonce_s[EAP_SIM_NONCE_S_LEN]; u8 k_aut[EAP_AKA_PRIME_K_AUT_LEN]; u8 k_encr[EAP_SIM_K_ENCR_LEN]; u8 k_re[EAP_AKA_PRIME_K_RE_LEN]; /* EAP-AKA' only */ u8 msk[EAP_SIM_KEYING_DATA_LEN]; u8 emsk[EAP_EMSK_LEN]; u8 rand[EAP_AKA_RAND_LEN]; u8 autn[EAP_AKA_AUTN_LEN]; u8 ck[EAP_AKA_CK_LEN]; u8 ik[EAP_AKA_IK_LEN]; u8 res[EAP_AKA_RES_MAX_LEN]; u8 reauth_mac[EAP_SIM_MAC_LEN]; size_t res_len; enum { IDENTITY, CHALLENGE, REAUTH, NOTIFICATION, SUCCESS, FAILURE } state; char *next_pseudonym; char *next_reauth_id; u16 counter; struct eap_sim_reauth *reauth; int auts_reported; /* whether the current AUTS has been reported to the * eap_sim_db */ u16 notification; int use_result_ind; struct wpabuf *id_msgs; int pending_id; u8 eap_method; u8 *network_name; size_t network_name_len; u16 kdf; int identity_round; char permanent[20]; /* Permanent username */ }; static void eap_aka_fullauth(struct eap_sm *sm, struct eap_aka_data *data); static const char * eap_aka_state_txt(int state) { switch (state) { case IDENTITY: return "IDENTITY"; case CHALLENGE: return "CHALLENGE"; case REAUTH: return "REAUTH"; case SUCCESS: return "SUCCESS"; case FAILURE: return "FAILURE"; case NOTIFICATION: return "NOTIFICATION"; default: return "Unknown?!"; } } static void eap_aka_state(struct eap_aka_data *data, int state) { wpa_printf(MSG_DEBUG, "EAP-AKA: %s -> %s", eap_aka_state_txt(data->state), eap_aka_state_txt(state)); data->state = state; } static int eap_aka_check_identity_reauth(struct eap_sm *sm, struct eap_aka_data *data, const char *username) { if (data->eap_method == EAP_TYPE_AKA_PRIME && username[0] != EAP_AKA_PRIME_REAUTH_ID_PREFIX) return 0; if (data->eap_method == EAP_TYPE_AKA && username[0] != EAP_AKA_REAUTH_ID_PREFIX) return 0; wpa_printf(MSG_DEBUG, "EAP-AKA: Reauth username '%s'", username); data->reauth = eap_sim_db_get_reauth_entry(sm->cfg->eap_sim_db_priv, username); if (data->reauth == NULL) { wpa_printf(MSG_DEBUG, "EAP-AKA: Unknown reauth identity - " "request full auth identity"); /* Remain in IDENTITY state for another round */ return 0; } if (data->reauth->counter > sm->cfg->eap_sim_aka_fast_reauth_limit) { wpa_printf(MSG_DEBUG, "EAP-AKA: Too many fast re-authentication attemps - fall back to full authentication"); if (sm->cfg->eap_sim_id & 0x04) { wpa_printf(MSG_DEBUG, "EAP-AKA: Permanent identity recognized - skip AKA-Identity exchange"); os_strlcpy(data->permanent, data->reauth->permanent, sizeof(data->permanent)); os_strlcpy(sm->sim_aka_permanent, data->reauth->permanent, sizeof(sm->sim_aka_permanent)); eap_sim_db_remove_reauth(sm->cfg->eap_sim_db_priv, data->reauth); data->reauth = NULL; eap_aka_fullauth(sm, data); return 1; } return 0; } wpa_printf(MSG_DEBUG, "EAP-AKA: Using fast re-authentication (counter=%d)", data->reauth->counter); os_strlcpy(data->permanent, data->reauth->permanent, sizeof(data->permanent)); data->counter = data->reauth->counter; if (data->eap_method == EAP_TYPE_AKA_PRIME) { os_memcpy(data->k_encr, data->reauth->k_encr, EAP_SIM_K_ENCR_LEN); os_memcpy(data->k_aut, data->reauth->k_aut, EAP_AKA_PRIME_K_AUT_LEN); os_memcpy(data->k_re, data->reauth->k_re, EAP_AKA_PRIME_K_RE_LEN); } else { os_memcpy(data->mk, data->reauth->mk, EAP_SIM_MK_LEN); } eap_aka_state(data, REAUTH); return 1; } static void eap_aka_check_identity(struct eap_sm *sm, struct eap_aka_data *data) { char *username; const u8 *identity = sm->identity; size_t identity_len = sm->identity_len; if (sm->sim_aka_permanent[0]) { identity = (const u8 *) sm->sim_aka_permanent; identity_len = os_strlen(sm->sim_aka_permanent); } /* Check if we already know the identity from EAP-Response/Identity */ username = sim_get_username(identity, identity_len); if (username == NULL) return; if (eap_aka_check_identity_reauth(sm, data, username) > 0) { os_free(username); /* * Since re-auth username was recognized, skip AKA/Identity * exchange. */ return; } if (sm->sim_aka_permanent[0] && data->state == IDENTITY) { /* Skip AKA/Identity exchange since the permanent identity * was recognized. */ os_free(username); os_strlcpy(data->permanent, sm->sim_aka_permanent, sizeof(data->permanent)); eap_aka_fullauth(sm, data); return; } if ((data->eap_method == EAP_TYPE_AKA_PRIME && username[0] == EAP_AKA_PRIME_PSEUDONYM_PREFIX) || (data->eap_method == EAP_TYPE_AKA && username[0] == EAP_AKA_PSEUDONYM_PREFIX)) { const char *permanent; wpa_printf(MSG_DEBUG, "EAP-AKA: Pseudonym username '%s'", username); permanent = eap_sim_db_get_permanent( sm->cfg->eap_sim_db_priv, username); if (permanent == NULL) { os_free(username); wpa_printf(MSG_DEBUG, "EAP-AKA: Unknown pseudonym " "identity - request permanent identity"); /* Remain in IDENTITY state for another round */ return; } os_strlcpy(data->permanent, permanent, sizeof(data->permanent)); /* * Since pseudonym username was recognized, skip AKA/Identity * exchange. */ eap_aka_fullauth(sm, data); } os_free(username); } static void * eap_aka_init(struct eap_sm *sm) { struct eap_aka_data *data; if (!sm->cfg->eap_sim_db_priv) { wpa_printf(MSG_WARNING, "EAP-AKA: eap_sim_db not configured"); return NULL; } data = os_zalloc(sizeof(*data)); if (data == NULL) return NULL; data->eap_method = EAP_TYPE_AKA; data->state = IDENTITY; data->pending_id = -1; eap_aka_check_identity(sm, data); return data; } #ifdef EAP_SERVER_AKA_PRIME static void * eap_aka_prime_init(struct eap_sm *sm) { struct eap_aka_data *data; /* TODO: make ANID configurable; see 3GPP TS 24.302 */ char *network_name = "WLAN"; if (sm->cfg->eap_sim_db_priv == NULL) { wpa_printf(MSG_WARNING, "EAP-AKA: eap_sim_db not configured"); return NULL; } data = os_zalloc(sizeof(*data)); if (data == NULL) return NULL; data->eap_method = EAP_TYPE_AKA_PRIME; data->network_name = (u8 *) os_strdup(network_name); if (data->network_name == NULL) { os_free(data); return NULL; } data->network_name_len = os_strlen(network_name); data->state = IDENTITY; data->pending_id = -1; eap_aka_check_identity(sm, data); return data; } #endif /* EAP_SERVER_AKA_PRIME */ static void eap_aka_reset(struct eap_sm *sm, void *priv) { struct eap_aka_data *data = priv; os_free(data->next_pseudonym); os_free(data->next_reauth_id); wpabuf_free(data->id_msgs); os_free(data->network_name); bin_clear_free(data, sizeof(*data)); } static int eap_aka_add_id_msg(struct eap_aka_data *data, const struct wpabuf *msg) { if (msg == NULL) return -1; if (data->id_msgs == NULL) { data->id_msgs = wpabuf_dup(msg); return data->id_msgs == NULL ? -1 : 0; } if (wpabuf_resize(&data->id_msgs, wpabuf_len(msg)) < 0) return -1; wpabuf_put_buf(data->id_msgs, msg); return 0; } static void eap_aka_add_checkcode(struct eap_aka_data *data, struct eap_sim_msg *msg) { const u8 *addr; size_t len; u8 hash[SHA256_MAC_LEN]; wpa_printf(MSG_DEBUG, " AT_CHECKCODE"); if (data->id_msgs == NULL) { /* * No EAP-AKA/Identity packets were exchanged - send empty * checkcode. */ eap_sim_msg_add(msg, EAP_SIM_AT_CHECKCODE, 0, NULL, 0); return; } /* Checkcode is SHA1 hash over all EAP-AKA/Identity packets. */ addr = wpabuf_head(data->id_msgs); len = wpabuf_len(data->id_msgs); wpa_hexdump(MSG_MSGDUMP, "EAP-AKA: AT_CHECKCODE data", addr, len); if (data->eap_method == EAP_TYPE_AKA_PRIME) sha256_vector(1, &addr, &len, hash); else sha1_vector(1, &addr, &len, hash); eap_sim_msg_add(msg, EAP_SIM_AT_CHECKCODE, 0, hash, data->eap_method == EAP_TYPE_AKA_PRIME ? EAP_AKA_PRIME_CHECKCODE_LEN : EAP_AKA_CHECKCODE_LEN); } static int eap_aka_verify_checkcode(struct eap_aka_data *data, const u8 *checkcode, size_t checkcode_len) { const u8 *addr; size_t len; u8 hash[SHA256_MAC_LEN]; size_t hash_len; if (checkcode == NULL) return -1; if (data->id_msgs == NULL) { if (checkcode_len != 0) { wpa_printf(MSG_DEBUG, "EAP-AKA: Checkcode from peer " "indicates that AKA/Identity messages were " "used, but they were not"); return -1; } return 0; } hash_len = data->eap_method == EAP_TYPE_AKA_PRIME ? EAP_AKA_PRIME_CHECKCODE_LEN : EAP_AKA_CHECKCODE_LEN; if (checkcode_len != hash_len) { wpa_printf(MSG_DEBUG, "EAP-AKA: Checkcode from peer indicates " "that AKA/Identity message were not used, but they " "were"); return -1; } /* Checkcode is SHA1 hash over all EAP-AKA/Identity packets. */ addr = wpabuf_head(data->id_msgs); len = wpabuf_len(data->id_msgs); if (data->eap_method == EAP_TYPE_AKA_PRIME) sha256_vector(1, &addr, &len, hash); else sha1_vector(1, &addr, &len, hash); if (os_memcmp_const(hash, checkcode, hash_len) != 0) { wpa_printf(MSG_DEBUG, "EAP-AKA: Mismatch in AT_CHECKCODE"); return -1; } return 0; } static struct wpabuf * eap_aka_build_identity(struct eap_sm *sm, struct eap_aka_data *data, u8 id) { struct eap_sim_msg *msg; struct wpabuf *buf; wpa_printf(MSG_DEBUG, "EAP-AKA: Generating Identity"); msg = eap_sim_msg_init(EAP_CODE_REQUEST, id, data->eap_method, EAP_AKA_SUBTYPE_IDENTITY); data->identity_round++; if (data->identity_round == 1) { /* * RFC 4187, Chap. 4.1.4 recommends that identity from EAP is * ignored and the AKA/Identity is used to request the * identity. */ wpa_printf(MSG_DEBUG, " AT_ANY_ID_REQ"); eap_sim_msg_add(msg, EAP_SIM_AT_ANY_ID_REQ, 0, NULL, 0); } else if (data->identity_round > 3) { /* Cannot use more than three rounds of Identity messages */ eap_sim_msg_free(msg); return NULL; } else if (sm->identity && sm->identity_len > 0 && (sm->identity[0] == EAP_AKA_REAUTH_ID_PREFIX || sm->identity[0] == EAP_AKA_PRIME_REAUTH_ID_PREFIX)) { /* Reauth id may have expired - try fullauth */ wpa_printf(MSG_DEBUG, " AT_FULLAUTH_ID_REQ"); eap_sim_msg_add(msg, EAP_SIM_AT_FULLAUTH_ID_REQ, 0, NULL, 0); } else { wpa_printf(MSG_DEBUG, " AT_PERMANENT_ID_REQ"); eap_sim_msg_add(msg, EAP_SIM_AT_PERMANENT_ID_REQ, 0, NULL, 0); } buf = eap_sim_msg_finish(msg, data->eap_method, NULL, NULL, 0); if (eap_aka_add_id_msg(data, buf) < 0) { wpabuf_free(buf); return NULL; } data->pending_id = id; return buf; } static int eap_aka_build_encr(struct eap_sm *sm, struct eap_aka_data *data, struct eap_sim_msg *msg, u16 counter, const u8 *nonce_s) { os_free(data->next_pseudonym); if (!(sm->cfg->eap_sim_id & 0x01)) { /* Use of pseudonyms disabled in configuration */ data->next_pseudonym = NULL; } else if (!nonce_s) { data->next_pseudonym = eap_sim_db_get_next_pseudonym( sm->cfg->eap_sim_db_priv, data->eap_method == EAP_TYPE_AKA_PRIME ? EAP_SIM_DB_AKA_PRIME : EAP_SIM_DB_AKA); } else { /* Do not update pseudonym during re-authentication */ data->next_pseudonym = NULL; } os_free(data->next_reauth_id); if (!(sm->cfg->eap_sim_id & 0x02)) { /* Use of fast reauth disabled in configuration */ data->next_reauth_id = NULL; } else if (data->counter <= EAP_AKA_MAX_FAST_REAUTHS) { data->next_reauth_id = eap_sim_db_get_next_reauth_id( sm->cfg->eap_sim_db_priv, data->eap_method == EAP_TYPE_AKA_PRIME ? EAP_SIM_DB_AKA_PRIME : EAP_SIM_DB_AKA); } else { wpa_printf(MSG_DEBUG, "EAP-AKA: Max fast re-authentication " "count exceeded - force full authentication"); data->next_reauth_id = NULL; } if (data->next_pseudonym == NULL && data->next_reauth_id == NULL && counter == 0 && nonce_s == NULL) return 0; wpa_printf(MSG_DEBUG, " AT_IV"); wpa_printf(MSG_DEBUG, " AT_ENCR_DATA"); eap_sim_msg_add_encr_start(msg, EAP_SIM_AT_IV, EAP_SIM_AT_ENCR_DATA); if (counter > 0) { wpa_printf(MSG_DEBUG, " *AT_COUNTER (%u)", counter); eap_sim_msg_add(msg, EAP_SIM_AT_COUNTER, counter, NULL, 0); } if (nonce_s) { wpa_printf(MSG_DEBUG, " *AT_NONCE_S"); eap_sim_msg_add(msg, EAP_SIM_AT_NONCE_S, 0, nonce_s, EAP_SIM_NONCE_S_LEN); } if (data->next_pseudonym) { wpa_printf(MSG_DEBUG, " *AT_NEXT_PSEUDONYM (%s)", data->next_pseudonym); eap_sim_msg_add(msg, EAP_SIM_AT_NEXT_PSEUDONYM, os_strlen(data->next_pseudonym), (u8 *) data->next_pseudonym, os_strlen(data->next_pseudonym)); } if (data->next_reauth_id) { wpa_printf(MSG_DEBUG, " *AT_NEXT_REAUTH_ID (%s)", data->next_reauth_id); eap_sim_msg_add(msg, EAP_SIM_AT_NEXT_REAUTH_ID, os_strlen(data->next_reauth_id), (u8 *) data->next_reauth_id, os_strlen(data->next_reauth_id)); } if (eap_sim_msg_add_encr_end(msg, data->k_encr, EAP_SIM_AT_PADDING)) { wpa_printf(MSG_WARNING, "EAP-AKA: Failed to encrypt " "AT_ENCR_DATA"); return -1; } return 0; } static struct wpabuf * eap_aka_build_challenge(struct eap_sm *sm, struct eap_aka_data *data, u8 id) { struct eap_sim_msg *msg; wpa_printf(MSG_DEBUG, "EAP-AKA: Generating Challenge"); msg = eap_sim_msg_init(EAP_CODE_REQUEST, id, data->eap_method, EAP_AKA_SUBTYPE_CHALLENGE); wpa_printf(MSG_DEBUG, " AT_RAND"); eap_sim_msg_add(msg, EAP_SIM_AT_RAND, 0, data->rand, EAP_AKA_RAND_LEN); wpa_printf(MSG_DEBUG, " AT_AUTN"); eap_sim_msg_add(msg, EAP_SIM_AT_AUTN, 0, data->autn, EAP_AKA_AUTN_LEN); if (data->eap_method == EAP_TYPE_AKA_PRIME) { if (data->kdf) { /* Add the selected KDF into the beginning */ wpa_printf(MSG_DEBUG, " AT_KDF"); eap_sim_msg_add(msg, EAP_SIM_AT_KDF, data->kdf, NULL, 0); } wpa_printf(MSG_DEBUG, " AT_KDF"); eap_sim_msg_add(msg, EAP_SIM_AT_KDF, EAP_AKA_PRIME_KDF, NULL, 0); wpa_printf(MSG_DEBUG, " AT_KDF_INPUT"); eap_sim_msg_add(msg, EAP_SIM_AT_KDF_INPUT, data->network_name_len, data->network_name, data->network_name_len); } if (eap_aka_build_encr(sm, data, msg, 0, NULL)) { eap_sim_msg_free(msg); return NULL; } eap_aka_add_checkcode(data, msg); if (sm->cfg->eap_sim_aka_result_ind) { wpa_printf(MSG_DEBUG, " AT_RESULT_IND"); eap_sim_msg_add(msg, EAP_SIM_AT_RESULT_IND, 0, NULL, 0); } #ifdef EAP_SERVER_AKA_PRIME if (data->eap_method == EAP_TYPE_AKA) { u16 flags = 0; int i; int aka_prime_preferred = 0; i = 0; while (sm->user && i < EAP_MAX_METHODS && (sm->user->methods[i].vendor != EAP_VENDOR_IETF || sm->user->methods[i].method != EAP_TYPE_NONE)) { if (sm->user->methods[i].vendor == EAP_VENDOR_IETF) { if (sm->user->methods[i].method == EAP_TYPE_AKA) break; if (sm->user->methods[i].method == EAP_TYPE_AKA_PRIME) { aka_prime_preferred = 1; break; } } i++; } if (aka_prime_preferred) flags |= EAP_AKA_BIDDING_FLAG_D; eap_sim_msg_add(msg, EAP_SIM_AT_BIDDING, flags, NULL, 0); } #endif /* EAP_SERVER_AKA_PRIME */ wpa_printf(MSG_DEBUG, " AT_MAC"); eap_sim_msg_add_mac(msg, EAP_SIM_AT_MAC); return eap_sim_msg_finish(msg, data->eap_method, data->k_aut, NULL, 0); } static struct wpabuf * eap_aka_build_reauth(struct eap_sm *sm, struct eap_aka_data *data, u8 id) { struct eap_sim_msg *msg; struct wpabuf *buf; wpa_printf(MSG_DEBUG, "EAP-AKA: Generating Re-authentication"); if (random_get_bytes(data->nonce_s, EAP_SIM_NONCE_S_LEN)) return NULL; wpa_hexdump_key(MSG_MSGDUMP, "EAP-AKA: NONCE_S", data->nonce_s, EAP_SIM_NONCE_S_LEN); if (data->eap_method == EAP_TYPE_AKA_PRIME) { eap_aka_prime_derive_keys_reauth(data->k_re, data->counter, sm->identity, sm->identity_len, data->nonce_s, data->msk, data->emsk); } else { eap_sim_derive_keys(data->mk, data->k_encr, data->k_aut, data->msk, data->emsk); eap_sim_derive_keys_reauth(data->counter, sm->identity, sm->identity_len, data->nonce_s, data->mk, data->msk, data->emsk); } msg = eap_sim_msg_init(EAP_CODE_REQUEST, id, data->eap_method, EAP_AKA_SUBTYPE_REAUTHENTICATION); if (eap_aka_build_encr(sm, data, msg, data->counter, data->nonce_s)) { eap_sim_msg_free(msg); return NULL; } eap_aka_add_checkcode(data, msg); if (sm->cfg->eap_sim_aka_result_ind) { wpa_printf(MSG_DEBUG, " AT_RESULT_IND"); eap_sim_msg_add(msg, EAP_SIM_AT_RESULT_IND, 0, NULL, 0); } wpa_printf(MSG_DEBUG, " AT_MAC"); eap_sim_msg_add_mac(msg, EAP_SIM_AT_MAC); buf = eap_sim_msg_finish(msg, data->eap_method, data->k_aut, NULL, 0); /* Remember this MAC before sending it to the peer. This MAC is used for * Session-Id calculation after receiving response from the peer and * after all other checks pass. */ os_memcpy(data->reauth_mac, wpabuf_head_u8(buf) + wpabuf_len(buf) - EAP_SIM_MAC_LEN, EAP_SIM_MAC_LEN); return buf; } static struct wpabuf * eap_aka_build_notification(struct eap_sm *sm, struct eap_aka_data *data, u8 id) { struct eap_sim_msg *msg; wpa_printf(MSG_DEBUG, "EAP-AKA: Generating Notification"); msg = eap_sim_msg_init(EAP_CODE_REQUEST, id, data->eap_method, EAP_AKA_SUBTYPE_NOTIFICATION); wpa_printf(MSG_DEBUG, " AT_NOTIFICATION (%d)", data->notification); eap_sim_msg_add(msg, EAP_SIM_AT_NOTIFICATION, data->notification, NULL, 0); if (data->use_result_ind) { if (data->reauth) { wpa_printf(MSG_DEBUG, " AT_IV"); wpa_printf(MSG_DEBUG, " AT_ENCR_DATA"); eap_sim_msg_add_encr_start(msg, EAP_SIM_AT_IV, EAP_SIM_AT_ENCR_DATA); wpa_printf(MSG_DEBUG, " *AT_COUNTER (%u)", data->counter); eap_sim_msg_add(msg, EAP_SIM_AT_COUNTER, data->counter, NULL, 0); if (eap_sim_msg_add_encr_end(msg, data->k_encr, EAP_SIM_AT_PADDING)) { wpa_printf(MSG_WARNING, "EAP-AKA: Failed to " "encrypt AT_ENCR_DATA"); eap_sim_msg_free(msg); return NULL; } } wpa_printf(MSG_DEBUG, " AT_MAC"); eap_sim_msg_add_mac(msg, EAP_SIM_AT_MAC); } return eap_sim_msg_finish(msg, data->eap_method, data->k_aut, NULL, 0); } static struct wpabuf * eap_aka_buildReq(struct eap_sm *sm, void *priv, u8 id) { struct eap_aka_data *data = priv; data->auts_reported = 0; switch (data->state) { case IDENTITY: return eap_aka_build_identity(sm, data, id); case CHALLENGE: return eap_aka_build_challenge(sm, data, id); case REAUTH: return eap_aka_build_reauth(sm, data, id); case NOTIFICATION: return eap_aka_build_notification(sm, data, id); default: wpa_printf(MSG_DEBUG, "EAP-AKA: Unknown state %d in " "buildReq", data->state); break; } return NULL; } static bool eap_aka_check(struct eap_sm *sm, void *priv, struct wpabuf *respData) { struct eap_aka_data *data = priv; const u8 *pos; size_t len; pos = eap_hdr_validate(EAP_VENDOR_IETF, data->eap_method, respData, &len); if (pos == NULL || len < 3) { wpa_printf(MSG_INFO, "EAP-AKA: Invalid frame"); return true; } return false; } static bool eap_aka_subtype_ok(struct eap_aka_data *data, u8 subtype) { if (subtype == EAP_AKA_SUBTYPE_CLIENT_ERROR || subtype == EAP_AKA_SUBTYPE_AUTHENTICATION_REJECT) return false; switch (data->state) { case IDENTITY: if (subtype != EAP_AKA_SUBTYPE_IDENTITY) { wpa_printf(MSG_INFO, "EAP-AKA: Unexpected response " "subtype %d", subtype); return true; } break; case CHALLENGE: if (subtype != EAP_AKA_SUBTYPE_CHALLENGE && subtype != EAP_AKA_SUBTYPE_SYNCHRONIZATION_FAILURE) { wpa_printf(MSG_INFO, "EAP-AKA: Unexpected response " "subtype %d", subtype); return true; } break; case REAUTH: if (subtype != EAP_AKA_SUBTYPE_REAUTHENTICATION) { wpa_printf(MSG_INFO, "EAP-AKA: Unexpected response " "subtype %d", subtype); return true; } break; case NOTIFICATION: if (subtype != EAP_AKA_SUBTYPE_NOTIFICATION) { wpa_printf(MSG_INFO, "EAP-AKA: Unexpected response " "subtype %d", subtype); return true; } break; default: wpa_printf(MSG_INFO, "EAP-AKA: Unexpected state (%d) for " "processing a response", data->state); return true; } return false; } static void eap_aka_determine_identity(struct eap_sm *sm, struct eap_aka_data *data) { char *username; wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Identity", sm->identity, sm->identity_len); username = sim_get_username(sm->identity, sm->identity_len); if (!username) goto fail; if (eap_aka_check_identity_reauth(sm, data, username) > 0) { os_free(username); return; } if (((data->eap_method == EAP_TYPE_AKA_PRIME && username[0] == EAP_AKA_PRIME_REAUTH_ID_PREFIX) || (data->eap_method == EAP_TYPE_AKA && username[0] == EAP_AKA_REAUTH_ID_PREFIX)) && data->identity_round == 1) { /* Remain in IDENTITY state for another round to request full * auth identity since we did not recognize reauth id */ os_free(username); return; } if ((data->eap_method == EAP_TYPE_AKA_PRIME && username[0] == EAP_AKA_PRIME_PSEUDONYM_PREFIX) || (data->eap_method == EAP_TYPE_AKA && username[0] == EAP_AKA_PSEUDONYM_PREFIX)) { const char *permanent; wpa_printf(MSG_DEBUG, "EAP-AKA: Pseudonym username '%s'", username); permanent = eap_sim_db_get_permanent( sm->cfg->eap_sim_db_priv, username); os_free(username); if (permanent == NULL) { wpa_printf(MSG_DEBUG, "EAP-AKA: Unknown pseudonym " "identity - request permanent identity"); /* Remain in IDENTITY state for another round */ return; } os_strlcpy(data->permanent, permanent, sizeof(data->permanent)); } else if ((data->eap_method == EAP_TYPE_AKA_PRIME && username[0] == EAP_AKA_PRIME_PERMANENT_PREFIX) || (data->eap_method == EAP_TYPE_AKA && username[0] == EAP_AKA_PERMANENT_PREFIX)) { wpa_printf(MSG_DEBUG, "EAP-AKA: Permanent username '%s'", username); os_strlcpy(data->permanent, username, sizeof(data->permanent)); os_free(username); #ifdef CRYPTO_RSA_OAEP_SHA256 } else if (sm->identity_len > 1 && sm->identity[0] == '\0') { char *enc_id, *pos, *end; size_t enc_id_len; u8 *decoded_id; size_t decoded_id_len; struct wpabuf *enc, *dec; u8 *new_id; os_free(username); if (!sm->cfg->imsi_privacy_key) { wpa_printf(MSG_DEBUG, "EAP-AKA: Received encrypted identity, but no IMSI privacy key configured to decrypt it"); goto fail; } enc_id = (char *) &sm->identity[1]; end = (char *) &sm->identity[sm->identity_len]; for (pos = enc_id; pos < end; pos++) { if (*pos == ',') break; } enc_id_len = pos - enc_id; wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Encrypted permanent identity", enc_id, enc_id_len); decoded_id = base64_decode(enc_id, enc_id_len, &decoded_id_len); if (!decoded_id) { wpa_printf(MSG_DEBUG, "EAP-AKA: Could not base64 decode encrypted identity"); goto fail; } wpa_hexdump(MSG_DEBUG, "EAP-AKA: Decoded encrypted permanent identity", decoded_id, decoded_id_len); enc = wpabuf_alloc_copy(decoded_id, decoded_id_len); os_free(decoded_id); if (!enc) goto fail; dec = crypto_rsa_oaep_sha256_decrypt(sm->cfg->imsi_privacy_key, enc); wpabuf_free(enc); if (!dec) { wpa_printf(MSG_DEBUG, "EAP-AKA: Failed to decrypt encrypted identity"); goto fail; } wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Decrypted permanent identity", wpabuf_head(dec), wpabuf_len(dec)); username = sim_get_username(wpabuf_head(dec), wpabuf_len(dec)); if (!username) { wpabuf_free(dec); goto fail; } new_id = os_memdup(wpabuf_head(dec), wpabuf_len(dec)); if (!new_id) { wpabuf_free(dec); goto fail; } os_free(sm->identity); sm->identity = new_id; sm->identity_len = wpabuf_len(dec); wpabuf_free(dec); os_strlcpy(data->permanent, username, sizeof(data->permanent)); os_free(username); #endif /* CRYPTO_RSA_OAEP_SHA256 */ } else { wpa_printf(MSG_DEBUG, "EAP-AKA: Unrecognized username '%s'", username); os_free(username); goto fail; return; } eap_aka_fullauth(sm, data); return; fail: data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); } static void eap_aka_fullauth(struct eap_sm *sm, struct eap_aka_data *data) { size_t identity_len; int res; res = eap_sim_db_get_aka_auth(sm->cfg->eap_sim_db_priv, data->permanent, data->rand, data->autn, data->ik, data->ck, data->res, &data->res_len, sm); if (res == EAP_SIM_DB_PENDING) { wpa_printf(MSG_DEBUG, "EAP-AKA: AKA authentication data " "not yet available - pending request"); sm->method_pending = METHOD_PENDING_WAIT; return; } if (data->permanent[0] == EAP_AKA_PERMANENT_PREFIX || data->permanent[0] == EAP_AKA_PRIME_PERMANENT_PREFIX) os_strlcpy(sm->imsi, &data->permanent[1], sizeof(sm->imsi)); #ifdef EAP_SERVER_AKA_PRIME if (data->eap_method == EAP_TYPE_AKA_PRIME) { /* Note: AUTN = (SQN ^ AK) || AMF || MAC which gives us the * needed 6-octet SQN ^AK for CK',IK' derivation */ eap_aka_prime_derive_ck_ik_prime(data->ck, data->ik, data->autn, data->network_name, data->network_name_len); } #endif /* EAP_SERVER_AKA_PRIME */ data->reauth = NULL; data->counter = 0; /* reset re-auth counter since this is full auth */ if (res != 0) { wpa_printf(MSG_INFO, "EAP-AKA: Failed to get AKA " "authentication data for the peer"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } if (sm->method_pending == METHOD_PENDING_WAIT) { wpa_printf(MSG_DEBUG, "EAP-AKA: AKA authentication data " "available - abort pending wait"); sm->method_pending = METHOD_PENDING_NONE; } identity_len = sm->identity_len; while (identity_len > 0 && sm->identity[identity_len - 1] == '\0') { wpa_printf(MSG_DEBUG, "EAP-AKA: Workaround - drop last null " "character from identity"); identity_len--; } wpa_hexdump_ascii(MSG_DEBUG, "EAP-AKA: Identity for MK derivation", sm->identity, identity_len); if (data->eap_method == EAP_TYPE_AKA_PRIME) { eap_aka_prime_derive_keys(sm->identity, identity_len, data->ik, data->ck, data->k_encr, data->k_aut, data->k_re, data->msk, data->emsk); } else { eap_aka_derive_mk(sm->identity, identity_len, data->ik, data->ck, data->mk); eap_sim_derive_keys(data->mk, data->k_encr, data->k_aut, data->msk, data->emsk); } eap_aka_state(data, CHALLENGE); } static void eap_aka_process_identity(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { u8 *new_identity; wpa_printf(MSG_DEBUG, "EAP-AKA: Processing Identity"); if (attr->mac || attr->iv || attr->encr_data) { wpa_printf(MSG_WARNING, "EAP-AKA: Unexpected attribute " "received in EAP-Response/AKA-Identity"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } /* * We always request identity with AKA/Identity, so the peer is * required to have replied with one. */ if (!attr->identity || attr->identity_len == 0) { wpa_printf(MSG_DEBUG, "EAP-AKA: Peer did not provide any " "identity"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } new_identity = os_malloc(attr->identity_len); if (new_identity == NULL) { data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } os_free(sm->identity); sm->identity = new_identity; os_memcpy(sm->identity, attr->identity, attr->identity_len); sm->identity_len = attr->identity_len; eap_aka_determine_identity(sm, data); if (eap_get_id(respData) == data->pending_id) { data->pending_id = -1; eap_aka_add_id_msg(data, respData); } } static int eap_aka_verify_mac(struct eap_aka_data *data, const struct wpabuf *req, const u8 *mac, const u8 *extra, size_t extra_len) { if (data->eap_method == EAP_TYPE_AKA_PRIME) return eap_sim_verify_mac_sha256(data->k_aut, req, mac, extra, extra_len); return eap_sim_verify_mac(data->k_aut, req, mac, extra, extra_len); } static void eap_aka_process_challenge(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { wpa_printf(MSG_DEBUG, "EAP-AKA: Processing Challenge"); #ifdef EAP_SERVER_AKA_PRIME #if 0 /* KDF negotiation; to be enabled only after more than one KDF is * supported */ if (data->eap_method == EAP_TYPE_AKA_PRIME && attr->kdf_count == 1 && attr->mac == NULL) { if (attr->kdf[0] != EAP_AKA_PRIME_KDF) { wpa_printf(MSG_WARNING, "EAP-AKA': Peer selected " "unknown KDF"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } data->kdf = attr->kdf[0]; /* Allow negotiation to continue with the selected KDF by * sending another Challenge message */ wpa_printf(MSG_DEBUG, "EAP-AKA': KDF %d selected", data->kdf); return; } #endif #endif /* EAP_SERVER_AKA_PRIME */ if (attr->checkcode && eap_aka_verify_checkcode(data, attr->checkcode, attr->checkcode_len)) { wpa_printf(MSG_WARNING, "EAP-AKA: Invalid AT_CHECKCODE in the " "message"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } if (attr->mac == NULL || eap_aka_verify_mac(data, respData, attr->mac, NULL, 0)) { wpa_printf(MSG_WARNING, "EAP-AKA: Challenge message " "did not include valid AT_MAC"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } /* * AT_RES is padded, so verify that there is enough room for RES and * that the RES length in bits matches with the expected RES. */ if (attr->res == NULL || attr->res_len < data->res_len || attr->res_len_bits != data->res_len * 8 || os_memcmp_const(attr->res, data->res, data->res_len) != 0) { wpa_printf(MSG_WARNING, "EAP-AKA: Challenge message did not " "include valid AT_RES (attr len=%lu, res len=%lu " "bits, expected %lu bits)", (unsigned long) attr->res_len, (unsigned long) attr->res_len_bits, (unsigned long) data->res_len * 8); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } wpa_printf(MSG_DEBUG, "EAP-AKA: Challenge response includes the " "correct AT_MAC"); if (sm->cfg->eap_sim_aka_result_ind && attr->result_ind) { data->use_result_ind = 1; data->notification = EAP_SIM_SUCCESS; eap_aka_state(data, NOTIFICATION); } else eap_aka_state(data, SUCCESS); if (data->next_pseudonym) { eap_sim_db_add_pseudonym(sm->cfg->eap_sim_db_priv, data->permanent, data->next_pseudonym); data->next_pseudonym = NULL; } if (data->next_reauth_id) { if (data->eap_method == EAP_TYPE_AKA_PRIME) { #ifdef EAP_SERVER_AKA_PRIME eap_sim_db_add_reauth_prime(sm->cfg->eap_sim_db_priv, data->permanent, data->next_reauth_id, data->counter + 1, data->k_encr, data->k_aut, data->k_re); #endif /* EAP_SERVER_AKA_PRIME */ } else { eap_sim_db_add_reauth(sm->cfg->eap_sim_db_priv, data->permanent, data->next_reauth_id, data->counter + 1, data->mk); } data->next_reauth_id = NULL; } } static void eap_aka_process_sync_failure(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { wpa_printf(MSG_DEBUG, "EAP-AKA: Processing Synchronization-Failure"); if (attr->auts == NULL) { wpa_printf(MSG_WARNING, "EAP-AKA: Synchronization-Failure " "message did not include valid AT_AUTS"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } /* Avoid re-reporting AUTS when processing pending EAP packet by * maintaining a local flag stating whether this AUTS has already been * reported. */ if (!data->auts_reported && eap_sim_db_resynchronize(sm->cfg->eap_sim_db_priv, data->permanent, attr->auts, data->rand)) { wpa_printf(MSG_WARNING, "EAP-AKA: Resynchronization failed"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } data->auts_reported = 1; /* Remain in CHALLENGE state to re-try after resynchronization */ eap_aka_fullauth(sm, data); } static void eap_aka_process_reauth(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { struct eap_sim_attrs eattr; u8 *decrypted = NULL; wpa_printf(MSG_DEBUG, "EAP-AKA: Processing Reauthentication"); if (attr->mac == NULL || eap_aka_verify_mac(data, respData, attr->mac, data->nonce_s, EAP_SIM_NONCE_S_LEN)) { wpa_printf(MSG_WARNING, "EAP-AKA: Re-authentication message " "did not include valid AT_MAC"); goto fail; } if (attr->encr_data == NULL || attr->iv == NULL) { wpa_printf(MSG_WARNING, "EAP-AKA: Reauthentication " "message did not include encrypted data"); goto fail; } decrypted = eap_sim_parse_encr(data->k_encr, attr->encr_data, attr->encr_data_len, attr->iv, &eattr, 0); if (decrypted == NULL) { wpa_printf(MSG_WARNING, "EAP-AKA: Failed to parse encrypted " "data from reauthentication message"); goto fail; } if (eattr.counter != data->counter) { wpa_printf(MSG_WARNING, "EAP-AKA: Re-authentication message " "used incorrect counter %u, expected %u", eattr.counter, data->counter); goto fail; } os_free(decrypted); decrypted = NULL; wpa_printf(MSG_DEBUG, "EAP-AKA: Re-authentication response includes " "the correct AT_MAC"); if (eattr.counter_too_small) { wpa_printf(MSG_DEBUG, "EAP-AKA: Re-authentication response " "included AT_COUNTER_TOO_SMALL - starting full " "authentication"); eap_aka_fullauth(sm, data); return; } if (sm->cfg->eap_sim_aka_result_ind && attr->result_ind) { data->use_result_ind = 1; data->notification = EAP_SIM_SUCCESS; eap_aka_state(data, NOTIFICATION); } else eap_aka_state(data, SUCCESS); if (data->next_reauth_id) { if (data->eap_method == EAP_TYPE_AKA_PRIME) { #ifdef EAP_SERVER_AKA_PRIME eap_sim_db_add_reauth_prime(sm->cfg->eap_sim_db_priv, data->permanent, data->next_reauth_id, data->counter + 1, data->k_encr, data->k_aut, data->k_re); #endif /* EAP_SERVER_AKA_PRIME */ } else { eap_sim_db_add_reauth(sm->cfg->eap_sim_db_priv, data->permanent, data->next_reauth_id, data->counter + 1, data->mk); } data->next_reauth_id = NULL; } else { eap_sim_db_remove_reauth(sm->cfg->eap_sim_db_priv, data->reauth); data->reauth = NULL; } return; fail: data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); eap_sim_db_remove_reauth(sm->cfg->eap_sim_db_priv, data->reauth); data->reauth = NULL; os_free(decrypted); } static void eap_aka_process_client_error(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { wpa_printf(MSG_DEBUG, "EAP-AKA: Client reported error %d", attr->client_error_code); if (data->notification == EAP_SIM_SUCCESS && data->use_result_ind) eap_aka_state(data, SUCCESS); else eap_aka_state(data, FAILURE); } static void eap_aka_process_authentication_reject( struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { wpa_printf(MSG_DEBUG, "EAP-AKA: Client rejected authentication"); eap_aka_state(data, FAILURE); } static void eap_aka_process_notification(struct eap_sm *sm, struct eap_aka_data *data, struct wpabuf *respData, struct eap_sim_attrs *attr) { wpa_printf(MSG_DEBUG, "EAP-AKA: Client replied to notification"); if (data->notification == EAP_SIM_SUCCESS && data->use_result_ind) eap_aka_state(data, SUCCESS); else eap_aka_state(data, FAILURE); } static void eap_aka_process(struct eap_sm *sm, void *priv, struct wpabuf *respData) { struct eap_aka_data *data = priv; const u8 *pos, *end; u8 subtype; size_t len; struct eap_sim_attrs attr; pos = eap_hdr_validate(EAP_VENDOR_IETF, data->eap_method, respData, &len); if (pos == NULL || len < 3) return; end = pos + len; subtype = *pos; pos += 3; if (eap_aka_subtype_ok(data, subtype)) { wpa_printf(MSG_DEBUG, "EAP-AKA: Unrecognized or unexpected " "EAP-AKA Subtype in EAP Response"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } if (eap_sim_parse_attr(pos, end, &attr, data->eap_method == EAP_TYPE_AKA_PRIME ? 2 : 1, 0)) { wpa_printf(MSG_DEBUG, "EAP-AKA: Failed to parse attributes"); data->notification = EAP_SIM_GENERAL_FAILURE_BEFORE_AUTH; eap_aka_state(data, NOTIFICATION); return; } if (subtype == EAP_AKA_SUBTYPE_CLIENT_ERROR) { eap_aka_process_client_error(sm, data, respData, &attr); return; } if (subtype == EAP_AKA_SUBTYPE_AUTHENTICATION_REJECT) { eap_aka_process_authentication_reject(sm, data, respData, &attr); return; } switch (data->state) { case IDENTITY: eap_aka_process_identity(sm, data, respData, &attr); break; case CHALLENGE: if (subtype == EAP_AKA_SUBTYPE_SYNCHRONIZATION_FAILURE) { eap_aka_process_sync_failure(sm, data, respData, &attr); } else { eap_aka_process_challenge(sm, data, respData, &attr); } break; case REAUTH: eap_aka_process_reauth(sm, data, respData, &attr); break; case NOTIFICATION: eap_aka_process_notification(sm, data, respData, &attr); break; default: wpa_printf(MSG_DEBUG, "EAP-AKA: Unknown state %d in " "process", data->state); break; } } static bool eap_aka_isDone(struct eap_sm *sm, void *priv) { struct eap_aka_data *data = priv; return data->state == SUCCESS || data->state == FAILURE; } static u8 * eap_aka_getKey(struct eap_sm *sm, void *priv, size_t *len) { struct eap_aka_data *data = priv; u8 *key; if (data->state != SUCCESS) return NULL; key = os_memdup(data->msk, EAP_SIM_KEYING_DATA_LEN); if (key == NULL) return NULL; *len = EAP_SIM_KEYING_DATA_LEN; return key; } static u8 * eap_aka_get_emsk(struct eap_sm *sm, void *priv, size_t *len) { struct eap_aka_data *data = priv; u8 *key; if (data->state != SUCCESS) return NULL; key = os_memdup(data->emsk, EAP_EMSK_LEN); if (key == NULL) return NULL; *len = EAP_EMSK_LEN; return key; } static bool eap_aka_isSuccess(struct eap_sm *sm, void *priv) { struct eap_aka_data *data = priv; return data->state == SUCCESS; } static u8 * eap_aka_get_session_id(struct eap_sm *sm, void *priv, size_t *len) { struct eap_aka_data *data = priv; u8 *id; if (data->state != SUCCESS) return NULL; if (!data->reauth) *len = 1 + EAP_AKA_RAND_LEN + EAP_AKA_AUTN_LEN; else *len = 1 + EAP_SIM_NONCE_S_LEN + EAP_SIM_MAC_LEN; id = os_malloc(*len); if (id == NULL) return NULL; id[0] = data->eap_method; if (!data->reauth) { os_memcpy(id + 1, data->rand, EAP_AKA_RAND_LEN); os_memcpy(id + 1 + EAP_AKA_RAND_LEN, data->autn, EAP_AKA_AUTN_LEN); } else { os_memcpy(id + 1, data->nonce_s, EAP_SIM_NONCE_S_LEN); os_memcpy(id + 1 + EAP_SIM_NONCE_S_LEN, data->reauth_mac, EAP_SIM_MAC_LEN); } wpa_hexdump(MSG_DEBUG, "EAP-AKA: Derived Session-Id", id, *len); return id; } int eap_server_aka_register(void) { struct eap_method *eap; eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, EAP_VENDOR_IETF, EAP_TYPE_AKA, "AKA"); if (eap == NULL) return -1; eap->init = eap_aka_init; eap->reset = eap_aka_reset; eap->buildReq = eap_aka_buildReq; eap->check = eap_aka_check; eap->process = eap_aka_process; eap->isDone = eap_aka_isDone; eap->getKey = eap_aka_getKey; eap->isSuccess = eap_aka_isSuccess; eap->get_emsk = eap_aka_get_emsk; eap->getSessionId = eap_aka_get_session_id; return eap_server_method_register(eap); } #ifdef EAP_SERVER_AKA_PRIME int eap_server_aka_prime_register(void) { struct eap_method *eap; eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, EAP_VENDOR_IETF, EAP_TYPE_AKA_PRIME, "AKA'"); if (eap == NULL) return -1; eap->init = eap_aka_prime_init; eap->reset = eap_aka_reset; eap->buildReq = eap_aka_buildReq; eap->check = eap_aka_check; eap->process = eap_aka_process; eap->isDone = eap_aka_isDone; eap->getKey = eap_aka_getKey; eap->isSuccess = eap_aka_isSuccess; eap->get_emsk = eap_aka_get_emsk; eap->getSessionId = eap_aka_get_session_id; return eap_server_method_register(eap); } #endif /* EAP_SERVER_AKA_PRIME */