From 62edb79a0c33392176baac2412798b69dbda8cdb Mon Sep 17 00:00:00 2001 From: Ilan Peer Date: Wed, 16 Dec 2020 13:00:56 +0200 Subject: [PATCH] AP: Support PASN with FILS key derivation As the PASN FILS authentication is only defined for FILS SK without PFS, and to support PASN authentication with FILS, implement the PASN with FILS processing as part of the PASN handling and not as part of the WPA Authenticator state machine. Signed-off-by: Ilan Peer --- src/ap/ieee802_11.c | 359 ++++++++++++++++++++++++++++++++++++++++++-- src/ap/ieee802_1x.c | 3 +- src/ap/sta_info.c | 11 ++ src/ap/sta_info.h | 21 +++ 4 files changed, 379 insertions(+), 15 deletions(-) diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index afb0b4b1b..42d5cee5c 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -66,6 +66,23 @@ prepare_auth_resp_fils(struct hostapd_data *hapd, const u8 *msk, size_t msk_len, int *is_pub); #endif /* CONFIG_FILS */ + +#ifdef CONFIG_PASN + +static int handle_auth_pasn_resp(struct hostapd_data *hapd, + struct sta_info *sta, + struct rsn_pmksa_cache_entry *pmksa, + u16 status); +#ifdef CONFIG_FILS + +static void pasn_fils_auth_resp(struct hostapd_data *hapd, + struct sta_info *sta, u16 status, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len); + +#endif /* CONFIG_FILS */ +#endif /* CONFIG_PASN */ + static void handle_auth(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, int rssi, int from_queue); @@ -2211,23 +2228,35 @@ void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, struct wpabuf *erp_resp, const u8 *msk, size_t msk_len) { - struct wpabuf *data; - int pub = 0; u16 resp; + u32 flags = sta->flags; - sta->flags &= ~WLAN_STA_PENDING_FILS_ERP; + sta->flags &= ~(WLAN_STA_PENDING_FILS_ERP | + WLAN_STA_PENDING_PASN_FILS_ERP); - if (!sta->fils_pending_cb) - return; resp = success ? WLAN_STATUS_SUCCESS : WLAN_STATUS_UNSPECIFIED_FAILURE; - data = prepare_auth_resp_fils(hapd, sta, &resp, NULL, erp_resp, - msk, msk_len, &pub); - if (!data) { - wpa_printf(MSG_DEBUG, - "%s: prepare_auth_resp_fils() returned failure", - __func__); + + if (flags & WLAN_STA_PENDING_FILS_ERP) { + struct wpabuf *data; + int pub = 0; + + if (!sta->fils_pending_cb) + return; + + data = prepare_auth_resp_fils(hapd, sta, &resp, NULL, erp_resp, + msk, msk_len, &pub); + if (!data) { + wpa_printf(MSG_DEBUG, + "%s: prepare_auth_resp_fils() failure", + __func__); + } + sta->fils_pending_cb(hapd, sta, resp, data, pub); +#ifdef CONFIG_PASN + } else if (flags & WLAN_STA_PENDING_PASN_FILS_ERP) { + pasn_fils_auth_resp(hapd, sta, resp, erp_resp, + msk, msk_len); +#endif /* CONFIG_PASN */ } - sta->fils_pending_cb(hapd, sta, resp, data, pub); } #endif /* CONFIG_FILS */ @@ -2500,6 +2529,253 @@ static struct wpabuf * pasn_get_sae_wd(struct hostapd_data *hapd, #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + +static struct wpabuf * pasn_get_fils_wd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct pasn_data *pasn = sta->pasn; + struct pasn_fils_data *fils = &pasn->fils; + struct wpabuf *buf = NULL; + + if (!fils->erp_resp) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing erp_resp"); + return NULL; + } + + buf = wpabuf_alloc(1500); + if (!buf) + return NULL; + + /* Add the authentication algorithm */ + wpabuf_put_le16(buf, WLAN_AUTH_FILS_SK); + + /* Authentication Transaction seq# */ + wpabuf_put_le16(buf, 2); + + /* Status Code */ + wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS); + + /* Own RSNE */ + wpa_pasn_add_rsne(buf, NULL, pasn->akmp, pasn->cipher); + + /* FILS Nonce */ + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 1 + FILS_NONCE_LEN); + wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_NONCE); + wpabuf_put_data(buf, fils->anonce, FILS_NONCE_LEN); + + /* FILS Session */ + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 1 + FILS_SESSION_LEN); + wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_SESSION); + wpabuf_put_data(buf, fils->session, FILS_SESSION_LEN); + + /* Wrapped Data */ + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 1 + wpabuf_len(fils->erp_resp)); + wpabuf_put_u8(buf, WLAN_EID_EXT_WRAPPED_DATA); + wpabuf_put_buf(buf, fils->erp_resp); + + return buf; +} + + +static void pasn_fils_auth_resp(struct hostapd_data *hapd, + struct sta_info *sta, u16 status, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len) +{ + struct pasn_data *pasn = sta->pasn; + struct pasn_fils_data *fils = &pasn->fils; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; + int ret; + + wpa_printf(MSG_DEBUG, "PASN: FILS: Handle AS response - status=%u", + status); + + if (status != WLAN_STATUS_SUCCESS) + goto fail; + + if (!pasn->secret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing secret"); + goto fail; + } + + if (random_get_bytes(fils->anonce, FILS_NONCE_LEN) < 0) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to get ANonce"); + goto fail; + } + + wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS ANonce", + fils->anonce, FILS_NONCE_LEN); + + ret = fils_rmsk_to_pmk(pasn->akmp, msk, msk_len, fils->nonce, + fils->anonce, NULL, 0, pmk, &pmk_len); + if (ret) { + wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK"); + goto fail; + } + + ret = pasn_pmk_to_ptk(pmk, pmk_len, sta->addr, hapd->own_addr, + wpabuf_head(pasn->secret), + wpabuf_len(pasn->secret), + &sta->pasn->ptk, sta->pasn->akmp, + sta->pasn->cipher, WPA_KDK_MAX_LEN); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to derive PTK"); + goto fail; + } + + wpa_printf(MSG_DEBUG, "PASN: PTK successfully derived"); + + wpabuf_free(pasn->secret); + pasn->secret = NULL; + + fils->erp_resp = erp_resp; + ret = handle_auth_pasn_resp(hapd, sta, NULL, WLAN_STATUS_SUCCESS); + fils->erp_resp = NULL; + + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to send response"); + goto fail; + } + + fils->state = PASN_FILS_STATE_COMPLETE; + return; +fail: + ap_free_sta(hapd, sta); +} + + +static int pasn_wd_handle_fils(struct hostapd_data *hapd, struct sta_info *sta, + struct wpabuf *wd) +{ + struct pasn_data *pasn = sta->pasn; + struct pasn_fils_data *fils = &pasn->fils; + struct ieee802_11_elems elems; + struct wpa_ie_data rsne_data; + struct wpabuf *fils_wd; + const u8 *data; + size_t buf_len; + u16 alg, seq, status; + int ret; + + if (fils->state != PASN_FILS_STATE_NONE) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Not expecting wrapped data"); + return -1; + } + + if (!wd) { + wpa_printf(MSG_DEBUG, "PASN: FILS: No wrapped data"); + return -1; + } + + data = wpabuf_head_u8(wd); + buf_len = wpabuf_len(wd); + + if (buf_len < 6) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Buffer too short. len=%lu", + buf_len); + return -1; + } + + alg = WPA_GET_LE16(data); + seq = WPA_GET_LE16(data + 2); + status = WPA_GET_LE16(data + 4); + + wpa_printf(MSG_DEBUG, "PASN: FILS: alg=%u, seq=%u, status=%u", + alg, seq, status); + + if (alg != WLAN_AUTH_FILS_SK || seq != 1 || + status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Dropping peer authentication"); + return -1; + } + + data += 6; + buf_len -= 6; + + if (ieee802_11_parse_elems(data, buf_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Could not parse elements"); + return -1; + } + + if (!elems.rsn_ie || !elems.fils_nonce || !elems.fils_nonce || + !elems.wrapped_data) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing IEs"); + return -1; + } + + ret = wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsne_data); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed parsing RNSE"); + return -1; + } + + ret = wpa_pasn_validate_rsne(&rsne_data); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed validating RSNE"); + return -1; + } + + if (rsne_data.num_pmkid) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Not expecting PMKID in RSNE"); + return -1; + } + + wpa_hexdump(MSG_DEBUG, "PASN: FILS: Nonce", elems.fils_nonce, + FILS_NONCE_LEN); + os_memcpy(fils->nonce, elems.fils_nonce, FILS_NONCE_LEN); + + wpa_hexdump(MSG_DEBUG, "PASN: FILS: Session", elems.fils_session, + FILS_SESSION_LEN); + os_memcpy(fils->session, elems.fils_session, FILS_SESSION_LEN); + +#ifdef CONFIG_NO_RADIUS + wpa_printf(MSG_DEBUG, "PASN: FILS: RADIUS is not configured. Fail"); + return -1; +#endif /* CONFIG_NO_RADIUS */ + + fils_wd = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION, + WLAN_EID_EXT_WRAPPED_DATA); + + if (!fils_wd) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing wrapped data"); + return -1; + } + + if (!sta->eapol_sm) + sta->eapol_sm = ieee802_1x_alloc_eapol_sm(hapd, sta); + + wpa_printf(MSG_DEBUG, + "PASN: FILS: Forward EAP-Initiate/Re-auth to AS"); + + ieee802_1x_encapsulate_radius(hapd, sta, wpabuf_head(fils_wd), + wpabuf_len(fils_wd)); + + sta->flags |= WLAN_STA_PENDING_PASN_FILS_ERP; + + fils->state = PASN_FILS_STATE_PENDING_AS; + + /* + * Calculate pending PMKID here so that we do not need to maintain a + * copy of the EAP-Initiate/Reautt message. + */ + fils_pmkid_erp(pasn->akmp, wpabuf_head(fils_wd), wpabuf_len(fils_wd), + fils->erp_pmkid); + + wpabuf_free(fils_wd); + return 0; +} + +#endif /* CONFIG_FILS */ + + static struct wpabuf * pasn_get_wrapped_data(struct hostapd_data *hapd, struct sta_info *sta) { @@ -2510,10 +2786,17 @@ static struct wpabuf * pasn_get_wrapped_data(struct hostapd_data *hapd, case WPA_KEY_MGMT_SAE: #ifdef CONFIG_SAE return pasn_get_sae_wd(hapd, sta); +#else /* CONFIG_SAE */ + wpa_printf(MSG_ERROR, + "PASN: SAE: Cannot derive wrapped data"); + return NULL; #endif /* CONFIG_SAE */ - /* fall through */ case WPA_KEY_MGMT_FILS_SHA256: case WPA_KEY_MGMT_FILS_SHA384: +#ifdef CONFIG_FILS + return pasn_get_fils_wd(hapd, sta); +#endif /* CONFIG_FILS */ + /* fall through */ case WPA_KEY_MGMT_FT_PSK: case WPA_KEY_MGMT_FT_IEEE8021X: case WPA_KEY_MGMT_FT_IEEE8021X_SHA384: @@ -2699,12 +2982,13 @@ static void handle_auth_pasn_1(struct hostapd_data *hapd, struct sta_info *sta, struct ieee802_11_elems elems; struct wpa_ie_data rsn_data; struct wpa_pasn_params_data pasn_params; - struct rsn_pmksa_cache_entry *pmksa; + struct rsn_pmksa_cache_entry *pmksa = NULL; struct wpabuf *wrapped_data = NULL, *secret = NULL; const int *groups = hapd->conf->pasn_groups; static const int default_groups[] = { 19, 0 }; u16 status = WLAN_STATUS_SUCCESS; int ret; + bool derive_keys; u32 i; if (!groups) @@ -2796,6 +3080,7 @@ static void handle_auth_pasn_1(struct hostapd_data *hapd, struct sta_info *sta, goto send_resp; } + derive_keys = true; if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) { wrapped_data = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION, @@ -2818,10 +3103,47 @@ static void handle_auth_pasn_1(struct hostapd_data *hapd, struct sta_info *sta, } } #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + if (sta->pasn->akmp == WPA_KEY_MGMT_FILS_SHA256 || + sta->pasn->akmp == WPA_KEY_MGMT_FILS_SHA384) { + ret = pasn_wd_handle_fils(hapd, sta, wrapped_data); + if (ret) { + wpa_printf(MSG_DEBUG, + "PASN: Failed processing FILS wrapped data"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto send_resp; + } + + wpa_printf(MSG_DEBUG, + "PASN: FILS: Pending AS response"); + + /* + * With PASN/FILS, keys can be derived only after a + * response from the AS is processed. + */ + derive_keys = false; + } +#endif /* CONFIG_FILS */ } sta->pasn->wrapped_data_format = pasn_params.wrapped_data_format; + ret = pasn_auth_frame_hash(sta->pasn->akmp, sta->pasn->cipher, + ((const u8 *) mgmt) + IEEE80211_HDRLEN, + len - IEEE80211_HDRLEN, sta->pasn->hash); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: Failed to compute hash"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto send_resp; + } + + if (!derive_keys) { + wpa_printf(MSG_DEBUG, "PASN: Storing secret"); + sta->pasn->secret = secret; + wpabuf_free(wrapped_data); + return; + } + if (rsn_data.num_pmkid) { wpa_printf(MSG_DEBUG, "PASN: Try to find PMKSA entry"); @@ -2955,6 +3277,15 @@ static void handle_auth_pasn_3(struct hostapd_data *hapd, struct sta_info *sta, } } #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + if (sta->pasn->akmp == WPA_KEY_MGMT_FILS_SHA256 || + sta->pasn->akmp == WPA_KEY_MGMT_FILS_SHA384) { + if (wrapped_data) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Ignore wrapped data"); + } + } +#endif /* CONFIG_FILS */ wpabuf_free(wrapped_data); } diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index ee095f618..753c88335 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -2067,7 +2067,8 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, #ifdef CONFIG_FILS #ifdef NEED_AP_MLME - if (sta->flags & WLAN_STA_PENDING_FILS_ERP) { + if (sta->flags & + (WLAN_STA_PENDING_FILS_ERP | WLAN_STA_PENDING_PASN_FILS_ERP)) { /* TODO: Add a PMKSA entry on success? */ ieee802_11_finish_fils_auth( hapd, sta, hdr->code == RADIUS_CODE_ACCESS_ACCEPT, diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 70c8585ea..ccd1ed931 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -166,9 +166,20 @@ void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta) if (sta->pasn->ecdh) crypto_ecdh_deinit(sta->pasn->ecdh); + + wpabuf_free(sta->pasn->secret); + sta->pasn->secret = NULL; + #ifdef CONFIG_SAE sae_clear_data(&sta->pasn->sae); #endif /* CONFIG_SAE */ + +#ifdef CONFIG_FILS + /* In practice this pointer should be NULL */ + wpabuf_free(sta->pasn->fils.erp_resp); + sta->pasn->fils.erp_resp = NULL; +#endif /* CONFIG_FILS */ + bin_clear_free(sta->pasn, sizeof(*sta->pasn)); sta->pasn = NULL; } diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 630ebd608..efa48e7e3 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -41,6 +41,7 @@ #define WLAN_STA_MULTI_AP BIT(23) #define WLAN_STA_HE BIT(24) #define WLAN_STA_6GHZ BIT(25) +#define WLAN_STA_PENDING_PASN_FILS_ERP BIT(26) #define WLAN_STA_PENDING_DISASSOC_CB BIT(29) #define WLAN_STA_PENDING_DEAUTH_CB BIT(30) #define WLAN_STA_NONERP BIT(31) @@ -65,6 +66,22 @@ struct pending_eapol_rx { struct os_reltime rx_time; }; +enum pasn_fils_state { + PASN_FILS_STATE_NONE = 0, + PASN_FILS_STATE_PENDING_AS, + PASN_FILS_STATE_COMPLETE +}; + +struct pasn_fils_data { + u8 state; + u8 nonce[FILS_NONCE_LEN]; + u8 anonce[FILS_NONCE_LEN]; + u8 session[FILS_SESSION_LEN]; + u8 erp_pmkid[PMKID_LEN]; + + struct wpabuf *erp_resp; +}; + struct pasn_data { int akmp; int cipher; @@ -76,9 +93,13 @@ struct pasn_data { struct wpa_ptk ptk; struct crypto_ecdh *ecdh; + struct wpabuf *secret; #ifdef CONFIG_SAE struct sae_data sae; #endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + struct pasn_fils_data fils; +#endif /* CONFIG_FILS */ }; struct sta_info {