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:
parent
db099951cb
commit
f23c5b17e1
2 changed files with 77 additions and 7 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue