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>
This commit is contained in:
parent
d567479153
commit
68921e24b2
2 changed files with 92 additions and 19 deletions
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue