Browse Source

AP: Extend EAPOL-Key msg 1/4 retry workaround for changing SNonce

If the 4-way handshake ends up having to retransmit the EAPOL-Key
message 1/4 due to a timeout on waiting for the response, it is possible
for the Supplicant to change SNonce between the first and second
EAPOL-Key message 2/4. This is not really desirable due to extra
complexities it causes on the Authenticator side, but some deployed
stations are doing this.

This message sequence looks like this:
AP->STA: EAPOL-Key 1/4 (replay counter 1, ANonce)
AP->STA: EAPOL-Key 1/4 (replay counter 2, ANonce)
STA->AP: EAPOL-Key 2/4 (replay counter 1, SNonce 1)
AP->STA: EAPOL-Key 3/4 (replay counter 3, ANonce)
STA->AP: EAPOL-Key 2/4 (replay counter 2, SNonce 2)
followed by either:
STA->AP: EAPOL-Key 4/4 (replay counter 3 using PTK from SNonce 1)
or:
AP->STA: EAPOL-Key 3/4 (replay counter 4, ANonce)
STA->AP: EAPOL-Key 4/4 (replay counter 4, using PTK from SNonce 2)

Previously, Authenticator implementation was able to handle the cases
where SNonce 1 and SNonce 2 were identifical (i.e., Supplicant did not
update SNonce which is the wpa_supplicant behavior) and where PTK
derived using SNonce 2 was used in EAPOL-Key 4/4. However, the case of
using PTK from SNonce 1 was rejected ("WPA: received EAPOL-Key 4/4
Pairwise with unexpected replay counter" since EAPOL-Key 3/4 TX and
following second EAPOL-Key 2/4 invalidated the Replay Counter that was
used previously with the first SNonce).

This commit extends the AP/Authenticator workaround to keep both SNonce
values in memory if two EAPOL-Key 2/4 messages are received with
different SNonce values. The following EAPOL-Key 4/4 message is then
accepted whether the MIC has been calculated with the latest SNonce (the
previously existing behavior) or with the earlier SNonce (the new
extension). This makes 4-way handshake more robust with stations that
update SNonce for each transmitted EAPOL-Key 2/4 message in cases where
EAPOL-Key message 1/4 needs to be retransmitted.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
Jouni Malinen 10 years ago
parent
commit
f23c5b17e1
2 changed files with 77 additions and 7 deletions
  1. 74 7
      src/ap/wpa_auth.c
  2. 3 0
      src/ap/wpa_auth_i.h

+ 74 - 7
src/ap/wpa_auth.c

@@ -43,6 +43,8 @@ static int wpa_gtk_update(struct wpa_authenticator *wpa_auth,
 			  struct wpa_group *group);
 static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth,
 				       struct wpa_group *group);
+static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce,
+			  const u8 *pmk, struct wpa_ptk *ptk);
 
 static const u32 dot11RSNAConfigGroupUpdateCount = 4;
 static const u32 dot11RSNAConfigPairwiseUpdateCount = 4;
@@ -794,6 +796,51 @@ static int wpa_receive_error_report(struct wpa_authenticator *wpa_auth,
 }
 
 
+static int wpa_try_alt_snonce(struct wpa_state_machine *sm, u8 *data,
+			      size_t data_len)
+{
+	struct wpa_ptk PTK;
+	int ok = 0;
+	const u8 *pmk = NULL;
+
+	for (;;) {
+		if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) {
+			pmk = wpa_auth_get_psk(sm->wpa_auth, sm->addr,
+					       sm->p2p_dev_addr, pmk);
+			if (pmk == NULL)
+				break;
+		} else
+			pmk = sm->PMK;
+
+		wpa_derive_ptk(sm, sm->alt_SNonce, pmk, &PTK);
+
+		if (wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK, data, data_len)
+		    == 0) {
+			ok = 1;
+			break;
+		}
+
+		if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt))
+			break;
+	}
+
+	if (!ok) {
+		wpa_printf(MSG_DEBUG,
+			   "WPA: Earlier SNonce did not result in matching MIC");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "WPA: Earlier SNonce resulted in matching MIC");
+	sm->alt_snonce_valid = 0;
+	os_memcpy(sm->SNonce, sm->alt_SNonce, WPA_NONCE_LEN);
+	os_memcpy(&sm->PTK, &PTK, sizeof(PTK));
+	sm->PTK_valid = TRUE;
+
+	return 0;
+}
+
+
 void wpa_receive(struct wpa_authenticator *wpa_auth,
 		 struct wpa_state_machine *sm,
 		 u8 *data, size_t data_len)
@@ -957,8 +1004,25 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
 					 "based on retransmitted EAPOL-Key "
 					 "1/4");
 			sm->update_snonce = 1;
-			wpa_replay_counter_mark_invalid(sm->prev_key_replay,
-							key->replay_counter);
+			os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN);
+			sm->alt_snonce_valid = TRUE;
+			os_memcpy(sm->alt_replay_counter,
+				  sm->key_replay[0].counter,
+				  WPA_REPLAY_COUNTER_LEN);
+			goto continue_processing;
+		}
+
+		if (msg == PAIRWISE_4 && sm->alt_snonce_valid &&
+		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+		    os_memcmp(key->replay_counter, sm->alt_replay_counter,
+			      WPA_REPLAY_COUNTER_LEN) == 0) {
+			/*
+			 * Supplicant may still be using the old SNonce since
+			 * there was two EAPOL-Key 2/4 messages and they had
+			 * different SNonce values.
+			 */
+			wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+					 "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4");
 			goto continue_processing;
 		}
 
@@ -1144,7 +1208,9 @@ continue_processing:
 	sm->MICVerified = FALSE;
 	if (sm->PTK_valid && !sm->update_snonce) {
 		if (wpa_verify_key_mic(sm->wpa_key_mgmt, &sm->PTK, data,
-				       data_len)) {
+				       data_len) &&
+		    (msg != PAIRWISE_4 || !sm->alt_snonce_valid ||
+		     wpa_try_alt_snonce(sm, data, data_len))) {
 			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
 					"received EAPOL-Key with invalid MIC");
 			return;
@@ -1808,6 +1874,7 @@ SM_STATE(WPA_PTK, PTKSTART)
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
 	sm->PTKRequest = FALSE;
 	sm->TimeoutEvt = FALSE;
+	sm->alt_snonce_valid = FALSE;
 
 	sm->TimeoutCtr++;
 	if (sm->TimeoutCtr > (int) dot11RSNAConfigPairwiseUpdateCount) {
@@ -1852,8 +1919,8 @@ SM_STATE(WPA_PTK, PTKSTART)
 }
 
 
-static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *pmk,
-			  struct wpa_ptk *ptk)
+static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce,
+			  const u8 *pmk, struct wpa_ptk *ptk)
 {
 	size_t ptk_len = wpa_cipher_key_len(sm->pairwise) + 32;
 #ifdef CONFIG_IEEE80211R
@@ -1862,7 +1929,7 @@ static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *pmk,
 #endif /* CONFIG_IEEE80211R */
 
 	wpa_pmk_to_ptk(pmk, PMK_LEN, "Pairwise key expansion",
-		       sm->wpa_auth->addr, sm->addr, sm->ANonce, sm->SNonce,
+		       sm->wpa_auth->addr, sm->addr, sm->ANonce, snonce,
 		       (u8 *) ptk, ptk_len,
 		       wpa_key_mgmt_sha256(sm->wpa_key_mgmt));
 
@@ -1892,7 +1959,7 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
 		} else
 			pmk = sm->PMK;
 
-		wpa_derive_ptk(sm, pmk, &PTK);
+		wpa_derive_ptk(sm, sm->SNonce, pmk, &PTK);
 
 		if (wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK,
 				       sm->last_rx_eapol_key,

+ 3 - 0
src/ap/wpa_auth_i.h

@@ -58,6 +58,8 @@ struct wpa_state_machine {
 	Boolean GUpdateStationKeys;
 	u8 ANonce[WPA_NONCE_LEN];
 	u8 SNonce[WPA_NONCE_LEN];
+	u8 alt_SNonce[WPA_NONCE_LEN];
+	u8 alt_replay_counter[WPA_REPLAY_COUNTER_LEN];
 	u8 PMK[PMK_LEN];
 	struct wpa_ptk PTK;
 	Boolean PTK_valid;
@@ -84,6 +86,7 @@ struct wpa_state_machine {
 	unsigned int mgmt_frame_prot:1;
 	unsigned int rx_eapol_key_secure:1;
 	unsigned int update_snonce:1;
+	unsigned int alt_snonce_valid:1;
 #ifdef CONFIG_IEEE80211R
 	unsigned int ft_completed:1;
 	unsigned int pmk_r1_name_valid:1;