diff mbox series

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

Message ID 20201216110105.8380-3-ilan.peer@intel.com
State Accepted
Headers show
Series Support PASN with SAE, FILS and FT | expand

Commit Message

Peer, Ilan Dec. 16, 2020, 11 a.m. UTC
Signed-off-by: Ilan Peer <ilan.peer@intel.com>
---
 src/ap/ieee802_11.c | 299 +++++++++++++++++++++++++++++++++++++++-----
 src/ap/sta_info.c   |   3 +
 src/ap/sta_info.h   |   5 +
 3 files changed, 276 insertions(+), 31 deletions(-)

Comments

Jouni Malinen Jan. 21, 2021, 2:01 p.m. UTC | #1
On Wed, Dec 16, 2020 at 01:00:53PM +0200, Ilan Peer wrote:
> diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c

> +static const char *sae_get_password(struct hostapd_data *hapd,

> -	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 ||
> -		 status_code == WLAN_STATUS_SAE_PK)
> -		use_pt = 1;

> +static struct wpabuf *auth_build_sae_commit(struct hostapd_data *hapd,

> +	if (status_code == WLAN_STATUS_SUCCESS)
> +		use_pt = 0;
> +	else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT)
> +		use_pt = 1;

What happened here to that rx_id && sae_pwe != 3 check and why? Isn't
this breaking that case?

Similarly, this seems to be breaking SAE-PK due to that missing
WLAN_STATUS_SAE_PK check.

Undesired whitespace changes make this diff more difficult to read than
necessary with the two functions getting mixed up. Anyway, it looks
clear that the sae_get_password() here is based on an older snapshot of
auth_build_sae_commit() implementation and instead of moving the current
implementation to a helper function, this moves back in time for that
moved segment and breaks newer items.

> +static int pasn_wd_handle_sae_commit(struct hostapd_data *hapd,
> +	if (alg != WLAN_AUTH_SAE || seq != 1 || status != WLAN_STATUS_SUCCESS) {

And just like for station side, this AP implementation seems to be
unaware of SAE H2E, so same questions apply here.
Peer, Ilan Jan. 21, 2021, 2:17 p.m. UTC | #2
Hi,

> -----Original Message-----
> From: Jouni Malinen <j@w1.fi>
> Sent: Thursday, January 21, 2021 16:01
> To: Peer, Ilan <ilan.peer@intel.com>
> Cc: hostap@lists.infradead.org
> Subject: Re: [PATCH v2 02/14] AP: Support PASN with SAE key derivation
> 
> On Wed, Dec 16, 2020 at 01:00:53PM +0200, Ilan Peer wrote:
> > diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
> 
> > +static const char *sae_get_password(struct hostapd_data *hapd,
> 
> > -	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 ||
> > -		 status_code == WLAN_STATUS_SAE_PK)
> > -		use_pt = 1;
> 
> > +static struct wpabuf *auth_build_sae_commit(struct hostapd_data
> > +*hapd,
> 
> > +	if (status_code == WLAN_STATUS_SUCCESS)
> > +		use_pt = 0;
> > +	else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT)
> > +		use_pt = 1;
> 
> What happened here to that rx_id && sae_pwe != 3 check and why? Isn't
> this breaking that case?
> 
> Similarly, this seems to be breaking SAE-PK due to that missing
> WLAN_STATUS_SAE_PK check.
> 

This is a mistake I made when preparing the patches for this submission. 
The implementation I have includes the support of the missing parts above.

> Undesired whitespace changes make this diff more difficult to read than
> necessary with the two functions getting mixed up. Anyway, it looks clear
> that the sae_get_password() here is based on an older snapshot of
> auth_build_sae_commit() implementation and instead of moving the current
> implementation to a helper function, this moves back in time for that moved
> segment and breaks newer items.
> 
> > +static int pasn_wd_handle_sae_commit(struct hostapd_data *hapd,
> > +	if (alg != WLAN_AUTH_SAE || seq != 1 || status !=
> > +WLAN_STATUS_SUCCESS) {
> 
> And just like for station side, this AP implementation seems to be unaware of
> SAE H2E, so same questions apply here.
> 

Same as my answer for the previous patch. The core implementation was done
prior to SAE H2E.

Let me know if you would like a fixed version of this patch, and I'll prepare it.

Regards,

Ilan.
Jouni Malinen Jan. 21, 2021, 6:50 p.m. UTC | #3
On Thu, Jan 21, 2021 at 02:17:01PM +0000, Peer, Ilan wrote:
> Same as my answer for the previous patch. The core implementation was done
> prior to SAE H2E.

I added TODO notes for this now, but I hope P802.11az gets updated to
require use of SAE H2E in the PASN-encapsulated SAE case and the
implementation will obviously then need to be updated to match. That can
happen in separate commits after these series, though.

> Let me know if you would like a fixed version of this patch, and I'll prepare it.

No need for that since I already fixed this in my work branch for
sae_pwe/SAE-PK. Or well, to be exact, I'm still seeing some SAE-PK test
case failures, but that may due to my cleanups having broken something
else, but anyway, no need to send an updated patch for now.
diff mbox series

Patch

diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index a1f6aa931e..72e8818906 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -469,34 +469,17 @@  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 sae_pk **s_pk)
 {
-	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;
-	const struct sae_pk *pk = NULL;
-
-	if (sta->sae->tmp) {
-		rx_id = sta->sae->tmp->pw_id;
-		use_pt = sta->sae->h2e;
-#ifdef CONFIG_SAE_PK
-		os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
-		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
-#endif /* CONFIG_SAE_PK */
-	}
-
-	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 ||
-		 status_code == WLAN_STATUS_SAE_PK)
-		use_pt = 1;
+	struct sae_pk *pk = NULL;
 
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
@@ -513,11 +496,51 @@  static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 			pk = pw->pk;
 		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;
+	if (s_pk)
+		*s_pk = pk;
+
+	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;
+	struct sae_pk *pk = NULL;
+
+	if (sta->sae->tmp) {
+		rx_id = sta->sae->tmp->pw_id;
+		use_pt = sta->sae->h2e;
+#ifdef CONFIG_SAE_PK
+		os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
+		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
+#endif /* CONFIG_SAE_PK */
+	}
+
+	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, &pk);
+	if (!password ||(use_pt && !pt)) {
 		wpa_printf(MSG_DEBUG, "SAE: No password available");
 		return NULL;
 	}
@@ -2297,6 +2320,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, 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)
@@ -2306,6 +2504,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:
@@ -2349,11 +2551,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,
@@ -2591,6 +2804,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;
@@ -2715,7 +2941,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 b28a2415bc..dd2d5cb2a8 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 d77d52da10..d0ec32ba65 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)
@@ -75,6 +76,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 {