FT: Fix FT 4-Way Handshake to include PMKR1Name in messages 2 and 3
authorJouni Malinen <j@w1.fi>
Wed, 7 Apr 2010 18:04:13 +0000 (21:04 +0300)
committerJouni Malinen <j@w1.fi>
Wed, 7 Apr 2010 18:04:13 +0000 (21:04 +0300)
IEEE Std 802.11r-2008, 11A.4.2 describes FT initial mobility domain
association in an RSN to include PMKR1Name in the PMKID-List field
in RSN IE in messages 2/4 and 3/4. This makes the RSN IE not be
bitwise identical with the values used in Beacon, Probe Response,
(Re)association Request frames.

The previous versions of wpa_supplicant and hostapd did not add the
PMKR1Name value in EAPOL-Key frame and did not accept it if added
(due to bitwise comparison of RSN IEs). This commit fixes the
implementation to be compliant with the standard by adding the
PMKR1Name value into EAPOL-Key messages during FT 4-Way Handshake and
by verifying that the received value matches with the value derived
locally.

This breaks interoperability with previous wpa_supplicant/hostapd
versions.

src/ap/wpa_auth.c
src/ap/wpa_auth_ft.c
src/ap/wpa_auth_i.h
src/common/wpa_common.c
src/common/wpa_common.h
src/rsn_supp/wpa.c
src/rsn_supp/wpa_ft.c

index 796f584..9a098c3 100644 (file)
@@ -609,6 +609,7 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
               SMK_M1, SMK_M3, SMK_ERROR } msg;
        char *msgtxt;
        struct wpa_eapol_ie_parse kde;
+       int ft;
 
        if (wpa_auth == NULL || !wpa_auth->conf.wpa || sm == NULL)
                return;
@@ -739,9 +740,12 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
                                         sm->wpa_ptk_state);
                        return;
                }
+               ft = sm->wpa == WPA_VERSION_WPA2 &&
+                       wpa_key_mgmt_ft(sm->wpa_key_mgmt);
                if (sm->wpa_ie == NULL ||
-                   sm->wpa_ie_len != key_data_length ||
-                   os_memcmp(sm->wpa_ie, key + 1, key_data_length) != 0) {
+                   wpa_compare_rsn_ie(ft,
+                                      sm->wpa_ie, sm->wpa_ie_len,
+                                      (u8 *) (key + 1), key_data_length)) {
                        wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
                                        "WPA IE from (Re)AssocReq did not "
                                        "match with msg 2/4");
@@ -755,6 +759,22 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
                        wpa_sta_disconnect(wpa_auth, sm->addr);
                        return;
                }
+#ifdef CONFIG_IEEE80211R
+               if (ft) {
+                       struct wpa_ie_data ie;
+                       if (wpa_parse_wpa_ie_rsn((u8 *) (key + 1),
+                                                key_data_length, &ie) < 0 ||
+                           ie.num_pmkid != 1 || ie.pmkid == NULL) {
+                               wpa_printf(MSG_DEBUG, "FT: No PMKR1Name in "
+                                          "FT 4-way handshake message 2/4");
+                               wpa_sta_disconnect(wpa_auth, sm->addr);
+                               return;
+                       }
+                       os_memcpy(sm->sup_pmk_r1_name, ie.pmkid, PMKID_LEN);
+                       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from Supplicant",
+                                   sm->sup_pmk_r1_name, PMKID_LEN);
+               }
+#endif /* CONFIG_IEEE80211R */
                break;
        case PAIRWISE_4:
                if (sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING ||
@@ -1487,6 +1507,27 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
                return;
        }
 
+#ifdef CONFIG_IEEE80211R
+       if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
+               /*
+                * Verify that PMKR1Name from EAPOL-Key message 2/4 matches
+                * with the value we derived.
+                */
+               if (os_memcmp(sm->sup_pmk_r1_name, sm->pmk_r1_name,
+                             WPA_PMK_NAME_LEN) != 0) {
+                       wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
+                                       "PMKR1Name mismatch in FT 4-way "
+                                       "handshake");
+                       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from "
+                                   "Supplicant",
+                                   sm->sup_pmk_r1_name, WPA_PMK_NAME_LEN);
+                       wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
+                                   sm->pmk_r1_name, WPA_PMK_NAME_LEN);
+                       return;
+               }
+       }
+#endif /* CONFIG_IEEE80211R */
+
        eloop_cancel_timeout(wpa_send_eapol_timeout, sm->wpa_auth, sm);
 
        if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) {
@@ -1611,6 +1652,10 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
        kde_len = wpa_ie_len + ieee80211w_kde_len(sm);
        if (gtk)
                kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
+#ifdef CONFIG_IEEE80211R
+       if (wpa_key_mgmt_ft(sm->wpa_key_mgmt))
+               kde_len += 2 + PMKID_LEN;
+#endif /* CONFIG_IEEE80211R */
        kde = os_malloc(kde_len);
        if (kde == NULL)
                return;
@@ -1618,6 +1663,18 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
        pos = kde;
        os_memcpy(pos, wpa_ie, wpa_ie_len);
        pos += wpa_ie_len;
+#ifdef CONFIG_IEEE80211R
+       if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
+               int res = wpa_insert_pmkid(kde, pos - kde, sm->pmk_r1_name);
+               if (res < 0) {
+                       wpa_printf(MSG_ERROR, "FT: Failed to insert "
+                                  "PMKR1Name into RSN IE in EAPOL-Key data");
+                       os_free(kde);
+                       return;
+               }
+               pos += res;
+       }
+#endif /* CONFIG_IEEE80211R */
        if (gtk) {
                u8 hdr[2];
                hdr[0] = keyidx & 0x03;
index 0034daf..5d085c6 100644 (file)
@@ -356,7 +356,7 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
                           struct wpa_ptk *ptk, size_t ptk_len)
 {
        u8 pmk_r0[PMK_LEN], pmk_r0_name[WPA_PMK_NAME_LEN];
-       u8 pmk_r1[PMK_LEN], pmk_r1_name[WPA_PMK_NAME_LEN];
+       u8 pmk_r1[PMK_LEN];
        u8 ptk_name[WPA_PMK_NAME_LEN];
        const u8 *mdid = sm->wpa_auth->conf.mobility_domain;
        const u8 *r0kh = sm->wpa_auth->conf.r0_key_holder;
@@ -380,14 +380,15 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
                            sm->pairwise);
 
        wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
-                         pmk_r1, pmk_r1_name);
+                         pmk_r1, sm->pmk_r1_name);
        wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", pmk_r1, PMK_LEN);
-       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", pmk_r1_name, WPA_PMK_NAME_LEN);
-       wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, pmk_r1_name,
+       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name,
+                   WPA_PMK_NAME_LEN);
+       wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, sm->pmk_r1_name,
                            sm->pairwise);
 
        wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr,
-                         sm->wpa_auth->addr, pmk_r1_name,
+                         sm->wpa_auth->addr, sm->pmk_r1_name,
                          (u8 *) ptk, ptk_len, ptk_name);
        wpa_hexdump_key(MSG_DEBUG, "FT: PTK", (u8 *) ptk, ptk_len);
        wpa_hexdump(MSG_DEBUG, "FT: PTKName", ptk_name, WPA_PMK_NAME_LEN);
index 86df3a4..6d6ed54 100644 (file)
@@ -116,6 +116,8 @@ struct wpa_state_machine {
                                           * Request */
        u8 r0kh_id[FT_R0KH_ID_MAX_LEN]; /* R0KH-ID from FT Auth Request */
        size_t r0kh_id_len;
+       u8 sup_pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name from EAPOL-Key
+                                              * message 2/4 */
 #endif /* CONFIG_IEEE80211R */
 };
 
index c9c0e00..84cbe9a 100644 (file)
@@ -669,3 +669,116 @@ const char * wpa_key_mgmt_txt(int key_mgmt, int proto)
                return "UNKNOWN";
        }
 }
+
+
+int wpa_compare_rsn_ie(int ft_initial_assoc,
+                      const u8 *ie1, size_t ie1len,
+                      const u8 *ie2, size_t ie2len)
+{
+       if (ie1len == ie2len && os_memcmp(ie1, ie2, ie1len) == 0)
+               return 0; /* identical IEs */
+
+#ifdef CONFIG_IEEE80211R
+       if (ft_initial_assoc) {
+               struct wpa_ie_data ie1d, ie2d;
+               /*
+                * The PMKID-List in RSN IE is different between Beacon/Probe
+                * Response/(Re)Association Request frames and EAPOL-Key
+                * messages in FT initial mobility domain association. Allow
+                * for this, but verify that other parts of the RSN IEs are
+                * identical.
+                */
+               if (wpa_parse_wpa_ie_rsn(ie1, ie1len, &ie1d) < 0 ||
+                   wpa_parse_wpa_ie_rsn(ie2, ie2len, &ie2d) < 0)
+                       return -1;
+               if (ie1d.proto == ie2d.proto &&
+                   ie1d.pairwise_cipher == ie2d.pairwise_cipher &&
+                   ie1d.group_cipher == ie2d.group_cipher &&
+                   ie1d.key_mgmt == ie2d.key_mgmt &&
+                   ie1d.capabilities == ie2d.capabilities &&
+                   ie1d.mgmt_group_cipher == ie2d.mgmt_group_cipher)
+                       return 0;
+       }
+#endif /* CONFIG_IEEE80211R */
+
+       return -1;
+}
+
+
+#ifdef CONFIG_IEEE80211R
+int wpa_insert_pmkid(u8 *ies, size_t ies_len, const u8 *pmkid)
+{
+       u8 *start, *end, *rpos, *rend;
+       int added = 0;
+
+       start = ies;
+       end = ies + ies_len;
+
+       while (start < end) {
+               if (*start == WLAN_EID_RSN)
+                       break;
+               start += 2 + start[1];
+       }
+       if (start >= end) {
+               wpa_printf(MSG_ERROR, "FT: Could not find RSN IE in "
+                          "IEs data");
+               return -1;
+       }
+       wpa_hexdump(MSG_DEBUG, "FT: RSN IE before modification",
+                   start, 2 + start[1]);
+
+       /* Find start of PMKID-Count */
+       rpos = start + 2;
+       rend = rpos + start[1];
+
+       /* Skip Version and Group Data Cipher Suite */
+       rpos += 2 + 4;
+       /* Skip Pairwise Cipher Suite Count and List */
+       rpos += 2 + WPA_GET_LE16(rpos) * RSN_SELECTOR_LEN;
+       /* Skip AKM Suite Count and List */
+       rpos += 2 + WPA_GET_LE16(rpos) * RSN_SELECTOR_LEN;
+
+       if (rpos == rend) {
+               /* Add RSN Capabilities */
+               os_memmove(rpos + 2, rpos, end - rpos);
+               *rpos++ = 0;
+               *rpos++ = 0;
+       } else {
+               /* Skip RSN Capabilities */
+               rpos += 2;
+               if (rpos > rend) {
+                       wpa_printf(MSG_ERROR, "FT: Could not parse RSN IE in "
+                                  "IEs data");
+                       return -1;
+               }
+       }
+
+       if (rpos == rend) {
+               /* No PMKID-Count field included; add it */
+               os_memmove(rpos + 2 + PMKID_LEN, rpos, end - rpos);
+               WPA_PUT_LE16(rpos, 1);
+               rpos += 2;
+               os_memcpy(rpos, pmkid, PMKID_LEN);
+               added += 2 + PMKID_LEN;
+               start[1] += 2 + PMKID_LEN;
+       } else {
+               /* PMKID-Count was included; use it */
+               if (WPA_GET_LE16(rpos) != 0) {
+                       wpa_printf(MSG_ERROR, "FT: Unexpected PMKID "
+                                  "in RSN IE in EAPOL-Key data");
+                       return -1;
+               }
+               WPA_PUT_LE16(rpos, 1);
+               rpos += 2;
+               os_memmove(rpos + PMKID_LEN, rpos, end - rpos);
+               os_memcpy(rpos, pmkid, PMKID_LEN);
+               added += PMKID_LEN;
+               start[1] += PMKID_LEN;
+       }
+
+       wpa_hexdump(MSG_DEBUG, "FT: RSN IE after modification "
+                   "(PMKID inserted)", start, 2 + start[1]);
+
+       return added;
+}
+#endif /* CONFIG_IEEE80211R */
index b8f43d5..fd8a79f 100644 (file)
@@ -343,5 +343,9 @@ void rsn_pmkid(const u8 *pmk, size_t pmk_len, const u8 *aa, const u8 *spa,
 
 const char * wpa_cipher_txt(int cipher);
 const char * wpa_key_mgmt_txt(int key_mgmt, int proto);
+int wpa_compare_rsn_ie(int ft_initial_assoc,
+                      const u8 *ie1, size_t ie1len,
+                      const u8 *ie2, size_t ie2len);
+int wpa_insert_pmkid(u8 *ies, size_t ies_len, const u8 *pmkid);
 
 #endif /* WPA_COMMON_H */
index a50dfd5..7e1f5ba 100644 (file)
@@ -261,6 +261,8 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst,
        size_t rlen;
        struct wpa_eapol_key *reply;
        u8 *rbuf;
+       u8 *rsn_ie_buf = NULL;
+       size_t rsn_ie_buf_len;
 
        if (wpa_ie == NULL) {
                wpa_printf(MSG_WARNING, "WPA: No wpa_ie set - cannot "
@@ -268,13 +270,37 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst,
                return -1;
        }
 
+#ifdef CONFIG_IEEE80211R
+       if (wpa_key_mgmt_ft(sm->key_mgmt)) {
+               int res;
+
+               /* Add PMKR1Name into RSN IE (PMKID-List) */
+               rsn_ie_buf_len = wpa_ie_len + 2 + 2 + PMKID_LEN;
+               rsn_ie_buf = os_malloc(rsn_ie_buf_len);
+               if (rsn_ie_buf == NULL)
+                       return -1;
+               os_memcpy(rsn_ie_buf, wpa_ie, wpa_ie_len);
+               res = wpa_insert_pmkid(rsn_ie_buf, wpa_ie_len,
+                                      sm->pmk_r1_name);
+               if (res < 0) {
+                       os_free(rsn_ie_buf);
+                       return -1;
+               }
+
+               wpa_ie = rsn_ie_buf;
+               wpa_ie_len += res;
+       }
+#endif /* CONFIG_IEEE80211R */
+
        wpa_hexdump(MSG_DEBUG, "WPA: WPA IE for msg 2/4", wpa_ie, wpa_ie_len);
 
        rbuf = wpa_sm_alloc_eapol(sm, IEEE802_1X_TYPE_EAPOL_KEY,
                                  NULL, sizeof(*reply) + wpa_ie_len,
                                  &rlen, (void *) &reply);
-       if (rbuf == NULL)
+       if (rbuf == NULL) {
+               os_free(rsn_ie_buf);
                return -1;
+       }
 
        reply->type = sm->proto == WPA_PROTO_RSN ?
                EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA;
@@ -289,6 +315,7 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst,
 
        WPA_PUT_BE16(reply->key_data_length, wpa_ie_len);
        os_memcpy(reply + 1, wpa_ie, wpa_ie_len);
+       os_free(rsn_ie_buf);
 
        os_memcpy(reply->key_nonce, nonce, WPA_NONCE_LEN);
 
@@ -784,8 +811,9 @@ static int wpa_supplicant_validate_ie(struct wpa_sm *sm,
             (ie->wpa_ie_len != sm->ap_wpa_ie_len ||
              os_memcmp(ie->wpa_ie, sm->ap_wpa_ie, ie->wpa_ie_len) != 0)) ||
            (ie->rsn_ie && sm->ap_rsn_ie &&
-            (ie->rsn_ie_len != sm->ap_rsn_ie_len ||
-             os_memcmp(ie->rsn_ie, sm->ap_rsn_ie, ie->rsn_ie_len) != 0))) {
+            wpa_compare_rsn_ie(wpa_key_mgmt_ft(sm->key_mgmt),
+                               sm->ap_rsn_ie, sm->ap_rsn_ie_len,
+                               ie->rsn_ie, ie->rsn_ie_len))) {
                wpa_report_ie_mismatch(sm, "IE in 3/4 msg does not match "
                                       "with IE in Beacon/ProbeResp",
                                       src_addr, ie->wpa_ie, ie->wpa_ie_len,
@@ -918,6 +946,34 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
        if (wpa_supplicant_validate_ie(sm, sm->bssid, &ie) < 0)
                goto failed;
 
+#ifdef CONFIG_IEEE80211R
+       if (wpa_key_mgmt_ft(sm->key_mgmt) && ie.rsn_ie) {
+               struct wpa_ie_data rsn;
+               /*
+                * Verify that PMKR1Name from EAPOL-Key message 3/4 matches
+                * with the value we derived.
+                */
+               if (wpa_parse_wpa_ie_rsn(ie.rsn_ie, ie.rsn_ie_len, &rsn) < 0 ||
+                   rsn.num_pmkid != 1 || rsn.pmkid == NULL) {
+                       wpa_printf(MSG_DEBUG, "FT: No PMKR1Name in "
+                                  "FT 4-way handshake message 3/4");
+                       return;
+               }
+
+               if (os_memcmp(rsn.pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN) !=
+                   0) {
+                       wpa_printf(MSG_DEBUG, "FT: PMKR1Name mismatch in "
+                                  "FT 4-way handshake message 3/4");
+                       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from "
+                                   "Authenticator",
+                                   rsn.pmkid, WPA_PMK_NAME_LEN);
+                       wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
+                                   sm->pmk_r1_name, WPA_PMK_NAME_LEN);
+                       return;
+               }
+       }
+#endif /* CONFIG_IEEE80211R */
+
        if (os_memcmp(sm->anonce, key->key_nonce, WPA_NONCE_LEN) != 0) {
                wpa_printf(MSG_WARNING, "WPA: ANonce from message 1 of 4-Way "
                           "Handshake differs from 3 of 4-Way Handshake - drop"
index 2840d7a..8eb28d5 100644 (file)
@@ -28,7 +28,6 @@ int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr,
                      const struct wpa_eapol_key *key,
                      struct wpa_ptk *ptk, size_t ptk_len)
 {
-       u8 pmk_r1_name[WPA_PMK_NAME_LEN];
        u8 ptk_name[WPA_PMK_NAME_LEN];
        const u8 *anonce = key->key_nonce;
 
@@ -46,11 +45,12 @@ int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr,
        wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name",
                    sm->pmk_r0_name, WPA_PMK_NAME_LEN);
        wpa_derive_pmk_r1(sm->pmk_r0, sm->pmk_r0_name, sm->r1kh_id,
-                         sm->own_addr, sm->pmk_r1, pmk_r1_name);
+                         sm->own_addr, sm->pmk_r1, sm->pmk_r1_name);
        wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", sm->pmk_r1, PMK_LEN);
-       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", pmk_r1_name, WPA_PMK_NAME_LEN);
+       wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name,
+                   WPA_PMK_NAME_LEN);
        wpa_pmk_r1_to_ptk(sm->pmk_r1, sm->snonce, anonce, sm->own_addr,
-                         sm->bssid, pmk_r1_name,
+                         sm->bssid, sm->pmk_r1_name,
                          (u8 *) ptk, ptk_len, ptk_name);
        wpa_hexdump_key(MSG_DEBUG, "FT: PTK", (u8 *) ptk, ptk_len);
        wpa_hexdump(MSG_DEBUG, "FT: PTKName", ptk_name, WPA_PMK_NAME_LEN);