diff mbox series

[v2,2/3] STA: Support Extended Key ID

Message ID 20200320190433.142039-2-alexander@wetzel-home.de
State Accepted
Headers show
Series [v2,1/3] AP: Support Extended Key ID | expand

Commit Message

Alexander Wetzel March 20, 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.

Signed-off-by: Alexander Wetzel <alexander@wetzel-home.de>
---
 src/rsn_supp/wpa.c                      | 135 ++++++++++++++++++++++--
 src/rsn_supp/wpa.h                      |  13 +++
 src/rsn_supp/wpa_ft.c                   |  27 ++++-
 src/rsn_supp/wpa_i.h                    |   3 +
 src/rsn_supp/wpa_ie.c                   |   2 +
 wpa_supplicant/ap.c                     |   1 +
 wpa_supplicant/config.c                 |   2 +
 wpa_supplicant/config.h                 |  18 ++++
 wpa_supplicant/config_file.c            |   3 +
 wpa_supplicant/config_winreg.c          |  10 ++
 wpa_supplicant/ctrl_iface.c             |  35 +++++-
 wpa_supplicant/dbus/dbus_new_handlers.c |   3 +-
 wpa_supplicant/driver_i.h               |   9 +-
 wpa_supplicant/wpa_cli.c                |   3 +-
 wpa_supplicant/wpa_supplicant.c         |  24 ++++-
 wpa_supplicant/wpa_supplicant.conf      |   7 ++
 wpa_supplicant/wpas_glue.c              |   6 +-
 17 files changed, 278 insertions(+), 23 deletions(-)

Comments

Jouni Malinen March 22, 2020, 10:58 p.m. UTC | #1
On Fri, Mar 20, 2020 at 08:04:32PM +0100, Alexander Wetzel wrote:
> diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
> @@ -607,6 +608,58 @@ static int wpa_derive_ptk(struct wpa_sm *sm, const unsigned char *src_addr,
> +#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;
> +
> +	sm->keyidx_active = 0;
> +	if (sm->extended_key_id && 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;
> +	else
> +		sm->use_extended_key_id = 0;
> +	return 0;
> +}
> +#endif /* CONFIG_FILS */

Similarly to the AP case, having these separate FT/FILS handlers seems
unnecessary to me.

> +static int handle_extended_key_id(struct wpa_sm *sm,
> +				  struct wpa_eapol_ie_parse *kde)
> +{
> +	struct wpa_ie_data rsn;
> +
> +	if (sm->extended_key_id && 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) {
> +		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;
> +		}

That 0xfe is not correct. Only two bits are defined to be used for the
Key ID value while the rest of the bits are reserved.

> @@ -762,9 +816,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" : "");

I don't think I want to modify this message since some external programs
may actually use it and might be surprised of the change here.. The
other debug messages that do not go out as control interface events
provide sufficient detail on whether Extended Key ID is used.

> +	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;

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

I don't think any of this is needed as FILS specific handling, i.e., it
is fine to simply use the generic RSNE handler and hardcode keyidx to 0
in that set_key call.

> diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
> @@ -44,6 +44,12 @@
> +#ifdef CONFIG_TESTING_OPTIONS
> +/* For now only testing is using Extended Key ID by default */
> +#define DEFAULT_EXTENDED_KEY_ID 1
> +#else /* CONFIG_TESTING_OPTIONS */
> +#define DEFAULT_EXTENDED_KEY_ID 0
> +#endif /* CONFIG_TESTING_OPTIONS */

Similarly to the hostapd changes, this is not something I'd like to see
change behavior based on CONFIG_TESTING_OPTIONS.

> diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
> @@ -746,11 +746,28 @@ static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s,
> +		struct wpa_ie_data data;
> +
>  		wpabuf_free(wpa_s->rsne_override_eapol);
> -		if (os_strcmp(value, "NULL") == 0)
> +		if (os_strcmp(value, "NULL") == 0) {
>  			wpa_s->rsne_override_eapol = NULL;
> -		else
> +		} else {
>  			wpa_s->rsne_override_eapol = wpabuf_parse_bin(value);
> +			if (!wpa_parse_wpa_ie_rsn(
> +				wpabuf_head(wpa_s->rsne_override_eapol),
> +				wpabuf_len(wpa_s->rsne_override_eapol),
> +				&data) &&
> +			    (data.capabilities &
> +				WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) {
> +				wpa_s->conf->extended_key_id = 1;
> +				wpa_printf(MSG_DEBUG,
> +					   "TESTING: rsne_override_eapol enables Extended Key ID");
> +			} else {
> +				wpa_s->conf->extended_key_id = 0;
> +				wpa_printf(MSG_DEBUG,
> +					   "TESTING: rsne_override_eapol disables Extended Key ID");
> +			}
> +		}

The hostapd comment on the IE overrides applies here as well.

> diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
> @@ -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";

This should be done based on the driver capability.


This is what I'm currently considering:

 From: Alexander Wetzel <alexander@wetzel-home.de>
 Subject: [PATCH 15/17] STA: Support Extended Key ID

Support Extended Key ID in wpa_supplicant according to
IEEE Std 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.

Signed-off-by: Alexander Wetzel <alexander@wetzel-home.de>
---
 src/rsn_supp/wpa.c                      | 117 ++++++++++++++++++++++--
 src/rsn_supp/wpa.h                      |  14 +++
 src/rsn_supp/wpa_ft.c                   |   2 +
 src/rsn_supp/wpa_i.h                    |   4 +
 src/rsn_supp/wpa_ie.c                   |   2 +
 wpa_supplicant/ap.c                     |   1 +
 wpa_supplicant/config.c                 |   2 +
 wpa_supplicant/config.h                 |  12 +++
 wpa_supplicant/config_file.c            |   3 +
 wpa_supplicant/config_winreg.c          |   9 ++
 wpa_supplicant/ctrl_iface.c             |   3 +
 wpa_supplicant/dbus/dbus_new_handlers.c |   4 +-
 wpa_supplicant/driver_i.h               |   9 +-
 wpa_supplicant/wpa_cli.c                |   3 +-
 wpa_supplicant/wpa_supplicant.c         |  35 ++++++-
 wpa_supplicant/wpa_supplicant.conf      |   5 +
 wpa_supplicant/wpas_glue.c              |   6 +-
 17 files changed, 215 insertions(+), 16 deletions(-)

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index ba0e5c3435c2..14fe0846d6df 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -183,7 +183,7 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise)
 	int key_info, ver;
 	u8 bssid[ETH_ALEN], *rbuf, *key_mic, *mic;
 
-	if (pairwise && sm->wpa_deny_ptk0_rekey &&
+	if (pairwise && sm->wpa_deny_ptk0_rekey && !sm->use_ext_key_id &&
 	    wpa_sm_get_state(sm) == WPA_COMPLETED) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"WPA: PTK0 rekey not allowed, reconnecting");
@@ -608,6 +608,51 @@ static int wpa_derive_ptk(struct wpa_sm *sm, const unsigned char *src_addr,
 }
 
 
+static int wpa_handle_ext_key_id(struct wpa_sm *sm,
+				 struct wpa_eapol_ie_parse *kde)
+{
+	if (sm->ext_key_id) {
+		u16 key_id;
+
+		if (!kde->key_id) {
+			wpa_msg(sm->ctx->msg_ctx,
+				sm->use_ext_key_id ? MSG_INFO : MSG_DEBUG,
+				"RSN: No Key ID in Extended Key ID handshake");
+			sm->keyidx_active = 0;
+			return sm->use_ext_key_id ? -1 : 0;
+		}
+
+		key_id = kde->key_id[0] & 0x03;
+		if (key_id > 1) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"RSN: Invalid Extended Key ID: %d", key_id);
+			return -1;
+		}
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"RSN: Using Extended Key ID %d", key_id);
+		sm->keyidx_active = key_id;
+		sm->use_ext_key_id = 1;
+	} else {
+		if (kde->key_id && (kde->key_id[0] & 0x03)) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"RSN: Non-zero Extended Key ID Key ID in PTK0 handshake");
+			return -1;
+		}
+
+		if (kde->key_id) {
+			/* This is not supposed to be included here, but ignore
+			 * the case of matching Key ID 0 just in case. */
+			wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG,
+				"RSN: Extended Key ID Key ID 0 in PTK0 handshake");
+		}
+		sm->keyidx_active = 0;
+		sm->use_ext_key_id = 0;
+	}
+
+	return 0;
+}
+
+
 static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
 					  const unsigned char *src_addr,
 					  const struct wpa_eapol_key *key,
@@ -626,7 +671,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_ext_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);
@@ -859,13 +905,14 @@ 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 key_flag=0x%x)",
+			alg, keylen, MAC2STR(sm->bssid),
+			sm->keyidx_active, key_flag);
 		return -1;
 	}
 
@@ -879,7 +926,23 @@ 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_WARNING,
+			"WPA: Failed to activate PTK for TX (idx=%d bssid="
+			MACSTR ")", sm->keyidx_active, MAC2STR(sm->bssid));
+		return -1;
+	}
 	return 0;
 }
 
@@ -1582,6 +1645,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 (wpa_handle_ext_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 "
@@ -1627,6 +1693,10 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 	}
 #endif /* CONFIG_OCV */
 
+	if (sm->use_ext_key_id &&
+	    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) {
 		goto failed;
@@ -1638,7 +1708,14 @@ 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))
+		int res;
+
+		if (sm->use_ext_key_id)
+			res = wpa_supplicant_activate_ptk(sm);
+		else
+			res = wpa_supplicant_install_ptk(sm, key,
+							 KEY_FLAG_RX_TX);
+		if (res)
 			goto failed;
 	}
 
@@ -2880,6 +2957,8 @@ void wpa_sm_notify_assoc(struct wpa_sm *sm, const u8 *bssid)
 #ifdef CONFIG_P2P
 	os_memset(sm->p2p_ip_addr, 0, sizeof(sm->p2p_ip_addr));
 #endif /* CONFIG_P2P */
+
+	sm->keyidx_active = 0;
 }
 
 
@@ -2911,6 +2990,7 @@ void wpa_sm_notify_disassoc(struct wpa_sm *sm)
 
 	/* Keys are not needed in the WPA state machine anymore */
 	wpa_sm_drop_sa(sm);
+	sm->keyidx_active = 0;
 
 	sm->msg_3_of_4_ok = 0;
 	os_memset(sm->bssid, 0, ETH_ALEN);
@@ -3164,6 +3244,12 @@ 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_EXT_KEY_ID:
+		sm->ext_key_id = value;
+		break;
+	case WPA_PARAM_USE_EXT_KEY_ID:
+		sm->use_ext_key_id = value;
+		break;
 	default:
 		break;
 	}
@@ -3238,6 +3324,18 @@ int wpa_sm_pmf_enabled(struct wpa_sm *sm)
 }
 
 
+int wpa_sm_ext_key_id(struct wpa_sm *sm)
+{
+	return sm ? sm->ext_key_id : 0;
+}
+
+
+int wpa_sm_ext_key_id_active(struct wpa_sm *sm)
+{
+	return sm ? sm->use_ext_key_id : 0;
+}
+
+
 int wpa_sm_ocv_enabled(struct wpa_sm *sm)
 {
 	struct wpa_ie_data rsn;
@@ -4253,6 +4351,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->ext_key_id)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	wpabuf_put_le16(buf, capab);
 
 	/* PMKID Count */
@@ -4680,6 +4780,7 @@ 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;
 	}
+
 	rsclen = wpa_cipher_rsc_len(sm->pairwise_cipher);
 	wpa_hexdump_key(MSG_DEBUG, "FILS: Set TK to driver",
 			sm->ptk.tk, keylen);
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index 0bd14495aebe..02b5df883a6e 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -102,6 +102,8 @@ enum wpa_sm_conf_params {
 	WPA_PARAM_OCV,
 	WPA_PARAM_SAE_PWE,
 	WPA_PARAM_DENY_PTK0_REKEY,
+	WPA_PARAM_EXT_KEY_ID,
+	WPA_PARAM_USE_EXT_KEY_ID,
 };
 
 struct rsn_supp_config {
@@ -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_ext_key_id(struct wpa_sm *sm);
+int wpa_sm_ext_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_ext_key_id(struct wpa_sm *sm)
+{
+	return 0;
+}
+
+static inline int wpa_sm_ext_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 6d92de5a6d9b..bec5eb0b2fa7 100644
--- a/src/rsn_supp/wpa_ft.c
+++ b/src/rsn_supp/wpa_ft.c
@@ -265,6 +265,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->ext_key_id)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	WPA_PUT_LE16(pos, capab);
 	pos += 2;
 
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 7af678dcd93d..5fd70a498b21 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -68,6 +68,10 @@ struct wpa_sm {
 	int wpa_rsc_relaxation;
 	int owe_ptk_workaround;
 	int beacon_prot;
+	int ext_key_id; /* whether Extended Key ID is enabled */
+	int use_ext_key_id; /* whether Extended Key ID has been detected
+			     * to be used */
+	int keyidx_active; /* Key ID for the active TK */
 
 	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 03c0d7e85c24..e8a040a2b055 100644
--- a/src/rsn_supp/wpa_ie.c
+++ b/src/rsn_supp/wpa_ie.c
@@ -221,6 +221,8 @@ 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->ext_key_id)
+		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 87573ef10325..ee7c755b5f33 100644
--- a/wpa_supplicant/ap.c
+++ b/wpa_supplicant/ap.c
@@ -344,6 +344,7 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_IEEE80211AX */
 
 	bss->isolate = !wpa_s->conf->p2p_intra_bss;
+	bss->extended_key_id = wpa_s->conf->extended_key_id;
 	bss->force_per_enrollee_psk = wpa_s->global->p2p_per_sta_psk;
 	bss->wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
 
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index 4f359ede04d4..30801abd9b9a 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -4293,6 +4293,7 @@ struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface,
 	config->key_mgmt_offload = DEFAULT_KEY_MGMT_OFFLOAD;
 	config->cert_in_cb = DEFAULT_CERT_IN_CB;
 	config->wpa_rsc_relaxation = DEFAULT_WPA_RSC_RELAXATION;
+	config->extended_key_id = DEFAULT_EXTENDED_KEY_ID;
 
 #ifdef CONFIG_MBO
 	config->mbo_cell_capa = DEFAULT_MBO_CELL_CAPA;
@@ -5057,6 +5058,7 @@ static const struct global_parse_data global_fields[] = {
 	{ INT_RANGE(coloc_intf_reporting, 0, 1), 0 },
 #ifdef CONFIG_WNM
 	{ INT_RANGE(disable_btm, 0, 1), CFG_CHANGED_DISABLE_BTM },
+	{ INT_RANGE(extended_key_id, 0, 1), 0 },
 #endif /* CONFIG_WNM */
 };
 
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index 486432e439fa..2d4cb1b8f683 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -44,6 +44,7 @@
 #define DEFAULT_MBO_CELL_CAPA MBO_CELL_CAPA_NOT_SUPPORTED
 #define DEFAULT_DISASSOC_IMMINENT_RSSI_THRESHOLD -75
 #define DEFAULT_OCE_SUPPORT OCE_STA
+#define DEFAULT_EXTENDED_KEY_ID 0
 
 #include "config_ssid.h"
 #include "wps/wps.h"
@@ -1570,6 +1571,17 @@ struct wpa_config {
 	 * By default BSS transition management is enabled
 	 */
 	int disable_btm;
+
+	/**
+	 * extended_key_id - Extended Key ID support
+	 *
+	 * IEEE Std 802.11-2016 optionally allows to use Key ID 0 and 1 for PTK
+	 * keys with Extended Key ID.
+	 *
+	 * 0 = don't use Extended Key ID
+	 * 1 = use Extended Key ID when possible
+	 */
+	int extended_key_id;
 };
 
 
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b8e56f5b2807..e77cbca4f015 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1596,6 +1596,9 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 			config->p2p_interface_random_mac_addr);
 	if (config->disable_btm)
 		fprintf(f, "disable_btm=1\n");
+	if (config->extended_key_id != DEFAULT_EXTENDED_KEY_ID)
+		fprintf(f, "extended_key_id=%d\n",
+			config->extended_key_id);
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */
diff --git a/wpa_supplicant/config_winreg.c b/wpa_supplicant/config_winreg.c
index 598bc77906eb..1b7f96ed2fb1 100644
--- a/wpa_supplicant/config_winreg.c
+++ b/wpa_supplicant/config_winreg.c
@@ -277,6 +277,15 @@ static int wpa_config_read_global(struct wpa_config *config, HKEY hk)
 	wpa_config_read_reg_dword(hk, TEXT("okc"), &config->okc);
 	wpa_config_read_reg_dword(hk, TEXT("pmf"), &val);
 	config->pmf = val;
+	if (wpa_config_read_reg_dword(hk, TEXT("extended_key_id"),
+				      &val) == 0) {
+		if (val < 0 || val > 1) {
+			wpa_printf(MSG_ERROR,
+				   "Invalid Extended Key ID setting (%d)", val);
+			errors++;
+		}
+		config->extended_key_id = val;
+	}
 
 	return errors ? -1 : 0;
 }
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 3b4c5002064b..0c79d5257f15 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -5374,6 +5374,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_ext_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 01fc67b6f2b2..94f586151c98 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,8 @@ dbus_bool_t wpas_dbus_getter_global_capabilities(
 #ifdef CONFIG_OWE
 	capabilities[num_items++] = "owe";
 #endif /* CONFIG_OWE */
+	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID)
+		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 d3fb5870794c..6a03d8e910e3 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--a 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 22885e646547..730d749fec25 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -502,6 +502,7 @@ static char ** wpa_cli_complete_set(const char *str, int pos)
 		"ignore_auth_resp",
 #endif /* CONFIG_TESTING_OPTIONS */
 		"relative_rssi", "relative_band_adjust",
+		"extended_key_id",
 	};
 	int i, num_fields = ARRAY_SIZE(fields);
 
@@ -593,7 +594,7 @@ static char ** wpa_cli_complete_get(const char *str, int pos)
 		"tdls_external_control", "osu_dir", "wowlan_triggers",
 		"p2p_search_delay", "mac_addr", "rand_addr_lifetime",
 		"preassoc_mac_addr", "key_mgmt_offload", "passive_scan",
-		"reassoc_same_bss_optim"
+		"reassoc_same_bss_optim", "extended_key_id"
 	};
 	int i, num_fields = ARRAY_SIZE(fields);
 
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index c638fe535130..f11bac0174c8 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -748,10 +748,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 ID 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,
@@ -1635,6 +1640,30 @@ 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 infrastructure BSS so far */
+	if (ssid->mode == WPAS_MODE_INFRA && wpa_s->conf->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)) {
+		int use_ext_key_id = 0;
+
+		wpa_msg(wpa_s, MSG_DEBUG,
+			"WPA: Enable Extended Key ID support");
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXT_KEY_ID,
+				 wpa_s->conf->extended_key_id);
+		if (bss_rsn &&
+		    wpa_s->conf->extended_key_id &&
+		    wpa_s->pairwise_cipher != WPA_CIPHER_TKIP &&
+		    (ie.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST))
+			use_ext_key_id = 1;
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_USE_EXT_KEY_ID,
+				 use_ext_key_id);
+	} else {
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_EXT_KEY_ID, 0);
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_USE_EXT_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 f3a750e3c118..591e1343f48d 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -802,6 +802,11 @@ fast_reauth=1
 # Set BIT(1) to Enable OCE in STA-CFON mode
 #oce=1
 
+# Extended Key ID support for Individually Addressed frames
+# 0 = force off: Do not use Extended Key ID (default)
+# 1 = auto: Activate Extended Key ID support if the driver supports it
+#extended_key_id=0
+
 # network block
 #
 # Each network (usually AP's sharing the same SSID) is configured as a separate
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 39b05b2b902a..a3049daf54f8 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_ext_key_id_active(wpa_s->wpa)) {
 		wpa_msg(wpa_s, MSG_INFO,
 			"WPA: PTK0 rekey not allowed, reconnecting");
 		wpa_supplicant_reconnect(wpa_s);
diff mbox series

Patch

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 5bb47bcbe..75d564b9c 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,58 @@  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;
+
+	sm->keyidx_active = 0;
+	if (sm->extended_key_id && 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;
+	else
+		sm->use_extended_key_id = 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;
+
+	if (sm->extended_key_id && 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) {
+		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 +679,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 +816,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 +914,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 +936,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 +1654,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 +1701,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 +1717,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 +2830,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 +3249,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 +3326,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;
@@ -4253,6 +4353,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)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	wpabuf_put_le16(buf, capab);
 
 	/* PMKID Count */
@@ -4645,6 +4747,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 +4783,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..585bbcc95 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 {
@@ -154,6 +155,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 +303,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 baa185ffe..9592d10d7 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)
+		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 && 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 assume
+	 * it must be 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..27c31f8ca 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;
+	int 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..c9899595e 100644
--- a/src/rsn_supp/wpa_ie.c
+++ b/src/rsn_supp/wpa_ie.c
@@ -221,6 +221,8 @@  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)
+		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..ee7c755b5 100644
--- a/wpa_supplicant/ap.c
+++ b/wpa_supplicant/ap.c
@@ -344,6 +344,7 @@  static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_IEEE80211AX */
 
 	bss->isolate = !wpa_s->conf->p2p_intra_bss;
+	bss->extended_key_id = wpa_s->conf->extended_key_id;
 	bss->force_per_enrollee_psk = wpa_s->global->p2p_per_sta_psk;
 	bss->wpa_deny_ptk0_rekey = ssid->wpa_deny_ptk0_rekey;
 
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index 2b9c3f53e..2fef83a5a 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -4293,6 +4293,7 @@  struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface,
 	config->key_mgmt_offload = DEFAULT_KEY_MGMT_OFFLOAD;
 	config->cert_in_cb = DEFAULT_CERT_IN_CB;
 	config->wpa_rsc_relaxation = DEFAULT_WPA_RSC_RELAXATION;
+	config->extended_key_id = DEFAULT_EXTENDED_KEY_ID;
 
 #ifdef CONFIG_MBO
 	config->mbo_cell_capa = DEFAULT_MBO_CELL_CAPA;
@@ -5057,6 +5058,7 @@  static const struct global_parse_data global_fields[] = {
 	{ INT_RANGE(coloc_intf_reporting, 0, 1), 0 },
 #ifdef CONFIG_WNM
 	{ INT_RANGE(disable_btm, 0, 1), CFG_CHANGED_DISABLE_BTM },
+	{ INT_RANGE(extended_key_id, 0, 1), 0 },
 #endif /* CONFIG_WNM */
 };
 
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index b3c779233..165be35f7 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -44,6 +44,12 @@ 
 #define DEFAULT_MBO_CELL_CAPA MBO_CELL_CAPA_NOT_SUPPORTED
 #define DEFAULT_DISASSOC_IMMINENT_RSSI_THRESHOLD -75
 #define DEFAULT_OCE_SUPPORT OCE_STA
+#ifdef CONFIG_TESTING_OPTIONS
+/* For now only testing is using Extended Key ID by default */
+#define DEFAULT_EXTENDED_KEY_ID 1
+#else /* CONFIG_TESTING_OPTIONS */
+#define DEFAULT_EXTENDED_KEY_ID 0
+#endif /* CONFIG_TESTING_OPTIONS */
 
 #include "config_ssid.h"
 #include "wps/wps.h"
@@ -1570,6 +1576,18 @@  struct wpa_config {
 	 * By default BSS transition management is enabled
 	 */
 	int disable_btm;
+
+	/** 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.
+	 *
+	 * 0 = don't use Extended Key ID
+	 * 1 = use Extended Key ID when possible
+	 *
+	 * By default Extended Key ID support is disabled
+	 */
+	int extended_key_id;
 };
 
 
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b8e56f5b2..e77cbca4f 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1596,6 +1596,9 @@  static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 			config->p2p_interface_random_mac_addr);
 	if (config->disable_btm)
 		fprintf(f, "disable_btm=1\n");
+	if (config->extended_key_id != DEFAULT_EXTENDED_KEY_ID)
+		fprintf(f, "extended_key_id=%d\n",
+			config->extended_key_id);
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */
diff --git a/wpa_supplicant/config_winreg.c b/wpa_supplicant/config_winreg.c
index 598bc7790..2ff5f27ec 100644
--- a/wpa_supplicant/config_winreg.c
+++ b/wpa_supplicant/config_winreg.c
@@ -277,6 +277,16 @@  static int wpa_config_read_global(struct wpa_config *config, HKEY hk)
 	wpa_config_read_reg_dword(hk, TEXT("okc"), &config->okc);
 	wpa_config_read_reg_dword(hk, TEXT("pmf"), &val);
 	config->pmf = val;
+	if (wpa_config_read_reg_dword(hk, TEXT("extended_key_id"),
+				      &config->extended_key_id) == 0) {
+		if (config->extended_key_id < 0 ||
+		    config->extended_key_id > 1) {
+			wpa_printf(MSG_ERROR,
+				   "Invalid Extended Key ID setting (%d)",
+				   config->extended_key_id);
+			errors++;
+		}
+	}
 
 	return errors ? -1 : 0;
 }
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 2321fc358..20ea5a515 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -746,11 +746,28 @@  static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s,
 			pos++;
 		}
 	} else if (os_strcasecmp(cmd, "rsne_override_eapol") == 0) {
+		struct wpa_ie_data data;
+
 		wpabuf_free(wpa_s->rsne_override_eapol);
-		if (os_strcmp(value, "NULL") == 0)
+		if (os_strcmp(value, "NULL") == 0) {
 			wpa_s->rsne_override_eapol = NULL;
-		else
+		} else {
 			wpa_s->rsne_override_eapol = wpabuf_parse_bin(value);
+			if (!wpa_parse_wpa_ie_rsn(
+				wpabuf_head(wpa_s->rsne_override_eapol),
+				wpabuf_len(wpa_s->rsne_override_eapol),
+				&data) &&
+			    (data.capabilities &
+				WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) {
+				wpa_s->conf->extended_key_id = 1;
+				wpa_printf(MSG_DEBUG,
+					   "TESTING: rsne_override_eapol enables Extended Key ID");
+			} else {
+				wpa_s->conf->extended_key_id = 0;
+				wpa_printf(MSG_DEBUG,
+					   "TESTING: rsne_override_eapol disables Extended Key ID");
+			}
+		}
 	} else if (os_strcasecmp(cmd, "rsnxe_override_assoc") == 0) {
 		wpabuf_free(wpa_s->rsnxe_override_assoc);
 		if (os_strcmp(value, "NULL") == 0)
@@ -5372,6 +5389,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,
@@ -9338,6 +9358,7 @@  static int wpas_ctrl_event_test(struct wpa_supplicant *wpa_s, const char *cmd)
 static int wpas_ctrl_test_assoc_ie(struct wpa_supplicant *wpa_s,
 				   const char *cmd)
 {
+	struct wpa_ie_data data;
 	struct wpabuf *buf;
 	size_t len;
 
@@ -9360,6 +9381,16 @@  static int wpas_ctrl_test_assoc_ie(struct wpa_supplicant *wpa_s,
 	}
 
 	wpa_sm_set_test_assoc_ie(wpa_s->wpa, buf);
+	if (!wpa_parse_wpa_ie_rsn(wpabuf_head(buf), wpabuf_len(buf), &data) &&
+	    (data.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) {
+		wpa_s->conf->extended_key_id = 1;
+		wpa_printf(MSG_DEBUG,
+			   "WPA: Forced own IE enables Extended Key ID");
+	} else {
+		wpa_s->conf->extended_key_id = 0;
+		wpa_printf(MSG_DEBUG,
+			   "WPA: Forced own IE disables Extended Key ID");
+	}
 	return 0;
 }
 
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index c842c50e9..d99242473 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..730d749fe 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -502,6 +502,7 @@  static char ** wpa_cli_complete_set(const char *str, int pos)
 		"ignore_auth_resp",
 #endif /* CONFIG_TESTING_OPTIONS */
 		"relative_rssi", "relative_band_adjust",
+		"extended_key_id",
 	};
 	int i, num_fields = ARRAY_SIZE(fields);
 
@@ -593,7 +594,7 @@  static char ** wpa_cli_complete_get(const char *str, int pos)
 		"tdls_external_control", "osu_dir", "wowlan_triggers",
 		"p2p_search_delay", "mac_addr", "rand_addr_lifetime",
 		"preassoc_mac_addr", "key_mgmt_offload", "passive_scan",
-		"reassoc_same_bss_optim"
+		"reassoc_same_bss_optim", "extended_key_id"
 	};
 	int i, num_fields = ARRAY_SIZE(fields);
 
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index c638fe535..d82e8828f 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -748,10 +748,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,
@@ -1635,6 +1640,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 && wpa_s->conf->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,
+				 wpa_s->conf->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..c75cc9248 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -802,6 +802,13 @@  fast_reauth=1
 # Set BIT(1) to Enable OCE in STA-CFON mode
 #oce=1
 
+# extended_key_id:
+# "Extended Key ID support for Individually Addressed Frames" according to
+# IEEE 802.11-2016.
+#
+# 0 = force off: Do not use Extended Key ID (default)
+# 1 = auto: Activate Extended Key ID support
+
 # network block
 #
 # Each network (usually AP's sharing the same SSID) is configured as a separate
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 39b05b2b9..7ca1a9f64 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);