Support Extended Key ID in hostapd according to IEEE 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 <alexander@xxxxxxxxxxxxxx> --- hostapd/config_file.c | 9 +++++ hostapd/hostapd.conf | 11 +++++++ src/ap/ap_config.c | 10 ++++++ src/ap/ap_config.h | 1 + src/ap/wpa_auth.c | 74 +++++++++++++++++++++++++++++++++++------- src/ap/wpa_auth.h | 1 + src/ap/wpa_auth_glue.c | 22 ++++++++++++- src/ap/wpa_auth_i.h | 3 ++ src/ap/wpa_auth_ie.c | 54 +++++++++++++++++++++++++++++- 9 files changed, 171 insertions(+), 14 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index b88071a58..417d4ee3c 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2875,6 +2875,15 @@ 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, "wpa_extended_key_id") == 0) { + bss->wpa_extended_key_id = atoi(pos); + if (bss->wpa_extended_key_id < 0 || + bss->wpa_extended_key_id > 1) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid wpa_extended_key_id=%d; allowed range 0..1", + line, bss->wpa_extended_key_id); + return 1; + } } 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/hostapd.conf b/hostapd/hostapd.conf index b61176717..9de9096fd 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1509,6 +1509,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 based on IEEE 802.11-2016 +# +# Extended Key ID allows to rekey PTK keys without the impacts the "normal" +# PTK0 rekeying has. (No data losses during the rekey and it requires less +# potentially missing special handling from the card/driver.) +# But it can only be used with wpa=2, CCMP/GCMP ciphers and when the +# card/driver has support for it. +# 0 = force off +# 1 = enable and use Extended Key ID support when possible (default) +#wpa_extended_key_id=1 + # 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.c b/src/ap/ap_config.c index 98a7f6e26..63174c133 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -62,6 +62,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss) bss->broadcast_key_idx_max = 2; bss->eap_reauth_period = 3600; + bss->wpa_extended_key_id = 1; bss->wpa_group_rekey = 600; bss->wpa_gmk_rekey = 86400; bss->wpa_deny_ptk0_rekey = PTK0_REKEY_ALLOW_NEVER; @@ -1143,6 +1144,15 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, } } + if (full_config && bss->wpa_extended_key_id && + !(bss->wpa & WPA_PROTO_RSN && + bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_CCMP_256 | + WPA_CIPHER_GCMP | WPA_CIPHER_GCMP_256))) { + wpa_printf(MSG_ERROR, + "Extended Key ID support requires WPA2 and CCMP/GCMP, disabling it"); + bss->wpa_extended_key_id = 0; + } + #ifdef CONFIG_IEEE80211R_AP if (full_config && wpa_key_mgmt_ft(bss->wpa_key_mgmt) && (bss->nas_identifier == NULL || diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 0b4483ea1..75f8dc432 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -345,6 +345,7 @@ struct hostapd_bss_config { * algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */ int wpa; /* bitfield of WPA_PROTO_WPA, WPA_PROTO_RSN */ + int wpa_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 7298e4928..c8ef366eb 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -756,7 +756,8 @@ 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_extended_key_id && + sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { wpa_printf(MSG_WARNING, "WPA: PTK0 rekey not allowed, disconnect " MACSTR, MAC2STR(sm->addr)); @@ -764,6 +765,8 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm) /* Try to encourage the STA reconnect */ sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; } else { + if (sm->use_extended_key_id) + sm->keyidx_active ^= 1; /* flip keyID */ sm->PTKRequest = TRUE; sm->PTK_valid = 0; } @@ -1727,6 +1730,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.wpa_extended_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 ID1 removal from the driver failed"); sm->pairwise_set = FALSE; eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); } @@ -1786,7 +1794,8 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) sm->AuthenticationRequest = TRUE; break; } else { - if (sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { + if (!sm->use_extended_key_id && + sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { wpa_printf(MSG_WARNING, "WPA: PTK0 rekey not allowed, disconnect " MACSTR, MAC2STR(sm->addr)); @@ -1795,6 +1804,9 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; break; } + + if (sm->use_extended_key_id) + sm->keyidx_active ^= 1; /* flip keyID */ } if (sm->GUpdateStationKeys) { /* @@ -3160,12 +3172,12 @@ static int ocv_oci_add(struct wpa_state_machine *sm, u8 **argpos) SM_STATE(WPA_PTK, PTKINITNEGOTIATING) { - u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde = NULL, *pos, dummy_gtk[32]; + u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *pos, dummy_gtk[32], hdr[2]; size_t gtk_len, kde_len; struct wpa_group *gsm = sm->group; u8 *wpa_ie; int wpa_ie_len, secure, gtkidx, encr = 0; - u8 *wpa_ie_buf = NULL; + u8 *wpa_ie_buf = NULL, *kde = NULL; SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk); sm->TimeoutEvt = FALSE; @@ -3231,6 +3243,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_extended_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]; @@ -3271,6 +3295,10 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) } kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + + if (sm->use_extended_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + if (gtk) kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; #ifdef CONFIG_IEEE80211R_AP @@ -3306,10 +3334,15 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING) pos += elen; } #endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_extended_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); } @@ -3391,9 +3424,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)) { + if (sm->use_extended_key_id) { + if (wpa_auth_set_key(sm->wpa_auth, 0, 0, sm->addr, + sm->keyidx_active, NULL, 0, + KEY_FLAG_PAIRWISE_RX_TX_MODIFY)) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + } else if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, + sm->PTK.tk, klen, + KEY_FLAG_PAIRWISE_RX_TX)) { wpa_sta_disconnect(sm->wpa_auth, sm->addr, WLAN_REASON_PREV_AUTH_NOT_VALID); return; @@ -5016,7 +5057,7 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm, void (*cb)(void *ctx1, void *ctx2), void *ctx1, void *ctx2) { - u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos; + u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos, hdr[2]; u8 *opos; size_t gtk_len, kde_len; struct wpa_group *gsm = sm->group; @@ -5074,6 +5115,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_extended_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + if (gtk) kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; #ifdef CONFIG_IEEE80211R_AP @@ -5106,10 +5151,15 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm, pos += elen; } #endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_extended_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 64c261964..332dff30c 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 wpa_extended_key_id; int wpa_key_mgmt; int wpa_pairwise; int wpa_group; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 7abb93e73..a918daf00 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -39,6 +39,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, { os_memset(wconf, 0, sizeof(*wconf)); wconf->wpa = conf->wpa; + wconf->wpa_extended_key_id = conf->wpa_extended_key_id; wconf->wpa_key_mgmt = conf->wpa_key_mgmt; wconf->wpa_pairwise = conf->wpa_pairwise; wconf->wpa_group = conf->wpa_group; @@ -374,7 +375,12 @@ 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_PAIRWISE_RX_TX_MODIFY) { + /* KEY_FLAG_PAIRWISE_RX installed the key and updated the + * variables. Doing it again for KEY_FLAG_PAIRWISE_RX_TX_MODIFY + * would overwrite the desired information with zeros. + */ + } else if (addr && !is_broadcast_ether_addr(addr)) { struct sta_info *sta; sta = ap_get_sta(hapd, addr); @@ -1360,6 +1366,20 @@ int hostapd_setup_wpa(struct hostapd_data *hapd) _conf.wpa_deny_ptk0_rekey = 0; } + if (_conf.wpa_extended_key_id) { + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + "Enable Extended Key ID support"); + } else { + wpa_msg(hapd->msg_ctx, MSG_INFO, + "Extended Key ID not supported by driver"); + _conf.wpa_extended_key_id = 0; + } + } else if (_conf.wpa & WPA_PROTO_RSN) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + "Extended Key ID support disabled"); + } + 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 a993f5008..0b182391b 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_extended_key_id; Boolean PTK_valid; Boolean pairwise_set; Boolean tk_already_set; @@ -285,6 +287,7 @@ int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth, int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth, int (*cb)(struct wpa_authenticator *a, void *ctx), void *cb_ctx); +int handle_extended_key_id(struct wpa_state_machine *sm, int capabilities); #ifdef CONFIG_IEEE80211R_AP int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len); diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index 2e6d05910..cb01e4d7f 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -284,6 +284,8 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, /* 4 PTKSA replay counters when using WMM */ capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); } + if (conf->wpa_extended_key_id) + capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST; if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { capab |= WPA_CAPABILITY_MFPC; if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) @@ -447,8 +449,9 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) { u8 *pos, buf[128]; int res; - #ifdef CONFIG_TESTING_OPTIONS + struct wpa_ie_data data; + if (wpa_auth->conf.own_ie_override_len) { wpa_hexdump(MSG_DEBUG, "WPA: Forced own IE(s) for testing", wpa_auth->conf.own_ie_override, @@ -461,6 +464,14 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) os_memcpy(wpa_auth->wpa_ie, wpa_auth->conf.own_ie_override, wpa_auth->conf.own_ie_override_len); wpa_auth->wpa_ie_len = wpa_auth->conf.own_ie_override_len; + if (wpa_parse_wpa_ie_rsn(wpa_auth->wpa_ie, + wpa_auth->wpa_ie_len, &data) || + !(data.capabilities & + WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) { + wpa_printf(MSG_DEBUG, + "WPA: Own IE forcing wpa_extended_key_id=0"); + wpa_auth->conf.wpa_extended_key_id = 0; + } return 0; } #endif /* CONFIG_TESTING_OPTIONS */ @@ -545,6 +556,44 @@ static int wpa_auth_okc_iter(struct wpa_authenticator *a, void *ctx) return 0; } +int handle_extended_key_id(struct wpa_state_machine *sm, int capabilities) +{ + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + + if (conf->wpa_extended_key_id && + sm->pairwise != WPA_CIPHER_TKIP && + capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST) { + if (!sm->use_extended_key_id && sm->pairwise_set) { + wpa_printf(MSG_ERROR, "STA " MACSTR + " tries to start using Extended Key ID on rekey", + MAC2STR(sm->addr)); + return -1; + } else if (!sm->use_extended_key_id) { + wpa_printf(MSG_DEBUG, "STA " MACSTR + " supports Extended Key ID", + MAC2STR(sm->addr)); + sm->use_extended_key_id = TRUE; + } else if (!sm->pairwise_set) { + wpa_printf(MSG_DEBUG, "STA " MACSTR + " is not supporting Extended Key ID", + MAC2STR(sm->addr)); + } + } else { + if (sm->use_extended_key_id && sm->pairwise_set) { + wpa_printf(MSG_ERROR, "STA " MACSTR + " is using Extended Key ID, can't rekey without it", + MAC2STR(sm->addr)); + return -1; + } else if (!sm->pairwise_set) { + wpa_printf(MSG_DEBUG, "STA " MACSTR + " can't use Extended Key ID", + MAC2STR(sm->addr)); + sm->use_extended_key_id = FALSE; + sm->keyidx_active = 0; + } + } + return 0; +} int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, struct wpa_state_machine *sm, int freq, @@ -870,6 +919,9 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, else sm->wpa = WPA_VERSION_WPA; + if (handle_extended_key_id(sm, data.capabilities)) + return WPA_INVALID_IE; + #if defined(CONFIG_IEEE80211R_AP) && defined(CONFIG_FILS) if ((sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA256 || sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA384) && -- 2.24.1 _______________________________________________ Hostap mailing list Hostap@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/hostap