From b6a3bcffd76b598ae44299f0a5860b9b82f2d99f Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sun, 24 May 2020 00:35:13 +0300 Subject: [PATCH] wlantest: Validate FT elements in Reassociation Response frame Verify that RSNE, MDE, and FTE have valid information in FT Reassociation Response frames. In addition, decrypt GTK, IGTK, and BIGTK from the frame. Signed-off-by: Jouni Malinen --- wlantest/rx_mgmt.c | 447 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 437 insertions(+), 10 deletions(-) diff --git a/wlantest/rx_mgmt.c b/wlantest/rx_mgmt.c index b9632474f..0bc7eb2b2 100644 --- a/wlantest/rx_mgmt.c +++ b/wlantest/rx_mgmt.c @@ -1055,6 +1055,228 @@ static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data, } +static void process_gtk_subelem(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, + const u8 *kek, size_t kek_len, + const u8 *gtk_elem, + size_t gtk_elem_len) +{ + u8 gtk[32]; + int keyidx; + enum wpa_alg alg; + size_t gtk_len, keylen; + const u8 *rsc; + + if (!gtk_elem) { + add_note(wt, MSG_INFO, "FT: No GTK included in FTE"); + return; + } + + wpa_hexdump(MSG_DEBUG, "FT: Received GTK in Reassoc Resp", + gtk_elem, gtk_elem_len); + + if (gtk_elem_len < 11 + 24 || (gtk_elem_len - 11) % 8 || + gtk_elem_len - 19 > sizeof(gtk)) { + add_note(wt, MSG_INFO, "FT: Invalid GTK sub-elem length %zu", + gtk_elem_len); + return; + } + gtk_len = gtk_elem_len - 19; + if (aes_unwrap(kek, kek_len, gtk_len / 8, gtk_elem + 11, gtk)) { + add_note(wt, MSG_INFO, + "FT: AES unwrap failed - could not decrypt GTK"); + return; + } + + keylen = wpa_cipher_key_len(bss->group_cipher); + alg = wpa_cipher_to_alg(bss->group_cipher); + if (alg == WPA_ALG_NONE) { + add_note(wt, MSG_INFO, "FT: Unsupported Group Cipher %d", + bss->group_cipher); + return; + } + + if (gtk_len < keylen) { + add_note(wt, MSG_INFO, "FT: Too short GTK in FTE"); + return; + } + + /* Key Info[2] | Key Length[1] | RSC[8] | Key[5..32]. */ + + keyidx = WPA_GET_LE16(gtk_elem) & 0x03; + + if (gtk_elem[2] != keylen) { + add_note(wt, MSG_INFO, + "FT: GTK length mismatch: received %u negotiated %zu", + gtk_elem[2], keylen); + return; + } + + add_note(wt, MSG_DEBUG, "GTK KeyID=%u", keyidx); + wpa_hexdump(MSG_DEBUG, "FT: GTK from Reassoc Resp", gtk, keylen); + if (bss->group_cipher == WPA_CIPHER_TKIP) { + /* Swap Tx/Rx keys for Michael MIC */ + u8 tmp[8]; + + os_memcpy(tmp, gtk + 16, 8); + os_memcpy(gtk + 16, gtk + 24, 8); + os_memcpy(gtk + 24, tmp, 8); + } + + bss->gtk_len[keyidx] = gtk_len; + sta->gtk_len = gtk_len; + os_memcpy(bss->gtk[keyidx], gtk, gtk_len); + os_memcpy(sta->gtk, gtk, gtk_len); + rsc = gtk_elem + 2; + bss->rsc[keyidx][0] = rsc[5]; + bss->rsc[keyidx][1] = rsc[4]; + bss->rsc[keyidx][2] = rsc[3]; + bss->rsc[keyidx][3] = rsc[2]; + bss->rsc[keyidx][4] = rsc[1]; + bss->rsc[keyidx][5] = rsc[0]; + bss->gtk_idx = keyidx; + sta->gtk_idx = keyidx; + wpa_hexdump(MSG_DEBUG, "RSC", bss->rsc[keyidx], 6); +} + + +static void process_igtk_subelem(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, + const u8 *kek, size_t kek_len, + const u8 *igtk_elem, size_t igtk_elem_len) +{ + u8 igtk[WPA_IGTK_MAX_LEN]; + size_t igtk_len; + u16 keyidx; + const u8 *ipn; + + if (bss->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_128 && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_256 && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_CMAC_256) + return; + + if (!igtk_elem) { + add_note(wt, MSG_INFO, "FT: No IGTK included in FTE"); + return; + } + + wpa_hexdump(MSG_DEBUG, "FT: Received IGTK in Reassoc Resp", + igtk_elem, igtk_elem_len); + + igtk_len = wpa_cipher_key_len(bss->mgmt_group_cipher); + if (igtk_elem_len != 2 + 6 + 1 + igtk_len + 8) { + add_note(wt, MSG_INFO, "FT: Invalid IGTK sub-elem length %zu", + igtk_elem_len); + return; + } + if (igtk_elem[8] != igtk_len) { + add_note(wt, MSG_INFO, + "FT: Invalid IGTK sub-elem Key Length %d", + igtk_elem[8]); + return; + } + + if (aes_unwrap(kek, kek_len, igtk_len / 8, igtk_elem + 9, igtk)) { + add_note(wt, MSG_INFO, + "FT: AES unwrap failed - could not decrypt IGTK"); + return; + } + + /* KeyID[2] | IPN[6] | Key Length[1] | Key[16+8] */ + + keyidx = WPA_GET_LE16(igtk_elem); + + wpa_hexdump(MSG_DEBUG, "FT: IGTK from Reassoc Resp", igtk, igtk_len); + + if (keyidx < 4 || keyidx > 5) { + add_note(wt, MSG_INFO, "Unexpected IGTK KeyID %u", keyidx); + return; + } + + add_note(wt, MSG_DEBUG, "IGTK KeyID %u", keyidx); + wpa_hexdump(MSG_DEBUG, "IPN", igtk_elem + 2, 6); + wpa_hexdump(MSG_DEBUG, "IGTK", igtk, igtk_len); + os_memcpy(bss->igtk[keyidx], igtk, igtk_len); + bss->igtk_len[keyidx] = igtk_len; + ipn = igtk_elem + 2; + bss->ipn[keyidx][0] = ipn[5]; + bss->ipn[keyidx][1] = ipn[4]; + bss->ipn[keyidx][2] = ipn[3]; + bss->ipn[keyidx][3] = ipn[2]; + bss->ipn[keyidx][4] = ipn[1]; + bss->ipn[keyidx][5] = ipn[0]; + bss->igtk_idx = keyidx; +} + + +static void process_bigtk_subelem(struct wlantest *wt, struct wlantest_bss *bss, + struct wlantest_sta *sta, + const u8 *kek, size_t kek_len, + const u8 *bigtk_elem, size_t bigtk_elem_len) +{ + u8 bigtk[WPA_BIGTK_MAX_LEN]; + size_t bigtk_len; + u16 keyidx; + const u8 *ipn; + + if (!bigtk_elem || + (bss->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_128 && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_GMAC_256 && + bss->mgmt_group_cipher != WPA_CIPHER_BIP_CMAC_256)) + return; + + wpa_hexdump_key(MSG_DEBUG, "FT: Received BIGTK in Reassoc Resp", + bigtk_elem, bigtk_elem_len); + + bigtk_len = wpa_cipher_key_len(bss->mgmt_group_cipher); + if (bigtk_elem_len != 2 + 6 + 1 + bigtk_len + 8) { + add_note(wt, MSG_INFO, + "FT: Invalid BIGTK sub-elem length %zu", + bigtk_elem_len); + return; + } + if (bigtk_elem[8] != bigtk_len) { + add_note(wt, MSG_INFO, + "FT: Invalid BIGTK sub-elem Key Length %d", + bigtk_elem[8]); + return; + } + + if (aes_unwrap(kek, kek_len, bigtk_len / 8, bigtk_elem + 9, bigtk)) { + add_note(wt, MSG_INFO, + "FT: AES unwrap failed - could not decrypt BIGTK"); + return; + } + + /* KeyID[2] | IPN[6] | Key Length[1] | Key[16+8] */ + + keyidx = WPA_GET_LE16(bigtk_elem); + + wpa_hexdump(MSG_DEBUG, "FT: BIGTK from Reassoc Resp", bigtk, bigtk_len); + + if (keyidx < 6 || keyidx > 7) { + add_note(wt, MSG_INFO, "Unexpected BIGTK KeyID %u", keyidx); + return; + } + + add_note(wt, MSG_DEBUG, "BIGTK KeyID %u", keyidx); + wpa_hexdump(MSG_DEBUG, "BIPN", bigtk_elem + 2, 6); + wpa_hexdump(MSG_DEBUG, "BIGTK", bigtk, bigtk_len); + os_memcpy(bss->igtk[keyidx], bigtk, bigtk_len); + bss->igtk_len[keyidx] = bigtk_len; + ipn = bigtk_elem + 2; + bss->ipn[keyidx][0] = ipn[5]; + bss->ipn[keyidx][1] = ipn[4]; + bss->ipn[keyidx][2] = ipn[3]; + bss->ipn[keyidx][3] = ipn[2]; + bss->ipn[keyidx][4] = ipn[1]; + bss->ipn[keyidx][5] = ipn[0]; + bss->bigtk_idx = keyidx; +} + + static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, size_t len) { @@ -1064,6 +1286,7 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, u16 capab, status, aid; const u8 *ies; size_t ies_len; + struct ieee802_11_elems elems; mgmt = (const struct ieee80211_mgmt *) data; bss = bss_get(wt, mgmt->bssid); @@ -1106,17 +1329,15 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, } } - if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { - struct ieee802_11_elems elems; + if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == ParseFailed) { + add_note(wt, MSG_INFO, + "Failed to parse IEs in ReassocResp from " MACSTR, + MAC2STR(mgmt->sa)); + } - if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == - ParseFailed) { - add_note(wt, MSG_INFO, "Failed to parse IEs in " - "ReassocResp from " MACSTR, - MAC2STR(mgmt->sa)); - } else if (elems.timeout_int == NULL || - elems.timeout_int[0] != - WLAN_TIMEOUT_ASSOC_COMEBACK) { + if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) { + if (!elems.timeout_int || + elems.timeout_int[0] != WLAN_TIMEOUT_ASSOC_COMEBACK) { add_note(wt, MSG_INFO, "No valid Timeout Interval IE " "with Assoc Comeback time in ReassocResp " "(status=30) from " MACSTR, @@ -1149,6 +1370,212 @@ static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data, MAC2STR(sta->addr), MAC2STR(bss->bssid)); sta->state = STATE3; } + + if (elems.ftie) { + struct wpa_ft_ies parse; + int use_sha384; + struct rsn_mdie *mde; + const u8 *anonce, *snonce, *fte_mic; + u8 fte_elem_count; + unsigned int count; + u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN]; + size_t mic_len = 16; + const u8 *kck, *kek; + size_t kck_len, kek_len; + + use_sha384 = wpa_key_mgmt_sha384(sta->key_mgmt); + + if (wpa_ft_parse_ies(ies, ies_len, &parse, use_sha384) < 0) { + add_note(wt, MSG_INFO, "FT: Failed to parse FT IEs"); + return; + } + + if (!parse.rsn) { + add_note(wt, MSG_INFO, "FT: No RSNE in Reassoc Resp"); + return; + } + + if (!parse.rsn_pmkid) { + add_note(wt, MSG_INFO, "FT: No PMKID in RSNE"); + return; + } + + if (os_memcmp_const(parse.rsn_pmkid, sta->pmk_r1_name, + WPA_PMK_NAME_LEN) != 0) { + add_note(wt, MSG_INFO, + "FT: PMKID in Reassoc Resp did not match PMKR1Name"); + wpa_hexdump(MSG_DEBUG, + "FT: Received RSNE[PMKR1Name]", + parse.rsn_pmkid, WPA_PMK_NAME_LEN); + wpa_hexdump(MSG_DEBUG, + "FT: Previously derived PMKR1Name", + sta->pmk_r1_name, WPA_PMK_NAME_LEN); + return; + } + + mde = (struct rsn_mdie *) parse.mdie; + if (!mde || parse.mdie_len < sizeof(*mde) || + os_memcmp(mde->mobility_domain, bss->mdid, + MOBILITY_DOMAIN_ID_LEN) != 0) { + add_note(wt, MSG_INFO, "FT: Invalid MDE"); + } + + if (use_sha384) { + struct rsn_ftie_sha384 *fte; + + fte = (struct rsn_ftie_sha384 *) parse.ftie; + if (!fte || parse.ftie_len < sizeof(*fte)) { + add_note(wt, MSG_INFO, "FT: Invalid FTE"); + return; + } + + anonce = fte->anonce; + snonce = fte->snonce; + fte_elem_count = fte->mic_control[1]; + fte_mic = fte->mic; + } else { + struct rsn_ftie *fte; + + fte = (struct rsn_ftie *) parse.ftie; + if (!fte || parse.ftie_len < sizeof(*fte)) { + add_note(wt, MSG_INFO, "FT: Invalid FTIE"); + return; + } + + anonce = fte->anonce; + snonce = fte->snonce; + fte_elem_count = fte->mic_control[1]; + fte_mic = fte->mic; + } + + if (os_memcmp(snonce, sta->snonce, WPA_NONCE_LEN) != 0) { + add_note(wt, MSG_INFO, "FT: SNonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received SNonce", + snonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce", + sta->snonce, WPA_NONCE_LEN); + return; + } + + if (os_memcmp(anonce, sta->anonce, WPA_NONCE_LEN) != 0) { + add_note(wt, MSG_INFO, "FT: ANonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received ANonce", + anonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce", + sta->anonce, WPA_NONCE_LEN); + return; + } + + if (!parse.r0kh_id) { + add_note(wt, MSG_INFO, "FT: No R0KH-ID subelem in FTE"); + return; + } + + if (parse.r0kh_id_len != bss->r0kh_id_len || + os_memcmp_const(parse.r0kh_id, bss->r0kh_id, + parse.r0kh_id_len) != 0) { + add_note(wt, MSG_INFO, + "FT: R0KH-ID in FTE did not match the current R0KH-ID"); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID in FTIE", + parse.r0kh_id, parse.r0kh_id_len); + wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID", + bss->r0kh_id, bss->r0kh_id_len); + os_memcpy(bss->r0kh_id, parse.r0kh_id, + parse.r0kh_id_len); + bss->r0kh_id_len = parse.r0kh_id_len; + } + + if (!parse.r1kh_id) { + add_note(wt, MSG_INFO, "FT: No R1KH-ID subelem in FTE"); + return; + } + + if (os_memcmp_const(parse.r1kh_id, bss->r1kh_id, + FT_R1KH_ID_LEN) != 0) { + add_note(wt, MSG_INFO, + "FT: Unknown R1KH-ID used in ReassocResp"); + os_memcpy(bss->r1kh_id, parse.r1kh_id, FT_R1KH_ID_LEN); + } + + count = 3; + if (parse.ric) + count += ieee802_11_ie_count(parse.ric, parse.ric_len); + if (parse.rsnxe) + count++; + if (fte_elem_count != count) { + add_note(wt, MSG_INFO, + "FT: Unexpected IE count in MIC Control: received %u expected %u", + fte_elem_count, count); + return; + } + + if (wpa_key_mgmt_fils(sta->key_mgmt)) { + kck = sta->ptk.kck2; + kck_len = sta->ptk.kck2_len; + kek = sta->ptk.kek2; + kek_len = sta->ptk.kek2_len; + } else { + kck = sta->ptk.kck; + kck_len = sta->ptk.kck_len; + kek = sta->ptk.kek; + kek_len = sta->ptk.kek_len; + } + if (wpa_ft_mic(kck, kck_len, sta->addr, bss->bssid, 6, + parse.mdie - 2, parse.mdie_len + 2, + parse.ftie - 2, parse.ftie_len + 2, + parse.rsn - 2, parse.rsn_len + 2, + parse.ric, parse.ric_len, + parse.rsnxe ? parse.rsnxe - 2 : NULL, + parse.rsnxe ? parse.rsnxe_len + 2 : 0, + mic) < 0) { + add_note(wt, MSG_INFO, "FT: Failed to calculate MIC"); + return; + } + + if (os_memcmp_const(mic, fte_mic, mic_len) != 0) { + add_note(wt, MSG_INFO, "FT: Invalid MIC in FTE"); + wpa_printf(MSG_DEBUG, + "FT: addr=" MACSTR " auth_addr=" MACSTR, + MAC2STR(sta->addr), + MAC2STR(bss->bssid)); + wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC", + fte_mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC", + mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: MDE", + parse.mdie - 2, parse.mdie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: FTE", + parse.ftie - 2, parse.ftie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: RSN", + parse.rsn - 2, parse.rsn_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE", + parse.rsnxe ? parse.rsnxe - 2 : NULL, + parse.rsnxe ? parse.rsnxe_len + 2 : 0); + return; + } + + add_note(wt, MSG_INFO, "FT: Valid FTE MIC"); + + if (wpa_compare_rsn_ie(wpa_key_mgmt_ft(sta->key_mgmt), + bss->rsnie, 2 + bss->rsnie[1], + parse.rsn - 2, parse.rsn_len + 2)) { + add_note(wt, MSG_INFO, + "FT: RSNE mismatch between Beacon/ProbeResp and FT protocol Reassociation Response frame"); + wpa_hexdump(MSG_INFO, "RSNE in Beacon/ProbeResp", + &bss->rsnie[2], bss->rsnie[1]); + wpa_hexdump(MSG_INFO, + "RSNE in FT protocol Reassociation Response frame", + parse.rsn ? parse.rsn - 2 : NULL, + parse.rsn ? parse.rsn_len + 2 : 0); + } + + process_gtk_subelem(wt, bss, sta, kek, kek_len, + parse.gtk, parse.gtk_len); + process_igtk_subelem(wt, bss, sta, kek, kek_len, + parse.igtk, parse.igtk_len); + process_bigtk_subelem(wt, bss, sta, kek, kek_len, + parse.bigtk, parse.bigtk_len); + } }