[RFC 17/30] WPA: Support station mode SMD per-AP MLD PTK

Ilan Peer ilan.peer at intel.com
Wed Oct 21 06:43:35 PDT 2026


- Support deriving a new PTK and store it as a temporary
  PTK.
- Support calculating MIC for per-AP MLD PTK ST flow.
- Support setting the temporary PTK to the current PTK.

TODO: Need to support installing keys to the driver.

Signed-off-by: Ilan Peer <ilan.peer at intel.com>
---
 src/common/wpa_common.h | 30 +++++++++++++
 src/rsn_supp/wpa.c      | 97 +++++++++++++++++++++++++++++++++++++++++
 src/rsn_supp/wpa.h      |  6 +++
 3 files changed, 133 insertions(+)

diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index b6bed37da7d9..6ade8f047058 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -41,6 +41,36 @@ enum rsn_hash_alg {
 	RSN_HASH_SHA512,
 };
 
+/**
+ * wpa_akm_hash_alg - Get the hash algorithm to use for a given AKM
+ * @akmp: Negotiated AKM
+ * @pmk_len: Length of the PMK
+ * Returns: The hash algorithm to use, or RSN_HASH_NOT_SPECIFIED if it cannot
+ * be determined
+ */
+static inline enum rsn_hash_alg wpa_akm_hash_alg(int akmp, size_t pmk_len)
+{
+	if (wpa_key_mgmt_sha384(akmp))
+		return RSN_HASH_SHA384;
+	if (wpa_key_mgmt_sha256(akmp))
+		return RSN_HASH_SHA256;
+
+	/*
+	 * For the remaining AKMs, e.g., SAE with extended key, the hash
+	 * algorithm is determined by the PMK length.
+	 */
+	switch (pmk_len) {
+	case 32:
+		return RSN_HASH_SHA256;
+	case 48:
+		return RSN_HASH_SHA384;
+	case 64:
+		return RSN_HASH_SHA512;
+	}
+
+	return RSN_HASH_NOT_SPECIFIED;
+}
+
 #ifdef CONFIG_NO_TKIP
 #define WPA_ALLOWED_PAIRWISE_CIPHERS \
 (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | WPA_CIPHER_NONE | \
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 6e6f67e0593b..1f129a3595a8 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -3659,12 +3659,25 @@ failed:
 
 static void wpa_sm_tptk_to_ptk(struct wpa_sm *sm)
 {
+	u8 tmp[WPA_SMD_KDK_MAX_LEN];
+	size_t smd_kdk_len = sm->ptk.smd_kdk_len;
+
+	if (sm->ptk.smd_kdk_len > 0) {
+		os_memcpy(tmp, sm->ptk.smd_kdk, sm->ptk.smd_kdk_len);
+		smd_kdk_len = sm->ptk.smd_kdk_len;
+	}
+
 	sm->tptk_set = 0;
 	sm->ptk_set = 1;
 	os_memcpy(&sm->ptk, &sm->tptk, sizeof(sm->ptk));
 	os_memset(&sm->tptk, 0, sizeof(sm->tptk));
 	sm->hash_alg = sm->ptk.hash_alg;
 
+	if (smd_kdk_len > 0) {
+		os_memcpy(sm->ptk.smd_kdk, tmp, smd_kdk_len);
+		sm->ptk.smd_kdk_len = smd_kdk_len;
+	}
+
 	if (wpa_sm_pmf_enabled(sm)) {
 		os_memcpy(sm->last_kck, sm->ptk.kck, sm->ptk.kck_len);
 		sm->last_kck_len = sm->ptk.kck_len;
@@ -8223,3 +8236,87 @@ int wpa_sm_get_cached_tk(struct wpa_sm *sm, u8 *tk, size_t *tk_len)
 	return 0;
 }
 #endif /* CONFIG_TESTING_OPTIONS */
+
+
+int wpa_sm_smd_calc_mic(struct wpa_sm *sm, const u8 *aa, const u8 *snonce,
+			const u8 *anonce, bool st_prep, u8 *mic)
+{
+	enum rsn_hash_alg hash_alg;
+
+	if (!sm->tptk_set || !sm->tptk.kck_len) {
+		wpa_printf(MSG_DEBUG,
+			   "SMD: TPKT not available for MIC calculation");
+		return -1;
+	}
+
+	hash_alg = sm->hash_alg;
+	if (hash_alg == RSN_HASH_NOT_SPECIFIED)
+		hash_alg = wpa_akm_hash_alg(sm->key_mgmt, sm->pmk_len);
+
+	return wpa_smd_mic(sm->tptk.kck, sm->tptk.kck_len,
+			   hash_alg, aa, sm->own_addr,
+			   anonce, snonce, st_prep, mic);
+}
+
+
+int wpa_sm_smd_derive_ptk(struct wpa_sm *sm, const u8 *aa, const u8 *snonce,
+			  const u8 *anonce, const u8 *z, size_t z_len)
+{
+	struct wpa_ptk *ptk = &sm->tptk;
+	const u8 *smd_id = wpa_sm_get_smd_id(sm);
+	u8 z_buf[WPA_MAX_Z_LEN];
+	int ret;
+
+	if (!sm->ptk.smd_kdk_len) {
+		wpa_printf(MSG_DEBUG, "SMD: KDK not available");
+		return -1;
+	}
+
+	if (!smd_id || z_len > WPA_MAX_Z_LEN - ETH_ALEN) {
+		wpa_printf(MSG_DEBUG, "SMD: Invalid PTK derivation context");
+		return -1;
+	}
+
+	/* Z = DHss || SMD Identifier */
+	os_memcpy(z_buf, z, z_len);
+	os_memcpy(z_buf + z_len, smd_id, ETH_ALEN);
+
+	ret = wpa_pmk_to_ptk(sm->ptk.smd_kdk, sm->ptk.smd_kdk_len,
+			     "Pairwise key expansion",
+			     sm->own_addr, aa,
+			     snonce, anonce,
+			     ptk, sm->key_mgmt,
+			     sm->pairwise_cipher,
+			     z_buf, z_len + ETH_ALEN,
+			     sm->ptk.kdk_len, sm->pasn_group, 0);
+	if (ret < 0) {
+		wpa_printf(MSG_DEBUG, "SMD: Failed to derive PTK");
+		return -1;
+	}
+
+	/*
+	 * The PTK is only a temporary one and should be used during ST flow.
+	 * Once the ST flow is done it can be installed
+	 */
+	sm->tptk_set = 1;
+
+	return 0;
+}
+
+
+int wpa_sm_smd_set_tptk_to_ptk(struct wpa_sm *sm)
+{
+	if (!sm->tptk_set) {
+		wpa_printf(MSG_DEBUG, "SMD: TPTK: not set");
+		return -1;
+	}
+
+	wpa_sm_tptk_to_ptk(sm);
+
+	wpa_printf(MSG_DEBUG, "SMD: TPTK->PTK");
+
+	/* TODO: Need to handle TK installation */
+	wpa_printf(MSG_DEBUG, "SMD: TODO: Need to install TK");
+
+	return 0;
+}
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index d510f372f357..5a57dd21d12f 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -752,4 +752,10 @@ bool wpa_sm_pmksa_privacy_supported(struct wpa_sm *sm);
 
 void wpa_sm_set_802_1x_auth_caps(struct wpa_sm *sm, u64 flags2);
 
+int wpa_sm_smd_calc_mic(struct wpa_sm *sm, const u8 *aa, const u8 *snonce,
+			const u8 *anone, bool st_prep, u8 *mic);
+int wpa_sm_smd_derive_ptk(struct wpa_sm *sm, const u8 *aa, const u8 *snonce,
+			  const u8 *anonce, const u8 *z, size_t z_len);
+int wpa_sm_smd_set_tptk_to_ptk(struct wpa_sm *sm);
+
 #endif /* WPA_H */
-- 
2.55.0




More information about the Hostap mailing list