[PATCH v3 24/27] EPPKE: Retrieve KCK/KEK from PTKSA and install group keys for GTK rekey

Ainy Kumari ainy.kumari at oss.qualcomm.com
Thu Jan 15 08:33:03 PST 2026


This change adds support for:
- Retrieving KCK (Key Confirmation Key) and KEK (Key Encryption Key) from
  PTKSA cache and storing them in wpa_sm for use during GTK rekey operations.
- Installing GTK and related group keys to the driver as part of EPPKE-based
  association flow.

Signed-off-by: Ainy Kumari <ainy.kumari at oss.qualcomm.com>
---
 src/rsn_supp/wpa.c      | 203 +++++++++++++++++++++++++++++++++++++++-
 src/rsn_supp/wpa.h      |   2 +
 wpa_supplicant/events.c |  32 +++++++
 3 files changed, 235 insertions(+), 2 deletions(-)

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 5f0b0e3d8..0897b1bef 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -3155,7 +3155,8 @@ static void wpa_supplicant_process_mlo_1_of_2(struct wpa_sm *sm,
 	u8 i;
 	struct wpa_eapol_ie_parse ie;
 
-	if (!sm->msg_3_of_4_ok && !wpa_fils_is_completed(sm)) {
+	if (!sm->msg_3_of_4_ok && !wpa_fils_is_completed(sm) &&
+	    !wpa_eppke_is_completed(sm)) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"MLO RSN: Group Key Handshake started prior to completion of 4-way handshake");
 		goto failed;
@@ -3391,7 +3392,8 @@ static void wpa_supplicant_process_1_of_2(struct wpa_sm *sm,
 	struct wpa_eapol_ie_parse ie;
 	u16 gtk_len;
 
-	if (!sm->msg_3_of_4_ok && !wpa_fils_is_completed(sm)) {
+	if (!sm->msg_3_of_4_ok && !wpa_fils_is_completed(sm) &&
+	    !wpa_eppke_is_completed(sm)) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"RSN: Group Key Handshake started prior to completion of 4-way handshake");
 		goto failed;
@@ -7086,6 +7088,203 @@ int wpa_eppke_is_completed(struct wpa_sm *sm)
 }
 
 
+#ifdef CONFIG_ENC_ASSOC
+int eppke_process_assoc_resp(struct wpa_sm *sm, u64 flags, int valid_links,
+			     const u8 *resp, size_t len)
+{
+	struct ieee802_11_elems elems;
+	struct wpa_gtk_data gd;
+	int maxkeylen;
+	struct wpa_eapol_ie_parse kde;
+
+	if (!sm || !sm->ptk_set) {
+		wpa_printf(MSG_DEBUG, "EPPKE: No KEK available");
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "EPPKE: (Re)Association Response frame",
+		    resp, len);
+
+	if (ieee802_11_parse_elems(resp, len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "EPPKE: Failed to parse decrypted elements");
+		goto fail;
+	}
+
+	if (!elems.rsn_ie) {
+		wpa_printf(MSG_DEBUG,
+			   "EPPKE: No RSNE in (Re)Association Response");
+	} else if (wpa_compare_rsn_ie(wpa_key_mgmt_sae(sm->key_mgmt),
+				      sm->ap_rsn_ie, sm->ap_rsn_ie_len,
+				      elems.rsn_ie - 2, elems.rsn_ie_len + 2)) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"EPPKE: RSNE mismatch between Beacon/Probe Response and (Re)Association Response");
+		wpa_hexdump(MSG_DEBUG, "EPPKE: RSNE in Beacon/Probe Response",
+			    sm->ap_rsn_ie, sm->ap_rsn_ie_len);
+		wpa_hexdump(MSG_DEBUG, "EPPKE: RSNE in (Re)Association Response",
+			    elems.rsn_ie, elems.rsn_ie_len);
+		goto fail;
+	}
+
+	if ((sm->ap_rsnxe && !elems.rsnxe) ||
+	    (!sm->ap_rsnxe && elems.rsnxe) ||
+	    (sm->ap_rsnxe && elems.rsnxe && sm->ap_rsnxe_len >= 2 &&
+	     (sm->ap_rsnxe_len != 2U + elems.rsnxe_len ||
+	      os_memcmp(sm->ap_rsnxe + 2, elems.rsnxe, sm->ap_rsnxe_len - 2) !=
+	      0))) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"EPPKE: RSNXE mismatch between Beacon/Probe Response and (Re)Association Response");
+		wpa_hexdump(MSG_INFO, "EPPKE: RSNXE in Beacon/Probe Response",
+			    sm->ap_rsnxe, sm->ap_rsnxe_len);
+		wpa_hexdump(MSG_INFO, "EPPKE: RSNXE in (Re)Association Response",
+			    elems.rsnxe, elems.rsnxe_len);
+		if (sm->assoc_rsnxe && sm->assoc_rsnxe_len)
+			goto fail;
+	}
+
+	/* Key Delivery */
+	if (!elems.key_delivery) {
+		wpa_printf(MSG_DEBUG, "EPPKE: No Key Delivery element");
+		goto fail;
+	}
+
+	/* Parse GTK and set the key to the driver */
+	os_memset(&gd, 0, sizeof(gd));
+	if (wpa_supplicant_parse_ies(elems.key_delivery + WPA_KEY_RSC_LEN,
+				     elems.key_delivery_len - WPA_KEY_RSC_LEN,
+				     &kde) < 0) {
+		wpa_printf(MSG_DEBUG, "EPPKE: Failed to parse KDEs");
+		goto fail;
+	}
+
+	if ((flags & WPA_DRIVER_FLAGS2_MLO) && valid_links != -1) {
+		u8 i;
+
+		for_each_link(valid_links, i) {
+			size_t gtk_kde_len;
+			struct wpa_gtk_data gd;
+			int rsc_len;
+
+			if (!kde.mlo_gtk[i]) {
+				wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
+					"EPPKE: No MLO GTK for link ID %u", i);
+				return -1;
+			}
+
+			os_memset(&gd, 0, sizeof(gd));
+			gtk_kde_len = kde.mlo_gtk_len[i];
+
+			/* Minimal sanity:
+			 * 1 byte KeyID + RSC + at least 5 bytes GTK
+			 */
+			if (gtk_kde_len < 1 + 6 + 5) {
+				wpa_printf(MSG_DEBUG, "EPPKE: Invalid MLO GTK"
+					   " KDE len=%lu for link %u",
+					   gtk_kde_len, i);
+				goto fail;
+			}
+
+			wpa_hexdump_key(MSG_DEBUG, "EPPKE: Received MLO GTK",
+					kde.mlo_gtk[i], gtk_kde_len);
+
+			/* KeyID in low 2 bits;
+			 * Tx bit workaround from KDE[0] bit2
+			 */
+			gd.keyidx = kde.mlo_gtk[i][0] & 0x3;
+			gd.tx = wpa_supplicant_gtk_tx_bit_workaround(sm,
+								     !!(kde.mlo_gtk[i][0] & BIT(2)));
+
+			rsc_len = wpa_cipher_rsc_len(sm->group_cipher); /* returns PN/RSC length */
+			if (rsc_len <= 0) {
+				wpa_printf(MSG_DEBUG, "EPPKE: Unsupported group cipher (no RSC len)");
+				goto fail;
+			}
+
+			/* Actual GTK length = total KDE - KeyID(1) - RSC/PN.
+			 * For CCMP/GCMP, gtk_kde_len is typically 23 -> 23 - 1 - 6 = 16.
+			 */
+			gd.gtk_len = gtk_kde_len - 1 - rsc_len;
+
+			/* Validate GTK length against algorithm requirements */
+			if (wpa_supplicant_check_group_cipher(sm, sm->group_cipher,
+							      gd.gtk_len, gd.gtk_len,
+							      &gd.key_rsc_len, &gd.alg))
+				goto fail;
+
+			if (gd.gtk_len > sizeof(gd.gtk)) {
+				wpa_printf(MSG_DEBUG, "EPPKE: Too long GTK in"
+					   " GTK KDE (len=%u)", gd.gtk_len);
+				goto fail;
+			}
+
+			os_memcpy(gd.gtk, kde.mlo_gtk[i] + 1 + rsc_len, gd.gtk_len);
+
+			if (wpa_supplicant_install_mlo_gtk(sm, i, &gd, elems.key_delivery, 0) < 0) {
+				wpa_printf(MSG_DEBUG, "EPPKE: Failed to set MLO GTK (link=%u)", i);
+				goto fail;
+			}
+
+			if (_mlo_ieee80211w_set_keys(sm, i, &kde) < 0) {
+				wpa_printf(MSG_DEBUG, "EPPKE: Failed to set MLO IGTK (link=%u)", i);
+				goto fail;
+			}
+		}
+	} else {
+		if (!kde.gtk) {
+			wpa_printf(MSG_DEBUG, "EPPKE: 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,
+						      &gd.key_rsc_len, &gd.alg))
+			goto fail;
+
+		wpa_hexdump_key(MSG_DEBUG, "EPPKE: Received GTK", kde.gtk, kde.gtk_len);
+		gd.keyidx = kde.gtk[0] & 0x3;
+		gd.tx = wpa_supplicant_gtk_tx_bit_workaround(sm,
+							     !!(kde.gtk[0] & BIT(2)));
+		if (kde.gtk_len - 2 > sizeof(gd.gtk)) {
+			wpa_printf(MSG_DEBUG, "EPPKE: Too long GTK in GTK KDE (len=%lu)",
+				   (unsigned long) kde.gtk_len - 2);
+			goto fail;
+		}
+		os_memcpy(gd.gtk, kde.gtk + 2, kde.gtk_len - 2);
+
+		wpa_printf(MSG_DEBUG, "EPPKE: Set GTK to driver");
+		if (wpa_supplicant_install_gtk(sm, &gd, elems.key_delivery, 0) < 0) {
+			wpa_printf(MSG_DEBUG, "EPPKE: Failed to set GTK");
+			goto fail;
+		}
+
+		if (ieee80211w_set_keys(sm, &kde) < 0) {
+			wpa_printf(MSG_DEBUG, "EPPKE: Failed to set IGTK");
+			goto fail;
+		}
+	}
+
+	wpa_sm_store_ptk(sm, sm->bssid, sm->pairwise_cipher,
+			 sm->dot11RSNAConfigPMKLifetime, &sm->ptk);
+
+	wpa_sm_set_rekey_offload(sm);
+
+	wpa_printf(MSG_DEBUG, "EPPKE: Auth+Assoc completed successfully");
+	sm->eppke_completed = 1;
+	forced_memzero(&gd, sizeof(gd));
+
+	return 0;
+fail:
+	forced_memzero(&gd, sizeof(gd));
+	return -1;
+}
+
+
+void wpa_sm_set_reset_eppke_completed(struct wpa_sm *sm, int set)
+{
+	if (sm)
+		sm->eppke_completed = !!set;
+}
+#endif /* CONFIG_ENC_ASSOC */
 #ifdef CONFIG_OWE
 
 struct wpabuf * owe_build_assoc_req(struct wpa_sm *sm, u16 group)
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index 46e03ad99..06c84fc88 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -670,6 +670,8 @@ struct wpabuf * fils_build_assoc_req(struct wpa_sm *sm, const u8 **kek,
 				     const struct wpabuf **hlp,
 				     unsigned int num_hlp);
 int fils_process_assoc_resp(struct wpa_sm *sm, const u8 *resp, size_t len);
+int eppke_process_assoc_resp(struct wpa_sm *sm, u64 flags, int link_id,
+			     const u8 *resp, size_t len);
 
 struct wpabuf * owe_build_assoc_req(struct wpa_sm *sm, u16 group);
 int owe_process_assoc_resp(struct wpa_sm *sm, const u8 *bssid,
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index ba79f7f6d..d1217ec5f 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -3660,7 +3660,39 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s,
 	    !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME))
 		wpa_sm_set_reset_fils_completed(wpa_s->wpa, 1);
 #endif /* CONFIG_FILS */
+#ifdef CONFIG_ENC_ASSOC
+#ifdef CONFIG_SME
+	if (wpa_s->sme.auth_alg == WPA_AUTH_ALG_EPPKE) {
+		struct ptksa_cache_entry *entry;
 
+		if (wpa_s->ptksa == NULL) {
+			wpa_printf(MSG_DEBUG, "EPPKE: PTKSA not cached");
+			return -1;
+		}
+
+		entry = ptksa_cache_get(wpa_s->ptksa, wpa_s->valid_links ?
+					wpa_s->ap_mld_addr : bssid,
+					wpa_s->pairwise_cipher);
+
+		wpa_sm_set_ptk_kck_kek(wpa_s->wpa, entry->ptk.kck,
+				       entry->ptk.kck_len, entry->ptk.kek,
+				       entry->ptk.kek_len);
+
+		if (!data->assoc_info.resp_ies ||
+		    eppke_process_assoc_resp(wpa_s->wpa,
+					    wpa_s->drv_flags2,
+					    wpa_s->valid_links ?
+					    wpa_s->valid_links : -1,
+					    data->assoc_info.resp_ies,
+					    data->assoc_info.resp_ies_len) <
+		    0) {
+			wpa_supplicant_deauthenticate(wpa_s,
+						      WLAN_REASON_UNSPECIFIED);
+			return -1;
+		}
+	}
+#endif /* CONFIG_SME */
+#endif /* CONFIG_ENC_ASSOC */
 #ifdef CONFIG_OWE
 	if (wpa_s->key_mgmt == WPA_KEY_MGMT_OWE &&
 	    !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_STA) &&
-- 
2.34.1




More information about the Hostap mailing list