diff mbox series

[v9,10/16] AP: Support Extended Key ID

Message ID 20200104221015.90469-11-alexander@wetzel-home.de
State Changes Requested
Headers show
Series Seamless PTK rekeys | expand

Commit Message

Alexander Wetzel Jan. 4, 2020, 10:10 p.m. UTC
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@wetzel-home.de>
---
 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(-)

Comments

Jouni Malinen Jan. 6, 2020, 9:26 a.m. UTC | #1
On Sat, Jan 04, 2020 at 11:10:09PM +0100, Alexander Wetzel wrote:
> 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.

The standard may claim to be fully backwards compatible, but that does
not mean there would be guarantee of no regressions or interop issues if
this were enabled.. I'd consider getting this in first with default
being the functionality being disabled. Once there is some more trust in
this actually working and not causing problems (e.g., an independent
implementation to test against), the default could be changed in the
future.
Johannes Berg Jan. 6, 2020, 9:28 a.m. UTC | #2
On Mon, 2020-01-06 at 11:26 +0200, Jouni Malinen wrote:
> On Sat, Jan 04, 2020 at 11:10:09PM +0100, Alexander Wetzel wrote:
> > 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.
> 
> The standard may claim to be fully backwards compatible, but that does
> not mean there would be guarantee of no regressions or interop issues if
> this were enabled.. I'd consider getting this in first with default
> being the functionality being disabled. Once there is some more trust in
> this actually working and not causing problems (e.g., an independent
> implementation to test against), the default could be changed in the
> future.

You'll never find out if you don't enable it though? :)

But are you thinking that clients might get confused by a new extended
capability bit getting set? That's something that happens all the time.
Or clients might erroneously set the extended capability bit for
extended PTK IDs?

johannes
Jouni Malinen Jan. 6, 2020, 9:44 a.m. UTC | #3
On Mon, Jan 06, 2020 at 10:28:21AM +0100, Johannes Berg wrote:
> On Mon, 2020-01-06 at 11:26 +0200, Jouni Malinen wrote:
> > The standard may claim to be fully backwards compatible, but that does
> > not mean there would be guarantee of no regressions or interop issues if
> > this were enabled.. I'd consider getting this in first with default
> > being the functionality being disabled. Once there is some more trust in
> > this actually working and not causing problems (e.g., an independent
> > implementation to test against), the default could be changed in the
> > future.
> 
> You'll never find out if you don't enable it though? :)

Sure, but I do not want to force that testing to unsuspecting end users
when they update an unrelated device and something does not work
anymore..

> But are you thinking that clients might get confused by a new extended
> capability bit getting set? That's something that happens all the time.
> Or clients might erroneously set the extended capability bit for
> extended PTK IDs?

My main concern is that latter one since there are known deployed
stations that copy RSN Capabilities from AP to Association Request frame
without understanding what they enable. In addition, I'd like to
minimize risk on different interpretation on how CCMP/GCMP handling of
different Key ID gets indicated and used in practice. That's supposed to
be straightforward, but I've seen way too many interop issues in the
past due to vendors managing to interpret the standard in very strange
ways and/or implementing things incorrectly.
Alexander Wetzel Jan. 6, 2020, 1:53 p.m. UTC | #4
Am 06.01.20 um 10:44 schrieb Jouni Malinen:
> On Mon, Jan 06, 2020 at 10:28:21AM +0100, Johannes Berg wrote:
>> On Mon, 2020-01-06 at 11:26 +0200, Jouni Malinen wrote:
>>> The standard may claim to be fully backwards compatible, but that does
>>> not mean there would be guarantee of no regressions or interop issues if
>>> this were enabled.. I'd consider getting this in first with default
>>> being the functionality being disabled. Once there is some more trust in
>>> this actually working and not causing problems (e.g., an independent
>>> implementation to test against), the default could be changed in the
>>> future.
>>
>> You'll never find out if you don't enable it though? :)
> 
> Sure, but I do not want to force that testing to unsuspecting end users
> when they update an unrelated device and something does not work
> anymore..
> 
>> But are you thinking that clients might get confused by a new extended
>> capability bit getting set? That's something that happens all the time.
>> Or clients might erroneously set the extended capability bit for
>> extended PTK IDs?
> 
> My main concern is that latter one since there are known deployed
> stations that copy RSN Capabilities from AP to Association Request frame
> without understanding what they enable. In addition, I'd like to
> minimize risk on different interpretation on how CCMP/GCMP handling of
> different Key ID gets indicated and used in practice. That's supposed to
> be straightforward, but I've seen way too many interop issues in the
> past due to vendors managing to interpret the standard in very strange
> ways and/or implementing things incorrectly.
> 
The chance of using Extended Key ID is today basically zero when you are 
not out to test it. And I would assume whoever is making the next 
implementation is testing it against this one and reaching out when 
disagreeing with some decision.

To use it today you would have to use an AP and a client both using a 
mac80211 card supporting ONLY SW crypto or - hopefully in the near 
future - any iwlwifi card.
I do not expect the situation to change radically in the next years. 
(After all it has taken more than 8 years and a frustated PTK rekey user 
to start playing developer to get the first implementation after 
Extended Key ID had been standardized.:-)

I have it running on my home router and with my very limited test set I 
already found that the Samsung Galaxy Tab S3 (using stock rom) sets the 
Extended Key ID cabability flag when the AP set it - looks like a copy - 
but is still not able to handle Extended Key ID. (The tablet really 
sucks at PTK rekeying regadless with or without Extended Key ID, so you 
hardly see a difference when you rekey all 30s.)
I was thinking about opening a ticket, but I could not figure out how...
(And I do not like my chances getting that through to someone actually 
able to understand and care about that.)

If I would work at e.g. Cisco and planning to sell a new AP able to use 
Extended Key ID I would for sure disable it by default. But for hostapd 
the idea was to enabling it now by default. So there are hopefully some 
Extended Key ID capable clients (iwlwifi with linux?) in most test 
setups soon and able to highlight the issue.

Changing the default later seems is only postponing the issue and making 
it worse when more drivers start implementing it.

Keeping it disabled on hostapd by default but enable it in 
wpa_supplicant may be a middle way for now, but I do not see how we can 
change the default later either.

Keeping it disabled everywhere seems to be the worst possible solution.
I have the impression that there can't be 10 persons world-wide who care 
about PTK rekeys while thousands are unknowingly using the insecure PTK0 
rekey.
Chances are nobody will enable it and we'll be stuck with the PTK0 rekey 
forever and only special setups who care about it enable it.

Honestly I would like to propose to make Extended Key ID mandatory in 
(one of) the next version of IEEE802.11. But I'm doing that here as a 
hobby and do not see how I can make such an suggestions without being a 
member and the backing of other. (But I did not spend much time figuring 
out how that is handled. There are so far more interesting things to 
work on:-)


Alexander
diff mbox series

Patch

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) &&