[PATCH 12/14] PASN: Support PASN with FT key derivation

Ilan Peer <[email protected]>
Newsgroups gmane.linux.drivers.hostap
Message-ID <[email protected]>
Add support for PASN authentication with FT key derivation:

- As Draft P802.11az_D2.0 states that wrapped data is optional and
  is only needed for further validation of the FT security parameters,
  do not include them in the first PASN frame.

- PASN with FT key derivation requires knowledge of the PMK-R1 and
  PMK-R1-Name for the target AP. As the WPA SM stores PMK-R1 etc.
  only for the currently associated AP, store the mapping of
  BSSID to R1KH-ID for each previous association, so the R1KH-ID
  could be used to derive PMK-R1 and PMK-R1-Name. Do so instead
  of storing the PMK-R1 to avoid maintaining keys that might not
  be used.

Signed-off-by: Ilan Peer <[email protected]>
---
 src/rsn_supp/wpa.c                |  5 ++
 src/rsn_supp/wpa.h                | 17 ++++++
 src/rsn_supp/wpa_ft.c             | 97 +++++++++++++++++++++++++++++++
 src/rsn_supp/wpa_i.h              | 16 +++++
 wpa_supplicant/pasn_supplicant.c  | 66 +++++++++++++++++----
 wpa_supplicant/wpa_supplicant_i.h |  6 ++
 6 files changed, 197 insertions(+), 10 deletions(-)

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index b158b37cdb..6736eeb107 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -3613,6 +3613,11 @@ void wpa_sm_drop_sa(struct wpa_sm *sm)
 	sm->pmk_r0_len = 0;
 	os_memset(sm->pmk_r1, 0, sizeof(sm->pmk_r1));
 	sm->pmk_r1_len = 0;
+#ifdef CONFIG_PASN
+	os_free(sm->pasn_r1kh);
+	sm->pasn_r1kh = NULL;
+	sm->n_pasn_r1kh = 0;
+#endif /* CONFIG_PASN */
 #endif /* CONFIG_IEEE80211R */
 }
 
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index f72725902f..569759c0a0 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -409,6 +409,13 @@ int wpa_ft_validate_reassoc_resp(struct wpa_sm *sm, const u8 *ies,
 int wpa_ft_start_over_ds(struct wpa_sm *sm, const u8 *target_ap,
 			 const u8 *mdie);
 
+#ifdef CONFIG_PASN
+
+int wpa_pasn_ft_derive_pmk_r1(struct wpa_sm *sm, int akmp, const u8 *r1kh_id,
+			      u8 *pmk_r1, size_t *pmk_r1_len, u8 *pmk_r1_name);
+
+#endif /* CONFIG_PASN */
+
 #else /* CONFIG_IEEE80211R */
 
 static inline int
@@ -452,6 +459,16 @@ wpa_ft_validate_reassoc_resp(struct wpa_sm *sm, const u8 *ies, size_t ies_len,
 	return -1;
 }
 
+#ifdef CONFIG_PASN
+
+int wpa_pasn_ft_derive_pmk_r1(struct wpa_sm *sm, int akmp, const u8 *r1kh_id,
+			      u8 *pmk_r1, size_t *pmk_r1_len, u8 *pmk_r1_name)
+{
+	return -1;
+}
+
+#endif /* CONFIG_PASN */
+
 #endif /* CONFIG_IEEE80211R */
 
 
diff --git a/src/rsn_supp/wpa_ft.c b/src/rsn_supp/wpa_ft.c
index 027a24ffcd..bb466ec3c2 100644
--- a/src/rsn_supp/wpa_ft.c
+++ b/src/rsn_supp/wpa_ft.c
@@ -23,6 +23,10 @@
 
 #ifdef CONFIG_IEEE80211R
 
+#ifdef CONFIG_PASN
+static void wpa_ft_pasn_store_r1kh(struct wpa_sm *sm, const u8 *bssid);
+#endif /* CONFIG_PASN */
+
 int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr,
 		      const struct wpa_eapol_key *key, struct wpa_ptk *ptk)
 {
@@ -61,6 +65,11 @@ int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr,
 	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", sm->pmk_r1, sm->pmk_r1_len);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name,
 		    WPA_PMK_NAME_LEN);
+
+#ifdef CONFIG_PASN
+	wpa_ft_pasn_store_r1kh(sm, src_addr);
+#endif /* CONFIG_PASN*/
+
 	return wpa_pmk_r1_to_ptk(sm->pmk_r1, sm->pmk_r1_len, sm->snonce, anonce,
 				 sm->own_addr, sm->bssid, sm->pmk_r1_name, ptk,
 				 ptk_name, sm->key_mgmt, sm->pairwise_cipher,
@@ -633,6 +642,11 @@ int wpa_ft_process_response(struct wpa_sm *sm, const u8 *ies, size_t ies_len,
 		    sm->pmk_r1_name, WPA_PMK_NAME_LEN);
 
 	bssid = target_ap;
+
+#ifdef CONFIG_PASN
+	wpa_ft_pasn_store_r1kh(sm, bssid);
+#endif /* CONFIG_PASN*/
+
 	if (wpa_pmk_r1_to_ptk(sm->pmk_r1, sm->pmk_r1_len, sm->snonce,
 			      anonce, sm->own_addr, bssid,
 			      sm->pmk_r1_name, &sm->ptk, ptk_name, sm->key_mgmt,
@@ -1168,4 +1182,87 @@ int wpa_ft_start_over_ds(struct wpa_sm *sm, const u8 *target_ap,
 	return 0;
 }
 
+#ifdef CONFIG_PASN
+
+static struct pasn_ft_r1kh *wpa_ft_pasn_get_r1kh(struct wpa_sm *sm,
+						 const u8 *bssid)
+{
+	size_t i;
+
+	for (i = 0; i < sm->n_pasn_r1kh; i++)
+		if (os_memcmp(sm->pasn_r1kh[i].bssid, bssid, ETH_ALEN) == 0)
+			return &sm->pasn_r1kh[i];
+
+	return NULL;
+}
+
+
+static void wpa_ft_pasn_store_r1kh(struct wpa_sm *sm, const u8 *bssid)
+{
+	struct pasn_ft_r1kh *tmp = wpa_ft_pasn_get_r1kh(sm, bssid);
+
+	if (tmp)
+		return;
+
+	tmp = os_realloc_array(sm->pasn_r1kh, sm->n_pasn_r1kh + 1,
+			       sizeof(*tmp));
+	if (!tmp) {
+		wpa_printf(MSG_DEBUG, "PASN: FT: failed to store r1kh");
+		return;
+	}
+
+	sm->pasn_r1kh = tmp;
+	tmp = &sm->pasn_r1kh[sm->n_pasn_r1kh];
+
+	wpa_printf(MSG_DEBUG, "PASN: FT: store R1KH for " MACSTR,
+		   MAC2STR(bssid));
+
+	os_memcpy(tmp->bssid, bssid, ETH_ALEN);
+	os_memcpy(tmp->r1kh_id, sm->r1kh_id, FT_R1KH_ID_LEN);
+
+	sm->n_pasn_r1kh++;
+}
+
+
+int wpa_pasn_ft_derive_pmk_r1(struct wpa_sm *sm, int akmp, const u8 *bssid,
+			      u8 *pmk_r1, size_t *pmk_r1_len, u8 *pmk_r1_name)
+{
+	struct pasn_ft_r1kh *r1kh_entry;
+
+	if (sm->key_mgmt != (unsigned int)akmp) {
+		wpa_printf(MSG_DEBUG,
+			   "PASN: FT: key management mismatch: %u != %u",
+			   sm->key_mgmt, akmp);
+		return -1;
+	}
+
+	r1kh_entry = wpa_ft_pasn_get_r1kh(sm, bssid);
+	if (!r1kh_entry) {
+		wpa_printf(MSG_DEBUG,
+			   "PASN: FT: Cannot find R1KH-ID for " MACSTR,
+			   MAC2STR(bssid));
+		return -1;
+	}
+
+	/*
+	 * Note: PMK R0 etc. were already derived and are maintained by the SM,
+	 * and as the same key hierarchy is used, there is no need to derive
+	 * them again, so only derive PMK R1 etc.
+	 */
+	if (wpa_derive_pmk_r1(sm->pmk_r0, sm->pmk_r0_len, sm->pmk_r0_name,
+			      r1kh_entry->r1kh_id, sm->own_addr, pmk_r1,
+			      pmk_r1_name) < 0)
+		return -1;
+
+	*pmk_r1_len = sm->pmk_r0_len;
+
+	wpa_hexdump_key(MSG_DEBUG, "PASN: FT: PMK-R1", pmk_r1, sm->pmk_r0_len);
+	wpa_hexdump(MSG_DEBUG, "PASN: FT: PMKR1Name", pmk_r1_name,
+		    WPA_PMK_NAME_LEN);
+
+	return 0;
+}
+
+#endif /* CONFIG_PASN */
+
 #endif /* CONFIG_IEEE80211R */
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index b9164fa111..2dc7c9ebd9 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -14,6 +14,11 @@
 struct wpa_tdls_peer;
 struct wpa_eapol_key;
 
+struct pasn_ft_r1kh {
+	u8 bssid[ETH_ALEN];
+	u8 r1kh_id[FT_R1KH_ID_LEN];
+};
+
 /**
  * struct wpa_sm - Internal WPA state machine data
  */
@@ -147,6 +152,17 @@ struct wpa_sm {
 	u8 mdie_ft_capab; /* FT Capability and Policy from target AP MDIE */
 	u8 *assoc_resp_ies; /* MDIE and FTIE from (Re)Association Response */
 	size_t assoc_resp_ies_len;
+#ifdef CONFIG_PASN
+	/*
+	 * Currently, the WPA SM stores the PMK-R1, PMK-R1-Name and R1KH-ID only
+	 * for the current association. As PMK-R1 is required in order to
+	 * perform PASN authentication with FT, store the R1KH-ID for previous
+	 * associations, which would later be used to derive the PMK-R1 as part
+	 * of the PASN authentication flow.
+	 */
+	struct pasn_ft_r1kh *pasn_r1kh;
+	size_t n_pasn_r1kh;
+#endif /* CONFIG_PASN*/
 #endif /* CONFIG_IEEE80211R */
 
 #ifdef CONFIG_P2P
diff --git a/wpa_supplicant/pasn_supplicant.c b/wpa_supplicant/pasn_supplicant.c
index 3d7971fc9e..c348a1341d 100644
--- a/wpa_supplicant/pasn_supplicant.c
+++ b/wpa_supplicant/pasn_supplicant.c
@@ -592,6 +592,12 @@ static struct wpabuf *wpas_pasn_get_wrapped_data(struct wpa_supplicant *wpa_s)
 	case WPA_KEY_MGMT_FT_PSK:
 	case WPA_KEY_MGMT_FT_IEEE8021X:
 	case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
+		/*
+		 * wrapped data with these AKMs is optional and is only needed
+		 * for further validation of FT security parameters. For now do
+		 * not use them.
+		 */
+		return NULL;
 	default:
 		wpa_printf(MSG_ERROR,
 			   "PASN: TODO: Wrapped data for akmp=0x%x",
@@ -616,7 +622,12 @@ static u8 wpas_pasn_get_wrapped_data_format(struct wpas_pasn *pasn)
 	case WPA_KEY_MGMT_FT_PSK:
 	case WPA_KEY_MGMT_FT_IEEE8021X:
 	case WPA_KEY_MGMT_FT_IEEE8021X_SHA384:
-		return WPA_PASN_WRAPPED_DATA_FT;
+		/*
+		 * wrapped data with these AKMs is optional and is only needed
+		 * for further validation of FT security parameters. For now do
+		 * not use them.
+		 */
+		return WPA_PASN_NO_WRAPPED_DATA;
 	case WPA_KEY_MGMT_PASN:
 	default:
 		return WPA_PASN_NO_WRAPPED_DATA;
@@ -629,7 +640,7 @@ static struct wpabuf *wpas_pasn_build_auth_1(struct wpa_supplicant *wpa_s,
 {
 	struct wpas_pasn *pasn = &wpa_s->pasn;
 	struct wpabuf *buf, *pubkey = NULL, *wrapped_data_buf = NULL;
-	struct rsn_pmksa_cache_entry *pmksa;
+	const u8 *pmkid;
 	u8 wrapped_data;
 	int ret;
 
@@ -657,22 +668,37 @@ static struct wpabuf *wpas_pasn_build_auth_1(struct wpa_supplicant *wpa_s,
 				   wpa_s->own_addr, pasn->bssid,
 				   pasn->trans_seq + 1, WLAN_STATUS_SUCCESS);
 
-	if (wrapped_data != WPA_PASN_NO_WRAPPED_DATA) {
-		pmksa = wpa_sm_pmksa_cache_get(wpa_s->wpa, pasn->bssid,
+	pmkid = NULL;
+	if (wpa_key_mgmt_ft(pasn->akmp)) {
+		ret = wpa_pasn_ft_derive_pmk_r1(wpa_s->wpa, pasn->akmp,
+						pasn->bssid,
+						pasn->pmk_r1,
+						&pasn->pmk_r1_len,
+						pasn->pmk_r1_name);
+		if (ret) {
+			wpa_printf(MSG_DEBUG,
+				   "PASN: FT: failed to derive keys");
+			goto fail;
+		}
+
+		pmkid = pasn->pmk_r1_name;
+	} else if (wrapped_data != WPA_PASN_NO_WRAPPED_DATA) {
+		struct rsn_pmksa_cache_entry *pmksa =
+			wpa_sm_pmksa_cache_get(wpa_s->wpa, pasn->bssid,
 					       NULL, NULL,
 					       pasn->akmp);
+		if (pmksa)
+			pmkid = pmksa->pmkid;
 
 		/*
-		 * Note: even when PMKSA is available, also add wrapped data as
-		 * it is possible that the PMKID is no longer valid at the AP.
+		 * Note: even when PMKSA is available, also add wrapped
+		 * data as it is possible that the PMKID is no longer
+		 * valid at the AP.
 		 */
 		wrapped_data_buf = wpas_pasn_get_wrapped_data(wpa_s);
-	} else {
-		pmksa = NULL;
 	}
 
-	wpa_pasn_add_rsne(buf, pmksa ? pmksa->pmkid : NULL,
-			  pasn->akmp, pasn->cipher);
+	wpa_pasn_add_rsne(buf, pmkid, pasn->akmp, pasn->cipher);
 
 	if (!wrapped_data_buf)
 		wrapped_data = WPA_PASN_NO_WRAPPED_DATA;
@@ -818,6 +844,11 @@ static void wpas_pasn_reset(struct wpa_supplicant *wpa_s)
 	os_memset(&pasn->fils, 0, sizeof(pasn->fils));
 #endif /* CONFIG_FILS*/
 
+#ifdef CONFIG_IEEE80211R
+	os_memset(pasn->pmk_r1, 0, sizeof(pasn->pmk_r1));
+	os_memset(pasn->pmk_r1_name, 0, sizeof(pasn->pmk_r1_name));
+	pasn->pmk_r1_len = 0;
+#endif /* CONFIG_IEEE80211R */
 	pasn->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
 }
 
@@ -843,6 +874,21 @@ static int wpas_pasn_set_pmk(struct wpa_supplicant *wpa_s,
 		return 0;
 	}
 
+	if (wpa_key_mgmt_ft(pasn->akmp)) {
+#ifdef CONFIG_IEEE80211R
+		wpa_printf(MSG_DEBUG, "PASN: FT: using PMK R1");
+
+		pasn->pmk_len = pasn->pmk_r1_len;
+		os_memcpy(pasn->pmk, pasn->pmk_r1, pasn->pmk_r1_len);
+		pasn->using_pmksa = 1;
+		return 0;
+
+#endif /* CONFIG_IEEE80211R */
+
+		wpa_printf(MSG_DEBUG, "PASN: FT: not supported");
+		return -1;
+	}
+
 	if (rsn_data->num_pmkid) {
 		struct rsn_pmksa_cache_entry *pmksa =
 			wpa_sm_pmksa_cache_get(wpa_s->wpa, pasn->bssid,
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index f5951a8496..6d49eea6c7 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -542,6 +542,12 @@ struct wpas_pasn {
 #ifdef CONFIG_FILS
 	struct pasn_fils fils;
 #endif /* CONFIG_FILS */
+
+#ifdef CONFIG_IEEE80211R
+	u8 pmk_r1[PMK_LEN_MAX];
+	size_t pmk_r1_len;
+	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
+#endif /* CONFIG_IEEE80211R */
 };
 
 #endif /* CONFIG_PASN */
-- 
2.17.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.