diff mbox series

[5/8] STA: Support Extended Key ID

Message ID 20200315190426.163478-6-alexander@wetzel-home.de
State Changes Requested
Headers show
Series Extended Key ID support | expand

Commit Message

Alexander Wetzel March 15, 2020, 7:04 p.m. UTC
Support Extended Key ID in wpa_supplicant according to
IEEE 802.11-2016 for infrastructure (AP) associations.

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 STAs to also connect to APs not supporting it.

The standard is not covering how to use it with extensions like FILS or
FT and wpa_supplicant sticks to the save settings by default but allows
the user to enable non-standard Extended Key ID support for FT and FILS.

BASIC Extended Key ID support does nothing not clearly covered by the
standard and force it to be off for compatibility when needed.
FT0, FILS0 and FILS_CUSTOM are not standardized extensions to also allow
Extended Key ID support to be used with FT and/or FILS.

FT0, FILS0 and FILS_CUSTOM are not standardized extensions allowing to
use Extended Key ID also with FT and/or FILS.
FILS0 and FILS_CUSTOM are both detecting when the AP is using the other
mode and switch over to it.

Signed-off-by: Alexander Wetzel <alexander@wetzel-home.de>
---
 src/rsn_supp/wpa.c                      | 183 ++++++++++++++++++++++--
 src/rsn_supp/wpa.h                      |  14 ++
 src/rsn_supp/wpa_ft.c                   |  27 +++-
 src/rsn_supp/wpa_i.h                    |   3 +
 src/rsn_supp/wpa_ie.c                   |   7 +
 wpa_supplicant/ap.c                     |   1 +
 wpa_supplicant/config.c                 |  50 +++++++
 wpa_supplicant/config_file.c            |   1 +
 wpa_supplicant/config_ssid.h            |   8 ++
 wpa_supplicant/ctrl_iface.c             |   3 +
 wpa_supplicant/dbus/dbus_new_handlers.c |   3 +-
 wpa_supplicant/driver_i.h               |   9 +-
 wpa_supplicant/wpa_cli.c                |   2 +-
 wpa_supplicant/wpa_supplicant.c         |  24 +++-
 wpa_supplicant/wpa_supplicant.conf      |  38 +++++
 wpa_supplicant/wpas_glue.c              |   7 +-
 16 files changed, 359 insertions(+), 21 deletions(-)

Comments

Jouni Malinen March 15, 2020, 10:26 p.m. UTC | #1
On Sun, Mar 15, 2020 at 08:04:23PM +0100, Alexander Wetzel wrote:
> Support Extended Key ID in wpa_supplicant according to
> IEEE 802.11-2016 for infrastructure (AP) associations.
> 
> 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 STAs to also connect to APs not supporting it.
> 
> The standard is not covering how to use it with extensions like FILS or
> FT and wpa_supplicant sticks to the save settings by default but allows
> the user to enable non-standard Extended Key ID support for FT and FILS.
> 
> BASIC Extended Key ID support does nothing not clearly covered by the
> standard and force it to be off for compatibility when needed.
> FT0, FILS0 and FILS_CUSTOM are not standardized extensions to also allow
> Extended Key ID support to be used with FT and/or FILS.
> 
> FT0, FILS0 and FILS_CUSTOM are not standardized extensions allowing to
> use Extended Key ID also with FT and/or FILS.
> FILS0 and FILS_CUSTOM are both detecting when the AP is using the other
> mode and switch over to it.

I'd like to get a version of this that includes only the fully
standardized parts, i.e., use of Extended Key ID when going through
4-way handshake.

As far as wpa_supplicant configuration for this is concerned, is there
real need for provide per-network profile configuration? I'm thinking of
simply adding a global ext_key_id parameter with 0/1 values that applies
to all cases. This would default to 0 (disabled) for now and then could
be changed to 1 once there has been some interoperability testing of the
functionality between vendors.
Alexander Wetzel March 16, 2020, 9:07 p.m. UTC | #2
Am 15.03.20 um 23:26 schrieb Jouni Malinen:
> On Sun, Mar 15, 2020 at 08:04:23PM +0100, Alexander Wetzel wrote:
>> Support Extended Key ID in wpa_supplicant according to
>> IEEE 802.11-2016 for infrastructure (AP) associations.
>>
>> 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 STAs to also connect to APs not supporting it.
>>
>> The standard is not covering how to use it with extensions like FILS or
>> FT and wpa_supplicant sticks to the save settings by default but allows
>> the user to enable non-standard Extended Key ID support for FT and FILS.
>>
>> BASIC Extended Key ID support does nothing not clearly covered by the
>> standard and force it to be off for compatibility when needed.
>> FT0, FILS0 and FILS_CUSTOM are not standardized extensions to also allow
>> Extended Key ID support to be used with FT and/or FILS.
>>
>> FT0, FILS0 and FILS_CUSTOM are not standardized extensions allowing to
>> use Extended Key ID also with FT and/or FILS.
>> FILS0 and FILS_CUSTOM are both detecting when the AP is using the other
>> mode and switch over to it.
> 
> I'd like to get a version of this that includes only the fully
> standardized parts, i.e., use of Extended Key ID when going through
> 4-way handshake.
> 
First, you see BASIC+FT0+FILS0 as standardized? (See previous mail)

The only question is then if we can "mark" the connections to be using 
Extended Key ID at the FT or FILS connect or shall wait till we rekey.
Since both handshakes have RSN and therefore the Extended Key ID 
Capability Bit the initial connect seems to be the logical point.

Alternatively we can make the check that the AP is not stopping using 
Extended Key ID less strict and allow e.g. PTK0 rekey for at least the 
first EAPOL rekey. Even when the RSN Capapilities at the initial connect 
told us it should be Extended Key ID.

> As far as wpa_supplicant configuration for this is concerned, is there
> real need for provide per-network profile configuration? I'm thinking of
> simply adding a global ext_key_id parameter with 0/1 values that applies
> to all cases. This would default to 0 (disabled) for now and then could
> be changed to 1 once there has been some interoperability testing of the
> functionality between vendors.
> 

wpa_supplicant can also be configured to start an AP. The network 
setting is passed through to the AP. Using a global setting for that 
will prevent to configure it for the AP at all. It either would be stuck 
to a hard coded value or to the global config setting. Shall I go for that?

Alexander
Jouni Malinen March 16, 2020, 9:37 p.m. UTC | #3
On Mon, Mar 16, 2020 at 10:07:26PM +0100, Alexander Wetzel wrote:
> First, you see BASIC+FT0+FILS0 as standardized? (See previous mail)

I don't understand what "FT0" and "FILS0" really mean.. If that means
never using KeyID 1 with FT protocol or FILS authentication when going
through PTK rekeying, no, I don't think I would say so. If those mean
that the very first KeyID for an association has to be 0, I guess I'd
agree. So in the simplest default case: KeyID 0 is always assigned first
regardless of which authentication mechanism is used and PTK rekeying
through 4-way handshake can then switch between KeyID 1 and 0 since that
same design is used with all cases.

> The only question is then if we can "mark" the connections to be using
> Extended Key ID at the FT or FILS connect or shall wait till we rekey.
> Since both handshakes have RSN and therefore the Extended Key ID Capability
> Bit the initial connect seems to be the logical point.
> 
> Alternatively we can make the check that the AP is not stopping using
> Extended Key ID less strict and allow e.g. PTK0 rekey for at least the first
> EAPOL rekey. Even when the RSN Capapilities at the initial connect told us
> it should be Extended Key ID.

I'm not sure I fully follow the terminology of "PTK0 rekey" and what
would you describe here.. I'd expect station/supplicant to follow
whatever the AP tells it for the KeyID. If there is no explicit
indication of KeyID for the initial TK at the beginning of association,
0 have to be assumed to be used. Whenever going through PTK rekeying
(4-way handshake), if there is an explicit indication of Key ID (i.e.,
Key ID KDE), that provides the Key ID to use; otherwise, 0 is assumed.

Or are you just talking about the wpa_deny_ptk0_rekey != 0 cases?
Wouldn't that be obvious at the time of the first rekey? If both the
initial TK and the second TK from that first rekey use Key ID 0, the AP
is not using Extended Key ID.

> wpa_supplicant can also be configured to start an AP. The network setting is
> passed through to the AP. Using a global setting for that will prevent to
> configure it for the AP at all. It either would be stuck to a hard coded
> value or to the global config setting. Shall I go for that?

Ah, yes.. We'll have to consider the implication for AP mode. I guess it
could be justifiable to do that with the shared global parameter as
well. A specific network profile for AP mode is explicitly selected
anyway, so there would be no significant limitation from requiring the
global parameter value to be set for cases where different setting for
use of Extended Key ID is needed. I'd probably also drop the special
case of starting with Key ID 1, i.e., simply have ext_key_id=0/1 as the
wpa_supplicant global parameter while hostapd would use ext_key_id=0/1/2
to allow start-with-KeyID-1 testing (with PSK/SAE; not with FT/FILS per
discussion on what the standard actually describes).
Alexander Wetzel March 16, 2020, 10:54 p.m. UTC | #4
Am 16.03.20 um 22:37 schrieb Jouni Malinen:
> On Mon, Mar 16, 2020 at 10:07:26PM +0100, Alexander Wetzel wrote:
>> First, you see BASIC+FT0+FILS0 as standardized? (See previous mail)
> 
> I don't understand what "FT0" and "FILS0" really mean.. If that means
> never using KeyID 1 with FT protocol or FILS authentication when going
> through PTK rekeying, no, I don't think I would say so. If those mean
> that the very first KeyID for an association has to be 0, I guess I'd
> agree. So in the simplest default case: KeyID 0 is always assigned first
> regardless of which authentication mechanism is used and PTK rekeying
> through 4-way handshake can then switch between KeyID 1 and 0 since that
> same design is used with all cases.
> 
>> The only question is then if we can "mark" the connections to be using
>> Extended Key ID at the FT or FILS connect or shall wait till we rekey.
>> Since both handshakes have RSN and therefore the Extended Key ID Capability
>> Bit the initial connect seems to be the logical point.
>>
>> Alternatively we can make the check that the AP is not stopping using
>> Extended Key ID less strict and allow e.g. PTK0 rekey for at least the first
>> EAPOL rekey. Even when the RSN Capapilities at the initial connect told us
>> it should be Extended Key ID.
> 
> I'm not sure I fully follow the terminology of "PTK0 rekey" and what
> would you describe here.. I'd expect station/supplicant to follow
> whatever the AP tells it for the KeyID. If there is no explicit
> indication of KeyID for the initial TK at the beginning of association,
> 0 have to be assumed to be used. Whenever going through PTK rekeying
> (4-way handshake), if there is an explicit indication of Key ID (i.e.,
> Key ID KDE), that provides the Key ID to use; otherwise, 0 is assumed.
> 

First: FT0 and FILS0 seem to to exactly what you want. I'm just not sure 
if you are ok with how these modes "pin" Extended Key Id support with 
the initial handshakes (FT or FILS) and make sure the rekeys are then 
using something different later.

I would also have simple told the STA to signal support for Extended Key 
ID and only check the KeyID in EAPOL#3: If there is a keyID use that. If 
not use zero. But the standard forces us to check the RSN (chapter 
12.7.6.4):
If the Extended Key ID for Individually Addressed Frames subfield of the 
RSN Capabilities field is 1 for both the Authenticator and Supplicant 
then prior to sending message 4, STA_P uses the MLME-SETKEYS.request 
primitive to configure the IEEE 802.11 MAC to receive individually
addressed MPDUs protected by the STK with the assigned Key ID.
Simply acting on the KeyID would therefore violate the standard.

> Or are you just talking about the wpa_deny_ptk0_rekey != 0 cases?
> Wouldn't that be obvious at the time of the first rekey? If both the
> initial TK and the second TK from that first rekey use Key ID 0, the AP
> is not using Extended Key ID.
> 

Yes, that would also be a problem... wpa_deny_ptk0_rekey != 0 is 
resetting the connection normally way prior to getting the new keyID.

when we not decide at the initial connection if we are using Extended 
Key ID or not we would have to reset the connection much later and not 
at the first indication one end plans to rekey.

>> wpa_supplicant can also be configured to start an AP. The network setting is
>> passed through to the AP. Using a global setting for that will prevent to
>> configure it for the AP at all. It either would be stuck to a hard coded
>> value or to the global config setting. Shall I go for that?
> 
> Ah, yes.. We'll have to consider the implication for AP mode. I guess it
> could be justifiable to do that with the shared global parameter as
> well. A specific network profile for AP mode is explicitly selected
> anyway, so there would be no significant limitation from requiring the
> global parameter value to be set for cases where different setting for
> use of Extended Key ID is needed. I'd probably also drop the special
> case of starting with Key ID 1, i.e., simply have ext_key_id=0/1 as the
> wpa_supplicant global parameter while hostapd would use ext_key_id=0/1/2
> to allow start-with-KeyID-1 testing (with PSK/SAE; not with FT/FILS per
> discussion on what the standard actually describes).
> 

Honestly I'm thinking of dropping the "start-with-keyid-1" outright now. 
Just using it for testcases is probably not worth it.

Alexander
Jouni Malinen March 22, 2020, 6:16 p.m. UTC | #5
On Mon, Mar 16, 2020 at 11:54:41PM +0100, Alexander Wetzel wrote:
> First: FT0 and FILS0 seem to to exactly what you want. I'm just not sure if
> you are ok with how these modes "pin" Extended Key Id support with the
> initial handshakes (FT or FILS) and make sure the rekeys are then using
> something different later.

I'm not I completely understood that pinning the support part, but I'll
take a closer look at the details in the updated patch.

> I would also have simple told the STA to signal support for Extended Key ID
> and only check the KeyID in EAPOL#3: If there is a keyID use that. If not
> use zero. But the standard forces us to check the RSN (chapter 12.7.6.4):
> If the Extended Key ID for Individually Addressed Frames subfield of the RSN
> Capabilities field is 1 for both the Authenticator and Supplicant then prior
> to sending message 4, STA_P uses the MLME-SETKEYS.request primitive to
> configure the IEEE 802.11 MAC to receive individually
> addressed MPDUs protected by the STK with the assigned Key ID.
> Simply acting on the KeyID would therefore violate the standard.

That design is broken and should be ignored. Extended Capabilities
element is not protected and the bits in in should certainly not
override the protected exchange of Key ID KDE in EAPOL-Key msg 3/4.
IMHO, it is perfectly fine and correct to not comply with that
statement. This full text is pretty descriptive instead of using clearly
normative language.. Anyway, I'll try to get this fixed in REVmd to use
the presence of Key ID KDE as the rule for the station side and also add
an explicit requirement for the AP to include Key ID KDE in msg 3/4
whenever using Extended Key ID (i.e., also require it to be there for Key
ID 0 case). I did not see such an explicit requirement in the standard.

> Yes, that would also be a problem... wpa_deny_ptk0_rekey != 0 is resetting
> the connection normally way prior to getting the new keyID.
> 
> when we not decide at the initial connection if we are using Extended Key ID
> or not we would have to reset the connection much later and not at the first
> indication one end plans to rekey.

I'm still not completely sure I understand why there would be such a
difference.. Station should assume the AP will be using Extended Key ID
based on the Extended Capabilities indication from scan results (i.e.,
known at the time of the initial association). I'll see if I can
understand this better when going through the updated patch.
Jouni Malinen March 22, 2020, 6:23 p.m. UTC | #6
On Sun, Mar 22, 2020 at 08:16:56PM +0200, Jouni Malinen wrote:
> That design is broken and should be ignored. Extended Capabilities
> element is not protected and the bits in in should certainly not
> override the protected exchange of Key ID KDE in EAPOL-Key msg 3/4.

Oops.. Clearly I don't remember where that bit is.. It was added in
RSNE, not Extended Capabilities element, so it will be protected.

> IMHO, it is perfectly fine and correct to not comply with that
> statement. This full text is pretty descriptive instead of using clearly
> normative language.. Anyway, I'll try to get this fixed in REVmd to use
> the presence of Key ID KDE as the rule for the station side and also add
> an explicit requirement for the AP to include Key ID KDE in msg 3/4
> whenever using Extended Key ID (i.e., also require it to be there for Key
> ID 0 case). I did not see such an explicit requirement in the standard.

It would seem to make more sense to use Key ID KDE as the rule here, but
it is not really that critical taken into account the RSNE capability
bit should really be used consistently.
diff mbox series

Patch

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 5bb47bcbe..dddaff47a 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -184,6 +184,7 @@  void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise)
 	u8 bssid[ETH_ALEN], *rbuf, *key_mic, *mic;
 
 	if (pairwise && sm->wpa_deny_ptk0_rekey &&
+	    !sm->use_extended_key_id &&
 	    wpa_sm_get_state(sm) == WPA_COMPLETED) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"WPA: PTK0 rekey not allowed, reconnecting");
@@ -607,6 +608,94 @@  static int wpa_derive_ptk(struct wpa_sm *sm, const unsigned char *src_addr,
 			      sm->pairwise_cipher, z, z_len);
 }
 
+#ifdef CONFIG_FILS
+static int fils_handle_extended_key_id(struct wpa_sm *sm,
+				       struct wpa_eapol_ie_parse *kde)
+{
+	struct wpa_ie_data rsn;
+
+	if (sm->extended_key_id & EXT_KEY_ID_FILS &&
+	    sm->pairwise_cipher != WPA_CIPHER_TKIP &&
+	    !wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &rsn) &&
+	    rsn.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST) {
+		sm->use_extended_key_id = 1;
+
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"FILS: using Extended Key ID (%s)",
+			sm->extended_key_id & EXT_KEY_ID_FILS0 ? "FILS0" :
+								 "FILS_CUSTOM");
+		if (kde->key_id && sm->extended_key_id & EXT_KEY_ID_FILS0)
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"KeyID in FILS0, using FILS_CUSTOM instead");
+		else if (!kde->key_id &&
+			   sm->extended_key_id & EXT_KEY_ID_FILS_CUSTOM)
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"No KeyID in FILS_CUSTOM, using FILS0 instead");
+		if (kde->key_id) {
+			if (kde->key_id[0] & 0xfe) {
+				wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+					"FILS: Invalid KeyID: %d",
+					kde->key_id[0]);
+				return -1;
+			}
+			sm->keyidx_active = kde->key_id[0];
+		} else {
+			sm->keyidx_active = 0;
+		}
+	} else {
+		sm->use_extended_key_id = 0;
+		sm->keyidx_active = 0;
+	}
+	return 0;
+}
+#endif /* CONFIG_FILS */
+
+static int handle_extended_key_id(struct wpa_sm *sm,
+				  struct wpa_eapol_ie_parse *kde)
+{
+	struct wpa_ie_data rsn;
+
+	/* IEEE 802.11-2016 requires the Extended Key ID bit to be set
+	 * in the RSN capabilities for both STAs to use it with CCMP/GCMP.
+	 * How to handle that in combination with FT/FILS is not specified and
+	 * therefore depends on configuration settings.
+	 */
+	if (sm->extended_key_id && sm->pairwise_cipher != WPA_CIPHER_TKIP &&
+	    (!(wpa_key_mgmt_ft(sm->key_mgmt) ||
+	       wpa_key_mgmt_fils(sm->key_mgmt)) ||
+	     (wpa_key_mgmt_ft(sm->key_mgmt) &&
+	      sm->extended_key_id & EXT_KEY_ID_FT0) ||
+	     (wpa_key_mgmt_fils(sm->key_mgmt) &&
+	      sm->extended_key_id & EXT_KEY_ID_FILS)) &&
+	    !wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &rsn) &&
+	    rsn.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST) {
+		if (!kde->key_id) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+				"WPA: No KeyID in Extended Key ID handshake");
+			return -1;
+		} else if (kde->key_id[0] & 0xfe) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+				"WPA: Invalid KeyID: %d", kde->key_id[0]);
+			return -1;
+		}
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"WPA: Using Extended Key ID");
+		sm->keyidx_active = kde->key_id[0];
+		sm->use_extended_key_id = 1;
+	} else {
+		if (kde->key_id && kde->key_id[0]) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+				"Non-zero Extended Key ID KeyID in PTK0 handshake");
+			return -1;
+		} else if (kde->key_id) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"Extended Key ID KeyID in PTK0 handshake");
+		}
+		sm->keyidx_active = 0;
+		sm->use_extended_key_id = 0;
+	}
+	return 0;
+}
 
 static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
 					  const unsigned char *src_addr,
@@ -626,7 +715,8 @@  static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
 		return;
 	}
 
-	if (sm->wpa_deny_ptk0_rekey && wpa_sm_get_state(sm) == WPA_COMPLETED) {
+	if (sm->wpa_deny_ptk0_rekey && !sm->use_extended_key_id &&
+	    wpa_sm_get_state(sm) == WPA_COMPLETED) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"WPA: PTK0 rekey not allowed, reconnecting");
 		wpa_sm_reconnect(sm);
@@ -762,9 +852,10 @@  static void wpa_supplicant_key_neg_complete(struct wpa_sm *sm,
 {
 	wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 		"WPA: Key negotiation completed with "
-		MACSTR " [PTK=%s GTK=%s]", MAC2STR(addr),
+		MACSTR " [PTK=%s GTK=%s%s]", MAC2STR(addr),
 		wpa_cipher_txt(sm->pairwise_cipher),
-		wpa_cipher_txt(sm->group_cipher));
+		wpa_cipher_txt(sm->group_cipher),
+		sm->use_extended_key_id ? " Extended_Key_ID" : "");
 	wpa_sm_cancel_auth_timeout(sm);
 	wpa_sm_set_state(sm, WPA_COMPLETED);
 
@@ -859,13 +950,15 @@  static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
 		wpa_hexdump(MSG_DEBUG, "WPA: RSC", key_rsc, rsclen);
 	}
 
-	if (wpa_sm_set_key(sm, alg, sm->bssid, 0, 1, key_rsc, rsclen,
-			   sm->ptk.tk, keylen,
+	if (wpa_sm_set_key(sm, alg, sm->bssid, sm->keyidx_active, 1, key_rsc,
+			   rsclen, sm->ptk.tk, keylen,
 			   KEY_FLAG_PAIRWISE | key_flag) < 0) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
-			"WPA: Failed to set PTK to the "
-			"driver (alg=%d keylen=%d bssid=" MACSTR ")",
-			alg, keylen, MAC2STR(sm->bssid));
+			"WPA: Failed to set PTK to the driver"
+			"(alg=%d keylen=%d bssid=" MACSTR
+			" idx=%d use_extended_key_id=%d key_flag=0x%x)",
+			alg, keylen, MAC2STR(sm->bssid),
+			sm->keyidx_active, sm->use_extended_key_id, key_flag);
 		return -1;
 	}
 
@@ -879,7 +972,22 @@  static int wpa_supplicant_install_ptk(struct wpa_sm *sm,
 		eloop_register_timeout(sm->wpa_ptk_rekey, 0, wpa_sm_rekey_ptk,
 				       sm, NULL);
 	}
+	return 0;
+}
 
+static int wpa_supplicant_activate_ptk(struct wpa_sm *sm)
+{
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+		"WPA: Activate PTK (idx=%d bssid=" MACSTR ")",
+		sm->keyidx_active, MAC2STR(sm->bssid));
+
+	if (wpa_sm_set_key(sm, 0, sm->bssid, sm->keyidx_active,
+	    0, NULL, 0, NULL, 0,  KEY_FLAG_PAIRWISE_RX_TX_MODIFY) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+			"WPA: Failed to activate PTK for Tx (idx=%d bssid="
+			MACSTR ")", sm->keyidx_active, MAC2STR(sm->bssid));
+		return -1;
+	}
 	return 0;
 }
 
@@ -1582,6 +1690,9 @@  static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 	if (wpa_supplicant_validate_ie(sm, sm->bssid, &ie) < 0)
 		goto failed;
 
+	if (handle_extended_key_id(sm, &ie))
+		goto failed;
+
 	if (os_memcmp(sm->anonce, key->key_nonce, WPA_NONCE_LEN) != 0) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
 			"WPA: ANonce from message 1 of 4-Way Handshake "
@@ -1626,6 +1737,10 @@  static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 		}
 	}
 #endif /* CONFIG_OCV */
+	if (sm->use_extended_key_id) {
+		if (wpa_supplicant_install_ptk(sm, key, KEY_FLAG_RX))
+			goto failed;
+	}
 
 	if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info,
 				       &sm->ptk) < 0) {
@@ -1638,8 +1753,13 @@  static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 	sm->renew_snonce = 1;
 
 	if (key_info & WPA_KEY_INFO_INSTALL) {
-		if (wpa_supplicant_install_ptk(sm, key, KEY_FLAG_RX_TX))
+		if (sm->use_extended_key_id) {
+			if (wpa_supplicant_activate_ptk(sm))
+				goto failed;
+		} else if (wpa_supplicant_install_ptk(sm, key,
+						      KEY_FLAG_RX_TX)) {
 			goto failed;
+		}
 	}
 
 	if (key_info & WPA_KEY_INFO_SECURE) {
@@ -2746,6 +2866,7 @@  struct wpa_sm * wpa_sm_init(struct wpa_sm_ctx *ctx)
 		return NULL;
 	dl_list_init(&sm->pmksa_candidates);
 	sm->renew_snonce = 1;
+	sm->keyidx_active = 0;
 	sm->ctx = ctx;
 
 	sm->dot11RSNAConfigPMKLifetime = 43200;
@@ -3164,6 +3285,9 @@  int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param,
 	case WPA_PARAM_DENY_PTK0_REKEY:
 		sm->wpa_deny_ptk0_rekey = value;
 		break;
+	case WPA_PARAM_EXTENDED_KEY_ID:
+		sm->extended_key_id = value;
+		break;
 	default:
 		break;
 	}
@@ -3238,6 +3362,18 @@  int wpa_sm_pmf_enabled(struct wpa_sm *sm)
 }
 
 
+int wpa_sm_extended_key_id(struct wpa_sm *sm)
+{
+	return sm->extended_key_id;
+}
+
+
+int wpa_sm_extended_key_id_active(struct wpa_sm *sm)
+{
+	return sm->use_extended_key_id;
+}
+
+
 int wpa_sm_ocv_enabled(struct wpa_sm *sm)
 {
 	struct wpa_ie_data rsn;
@@ -3268,6 +3404,9 @@  int wpa_sm_set_assoc_wpa_ie_default(struct wpa_sm *sm, u8 *wpa_ie,
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (sm->test_assoc_ie) {
+		struct wpa_eapol_ie_parse ie;
+		struct wpa_ie_data rsn;
+
 		wpa_printf(MSG_DEBUG,
 			   "TESTING: Replace association WPA/RSN IE");
 		if (*wpa_ie_len < wpabuf_len(sm->test_assoc_ie))
@@ -3275,6 +3414,15 @@  int wpa_sm_set_assoc_wpa_ie_default(struct wpa_sm *sm, u8 *wpa_ie,
 		os_memcpy(wpa_ie, wpabuf_head(sm->test_assoc_ie),
 			  wpabuf_len(sm->test_assoc_ie));
 		res = wpabuf_len(sm->test_assoc_ie);
+
+		if (wpa_supplicant_parse_ies(wpa_ie, res, &ie) ||
+		    wpa_parse_wpa_ie_rsn(ie.rsn_ie, ie.rsn_ie_len, &rsn) ||
+		    !(rsn.capabilities &
+		      WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) {
+			wpa_printf(MSG_DEBUG,
+				   "WPA: Forced own IE disables Extended Key ID");
+			sm->extended_key_id = 0;
+		}
 	} else
 #endif /* CONFIG_TESTING_OPTIONS */
 	res = wpa_gen_wpa_ie(sm, wpa_ie, *wpa_ie_len);
@@ -4253,6 +4401,8 @@  static int fils_ft_build_assoc_req_rsne(struct wpa_sm *sm, struct wpabuf *buf)
 		capab |= WPA_CAPABILITY_MFPR;
 	if (sm->ocv)
 		capab |= WPA_CAPABILITY_OCVC;
+	if (sm->extended_key_id & EXT_KEY_ID_FILS)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	wpabuf_put_le16(buf, capab);
 
 	/* PMKID Count */
@@ -4645,6 +4795,7 @@  int fils_process_assoc_resp(struct wpa_sm *sm, const u8 *resp, size_t len)
 		wpa_printf(MSG_DEBUG, "FILS: No GTK KDE");
 		goto fail;
 	}
+
 	maxkeylen = gd.gtk_len = kde.gtk_len - 2;
 	if (wpa_supplicant_check_group_cipher(sm, sm->group_cipher,
 					      gd.gtk_len, maxkeylen,
@@ -4680,11 +4831,21 @@  int fils_process_assoc_resp(struct wpa_sm *sm, const u8 *resp, size_t len)
 			   keylen, (long unsigned int) sm->ptk.tk_len);
 		goto fail;
 	}
+
+	if (elems.rsn_ie) {
+		/* link in RSN for fils_handle_extended_key_id() */
+		kde.rsn_ie = elems.rsn_ie - 2;
+		kde.rsn_ie_len = elems.rsn_ie_len + 2;
+	}
+	if (fils_handle_extended_key_id(sm, &kde))
+		goto fail;
+
 	rsclen = wpa_cipher_rsc_len(sm->pairwise_cipher);
 	wpa_hexdump_key(MSG_DEBUG, "FILS: Set TK to driver",
 			sm->ptk.tk, keylen);
-	if (wpa_sm_set_key(sm, alg, sm->bssid, 0, 1, null_rsc, rsclen,
-			   sm->ptk.tk, keylen, KEY_FLAG_PAIRWISE_RX_TX) < 0) {
+	if (wpa_sm_set_key(sm, alg, sm->bssid, sm->keyidx_active, 1,
+			   null_rsc, rsclen, sm->ptk.tk, keylen,
+			   KEY_FLAG_PAIRWISE_RX_TX) < 0) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
 			"FILS: Failed to set PTK to the driver (alg=%d keylen=%d bssid="
 			MACSTR ")",
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index 0bd14495a..7202125b1 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -102,6 +102,7 @@  enum wpa_sm_conf_params {
 	WPA_PARAM_OCV,
 	WPA_PARAM_SAE_PWE,
 	WPA_PARAM_DENY_PTK0_REKEY,
+	WPA_PARAM_EXTENDED_KEY_ID,
 };
 
 struct rsn_supp_config {
@@ -109,6 +110,7 @@  struct rsn_supp_config {
 	int allowed_pairwise_cipher; /* bitfield of WPA_CIPHER_* */
 	int proactive_key_caching;
 	int eap_workaround;
+	enum ext_key_id_support extended_key_id;
 	void *eap_conf_ctx;
 	const u8 *ssid;
 	size_t ssid_len;
@@ -154,6 +156,8 @@  int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param,
 int wpa_sm_get_status(struct wpa_sm *sm, char *buf, size_t buflen,
 		      int verbose);
 int wpa_sm_pmf_enabled(struct wpa_sm *sm);
+int wpa_sm_extended_key_id(struct wpa_sm *sm);
+int wpa_sm_extended_key_id_active(struct wpa_sm *sm);
 int wpa_sm_ocv_enabled(struct wpa_sm *sm);
 
 void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise);
@@ -300,6 +304,16 @@  static inline int wpa_sm_pmf_enabled(struct wpa_sm *sm)
 	return 0;
 }
 
+static inline int wpa_sm_extended_key_id(struct wpa_sm *sm)
+{
+	return 0;
+}
+
+static inline int wpa_sm_extended_key_id_active(struct wpa_sm *sm)
+{
+	return 0;
+}
+
 static inline int wpa_sm_ocv_enabled(struct wpa_sm *sm)
 {
 	return 0;
diff --git a/src/rsn_supp/wpa_ft.c b/src/rsn_supp/wpa_ft.c
index 6d627b78f..2caae8334 100644
--- a/src/rsn_supp/wpa_ft.c
+++ b/src/rsn_supp/wpa_ft.c
@@ -263,6 +263,8 @@  static u8 * wpa_ft_gen_req_ies(struct wpa_sm *sm, size_t *len,
 		capab |= WPA_CAPABILITY_MFPR;
 	if (sm->ocv)
 		capab |= WPA_CAPABILITY_OCVC;
+	if (sm->extended_key_id & EXT_KEY_ID_FT0)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	WPA_PUT_LE16(pos, capab);
 	pos += 2;
 
@@ -429,8 +431,8 @@  static int wpa_ft_install_ptk(struct wpa_sm *sm, const u8 *bssid)
 	alg = wpa_cipher_to_alg(sm->pairwise_cipher);
 	keylen = wpa_cipher_key_len(sm->pairwise_cipher);
 
-	if (wpa_sm_set_key(sm, alg, bssid, 0, 1, null_rsc, sizeof(null_rsc),
-			   (u8 *) sm->ptk.tk, keylen,
+	if (wpa_sm_set_key(sm, alg, bssid, sm->keyidx_active, 1, null_rsc,
+			   sizeof(null_rsc), (u8 *) sm->ptk.tk, keylen,
 			   KEY_FLAG_PAIRWISE_RX_TX) < 0) {
 		wpa_printf(MSG_WARNING, "FT: Failed to set PTK to the driver");
 		return -1;
@@ -440,6 +442,26 @@  static int wpa_ft_install_ptk(struct wpa_sm *sm, const u8 *bssid)
 }
 
 
+static void ft_handle_extended_key_id(struct wpa_sm *sm,
+				      struct wpa_ft_ies *parse)
+{
+	if (sm->extended_key_id & EXT_KEY_ID_FT0 &&
+	    parse->rsn && sm->pairwise_cipher != WPA_CIPHER_TKIP &&
+	    parse->rsn_capab & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"FT: Using Extended Key ID");
+		sm->use_extended_key_id = 1;
+	} else {
+		sm->use_extended_key_id = 0;
+	}
+	/* There is no standardized way to hand over the KeyID in FT.
+	 * Since FT can only be used for the initial connect we simply
+	 * assume it must be always 0.
+	 */
+	sm->keyidx_active = 0;
+}
+
+
 /**
  * wpa_ft_prepare_auth_request - Generate over-the-air auth request
  * @sm: Pointer to WPA state machine data from wpa_sm_init()
@@ -661,6 +683,7 @@  int wpa_ft_process_response(struct wpa_sm *sm, const u8 *ies, size_t ies_len,
 		os_free(ft_ies);
 	}
 
+	ft_handle_extended_key_id(sm, &parse);
 	wpa_sm_mark_authenticated(sm, bssid);
 	ret = wpa_ft_install_ptk(sm, bssid);
 	if (ret) {
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 7af678dcd..41c57e3a8 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -26,6 +26,7 @@  struct wpa_sm {
 	u8 snonce[WPA_NONCE_LEN];
 	u8 anonce[WPA_NONCE_LEN]; /* ANonce from the last 1/4 msg */
 	int renew_snonce;
+	int keyidx_active;
 	u8 rx_replay_counter[WPA_REPLAY_COUNTER_LEN];
 	int rx_replay_counter_set;
 	u8 request_counter[WPA_REPLAY_COUNTER_LEN];
@@ -68,6 +69,8 @@  struct wpa_sm {
 	int wpa_rsc_relaxation;
 	int owe_ptk_workaround;
 	int beacon_prot;
+	enum ext_key_id_support extended_key_id;
+	int use_extended_key_id;
 
 	u8 own_addr[ETH_ALEN];
 	const char *ifname;
diff --git a/src/rsn_supp/wpa_ie.c b/src/rsn_supp/wpa_ie.c
index 03c0d7e85..ed0e1f6c7 100644
--- a/src/rsn_supp/wpa_ie.c
+++ b/src/rsn_supp/wpa_ie.c
@@ -221,6 +221,13 @@  static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len,
 		capab |= WPA_CAPABILITY_MFPR;
 	if (sm->ocv)
 		capab |= WPA_CAPABILITY_OCVC;
+	if (sm->extended_key_id &&
+	    (!(wpa_key_mgmt_ft(key_mgmt) || wpa_key_mgmt_fils(key_mgmt)) ||
+	     (wpa_key_mgmt_ft(key_mgmt) &&
+	      sm->extended_key_id & EXT_KEY_ID_FT0) ||
+	     (wpa_key_mgmt_fils(key_mgmt) &&
+	      sm->extended_key_id & EXT_KEY_ID_FILS)))
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	WPA_PUT_LE16(pos, capab);
 	pos += 2;
 
diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c
index 87573ef10..5d227519c 100644
--- a/wpa_supplicant/ap.c
+++ b/wpa_supplicant/ap.c
@@ -346,6 +346,7 @@  static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s,
 	bss->isolate = !wpa_s->conf->p2p_intra_bss;
 	bss->force_per_enrollee_psk = wpa_s->global->p2p_per_sta_psk;
 	bss->wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
+	bss->extended_key_id = ssid->extended_key_id;
 
 	if (ssid->p2p_group) {
 		os_memcpy(bss->ip_addr_go, wpa_s->p2pdev->conf->ip_addr_go, 4);
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index 2b9c3f53e..4de450aa6 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -1147,6 +1147,54 @@  static char * wpa_config_write_pairwise(const struct parse_data *data,
 #endif /* NO_CONFIG_WRITE */
 
 
+static int wpa_config_parse_extended_key_id(const struct parse_data *data,
+					    struct wpa_ssid *ssid, int line,
+					    const char *value)
+{
+#ifdef CONFIG_NO_WPA
+	return -1;
+#else /* CONFIG_NO_WPA */
+	int val = wpa_parse_extended_key_id(value);
+	if (val < 0) {
+		wpa_printf(MSG_ERROR, "Line %d: extended_key_id=%s invalid",
+			   line, value);
+		return -1;
+	}
+
+	ssid->extended_key_id = val;
+	return 0;
+#endif /* CONFIG_NO_WPA */
+}
+
+
+#ifndef NO_CONFIG_WRITE
+static char * wpa_config_write_extended_key_id(const struct parse_data *data,
+					       struct wpa_ssid *ssid)
+{
+#ifdef CONFIG_NO_WPA
+	return NULL;
+#else /* CONFIG_NO_WPA */
+	char *buf;
+
+	if (ssid->extended_key_id == EXT_KEY_ID_DEFAULT)
+		return NULL;
+
+	buf = os_zalloc(30);
+	if (buf == NULL)
+		return NULL;
+
+	if (wpa_write_extended_key_id(buf, buf + 30,
+				      ssid->extended_key_id) < 0) {
+		os_free(buf);
+		return NULL;
+	}
+
+	return buf;
+#endif /* CONFIG_NO_WPA */
+}
+#endif /* NO_CONFIG_WRITE */
+
+
 static int wpa_config_parse_group(const struct parse_data *data,
 				  struct wpa_ssid *ssid, int line,
 				  const char *value)
@@ -2502,6 +2550,7 @@  static const struct parse_data ssid_fields[] = {
 #endif /* CONFIG_MESH */
 	{ INT(wpa_ptk_rekey) },
 	{ INT_RANGE(wpa_deny_ptk0_rekey, 0, 2) },
+	{ FUNC(extended_key_id) },
 	{ INT(group_rekey) },
 	{ STR(bgscan) },
 	{ INT_RANGE(ignore_broadcast_ssid, 0, 2) },
@@ -3028,6 +3077,7 @@  void wpa_config_set_network_defaults(struct wpa_ssid *ssid)
 	ssid->group_cipher = DEFAULT_GROUP;
 	ssid->key_mgmt = DEFAULT_KEY_MGMT;
 	ssid->wpa_deny_ptk0_rekey = PTK0_REKEY_ALLOW_ALWAYS;
+	ssid->extended_key_id = EXT_KEY_ID_DEFAULT;
 	ssid->bg_scan_period = DEFAULT_BG_SCAN_PERIOD;
 	ssid->ht = 1;
 #ifdef IEEE8021X_EAPOL
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b8e56f5b2..69c428603 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -921,6 +921,7 @@  static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
 #endif /* CONFIG_MESH */
 	INT(wpa_ptk_rekey);
 	INT(wpa_deny_ptk0_rekey);
+	STR(extended_key_id);
 	INT(group_rekey);
 	INT(ignore_broadcast_ssid);
 #ifdef CONFIG_DPP
diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h
index 24c7a3d9b..fbe865b9b 100644
--- a/wpa_supplicant/config_ssid.h
+++ b/wpa_supplicant/config_ssid.h
@@ -547,6 +547,14 @@  struct wpa_ssid {
 	unsigned int vht_center_freq1;
 	unsigned int vht_center_freq2;
 
+	/** extended_key_id - Extended Key ID support
+	 *
+	 * IEEE 802.11-2016 optionally allows to use keyid 0 and 1 for PTK keys
+	 * with Extended Key ID. This variable controls if and when we want to
+	 * use it.
+	 */
+	enum ext_key_id_support extended_key_id;
+
 	/**
 	 * wpa_ptk_rekey - Maximum lifetime for PTK in seconds
 	 *
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 077bd6449..664dfa7ed 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -5366,6 +5366,9 @@  static void wpa_supplicant_ctrl_iface_drop_sa(struct wpa_supplicant *wpa_s)
 
 	wpa_drv_set_key(wpa_s, WPA_ALG_NONE, wpa_s->bssid, 0, 0, NULL, 0, NULL,
 			0, KEY_FLAG_PAIRWISE);
+	if (wpa_sm_extended_key_id(wpa_s->wpa))
+		wpa_drv_set_key(wpa_s, WPA_ALG_NONE, wpa_s->bssid, 1, 0,
+				NULL, 0, NULL, 0, KEY_FLAG_PAIRWISE);
 	/* MLME-SETPROTECTION.request(None) */
 	wpa_drv_mlme_setprotection(wpa_s, wpa_s->bssid,
 				   MLME_SETPROTECTION_PROTECT_TYPE_NONE,
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index c842c50e9..e0eddb570 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers.c
@@ -991,7 +991,7 @@  dbus_bool_t wpas_dbus_getter_global_capabilities(
 	const struct wpa_dbus_property_desc *property_desc,
 	DBusMessageIter *iter, DBusError *error, void *user_data)
 {
-	const char *capabilities[11];
+	const char *capabilities[12];
 	size_t num_items = 0;
 #ifdef CONFIG_FILS
 	struct wpa_global *global = user_data;
@@ -1037,6 +1037,7 @@  dbus_bool_t wpas_dbus_getter_global_capabilities(
 #ifdef CONFIG_OWE
 	capabilities[num_items++] = "owe";
 #endif /* CONFIG_OWE */
+	capabilities[num_items++] = "extended-key-id";
 
 	return wpas_dbus_simple_array_property_getter(iter,
 						      DBUS_TYPE_STRING,
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index d3fb58707..edde6fa99 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -165,7 +165,14 @@  static inline int wpa_drv_set_key(struct wpa_supplicant *wpa_s,
 	params.key_flag = key_flag;
 
 	if (alg != WPA_ALG_NONE) {
-		if (key_idx >= 0 && key_idx <= 6)
+		/* keyidx = 1 can be either a broadcast or - with
+		 * Extended Key ID - an unicast key. Use bit 15 for
+		 * the pairwise keyidx 1 which is hopefully high enough
+		 * to not clash with future extensions.
+		 */
+		if (key_idx == 1 && key_flag & KEY_FLAG_PAIRWISE)
+			wpa_s->keys_cleared &= ~BIT(15);
+		else if (key_idx >= 0 && key_idx <= 5)
 			wpa_s->keys_cleared &= ~BIT(key_idx);
 		else
 			wpa_s->keys_cleared = 0;
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 22885e646..14cadcedc 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -1441,7 +1441,7 @@  static const char *network_fields[] = {
 	"dot11MeshHoldingTimeout",
 #endif /* CONFIG_MESH */
 	"wpa_ptk_rekey", "bgscan", "ignore_broadcast_ssid",
-	"wpa_deny_ptk0_rekey",
+	"wpa_deny_ptk0_rekey", "extended_key_id",
 	"enable_edmg", "edmg_channel",
 #ifdef CONFIG_P2P
 	"go_p2p_dev_addr", "p2p_client_list", "psk_list",
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 44c34f041..5f8a108c0 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -746,10 +746,15 @@  void wpa_clear_keys(struct wpa_supplicant *wpa_s, const u8 *addr)
 		wpa_drv_set_key(wpa_s, WPA_ALG_NONE, NULL, i, 0, NULL, 0,
 				NULL, 0, KEY_FLAG_GROUP);
 	}
-	if (!(wpa_s->keys_cleared & BIT(0)) && addr &&
+	/* Pairwise key idx 1 for Extended Key ID is tracked in bit 15 */
+	if (~wpa_s->keys_cleared & (BIT(0) | BIT(15)) && addr &&
 	    !is_zero_ether_addr(addr)) {
-		wpa_drv_set_key(wpa_s, WPA_ALG_NONE, addr, 0, 0, NULL, 0, NULL,
-				0, KEY_FLAG_PAIRWISE);
+		if (!(wpa_s->keys_cleared & (BIT(0))))
+			wpa_drv_set_key(wpa_s, WPA_ALG_NONE, addr, 0, 0, NULL,
+					0, NULL, 0, KEY_FLAG_PAIRWISE);
+		if (!(wpa_s->keys_cleared & (BIT(15))))
+			wpa_drv_set_key(wpa_s, WPA_ALG_NONE, addr, 1, 0, NULL,
+					0, NULL, 0, KEY_FLAG_PAIRWISE);
 		/* MLME-SETPROTECTION.request(None) */
 		wpa_drv_mlme_setprotection(
 			wpa_s, addr,
@@ -1633,6 +1638,19 @@  int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
 		sae_pwe = 1;
 	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PWE, sae_pwe);
 
+	/* Extended Key ID is only supported in INFRA mode so far */
+	if (ssid->mode == WPAS_MODE_INFRA && ssid->extended_key_id &&
+	    ssid->proto & WPA_PROTO_RSN &&
+	    ssid->pairwise_cipher & (WPA_CIPHER_CCMP | WPA_CIPHER_CCMP_256 |
+				     WPA_CIPHER_GCMP | WPA_CIPHER_GCMP_256) &&
+	    wpa_s->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID) {
+		wpa_msg(wpa_s, MSG_INFO, "Enable Extended Key ID support");
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXTENDED_KEY_ID,
+				 ssid->extended_key_id);
+	} else {
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXTENDED_KEY_ID, 0);
+	}
+
 	if (wpa_sm_set_assoc_wpa_ie_default(wpa_s->wpa, wpa_ie, wpa_ie_len)) {
 		wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to generate WPA IE");
 		return -1;
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index f3a750e3c..18ba69eb4 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -1098,6 +1098,44 @@  fast_reauth=1
 # hex without quotation, e.g., 0102030405)
 # wep_tx_keyidx: Default WEP key index (TX) (0..3)
 #
+# extended_key_id:
+# "Extended Key ID support for Individually Addressed Frames" according to
+# IEEE 802.11-2016.
+#
+# Extended Key ID allows to rekey PTK keys without the impacts PTK0 rekeying
+# has. It can only be used when the driver supports it and requires wpa=2
+# with a CCMP/GCMP cipher.
+#
+# The standard is not regulating if or how Extended Key ID can be used in
+# combination with FT or FILS. Enabling it for FT or FILS may therefore be
+# incompatible with other implementations.
+#
+# Available options, can be combined with '+':
+# OFF           : Disable Extended Key ID support
+# BASIC         : Enable core Extended Key ID support according to
+#		  IEEE 802.11-2016 when the driver supports it
+# PREFER0       : Use key 0 for the initial key when either 0 or 1 can be used
+#		  in AP mode and otherwise ignored
+# FT0           : - NOT PART OF IEEE 802.11-2016 -
+#		  Enable Extended Key ID for FT and assume keyid 0 for all FT
+#		  handshakes
+# FILS0         : - NOT PART OF IEEE 802.11-2016 -
+#		  Enable Extended Key ID with FILS and assume keyid 0 for all
+#		  FILS handshakes (detects and use FILS_CUSTOM when needed)
+# FILS_CUSTOM   : - NOT PART OF IEEE 802.11-2016 -
+#		  Enable Extended Key ID with FILS and add the keyid to the
+#		  FILS handshakes (detects and use FILS0 when needed)
+# Rules:
+# - Either OFF or BASIC must be defined
+# - BASIC can be combined with anything except OFF
+# - FILS0 and FILS_CUSTOM are mutually exclusive
+#
+# Example:
+# extended_key_id=BASIC+FT0+FILS0
+#
+# Default:
+# extended_key_id=BASIC
+#
 # wpa_ptk_rekey: Maximum lifetime for PTK in seconds. This can be used to
 # enforce rekeying of PTK to mitigate some attacks against TKIP deficiencies.
 #
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 39b05b2b9..574a3a827 100644
--- a/wpa_supplicant/wpas_glue.c
+++ b/wpa_supplicant/wpas_glue.c
@@ -533,7 +533,8 @@  static int wpa_supplicant_set_key(void *_wpa_s, enum wpa_alg alg,
 	}
 #endif /* CONFIG_TESTING_GET_GTK */
 #ifdef CONFIG_TESTING_OPTIONS
-	if (addr && !is_broadcast_ether_addr(addr)) {
+	if (addr && !is_broadcast_ether_addr(addr) &&
+	    !(key_flag & KEY_FLAG_MODIFY)) {
 		wpa_s->last_tk_alg = alg;
 		os_memcpy(wpa_s->last_tk_addr, addr, ETH_ALEN);
 		wpa_s->last_tk_key_idx = key_idx;
@@ -1077,7 +1078,8 @@  static int wpa_supplicant_eap_auth_start_cb(void *ctx)
 {
 	struct wpa_supplicant *wpa_s = ctx;
 
-	if (!wpa_s->new_connection && wpa_s->deny_ptk0_rekey) {
+	if (!wpa_s->new_connection && wpa_s->deny_ptk0_rekey &&
+	    !wpa_sm_extended_key_id_active(wpa_s->wpa)) {
 		wpa_msg(wpa_s, MSG_INFO,
 			"WPA: PTK0 rekey not allowed, reconnecting");
 		wpa_supplicant_reconnect(wpa_s);
@@ -1318,6 +1320,7 @@  void wpa_supplicant_rsn_supp_set_config(struct wpa_supplicant *wpa_s,
 #endif /* IEEE8021X_EAPOL */
 		conf.ssid = ssid->ssid;
 		conf.ssid_len = ssid->ssid_len;
+		conf.extended_key_id = ssid->extended_key_id;
 		conf.wpa_ptk_rekey = ssid->wpa_ptk_rekey;
 		conf.wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
 		conf.owe_ptk_workaround = ssid->owe_ptk_workaround;