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>
This commit is contained in:
Jouni Malinen 2014-11-21 17:02:00 +02:00 committed by Jouni Malinen
parent db099951cb
commit f23c5b17e1
2 changed files with 77 additions and 7 deletions

View file

@ -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,

View file

@ -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;