]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
wlantest: Validate FT elements in Reassociation Response frame
authorJouni Malinen <j@w1.fi>
Sat, 23 May 2020 21:35:13 +0000 (00:35 +0300)
committerJouni Malinen <j@w1.fi>
Sat, 23 May 2020 21:35:56 +0000 (00:35 +0300)
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 <j@w1.fi>
wlantest/rx_mgmt.c

index b9632474fad803a53c3675f393b39a186ed19714..0bc7eb2b2d93862b5c2daf98ca536123ab60c43b 100644 (file)
@@ -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);
+       }
 }