diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 461e89093..b47b659b4 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -1004,6 +1004,294 @@ static u16 wpa_res_to_status_code(int res) } +#ifdef CONFIG_FILS + +static void handle_auth_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct rsn_pmksa_cache_entry *pmksa, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len); + +static void handle_auth_fils(struct hostapd_data *hapd, struct sta_info *sta, + const struct ieee80211_mgmt *mgmt, size_t len, + u16 auth_transaction, u16 status_code) +{ + u16 resp = WLAN_STATUS_SUCCESS; + const u8 *pos, *end; + struct ieee802_11_elems elems; + int res; + struct wpa_ie_data rsn; + struct rsn_pmksa_cache_entry *pmksa = NULL; + + if (auth_transaction != 1 || status_code != WLAN_STATUS_SUCCESS) + return; + + pos = mgmt->u.auth.variable; + end = ((const u8 *) mgmt) + len; + + wpa_hexdump(MSG_DEBUG, "FILS: Authentication frame fields", + pos, end - pos); + + /* TODO: Finite Cyclic Group when using PK or PFS */ + /* TODO: Element when using PK or PFS */ + + wpa_hexdump(MSG_DEBUG, "FILS: Remaining IEs", pos, end - pos); + if (ieee802_11_parse_elems(pos, end - pos, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "FILS: Could not parse elements"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + /* RSNE */ + wpa_hexdump(MSG_DEBUG, "FILS: RSN element", + elems.rsn_ie, elems.rsn_ie_len); + if (!elems.rsn_ie || + wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsn) < 0) { + wpa_printf(MSG_DEBUG, "FILS: No valid RSN element"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + if (!sta->wpa_sm) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, sta->addr, + NULL); + if (!sta->wpa_sm) { + wpa_printf(MSG_DEBUG, + "FILS: Failed to initialize RSN state machine"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm, + elems.rsn_ie - 2, elems.rsn_ie_len + 2, + elems.mdie, elems.mdie_len); + resp = wpa_res_to_status_code(res); + if (resp != WLAN_STATUS_SUCCESS) + goto fail; + + /* TODO: MDE when using FILS+FT */ + /* TODO: FTE when using FILS+FT */ + + if (!elems.fils_nonce) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Nonce field"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "FILS: SNonce", elems.fils_nonce, + FILS_NONCE_LEN); + os_memcpy(sta->fils_snonce, elems.fils_nonce, FILS_NONCE_LEN); + + /* PMKID List */ + if (rsn.pmkid && rsn.num_pmkid > 0) { + u8 num; + const u8 *pmkid; + + wpa_hexdump(MSG_DEBUG, "FILS: PMKID List", + rsn.pmkid, rsn.num_pmkid * PMKID_LEN); + + pmkid = rsn.pmkid; + num = rsn.num_pmkid; + while (num) { + wpa_hexdump(MSG_DEBUG, "FILS: PMKID", pmkid, PMKID_LEN); + pmksa = wpa_auth_pmksa_get(hapd->wpa_auth, sta->addr, + pmkid); + if (pmksa) + break; + pmkid += PMKID_LEN; + num--; + } + } + if (pmksa && wpa_auth_sta_key_mgmt(sta->wpa_sm) != pmksa->akmp) { + wpa_printf(MSG_DEBUG, + "FILS: Matching PMKSA cache entry has different AKMP (0x%x != 0x%x) - ignore", + wpa_auth_sta_key_mgmt(sta->wpa_sm), pmksa->akmp); + pmksa = NULL; + } + if (pmksa) + wpa_printf(MSG_DEBUG, "FILS: Found matching PMKSA cache entry"); + + /* FILS Session */ + if (!elems.fils_session) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Session element"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "FILS: FILS Session", elems.fils_session, + FILS_SESSION_LEN); + os_memcpy(sta->fils_session, elems.fils_session, FILS_SESSION_LEN); + + /* FILS Wrapped Data */ + if (elems.fils_wrapped_data) { + wpa_hexdump(MSG_DEBUG, "FILS: Wrapped Data", + elems.fils_wrapped_data, + elems.fils_wrapped_data_len); + if (!pmksa) { +#ifndef CONFIG_NO_RADIUS + if (!sta->eapol_sm) { + sta->eapol_sm = + ieee802_1x_alloc_eapol_sm(hapd, sta); + } + wpa_printf(MSG_DEBUG, + "FILS: Forward EAP-Identity/Re-auth Start to authentication server"); + ieee802_1x_encapsulate_radius( + hapd, sta, elems.fils_wrapped_data, + elems.fils_wrapped_data_len); + wpa_printf(MSG_DEBUG, + "FILS: Will send Authentication frame once the response from authentication server is available"); + sta->flags |= WLAN_STA_PENDING_FILS_ERP; + return; +#else /* CONFIG_NO_RADIUS */ + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; +#endif /* CONFIG_NO_RADIUS */ + } + } + +fail: + handle_auth_fils_finish(hapd, sta, resp, pmksa, NULL, NULL, 0); +} + + +static void handle_auth_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct rsn_pmksa_cache_entry *pmksa, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len) +{ + u8 fils_nonce[FILS_NONCE_LEN]; + size_t ielen; + struct wpabuf *data = NULL; + const u8 *ie; + u8 *ie_buf = NULL; + const u8 *pmk = NULL; + size_t pmk_len = 0; + + if (resp != WLAN_STATUS_SUCCESS) + goto fail; + + ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &ielen); + if (!ie) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + if (pmksa) { + /* Add PMKID of the selected PMKSA into RSNE */ + ie_buf = os_malloc(ielen + 2 + 2 + PMKID_LEN); + if (!ie_buf) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + os_memcpy(ie_buf, ie, ielen); + if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid) < 0) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + ie = ie_buf; + } + + if (random_get_bytes(fils_nonce, FILS_NONCE_LEN) < 0) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS Nonce", + fils_nonce, FILS_NONCE_LEN); + + data = wpabuf_alloc(1000 + ielen); + if (!data) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + /* TODO: Finite Cyclic Group when using PK or PFS */ + /* TODO: Element when using PK or PFS */ + + /* RSNE */ + wpabuf_put_data(data, ie, ielen); + + /* TODO: MDE when using FILS+FT */ + /* TODO: FTE when using FILS+FT */ + + /* FILS Nonce */ + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + FILS_NONCE_LEN); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_FILS_NONCE); + wpabuf_put_data(data, fils_nonce, FILS_NONCE_LEN); + + /* FILS Session */ + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + FILS_SESSION_LEN); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_FILS_SESSION); + wpabuf_put_data(data, sta->fils_session, FILS_SESSION_LEN); + + /* FILS Wrapped Data */ + if (!pmksa && erp_resp) { + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + wpabuf_len(erp_resp)); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_FILS_WRAPPED_DATA); + wpabuf_put_buf(data, erp_resp); + + pmk = msk; + pmk_len = msk_len > PMK_LEN ? PMK_LEN : msk_len; + } else if (pmksa) { + pmk = pmksa->pmk; + pmk_len = pmksa->pmk_len; + } + + if (!pmk) { + wpa_printf(MSG_DEBUG, "FILS: No PMK available"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpabuf_free(data); + data = NULL; + goto fail; + } + + if (fils_auth_pmk_to_ptk(sta->wpa_sm, pmk, pmk_len, + sta->fils_snonce, fils_nonce) < 0) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpabuf_free(data); + data = NULL; + goto fail; + } + +fail: + send_auth_reply(hapd, sta->addr, hapd->own_addr, WLAN_AUTH_FILS_SK, 2, + resp, + data ? wpabuf_head(data) : (u8 *) "", + data ? wpabuf_len(data) : 0); + wpabuf_free(data); + + if (resp == WLAN_STATUS_SUCCESS) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication OK (FILS)"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sta->auth_alg = WLAN_AUTH_FILS_SK; + mlme_authenticate_indication(hapd, sta); + } + + os_free(ie_buf); +} + + +void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, + struct sta_info *sta, int success, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len) +{ + sta->flags &= ~WLAN_STA_PENDING_FILS_ERP; + handle_auth_fils_finish(hapd, sta, success ? WLAN_STATUS_SUCCESS : + WLAN_STATUS_UNSPECIFIED_FAILURE, NULL, + erp_resp, msk, msk_len); +} + +#endif /* CONFIG_FILS */ + + static void handle_auth(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len) { @@ -1085,6 +1373,10 @@ static void handle_auth(struct hostapd_data *hapd, (hapd->conf->wpa && wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && auth_alg == WLAN_AUTH_SAE) || #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + (hapd->conf->wpa && wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt) && + auth_alg == WLAN_AUTH_FILS_SK) || +#endif /* CONFIG_FILS */ ((hapd->conf->auth_algs & WPA_AUTH_ALG_SHARED) && auth_alg == WLAN_AUTH_SHARED_KEY))) { wpa_printf(MSG_INFO, "Unsupported authentication algorithm (%d)", @@ -1186,6 +1478,7 @@ static void handle_auth(struct hostapd_data *hapd, sta = ap_get_sta(hapd, mgmt->sa); if (sta) { + sta->flags &= ~WLAN_STA_PENDING_FILS_ERP; if ((fc & WLAN_FC_RETRY) && sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ && sta->last_seq_ctrl == seq_ctrl && @@ -1381,6 +1674,12 @@ static void handle_auth(struct hostapd_data *hapd, status_code); return; #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + case WLAN_AUTH_FILS_SK: + handle_auth_fils(hapd, sta, mgmt, len, auth_transaction, + status_code); + return; +#endif /* CONFIG_FILS */ } fail: diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index d5627dc44..46c92b785 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -136,5 +136,9 @@ void ap_copy_sta_supp_op_classes(struct sta_info *sta, size_t supp_op_classes_len); u8 * hostapd_eid_fils_indic(struct hostapd_data *hapd, u8 *eid, int hessid); +void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, + struct sta_info *sta, int success, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len); #endif /* IEEE802_11_H */ diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index 2dba49bab..cffef43a2 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -31,6 +31,8 @@ #include "ap_drv_ops.h" #include "wps_hostapd.h" #include "hs20.h" +/* FIX: Not really a good thing to require ieee802_11.h here.. (FILS) */ +#include "ieee802_11.h" #include "ieee802_1x.h" @@ -1838,6 +1840,19 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, if (override_eapReq) sm->eap_if->aaaEapReq = FALSE; +#ifdef CONFIG_FILS +#ifdef NEED_AP_MLME + if (sta->flags & WLAN_STA_PENDING_FILS_ERP) { + /* TODO: Add a PMKSA entry on success? */ + ieee802_11_finish_fils_auth( + hapd, sta, hdr->code == RADIUS_CODE_ACCESS_ACCEPT, + sm->eap_if->aaaEapReqData, + sm->eap_if->aaaEapKeyData, + sm->eap_if->aaaEapKeyDataLen); + } +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_FILS */ + eapol_auth_step(sm); return RADIUS_RX_QUEUED; diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 099de62d1..a41633714 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -17,6 +17,7 @@ #include "list.h" #include "vlan.h" +#include "common/ieee802_11_defs.h" /* STA flags */ #define WLAN_STA_AUTH BIT(0) @@ -38,6 +39,7 @@ #define WLAN_STA_WNM_SLEEP_MODE BIT(19) #define WLAN_STA_VHT_OPMODE_ENABLED BIT(20) #define WLAN_STA_VENDOR_VHT BIT(21) +#define WLAN_STA_PENDING_FILS_ERP BIT(22) #define WLAN_STA_PENDING_DISASSOC_CB BIT(29) #define WLAN_STA_PENDING_DEAUTH_CB BIT(30) #define WLAN_STA_NONERP BIT(31) @@ -218,6 +220,11 @@ struct sta_info { struct wpabuf *probe_ie_taxonomy; struct wpabuf *assoc_ie_taxonomy; #endif /* CONFIG_TAXONOMY */ + +#ifdef CONFIG_FILS + u8 fils_snonce[FILS_NONCE_LEN]; + u8 fils_session[FILS_SESSION_LEN]; +#endif /* CONFIG_FILS */ }; diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index d14ae6de0..a64e927aa 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -2035,6 +2035,32 @@ static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, #ifdef CONFIG_FILS + +int fils_auth_pmk_to_ptk(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *snonce, const u8 *anonce) +{ + u8 ick[FILS_ICK_MAX_LEN]; + size_t ick_len; + int res; + + res = fils_pmk_to_ptk(pmk, pmk_len, sm->addr, sm->wpa_auth->addr, + snonce, anonce, &sm->PTK, ick, &ick_len, + sm->wpa_key_mgmt, sm->pairwise); + if (res < 0) + return res; + sm->PTK_valid = TRUE; + + res = fils_key_auth_sk(ick, ick_len, snonce, anonce, + sm->addr, sm->wpa_auth->addr, + NULL, 0, NULL, 0, /* TODO: SK+PFS */ + sm->wpa_key_mgmt, sm->fils_key_auth_sta, + sm->fils_key_auth_ap, + &sm->fils_key_auth_len); + os_memset(ick, 0, sizeof(ick)); + return res; +} + + static int wpa_aead_decrypt(struct wpa_state_machine *sm, struct wpa_ptk *ptk, u8 *buf, size_t buf_len, u16 *_key_data_len) { diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index 4df87df62..5ab780276 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -348,5 +348,7 @@ void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth); int wpa_auth_ensure_group(struct wpa_authenticator *wpa_auth, int vlan_id); int wpa_auth_release_group(struct wpa_authenticator *wpa_auth, int vlan_id); +int fils_auth_pmk_to_ptk(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *snonce, const u8 *anonce); #endif /* WPA_AUTH_H */ diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index 72b7eb37a..bc5048fe5 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -138,6 +138,12 @@ struct wpa_state_machine { #ifdef CONFIG_P2P u8 ip_addr[4]; #endif /* CONFIG_P2P */ + +#ifdef CONFIG_FILS + u8 fils_key_auth_sta[FILS_MAX_KEY_AUTH_LEN]; + u8 fils_key_auth_ap[FILS_MAX_KEY_AUTH_LEN]; + size_t fils_key_auth_len; +#endif /* CONFIG_FILS */ };