From 3b5b7aa8fb1ea0cd83cfdb246dc47d17734a5bc6 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Thu, 3 Sep 2015 15:59:44 +0300 Subject: [PATCH] FILS: Use AEAD cipher to check received EAPOL-Key frames (AP) This changes 4-way handshake authenticator processing to decrypt the EAPOL-Key frames using an AEAD cipher (AES-SIV with FILS AKMs) before processing the Key Data field. This replaces Key MIC validation for the cases where AEAD cipher is used. This needs to move the EAPOL-Key msg 2/4 RSN element processing to happen only after the PTK has been derived and validated. That is done for all AKMs to avoid extra complexity with having to maintain two code paths for this. Signed-off-by: Jouni Malinen --- hostapd/Android.mk | 4 + hostapd/Makefile | 4 + src/ap/wpa_auth.c | 231 ++++++++++++++++++++++++++++++++------------- 3 files changed, 172 insertions(+), 67 deletions(-) diff --git a/hostapd/Android.mk b/hostapd/Android.mk index da92639f8..1cc78d7e2 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -262,6 +262,7 @@ ifdef CONFIG_FILS L_CFLAGS += -DCONFIG_FILS NEED_CRC32=y NEED_SHA384=y +NEED_AES_SIV=y endif ifdef CONFIG_WNM @@ -743,6 +744,9 @@ ifneq ($(CONFIG_TLS), openssl) AESOBJS += src/crypto/aes-cbc.c endif endif +ifdef NEED_AES_SIV +AESOBJS += src/crypto/aes-siv.c +endif ifdef NEED_AES_DEC ifdef CONFIG_INTERNAL_AES AESOBJS += src/crypto/aes-internal-dec.c diff --git a/hostapd/Makefile b/hostapd/Makefile index 61daf0dc9..044c8c0ec 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -309,6 +309,7 @@ ifdef CONFIG_FILS CFLAGS += -DCONFIG_FILS NEED_CRC32=y NEED_SHA384=y +NEED_AES_SIV=y endif ifdef CONFIG_WNM @@ -783,6 +784,9 @@ ifneq ($(CONFIG_TLS), openssl) AESOBJS += ../src/crypto/aes-cbc.o endif endif +ifdef NEED_AES_SIV +AESOBJS += ../src/crypto/aes-siv.o +endif ifdef NEED_AES_DEC ifdef CONFIG_INTERNAL_AES AESOBJS += ../src/crypto/aes-internal-dec.o diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 28343d1fd..96af41b68 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -13,7 +13,9 @@ #include "utils/state_machine.h" #include "utils/bitfield.h" #include "common/ieee802_11_defs.h" +#include "crypto/aes.h" #include "crypto/aes_wrap.h" +#include "crypto/aes_siv.h" #include "crypto/crypto.h" #include "crypto/sha1.h" #include "crypto/sha256.h" @@ -882,9 +884,8 @@ void wpa_receive(struct wpa_authenticator *wpa_auth, SMK_M1, SMK_M3, SMK_ERROR } msg; char *msgtxt; struct wpa_eapol_ie_parse kde; - int ft; - const u8 *eapol_key_ie, *key_data; - size_t eapol_key_ie_len, keyhdrlen, mic_len; + const u8 *key_data; + size_t keyhdrlen, mic_len; u8 *mic; if (wpa_auth == NULL || !wpa_auth->conf.wpa || sm == NULL) @@ -972,7 +973,9 @@ void wpa_receive(struct wpa_authenticator *wpa_auth, } else if (!(key_info & WPA_KEY_INFO_KEY_TYPE)) { msg = GROUP_2; msgtxt = "2/2 Group"; - } else if (key_data_length == 0) { + } else if (key_data_length == 0 || + (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) && + key_data_length == AES_BLOCK_SIZE)) { msg = PAIRWISE_4; msgtxt = "4/4 Pairwise"; } else { @@ -1097,6 +1100,15 @@ void wpa_receive(struct wpa_authenticator *wpa_auth, } continue_processing: +#ifdef CONFIG_FILS + if (sm->wpa == WPA_VERSION_WPA2 && mic_len == 0 && + !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "WPA: Encr Key Data bit not set even though AEAD cipher is supposed to be used - drop frame"); + return; + } +#endif /* CONFIG_FILS */ + switch (msg) { case PAIRWISE_2: if (sm->wpa_ptk_state != WPA_PTK_PTKSTART && @@ -1127,67 +1139,6 @@ continue_processing: wpa_sta_disconnect(wpa_auth, sm->addr); return; } - if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { - wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, - "received EAPOL-Key msg 2/4 with " - "invalid Key Data contents"); - return; - } - if (kde.rsn_ie) { - eapol_key_ie = kde.rsn_ie; - eapol_key_ie_len = kde.rsn_ie_len; - } else if (kde.osen) { - eapol_key_ie = kde.osen; - eapol_key_ie_len = kde.osen_len; - } else { - eapol_key_ie = kde.wpa_ie; - eapol_key_ie_len = kde.wpa_ie_len; - } - ft = sm->wpa == WPA_VERSION_WPA2 && - wpa_key_mgmt_ft(sm->wpa_key_mgmt); - if (sm->wpa_ie == NULL || - wpa_compare_rsn_ie(ft, - sm->wpa_ie, sm->wpa_ie_len, - eapol_key_ie, eapol_key_ie_len)) { - wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, - "WPA IE from (Re)AssocReq did not " - "match with msg 2/4"); - if (sm->wpa_ie) { - wpa_hexdump(MSG_DEBUG, "WPA IE in AssocReq", - sm->wpa_ie, sm->wpa_ie_len); - } - wpa_hexdump(MSG_DEBUG, "WPA IE in msg 2/4", - eapol_key_ie, eapol_key_ie_len); - /* MLME-DEAUTHENTICATE.request */ - wpa_sta_disconnect(wpa_auth, sm->addr); - return; - } -#ifdef CONFIG_IEEE80211R - if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) { - wpa_sta_disconnect(wpa_auth, sm->addr); - return; - } -#endif /* CONFIG_IEEE80211R */ -#ifdef CONFIG_P2P - if (kde.ip_addr_req && kde.ip_addr_req[0] && - wpa_auth->ip_pool && WPA_GET_BE32(sm->ip_addr) == 0) { - int idx; - wpa_printf(MSG_DEBUG, "P2P: IP address requested in " - "EAPOL-Key exchange"); - idx = bitfield_get_first_zero(wpa_auth->ip_pool); - if (idx >= 0) { - u32 start = WPA_GET_BE32(wpa_auth->conf. - ip_addr_start); - bitfield_set(wpa_auth->ip_pool, idx); - WPA_PUT_BE32(sm->ip_addr, start + idx); - wpa_printf(MSG_DEBUG, "P2P: Assigned IP " - "address %u.%u.%u.%u to " MACSTR, - sm->ip_addr[0], sm->ip_addr[1], - sm->ip_addr[2], sm->ip_addr[3], - MAC2STR(sm->addr)); - } - } -#endif /* CONFIG_P2P */ break; case PAIRWISE_4: if (sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING || @@ -1262,7 +1213,8 @@ continue_processing: sm->MICVerified = FALSE; if (sm->PTK_valid && !sm->update_snonce) { - if (wpa_verify_key_mic(sm->wpa_key_mgmt, &sm->PTK, data, + if (mic_len && + wpa_verify_key_mic(sm->wpa_key_mgmt, &sm->PTK, data, data_len) && (msg != PAIRWISE_4 || !sm->alt_snonce_valid || wpa_try_alt_snonce(sm, data, data_len))) { @@ -2040,17 +1992,84 @@ static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, } +#ifdef CONFIG_FILS +static int wpa_aead_decrypt(struct wpa_state_machine *sm, struct wpa_ptk *ptk, + u16 *_key_data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + u8 *pos; + u16 key_data_len; + u8 *tmp; + const u8 *aad[1]; + size_t aad_len[1]; + + hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key; + key = (struct wpa_eapol_key *) (hdr + 1); + pos = (u8 *) (key + 1); + key_data_len = WPA_GET_BE16(pos); + if (key_data_len < AES_BLOCK_SIZE || + key_data_len > buf_len - sizeof(*hdr) - sizeof(*key) - 2) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "No room for AES-SIV data in the frame"); + return -1; + } + pos += 2; /* Pointing at the Encrypted Key Data field */ + + tmp = os_malloc(key_data_len); + if (!tmp) + return -1; + + /* AES-SIV AAD from EAPOL protocol version field (inclusive) to + * to Key Data (exclusive). */ + aad[0] = sm->last_rx_eapol_key; + aad_len[0] = pos - sm->last_rx_eapol_key; + if (aes_siv_decrypt(ptk->kek, ptk->kek_len, pos, key_data_len, + 1, aad, aad_len, tmp) < 0) { + wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO, + "Invalid AES-SIV data in the frame"); + bin_clear_free(tmp, key_data_len); + return -1; + } + + /* AEAD decryption and validation completed successfully */ + key_data_len -= AES_BLOCK_SIZE; + wpa_hexdump_key(MSG_DEBUG, "WPA: Decrypted Key Data", + tmp, key_data_len); + + /* Replace Key Data field with the decrypted version */ + os_memcpy(pos, tmp, key_data_len); + pos -= 2; /* Key Data Length field */ + WPA_PUT_BE16(pos, key_data_len); + bin_clear_free(tmp, key_data_len); + if (_key_data_len) + *_key_data_len = key_data_len; + return 0; +} +#endif /* CONFIG_FILS */ + + SM_STATE(WPA_PTK, PTKCALCNEGOTIATING) { + struct wpa_authenticator *wpa_auth = sm->wpa_auth; struct wpa_ptk PTK; int ok = 0, psk_found = 0; const u8 *pmk = NULL; unsigned int pmk_len; + int ft; + const u8 *eapol_key_ie, *key_data, *mic; + u16 key_data_length; + size_t mic_len, eapol_key_ie_len; + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_ie_parse kde; SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk); sm->EAPOLKeyReceived = FALSE; sm->update_snonce = FALSE; + mic_len = wpa_mic_len(sm->wpa_key_mgmt); + /* WPA with IEEE 802.1X: use the derived PMK from EAP * WPA-PSK: iterate through possible PSKs and select the one matching * the packet */ @@ -2069,13 +2088,21 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING) wpa_derive_ptk(sm, sm->SNonce, pmk, pmk_len, &PTK); - if (wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK, + if (mic_len && + wpa_verify_key_mic(sm->wpa_key_mgmt, &PTK, sm->last_rx_eapol_key, sm->last_rx_eapol_key_len) == 0) { ok = 1; break; } +#ifdef CONFIG_FILS + if (!mic_len && wpa_aead_decrypt(sm, &PTK, NULL) == 0) { + ok = 1; + break; + } +#endif /* CONFIG_FILS */ + if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt)) break; } @@ -2088,6 +2115,76 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING) return; } + /* + * Note: last_rx_eapol_key length fields have already been validated in + * wpa_receive(). + */ + hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key; + key = (struct wpa_eapol_key *) (hdr + 1); + mic = (u8 *) (key + 1); + key_data = mic + mic_len + 2; + key_data_length = WPA_GET_BE16(mic + mic_len); + if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) - + sizeof(*key) - mic_len - 2) + return; + + if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO, + "received EAPOL-Key msg 2/4 with invalid Key Data contents"); + return; + } + if (kde.rsn_ie) { + eapol_key_ie = kde.rsn_ie; + eapol_key_ie_len = kde.rsn_ie_len; + } else if (kde.osen) { + eapol_key_ie = kde.osen; + eapol_key_ie_len = kde.osen_len; + } else { + eapol_key_ie = kde.wpa_ie; + eapol_key_ie_len = kde.wpa_ie_len; + } + ft = sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt); + if (sm->wpa_ie == NULL || + wpa_compare_rsn_ie(ft, sm->wpa_ie, sm->wpa_ie_len, + eapol_key_ie, eapol_key_ie_len)) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, + "WPA IE from (Re)AssocReq did not match with msg 2/4"); + if (sm->wpa_ie) { + wpa_hexdump(MSG_DEBUG, "WPA IE in AssocReq", + sm->wpa_ie, sm->wpa_ie_len); + } + wpa_hexdump(MSG_DEBUG, "WPA IE in msg 2/4", + eapol_key_ie, eapol_key_ie_len); + /* MLME-DEAUTHENTICATE.request */ + wpa_sta_disconnect(wpa_auth, sm->addr); + return; + } +#ifdef CONFIG_IEEE80211R + if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) { + wpa_sta_disconnect(wpa_auth, sm->addr); + return; + } +#endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_P2P + if (kde.ip_addr_req && kde.ip_addr_req[0] && + wpa_auth->ip_pool && WPA_GET_BE32(sm->ip_addr) == 0) { + int idx; + wpa_printf(MSG_DEBUG, + "P2P: IP address requested in EAPOL-Key exchange"); + idx = bitfield_get_first_zero(wpa_auth->ip_pool); + if (idx >= 0) { + u32 start = WPA_GET_BE32(wpa_auth->conf.ip_addr_start); + bitfield_set(wpa_auth->ip_pool, idx); + WPA_PUT_BE32(sm->ip_addr, start + idx); + wpa_printf(MSG_DEBUG, + "P2P: Assigned IP address %u.%u.%u.%u to " + MACSTR, sm->ip_addr[0], sm->ip_addr[1], + sm->ip_addr[2], sm->ip_addr[3], + MAC2STR(sm->addr)); + } + } +#endif /* CONFIG_P2P */ + #ifdef CONFIG_IEEE80211R if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { /*