aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJouni Malinen <jouni@qca.qualcomm.com>2012-01-02 20:36:11 (GMT)
committerJouni Malinen <j@w1.fi>2012-01-02 20:36:11 (GMT)
commit68921e24b2ef867d8b4d57192ddf1a3075c57edf (patch)
tree427fe8ef530459a6fbf0cdcdbea18677429d5758
parentd567479153b9db89213ecd9c42188abfc37c79ba (diff)
downloadhostap-68921e24b2ef867d8b4d57192ddf1a3075c57edf.zip
hostap-68921e24b2ef867d8b4d57192ddf1a3075c57edf.tar.gz
hostap-68921e24b2ef867d8b4d57192ddf1a3075c57edf.tar.bz2
Allow SNonce update after sending EAPOL-Key 3/4 if 1/4 was retransmitted
Some supplicant implementations (e.g., Windows XP WZC) update SNonce for each EAPOL-Key 2/4. This breaks the workaround on accepting any of the pending requests, so allow the SNonce to be updated even if we have already sent out EAPOL-Key 3/4. While the issue was made less likely to occur when the retransmit timeout for the initial EAPOL-Key msg 1/4 was increased to 1000 ms, this fixes the problem even if that timeout is not long enough. Signed-hostap: Jouni Malinen <jouni@qca.qualcomm.com>
-rw-r--r--src/ap/wpa_auth.c105
-rw-r--r--src/ap/wpa_auth_i.h6
2 files changed, 92 insertions, 19 deletions
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index adc69e2..9da5609 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -647,14 +647,14 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm)
}
-static int wpa_replay_counter_valid(struct wpa_state_machine *sm,
+static int wpa_replay_counter_valid(struct wpa_key_replay_counter *ctr,
const u8 *replay_counter)
{
int i;
for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
- if (!sm->key_replay[i].valid)
+ if (!ctr[i].valid)
break;
- if (os_memcmp(replay_counter, sm->key_replay[i].counter,
+ if (os_memcmp(replay_counter, ctr[i].counter,
WPA_REPLAY_COUNTER_LEN) == 0)
return 1;
}
@@ -662,6 +662,20 @@ static int wpa_replay_counter_valid(struct wpa_state_machine *sm,
}
+static void wpa_replay_counter_mark_invalid(struct wpa_key_replay_counter *ctr,
+ const u8 *replay_counter)
+{
+ int i;
+ for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
+ if (ctr[i].valid &&
+ (replay_counter == NULL ||
+ os_memcmp(replay_counter, ctr[i].counter,
+ WPA_REPLAY_COUNTER_LEN) == 0))
+ ctr[i].valid = FALSE;
+ }
+}
+
+
#ifdef CONFIG_IEEE80211R
static int ft_check_msg_2_of_4(struct wpa_authenticator *wpa_auth,
struct wpa_state_machine *sm,
@@ -868,11 +882,44 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
}
if (!(key_info & WPA_KEY_INFO_REQUEST) &&
- !wpa_replay_counter_valid(sm, key->replay_counter)) {
+ !wpa_replay_counter_valid(sm->key_replay, key->replay_counter)) {
int i;
- wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
- "received EAPOL-Key %s with unexpected "
- "replay counter", msgtxt);
+
+ if (msg == PAIRWISE_2 &&
+ wpa_replay_counter_valid(sm->prev_key_replay,
+ key->replay_counter) &&
+ sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+ os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0)
+ {
+ /*
+ * Some supplicant implementations (e.g., Windows XP
+ * WZC) update SNonce for each EAPOL-Key 2/4. This
+ * breaks the workaround on accepting any of the
+ * pending requests, so allow the SNonce to be updated
+ * even if we have already sent out EAPOL-Key 3/4.
+ */
+ wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+ "Process SNonce update from STA "
+ "based on retransmitted EAPOL-Key "
+ "1/4");
+ sm->update_snonce = 1;
+ wpa_replay_counter_mark_invalid(sm->prev_key_replay,
+ key->replay_counter);
+ goto continue_processing;
+ }
+
+ if (msg == PAIRWISE_2 &&
+ wpa_replay_counter_valid(sm->prev_key_replay,
+ key->replay_counter) &&
+ sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
+ wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+ "ignore retransmitted EAPOL-Key %s - "
+ "SNonce did not change", msgtxt);
+ } else {
+ wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+ "received EAPOL-Key %s with "
+ "unexpected replay counter", msgtxt);
+ }
for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
if (!sm->key_replay[i].valid)
break;
@@ -885,10 +932,13 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
return;
}
+continue_processing:
switch (msg) {
case PAIRWISE_2:
if (sm->wpa_ptk_state != WPA_PTK_PTKSTART &&
- sm->wpa_ptk_state != WPA_PTK_PTKCALCNEGOTIATING) {
+ sm->wpa_ptk_state != WPA_PTK_PTKCALCNEGOTIATING &&
+ (!sm->update_snonce ||
+ sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING)) {
wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
"received EAPOL-Key msg 2/4 in "
"invalid state (%d) - dropped",
@@ -1017,7 +1067,7 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
}
sm->MICVerified = FALSE;
- if (sm->PTK_valid) {
+ if (sm->PTK_valid && !sm->update_snonce) {
if (wpa_verify_key_mic(&sm->PTK, data, data_len)) {
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
"received EAPOL-Key with invalid MIC");
@@ -1075,12 +1125,30 @@ void wpa_receive(struct wpa_authenticator *wpa_auth,
wpa_rekey_gtk(wpa_auth, NULL);
}
} else {
- /* Do not allow the same key replay counter to be reused. This
- * does also invalidate all other pending replay counters if
- * retransmissions were used, i.e., we will only process one of
- * the pending replies and ignore rest if more than one is
- * received. */
- sm->key_replay[0].valid = FALSE;
+ /* Do not allow the same key replay counter to be reused. */
+ wpa_replay_counter_mark_invalid(sm->key_replay,
+ key->replay_counter);
+
+ if (msg == PAIRWISE_2) {
+ /*
+ * Maintain a copy of the pending EAPOL-Key frames in
+ * case the EAPOL-Key frame was retransmitted. This is
+ * needed to allow EAPOL-Key msg 2/4 reply to another
+ * pending msg 1/4 to update the SNonce to work around
+ * unexpected supplicant behavior.
+ */
+ os_memcpy(sm->prev_key_replay, sm->key_replay,
+ sizeof(sm->key_replay));
+ } else {
+ os_memset(sm->prev_key_replay, 0,
+ sizeof(sm->prev_key_replay));
+ }
+
+ /*
+ * Make sure old valid counters are not accepted anymore and
+ * do not get copied again.
+ */
+ wpa_replay_counter_mark_invalid(sm->key_replay, NULL);
}
#ifdef CONFIG_PEERKEY
@@ -1713,6 +1781,7 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk);
sm->EAPOLKeyReceived = FALSE;
+ sm->update_snonce = FALSE;
/* WPA with IEEE 802.1X: use the derived PMK from EAP
* WPA-PSK: iterate through possible PSKs and select the one matching
@@ -2132,8 +2201,10 @@ SM_STEP(WPA_PTK)
SM_ENTER(WPA_PTK, PTKINITNEGOTIATING);
break;
case WPA_PTK_PTKINITNEGOTIATING:
- if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest &&
- sm->EAPOLKeyPairwise && sm->MICVerified)
+ if (sm->update_snonce)
+ SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING);
+ else if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest &&
+ sm->EAPOLKeyPairwise && sm->MICVerified)
SM_ENTER(WPA_PTK, PTKINITDONE);
else if (sm->TimeoutCtr >
(int) dot11RSNAConfigPairwiseUpdateCount) {
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index d82192a..500d5a9 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -69,10 +69,11 @@ struct wpa_state_machine {
Boolean pairwise_set;
int keycount;
Boolean Pair;
- struct {
+ struct wpa_key_replay_counter {
u8 counter[WPA_REPLAY_COUNTER_LEN];
Boolean valid;
- } key_replay[RSNA_MAX_EAPOL_RETRIES];
+ } key_replay[RSNA_MAX_EAPOL_RETRIES],
+ prev_key_replay[RSNA_MAX_EAPOL_RETRIES];
Boolean PInitAKeys; /* WPA only, not in IEEE 802.11i */
Boolean PTKRequest; /* not in IEEE 802.11i state machine */
Boolean has_GTK;
@@ -87,6 +88,7 @@ struct wpa_state_machine {
unsigned int started:1;
unsigned int mgmt_frame_prot:1;
unsigned int rx_eapol_key_secure:1;
+ unsigned int update_snonce:1;
#ifdef CONFIG_IEEE80211R
unsigned int ft_completed:1;
unsigned int pmk_r1_name_valid:1;