[PATCH 02/14] AP: Support PASN with SAE key derivation

Ilan Peer <[email protected]>
Newsgroups gmane.linux.drivers.hostap
Message-ID <[email protected]>
Signed-off-by: Ilan Peer <[email protected]>
---
 src/ap/ieee802_11.c | 284 ++++++++++++++++++++++++++++++++++++++++----
 src/ap/sta_info.c   |   3 +
 src/ap/sta_info.h   |   5 +
 3 files changed, 267 insertions(+), 25 deletions(-)

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 7a16ec66b6..96972677fd 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -463,29 +463,16 @@ static void sae_set_state(struct sta_info *sta, enum sae_state state,
 }
 
 
-static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
-					     struct sta_info *sta, int update,
-					     int status_code)
+static const char *sae_get_password(struct hostapd_data *hapd,
+				    struct sta_info *sta,
+				    const char *rx_id,
+				    struct sae_password_entry **pw_entry,
+				    struct sae_pt **s_pt)
 {
-	struct wpabuf *buf;
 	const char *password = NULL;
 	struct sae_password_entry *pw;
-	const char *rx_id = NULL;
-	int use_pt = 0;
 	struct sae_pt *pt = NULL;
 
-	if (sta->sae->tmp) {
-		rx_id = sta->sae->tmp->pw_id;
-		use_pt = sta->sae->tmp->h2e;
-	}
-
-	if (rx_id && hapd->conf->sae_pwe != 3)
-		use_pt = 1;
-	else if (status_code == WLAN_STATUS_SUCCESS)
-		use_pt = 0;
-	else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT)
-		use_pt = 1;
-
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
 		    os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0)
@@ -499,11 +486,44 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 		pt = pw->pt;
 		break;
 	}
+
 	if (!password) {
 		password = hapd->conf->ssid.wpa_passphrase;
 		pt = hapd->conf->ssid.pt;
 	}
-	if (!password || (use_pt && !pt)) {
+
+	if (pw_entry)
+		*pw_entry = pw;
+	if (s_pt)
+		*s_pt = pt;
+
+	return password;
+}
+
+
+static struct wpabuf *auth_build_sae_commit(struct hostapd_data *hapd,
+					    struct sta_info *sta, int update,
+					    int status_code)
+{
+	struct wpabuf *buf;
+	const char *password = NULL;
+	struct sae_password_entry *pw;
+	const char *rx_id = NULL;
+	int use_pt = 0;
+	struct sae_pt *pt = NULL;
+
+	if (sta->sae->tmp) {
+		rx_id = sta->sae->tmp->pw_id;
+		use_pt = sta->sae->tmp->h2e;
+	}
+
+	if (status_code == WLAN_STATUS_SUCCESS)
+		use_pt = 0;
+	else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT)
+		use_pt = 1;
+
+	password = sae_get_password(hapd, sta, rx_id, &pw, &pt);
+	if (!password ||(use_pt && !pt)) {
 		wpa_printf(MSG_DEBUG, "SAE: No password available");
 		return NULL;
 	}
@@ -2225,6 +2245,181 @@ ieee802_11_set_radius_info(struct hostapd_data *hapd, struct sta_info *sta,
 
 
 #ifdef CONFIG_PASN
+#ifdef CONFIG_SAE
+
+static int pasn_wd_handle_sae_commit(struct hostapd_data *hapd,
+				     struct sta_info *sta,
+				     struct wpabuf *wd)
+{
+	struct pasn_data *pasn = sta->pasn;
+	const char *password = NULL;
+	const u8 *data;
+	size_t buf_len;
+	u16 res, alg, seq, status;
+	int groups[] = { pasn->group, 0 };
+	int ret;
+
+	if (!wd)
+		return -1;
+
+	data = wpabuf_head_u8(wd);
+	buf_len = wpabuf_len(wd);
+
+	if (buf_len < 6) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE buffer too short. len=%lu",
+			   buf_len);
+		return -1;
+	}
+
+	alg = WPA_GET_LE16(data);
+	seq = WPA_GET_LE16(data + 2);
+	status = WPA_GET_LE16(data + 4);
+
+	wpa_printf(MSG_DEBUG, "PASN: SAE: commit: alg=%u, seq=%u, status=%u",
+		   alg, seq, status);
+
+	if (alg != WLAN_AUTH_SAE || seq != 1 || status != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE: dropping peer commit");
+		return -1;
+	}
+
+	sae_clear_data(&pasn->sae);
+	pasn->sae.state = SAE_NOTHING;
+
+	ret = sae_set_group(&pasn->sae, pasn->group);
+	if (ret) {
+		wpa_printf(MSG_DEBUG, "PASN: Failed to set SAE group");
+		return -1;
+	}
+
+	password = sae_get_password(hapd, sta, NULL, NULL, NULL);
+	if (!password) {
+		wpa_printf(MSG_DEBUG, "PASN: No SAE password found");
+		return -1;
+	}
+
+	ret = sae_prepare_commit(hapd->own_addr, sta->addr,
+				 (u8 *)password,
+				 os_strlen(password), 0,
+				 &pasn->sae);
+	if (ret) {
+		wpa_printf(MSG_DEBUG, "PASN: Failed to prepare SAE commit");
+		return -1;
+	}
+
+	res = sae_parse_commit(&pasn->sae, data + 6, buf_len - 6, NULL, 0,
+			       groups, 0);
+	if (res != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE failed parsing commit");
+		return -1;
+	}
+
+	/* process the commit message and derive the PMK */
+	ret = sae_process_commit(&pasn->sae);
+	if (ret) {
+		wpa_printf(MSG_DEBUG, "SAE: Failed to process peer commit");
+		return -1;
+	}
+
+	pasn->sae.state = SAE_COMMITTED;
+
+	return 0;
+}
+
+
+static int pasn_wd_handle_sae_confirm(struct hostapd_data *hapd,
+				      struct sta_info *sta,
+				      struct wpabuf *wd)
+{
+	struct pasn_data *pasn = sta->pasn;
+	const u8 *data;
+	size_t buf_len;
+	u16 res, alg, seq, status;
+
+	if (!wd)
+		return -1;
+
+	data = wpabuf_head_u8(wd);
+	buf_len = wpabuf_len(wd);
+
+	if (buf_len < 6) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE buffer too short. len=%lu",
+			   buf_len);
+		return -1;
+	}
+
+	alg = WPA_GET_LE16(data);
+	seq = WPA_GET_LE16(data + 2);
+	status = WPA_GET_LE16(data + 4);
+
+	wpa_printf(MSG_DEBUG, "PASN: SAE: commit: alg=%u, seq=%u, status=%u",
+		   alg, seq, status);
+
+	if (alg != WLAN_AUTH_SAE || seq != 2 || status != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE: dropping peer commit");
+		return -1;
+	}
+
+	res = sae_check_confirm(&pasn->sae, data + 6, buf_len - 6);
+	if (res != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "PASN: SAE failed checking confirm");
+		return -1;
+	}
+
+	pasn->sae.state = SAE_ACCEPTED;
+
+	/*
+	 * TODO: Based on on P802.11az_D2.0, the PMKSA derived with PASN/SAE
+	 * should only be allowed with future PASN only. For now do not restrict
+	 * this only for PASN.
+	 */
+	wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr,
+			       pasn->sae.pmk, pasn->sae.pmkid);
+	return 0;
+}
+
+
+static struct wpabuf *pasn_get_sae_wd(struct hostapd_data *hapd,
+				      struct sta_info *sta)
+{
+	struct pasn_data *pasn = sta->pasn;
+	struct wpabuf *buf = NULL;
+	u8 *len_ptr;
+	size_t len;
+
+	/* Need to add the entire authentication frame body */
+	buf = wpabuf_alloc(8 + SAE_COMMIT_MAX_LEN + 8 + SAE_CONFIRM_MAX_LEN);
+	if (!buf) {
+		wpa_printf(MSG_DEBUG, "PASN: Failed to allocate SAE buffer");
+		return NULL;
+	}
+
+	/* Need to add the entire authentication frame body for the commit */
+	len_ptr = wpabuf_put(buf, 2);
+	wpabuf_put_le16(buf, WLAN_AUTH_SAE);
+	wpabuf_put_le16(buf, 1);
+	wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS);
+
+	/* write the actual commit and update the length accordingly */
+	sae_write_commit(&pasn->sae, buf, NULL, 0);
+	len = wpabuf_len(buf);
+	WPA_PUT_LE16(len_ptr, len - 2);
+
+	/* Need to add the entire authentication frame body for the confirm */
+	len_ptr = wpabuf_put(buf, 2);
+	wpabuf_put_le16(buf, WLAN_AUTH_SAE);
+	wpabuf_put_le16(buf, 2);
+	wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS);
+
+	sae_write_confirm(&pasn->sae, buf);
+	WPA_PUT_LE16(len_ptr, wpabuf_len(buf) - len - 2);
+
+	pasn->sae.state = SAE_CONFIRMED;
+
+	return buf;
+}
+
+#endif /* CONFIG_SAE */
 
 static struct wpabuf *pasn_get_wrapped_data(struct hostapd_data *hapd,
 					    struct sta_info *sta)
@@ -2234,6 +2429,10 @@ static struct wpabuf *pasn_get_wrapped_data(struct hostapd_data *hapd,
 		/* no wrapped data */
 		return NULL;
 	case WPA_KEY_MGMT_SAE:
+#ifdef CONFIG_SAE
+		return pasn_get_sae_wd(hapd, sta);
+#endif /* CONFIG_SAE */
+		/* fall through */
 	case WPA_KEY_MGMT_FILS_SHA256:
 	case WPA_KEY_MGMT_FILS_SHA384:
 	case WPA_KEY_MGMT_FT_PSK:
@@ -2277,11 +2476,22 @@ pasn_derive_keys(struct hostapd_data *hapd, struct sta_info *sta,
 		pmk_len = pmksa->pmk_len;
 		os_memcpy(pmk, pmksa->pmk, pmksa->pmk_len);
 	} else {
-		/* TODO: derive PMK based on wrapped data */
-		wpa_printf(MSG_DEBUG,
-			   "PASN: missing implementation to derive PMK");
-
-		return -1;
+		switch (sta->pasn->akmp) {
+#ifdef CONFIG_SAE
+		case WPA_KEY_MGMT_SAE:
+			if (sta->pasn->sae.state == SAE_COMMITTED) {
+				pmk_len = PMK_LEN;
+				os_memcpy(pmk, sta->pasn->sae.pmk, PMK_LEN);
+				break;
+			}
+#endif /* CONFIG_SAE */
+			/* fall through */
+		default:
+			/* TODO: derive PMK based on wrapped data */
+			wpa_printf(MSG_DEBUG,
+				   "PASN: missing PMK derivation");
+			return -1;
+		}
 	}
 
 	ret = pasn_pmk_to_ptk(pmk, pmk_len,
@@ -2519,6 +2729,19 @@ static void handle_auth_pasn_1(struct hostapd_data *hapd, struct sta_info *sta,
 			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto send_resp;
 		}
+
+#ifdef CONFIG_SAE
+		if (sta->pasn->akmp == WPA_KEY_MGMT_SAE) {
+			ret = pasn_wd_handle_sae_commit(hapd, sta,
+							wrapped_data);
+			if (ret) {
+				wpa_printf(MSG_DEBUG,
+					   "PASN: failed processing SAE commit");
+				status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto send_resp;
+			}
+		}
+#endif /* CONFIG_SAE */
 	}
 
 	sta->pasn->wrapped_data_format = pasn_params.wrapped_data_format;
@@ -2643,7 +2866,18 @@ static void handle_auth_pasn_3(struct hostapd_data *hapd, struct sta_info *sta,
 			goto fail;
 		}
 
-		/* TODO: handle wrapped data */
+#ifdef CONFIG_SAE
+		if (sta->pasn->akmp == WPA_KEY_MGMT_SAE) {
+			ret = pasn_wd_handle_sae_confirm(hapd, sta,
+							 wrapped_data);
+			if (ret) {
+				wpa_printf(MSG_DEBUG,
+					   "PASN: failed processing SAE confirm");
+				wpabuf_free(wrapped_data);
+				goto fail;
+			}
+		}
+#endif /* CONFIG_SAE */
 		wpabuf_free(wrapped_data);
 	}
 
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index bb2566d65b..44d6bbcd73 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -165,6 +165,9 @@ void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta)
 	if (sta->pasn) {
 		if (sta->pasn->ecdh)
 			crypto_ecdh_deinit(sta->pasn->ecdh);
+#ifdef CONFIG_SAE
+		sae_clear_data(&sta->pasn->sae);
+#endif /* CONFIG_SAE */
 		os_free(sta->pasn);
 		sta->pasn = NULL;
 	}
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 1957d8ecd4..3da51a59a7 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -15,6 +15,7 @@
 #include "common/wpa_common.h"
 #include "common/ieee802_11_defs.h"
 #include "crypto/sha384.h"
+#include "common/sae.h"
 
 /* STA flags */
 #define WLAN_STA_AUTH BIT(0)
@@ -74,6 +75,10 @@ struct pasn_data {
 	u8 hash[SHA384_MAC_LEN];
 	struct wpa_ptk ptk;
 	struct crypto_ecdh *ecdh;
+
+#ifdef CONFIG_SAE
+	struct sae_data sae;
+#endif /* CONFIG_SAE */
 };
 
 struct sta_info {
-- 
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.