From c72df3c67c5435817783e4375b50bb55107dc057 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Tue, 5 Sep 2017 19:01:59 +0300 Subject: [PATCH] wlantest: FILS keys and (Re)Association Request/Response frames Try to derive PTK when FILS shared key authentication is used without PFS. The list of available PMKs is interpreted as rMSK for this purpose and PMK and PTK is derived from that. If the resulting PTK (KEK) can be used to decrypt the encrypted parts of (Re)Association Request/Response frames, mark the PTK as derived so that encrypted frames during the association can be decrypted. In addition, write a decrypted version of the (Re)Association Request/Response frames into the output file. Signed-off-by: Jouni Malinen --- wlantest/rx_mgmt.c | 320 ++++++++++++++++++++++++++++++++++++++++++-- wlantest/wlantest.h | 1 + 2 files changed, 313 insertions(+), 8 deletions(-) diff --git a/wlantest/rx_mgmt.c b/wlantest/rx_mgmt.c index 6242bc600..b15f561e3 100644 --- a/wlantest/rx_mgmt.c +++ b/wlantest/rx_mgmt.c @@ -12,6 +12,9 @@ #include "common/defs.h" #include "common/ieee802_11_defs.h" #include "common/ieee802_11_common.h" +#include "common/wpa_common.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" #include "crypto/aes_wrap.h" #include "wlantest.h" @@ -110,6 +113,55 @@ static void rx_mgmt_probe_resp(struct wlantest *wt, const u8 *data, size_t len) } +static void process_fils_auth(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + struct ieee802_11_elems elems; + u16 trans; + struct wpa_ie_data data; + + if (sta->auth_alg != WLAN_AUTH_FILS_SK || + len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) + return; + + trans = le_to_host16(mgmt->u.auth.auth_transaction); + + if (ieee802_11_parse_elems(mgmt->u.auth.variable, + len - IEEE80211_HDRLEN - + sizeof(mgmt->u.auth), &elems, 0) == + ParseFailed) + return; + + if (trans == 1) { + if (!elems.rsn_ie) { + add_note(wt, MSG_INFO, + "FILS Authentication frame missing RSNE"); + return; + } + if (wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, + elems.rsn_ie_len + 2, &data) < 0) { + add_note(wt, MSG_INFO, + "Invalid RSNE in FILS Authentication frame"); + return; + } + sta->key_mgmt = data.key_mgmt; + sta->pairwise_cipher = data.pairwise_cipher; + } + + if (!elems.fils_nonce) { + add_note(wt, MSG_INFO, + "FILS Authentication frame missing nonce"); + return; + } + + if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0) + os_memcpy(sta->anonce, elems.fils_nonce, FILS_NONCE_LEN); + else + os_memcpy(sta->snonce, elems.fils_nonce, FILS_NONCE_LEN); +} + + static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len) { const struct ieee80211_mgmt *mgmt; @@ -135,6 +187,7 @@ static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len) } alg = le_to_host16(mgmt->u.auth.auth_alg); + sta->auth_alg = alg; trans = le_to_host16(mgmt->u.auth.auth_transaction); status = le_to_host16(mgmt->u.auth.status_code); @@ -155,6 +208,8 @@ static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len) sta->counters[WLANTEST_STA_COUNTER_AUTH_RX]++; else sta->counters[WLANTEST_STA_COUNTER_AUTH_TX]++; + + process_fils_auth(wt, bss, sta, mgmt, len); } @@ -257,12 +312,137 @@ static void rx_mgmt_deauth(struct wlantest *wt, const u8 *data, size_t len, } +static const u8 * get_fils_session(const u8 *ies, size_t ies_len) +{ + const u8 *ie, *end; + + ie = ies; + end = ((const u8 *) ie) + ies_len; + while (ie + 1 < end) { + if (ie + 2 + ie[1] > end) + break; + if (ie[0] == WLAN_EID_EXTENSION && + ie[1] >= 1 + FILS_SESSION_LEN && + ie[2] == WLAN_EID_EXT_FILS_SESSION) + return ie; + ie += 2 + ie[1]; + } + return NULL; +} + + +static int try_rmsk(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, struct wlantest_pmk *pmk, + const u8 *frame_start, const u8 *frame_ad, + const u8 *frame_ad_end, const u8 *encr_end) +{ + size_t pmk_len = 0; + u8 pmk_buf[PMK_LEN_MAX]; + struct wpa_ptk ptk; + u8 ick[FILS_ICK_MAX_LEN]; + size_t ick_len; + const u8 *aad[5]; + size_t aad_len[5]; + u8 buf[2000]; + + if (fils_rmsk_to_pmk(sta->key_mgmt, pmk->pmk, pmk->pmk_len, + sta->snonce, sta->anonce, NULL, 0, + pmk_buf, &pmk_len) < 0) + return -1; + + if (fils_pmk_to_ptk(pmk_buf, pmk_len, sta->addr, bss->bssid, + sta->snonce, sta->anonce, &ptk, ick, &ick_len, + sta->key_mgmt, sta->pairwise_cipher, + NULL, NULL) < 0) + return -1; + + /* Check AES-SIV decryption with the derived key */ + + /* AES-SIV AAD vectors */ + + /* The STA's MAC address */ + aad[0] = sta->addr; + aad_len[0] = ETH_ALEN; + /* The AP's BSSID */ + aad[1] = bss->bssid; + aad_len[1] = ETH_ALEN; + /* The STA's nonce */ + aad[2] = sta->snonce; + aad_len[2] = FILS_NONCE_LEN; + /* The AP's nonce */ + aad[3] = sta->anonce; + aad_len[3] = FILS_NONCE_LEN; + /* + * The (Re)Association Request frame from the Capability Information + * field to the FILS Session element (both inclusive). + */ + aad[4] = frame_ad; + aad_len[4] = frame_ad_end - frame_ad; + + if (encr_end - frame_ad_end < AES_BLOCK_SIZE || + encr_end - frame_ad_end > sizeof(buf)) + return -1; + if (aes_siv_decrypt(ptk.kek, ptk.kek_len, + frame_ad_end, encr_end - frame_ad_end, + 5, aad, aad_len, buf) < 0) { + wpa_printf(MSG_DEBUG, + "FILS: Derived PTK did not match AES-SIV data"); + return -1; + } + + add_note(wt, MSG_DEBUG, "Derived FILS PTK"); + os_memcpy(&sta->ptk, &ptk, sizeof(ptk)); + sta->ptk_set = 1; + sta->counters[WLANTEST_STA_COUNTER_PTK_LEARNED]++; + wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Request elements", + buf, encr_end - frame_ad_end - AES_BLOCK_SIZE); + + if (wt->write_pcap_dumper || wt->pcapng) { + write_pcap_decrypted(wt, frame_start, + frame_ad_end - frame_start, + buf, + encr_end - frame_ad_end - AES_BLOCK_SIZE); + } + + return 0; +} + + +static void derive_fils_keys(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, const u8 *frame_start, + const u8 *frame_ad, const u8 *frame_ad_end, + const u8 *encr_end) +{ + struct wlantest_pmk *pmk; + + wpa_printf(MSG_DEBUG, "Trying to derive PTK for " MACSTR + " from FILS rMSK", MAC2STR(sta->addr)); + + dl_list_for_each(pmk, &bss->pmk, struct wlantest_pmk, + list) { + wpa_printf(MSG_DEBUG, "Try per-BSS PMK"); + if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad, + frame_ad_end, encr_end) == 0) + return; + } + + dl_list_for_each(pmk, &wt->pmk, struct wlantest_pmk, list) { + wpa_printf(MSG_DEBUG, "Try global PMK"); + if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad, + frame_ad_end, encr_end) == 0) + return; + } +} + + static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len) { const struct ieee80211_mgmt *mgmt; struct wlantest_bss *bss; struct wlantest_sta *sta; struct ieee802_11_elems elems; + const u8 *ie; + size_t ie_len; mgmt = (const struct ieee80211_mgmt *) data; bss = bss_get(wt, mgmt->bssid); @@ -286,9 +466,24 @@ static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len) sta->counters[WLANTEST_STA_COUNTER_ASSOCREQ_TX]++; - if (ieee802_11_parse_elems(mgmt->u.assoc_req.variable, - len - (mgmt->u.assoc_req.variable - data), - &elems, 0) == ParseFailed) { + ie = mgmt->u.assoc_req.variable; + ie_len = len - (mgmt->u.assoc_req.variable - data); + + if (sta->auth_alg == WLAN_AUTH_FILS_SK) { + const u8 *session, *frame_ad, *frame_ad_end, *encr_end; + + session = get_fils_session(ie, ie_len); + if (session) { + frame_ad = (const u8 *) &mgmt->u.assoc_req.capab_info; + frame_ad_end = session + 2 + session[1]; + encr_end = data + len; + derive_fils_keys(wt, bss, sta, data, frame_ad, + frame_ad_end, encr_end); + ie_len = session - ie; + } + } + + if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) { add_note(wt, MSG_INFO, "Invalid IEs in Association Request " "frame from " MACSTR, MAC2STR(mgmt->sa)); return; @@ -308,6 +503,65 @@ static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len) } +static void decrypt_fils_assoc_resp(struct wlantest *wt, + struct wlantest_bss *bss, + struct wlantest_sta *sta, + const u8 *frame_start, const u8 *frame_ad, + const u8 *frame_ad_end, const u8 *encr_end) +{ + const u8 *aad[5]; + size_t aad_len[5]; + u8 buf[2000]; + + if (!sta->ptk_set) + return; + + /* Check AES-SIV decryption with the derived key */ + + /* AES-SIV AAD vectors */ + + /* The AP's BSSID */ + aad[0] = bss->bssid; + aad_len[0] = ETH_ALEN; + /* The STA's MAC address */ + aad[1] = sta->addr; + aad_len[1] = ETH_ALEN; + /* The AP's nonce */ + aad[2] = sta->anonce; + aad_len[2] = FILS_NONCE_LEN; + /* The STA's nonce */ + aad[3] = sta->snonce; + aad_len[3] = FILS_NONCE_LEN; + /* + * The (Re)Association Response frame from the Capability Information + * field to the FILS Session element (both inclusive). + */ + aad[4] = frame_ad; + aad_len[4] = frame_ad_end - frame_ad; + + if (encr_end - frame_ad_end < AES_BLOCK_SIZE || + encr_end - frame_ad_end > sizeof(buf)) + return; + if (aes_siv_decrypt(sta->ptk.kek, sta->ptk.kek_len, + frame_ad_end, encr_end - frame_ad_end, + 5, aad, aad_len, buf) < 0) { + wpa_printf(MSG_DEBUG, + "FILS: Derived PTK did not match AES-SIV data"); + return; + } + + wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Response elements", + buf, encr_end - frame_ad_end - AES_BLOCK_SIZE); + + if (wt->write_pcap_dumper || wt->pcapng) { + write_pcap_decrypted(wt, frame_start, + frame_ad_end - frame_start, + buf, + encr_end - frame_ad_end - AES_BLOCK_SIZE); + } +} + + static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len) { const struct ieee80211_mgmt *mgmt; @@ -344,6 +598,20 @@ static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len) MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status, aid & 0x3fff); + if (sta->auth_alg == WLAN_AUTH_FILS_SK) { + const u8 *session, *frame_ad, *frame_ad_end, *encr_end; + + session = get_fils_session(ies, ies_len); + if (session) { + frame_ad = (const u8 *) &mgmt->u.assoc_resp.capab_info; + frame_ad_end = session + 2 + session[1]; + encr_end = data + len; + decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad, + frame_ad_end, encr_end); + ies_len = session - ies; + } + } + if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { struct ieee802_11_elems elems; if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == @@ -406,6 +674,8 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data, struct wlantest_bss *bss; struct wlantest_sta *sta; struct ieee802_11_elems elems; + const u8 *ie; + size_t ie_len; mgmt = (const struct ieee80211_mgmt *) data; bss = bss_get(wt, mgmt->bssid); @@ -430,9 +700,24 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data, sta->counters[WLANTEST_STA_COUNTER_REASSOCREQ_TX]++; - if (ieee802_11_parse_elems(mgmt->u.reassoc_req.variable, - len - (mgmt->u.reassoc_req.variable - data), - &elems, 0) == ParseFailed) { + ie = mgmt->u.reassoc_req.variable; + ie_len = len - (mgmt->u.reassoc_req.variable - data); + + if (sta->auth_alg == WLAN_AUTH_FILS_SK) { + const u8 *session, *frame_ad, *frame_ad_end, *encr_end; + + session = get_fils_session(ie, ie_len); + if (session) { + frame_ad = (const u8 *) &mgmt->u.reassoc_req.capab_info; + frame_ad_end = session + 2 + session[1]; + encr_end = data + len; + derive_fils_keys(wt, bss, sta, data, frame_ad, + frame_ad_end, encr_end); + ie_len = session - ie; + } + } + + if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) { add_note(wt, MSG_INFO, "Invalid IEs in Reassociation Request " "frame from " MACSTR, MAC2STR(mgmt->sa)); return; @@ -460,6 +745,8 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, struct wlantest_bss *bss; struct wlantest_sta *sta; u16 capab, status, aid; + const u8 *ies; + size_t ies_len; mgmt = (const struct ieee80211_mgmt *) data; bss = bss_get(wt, mgmt->bssid); @@ -475,6 +762,9 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, return; } + ies = mgmt->u.reassoc_resp.variable; + ies_len = len - (mgmt->u.reassoc_resp.variable - data); + capab = le_to_host16(mgmt->u.reassoc_resp.capab_info); status = le_to_host16(mgmt->u.reassoc_resp.status_code); aid = le_to_host16(mgmt->u.reassoc_resp.aid); @@ -484,10 +774,24 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status, aid & 0x3fff); + if (sta->auth_alg == WLAN_AUTH_FILS_SK) { + const u8 *session, *frame_ad, *frame_ad_end, *encr_end; + + session = get_fils_session(ies, ies_len); + if (session) { + frame_ad = (const u8 *) + &mgmt->u.reassoc_resp.capab_info; + frame_ad_end = session + 2 + session[1]; + encr_end = data + len; + decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad, + frame_ad_end, encr_end); + ies_len = session - ies; + } + } + if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { struct ieee802_11_elems elems; - const u8 *ies = mgmt->u.reassoc_resp.variable; - size_t ies_len = len - (mgmt->u.reassoc_resp.variable - data); + if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == ParseFailed) { add_note(wt, MSG_INFO, "Failed to parse IEs in " diff --git a/wlantest/wlantest.h b/wlantest/wlantest.h index 7a56b97b7..a841c18b7 100644 --- a/wlantest/wlantest.h +++ b/wlantest/wlantest.h @@ -60,6 +60,7 @@ struct wlantest_sta { STATE2 /* authenticated */, STATE3 /* associated */ } state; + u16 auth_alg; u16 aid; u8 rsnie[257]; /* WPA/RSN IE */ u8 osenie[257]; /* OSEN IE */