From 862aac1fcd20b1136f926c16afb21cfa1defb157 Mon Sep 17 00:00:00 2001 From: Alexander Wetzel Date: Fri, 20 Mar 2020 20:04:31 +0100 Subject: [PATCH] AP: Support Extended Key ID Support Extended Key ID in hostapd according to IEEE Std 802.11-2016. Extended Key ID allows to rekey pairwise keys without the otherwise unavoidable MPDU losses on a busy link. The standard is fully backward compatible, allowing an AP to serve STAs with and without Extended Key ID support in the same BSS. Signed-off-by: Alexander Wetzel --- hostapd/config_file.c | 10 ++++++ hostapd/ctrl_iface.c | 8 +++++ hostapd/hostapd.conf | 11 +++++++ src/ap/ap_config.h | 1 + src/ap/wpa_auth.c | 74 ++++++++++++++++++++++++++++++++++++------ src/ap/wpa_auth.h | 1 + src/ap/wpa_auth_ft.c | 2 +- src/ap/wpa_auth_glue.c | 13 +++++++- src/ap/wpa_auth_i.h | 2 ++ src/ap/wpa_auth_ie.c | 20 ++++++++++++ 10 files changed, 130 insertions(+), 12 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 3c7bb395f..2bc0679b5 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2869,6 +2869,16 @@ static int hostapd_config_fill(struct hostapd_config *conf, } } else if (os_strcmp(buf, "wpa") == 0) { bss->wpa = atoi(pos); + } else if (os_strcmp(buf, "extended_key_id") == 0) { + int val = atoi(pos); + + if (bss->extended_key_id < 0 || bss->extended_key_id > 2) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid extended_key_id=%d; allowed range 0..2", + line, bss->extended_key_id); + return 1; + } + bss->extended_key_id = val; } else if (os_strcmp(buf, "wpa_group_rekey") == 0) { bss->wpa_group_rekey = atoi(pos); bss->wpa_group_rekey_set = 1; diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 6d2ecbc9c..1a369ed2a 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -1295,6 +1295,14 @@ static int hostapd_ctrl_iface_get_config(struct hostapd_data *hapd, pos += ret; } + if ((hapd->conf->wpa & WPA_PROTO_RSN) && hapd->conf->extended_key_id) { + ret = os_snprintf(pos, end - pos, "extended_key_id=%d\n", + hapd->conf->extended_key_id); + if (os_snprintf_error(end - pos, ret)) + return pos - buf; + pos += ret; + } + return pos - buf; } diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index bc5d1a7f6..b09f6ed0a 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1510,6 +1510,17 @@ own_ip_addr=127.0.0.1 # wpa_key_mgmt=SAE for WPA3-Personal instead of wpa_key_mgmt=WPA-PSK). #wpa=2 +# Extended Key ID support for Individually Addressed frames +# +# Extended Key ID allows to rekey PTK keys without the impacts the "normal" +# PTK rekeying with only a single Key ID 0 has. It can only be used when the +# driver supports it and RSN/WPA2 is used with a CCMP/GCMP pairwise cipher. +# +# 0 = force off, i.e., use only Key ID 0 (default) +# 1 = enable and use Extended Key ID support when possible +# 2 = identical to 1 but start with Key ID 1 when possible +#extended_key_id=0 + # WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit # secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase # (8..63 characters) that will be converted to PSK. This conversion uses SSID diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 80be7ed39..5f4665c07 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -354,6 +354,7 @@ struct hostapd_bss_config { * algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */ int wpa; /* bitfield of WPA_PROTO_WPA, WPA_PROTO_RSN */ + int extended_key_id; int wpa_key_mgmt; enum mfp_options ieee80211w; int group_mgmt_cipher; diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 64fc09ca6..6512c0194 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -781,7 +781,7 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm) if (sm == NULL) return; - if (sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { + if (!sm->use_ext_key_id && sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { wpa_printf(MSG_INFO, "WPA: PTK0 rekey not allowed, disconnect " MACSTR, MAC2STR(sm->addr)); @@ -790,6 +790,8 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm) sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; } else { + if (sm->use_ext_key_id) + sm->keyidx_active ^= 1; /* flip Key ID */ sm->PTKRequest = TRUE; sm->PTK_valid = 0; } @@ -1754,6 +1756,11 @@ void wpa_remove_ptk(struct wpa_state_machine *sm) 0, KEY_FLAG_PAIRWISE)) wpa_printf(MSG_DEBUG, "RSN: PTK removal from the driver failed"); + if (sm->wpa_auth->conf.extended_key_id && sm->use_ext_key_id && + wpa_auth_set_key(sm->wpa_auth, 0, WPA_ALG_NONE, sm->addr, 1, NULL, + 0, KEY_FLAG_PAIRWISE)) + wpa_printf(MSG_DEBUG, + "RSN: PTK Key ID 1 removal from the driver failed"); sm->pairwise_set = FALSE; eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); } @@ -1812,16 +1819,23 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) sm->Init = FALSE; sm->AuthenticationRequest = TRUE; break; - } else if (sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { + } + + if (!sm->use_ext_key_id && + sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { wpa_printf(MSG_INFO, "WPA: PTK0 rekey not allowed, disconnect " MACSTR, MAC2STR(sm->addr)); sm->Disconnect = TRUE; - /* Try to encourage the STA reconnect */ + /* Try to encourage the STA to reconnect */ sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; break; } + + if (sm->use_ext_key_id) + sm->keyidx_active ^= 1; /* flip Key ID */ + if (sm->GUpdateStationKeys) { /* * Reauthentication cancels the pending group key @@ -3261,6 +3275,7 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) u8 *wpa_ie; int secure, gtkidx, encr = 0; u8 *wpa_ie_buf = NULL, *wpa_ie_buf2 = NULL; + u8 hdr[2]; SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk); sm->TimeoutEvt = FALSE; @@ -3317,6 +3332,18 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG, "sending 3/4 msg of 4-Way Handshake"); if (sm->wpa == WPA_VERSION_WPA2) { + if (sm->use_ext_key_id && sm->TimeoutCtr == 1 && + wpa_auth_set_key(sm->wpa_auth, 0, + wpa_cipher_to_alg(sm->pairwise), + sm->addr, + sm->keyidx_active, sm->PTK.tk, + wpa_cipher_key_len(sm->pairwise), + KEY_FLAG_PAIRWISE_RX)) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + /* WPA2 send GTK in the 4-way handshake */ secure = 1; gtk = gsm->GTK[gsm->GN - 1]; @@ -3357,6 +3384,10 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) } kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + + if (sm->use_ext_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + if (gtk) kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; #ifdef CONFIG_IEEE80211R_AP @@ -3392,10 +3423,15 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) pos += elen; } #endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_ext_key_id) { + hdr[0] = sm->keyidx_active & 0x01; + pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0); + } + if (gtk) { - u8 hdr[2]; hdr[0] = gtkidx & 0x03; - hdr[1] = 0; pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, gtk, gtk_len); } @@ -3478,9 +3514,17 @@ SM_STATE(WPA_PTK, PTKINITDONE) if (sm->Pair) { enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise); int klen = wpa_cipher_key_len(sm->pairwise); - if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, - sm->PTK.tk, klen, - KEY_FLAG_PAIRWISE_RX_TX)) { + int res; + + if (sm->use_ext_key_id) + res = wpa_auth_set_key(sm->wpa_auth, 0, 0, sm->addr, + sm->keyidx_active, NULL, 0, + KEY_FLAG_PAIRWISE_RX_TX_MODIFY); + else + res = wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, + 0, sm->PTK.tk, klen, + KEY_FLAG_PAIRWISE_RX_TX); + if (res) { wpa_sta_disconnect(sm->wpa_auth, sm->addr, WLAN_REASON_PREV_AUTH_NOT_VALID); return; @@ -5167,6 +5211,7 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm, struct wpa_group *gsm = sm->group; u8 *wpa_ie; int wpa_ie_len, secure, gtkidx, encr = 0; + u8 hdr[2]; /* Send EAPOL(1, 1, 1, Pair, P, RSC, ANonce, MIC(PTK), RSNIE, [MDIE], GTK[GN], IGTK, [BIGTK], [FTIE], [TIE * 2]) @@ -5219,6 +5264,10 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm, } kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + + if (sm->use_ext_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + if (gtk) kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; #ifdef CONFIG_IEEE80211R_AP @@ -5251,10 +5300,15 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm, pos += elen; } #endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_ext_key_id) { + hdr[0] = sm->keyidx_active & 0x01; + pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0); + } + if (gtk) { - u8 hdr[2]; hdr[0] = gtkidx & 0x03; - hdr[1] = 0; pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, gtk, gtk_len); } diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index 58aa9ff21..528516195 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -169,6 +169,7 @@ struct ft_remote_r1kh { struct wpa_auth_config { int wpa; + int extended_key_id; int wpa_key_mgmt; int wpa_pairwise; int wpa_group; diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index 5ed78e6a6..476a2be69 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -2775,7 +2775,7 @@ void wpa_ft_install_ptk(struct wpa_state_machine *sm) * again after association to get the PTK configured, but that could be * optimized by adding the STA entry earlier. */ - if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, + if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, sm->keyidx_active, sm->PTK.tk, klen, KEY_FLAG_PAIRWISE_RX_TX)) return; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 926ff455f..70d32818a 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -41,6 +41,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, os_memset(wconf, 0, sizeof(*wconf)); wconf->wpa = conf->wpa; + wconf->extended_key_id = conf->extended_key_id; wconf->wpa_key_mgmt = conf->wpa_key_mgmt; wconf->wpa_pairwise = conf->wpa_pairwise; wconf->wpa_group = conf->wpa_group; @@ -433,7 +434,11 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg, } #ifdef CONFIG_TESTING_OPTIONS - if (addr && !is_broadcast_ether_addr(addr)) { + if (key_flag & KEY_FLAG_MODIFY) { + /* We are updating an already installed key. Don't overwrite + * the already stored key information with zeros. + */ + } else if (addr && !is_broadcast_ether_addr(addr)) { struct sta_info *sta; sta = ap_get_sta(hapd, addr); @@ -1419,6 +1424,12 @@ int hostapd_setup_wpa(struct hostapd_data *hapd) _conf.wpa_deny_ptk0_rekey = 1; } + if (_conf.extended_key_id && + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID)) + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Extended Key ID supported"); + else + _conf.extended_key_id = 0; + hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb, hapd); if (hapd->wpa_auth == NULL) { wpa_printf(MSG_ERROR, "WPA initialization failed."); diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index 5d7b96c6f..bc59d6a4c 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -61,6 +61,8 @@ struct wpa_state_machine { unsigned int pmk_len; u8 pmkid[PMKID_LEN]; /* valid if pmkid_set == 1 */ struct wpa_ptk PTK; + u8 keyidx_active; + Boolean use_ext_key_id; Boolean PTK_valid; Boolean pairwise_set; Boolean tk_already_set; diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index 2e6d05910..11153e0b8 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -297,6 +297,8 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, if (rsn_testing) capab |= BIT(8) | BIT(15); #endif /* CONFIG_RSN_TESTING */ + if (conf->extended_key_id) + capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST; WPA_PUT_LE16(pos, capab); pos += 2; @@ -553,6 +555,7 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, const u8 *mdie, size_t mdie_len, const u8 *owe_dh, size_t owe_dh_len) { + struct wpa_auth_config *conf = &wpa_auth->conf; struct wpa_ie_data data; int ciphers, key_mgmt, res, version; u32 selector; @@ -944,6 +947,23 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, } #endif /* CONFIG_DPP */ + if (conf->extended_key_id && sm->wpa == WPA_VERSION_WPA2 && + sm->pairwise != WPA_CIPHER_TKIP && + (data.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) { + sm->use_ext_key_id = TRUE; + if (conf->extended_key_id == 2 && + !wpa_key_mgmt_ft(sm->wpa_key_mgmt) && + !wpa_key_mgmt_fils(sm->wpa_key_mgmt)) + sm->keyidx_active = 1; + else + sm->keyidx_active = 0; + wpa_printf(MSG_DEBUG, + "RSN: Extended Key ID supported (start with %d)", + sm->keyidx_active); + } else { + sm->use_ext_key_id = FALSE; + } + if (sm->wpa_ie == NULL || sm->wpa_ie_len < wpa_ie_len) { os_free(sm->wpa_ie); sm->wpa_ie = os_malloc(wpa_ie_len);