[RFC PATCH v1 10/13] hostapd: Add DecoyAuth SAE responder

Jeff Hansen x at jeffhansen.com
Fri Sep 11 12:20:03 PDT 2026


Negotiate DecoyAuth in H2E SAE Authentication frames and derive
per-exchange candidate material. Process fragmented exchanges while
preserving standard SAE behavior for legacy peers in optional mode.

Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
 src/ap/beacon.c     |  28 ++-
 src/ap/beacon.h     |   1 -
 src/ap/ieee802_11.c | 454 +++++++++++++++++++++++++++++++++++++++++---
 src/ap/ieee802_11.h |   6 +
 4 files changed, 452 insertions(+), 37 deletions(-)

diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index a5565e96b..775ea7c98 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -736,13 +736,12 @@ struct probe_resp_params {
 #endif /* CONFIG_IEEE80211AX */
 };
 
-
 static void hostapd_free_probe_resp_params(struct probe_resp_params *params)
 {
-#ifdef CONFIG_IEEE80211BE
 	if (!params)
 		return;
 
+#ifdef CONFIG_IEEE80211BE
 	os_free(params->mld_info);
 	params->mld_info = NULL;
 #endif /* CONFIG_IEEE80211BE */
@@ -1315,7 +1314,9 @@ static struct hostapd_sta_info * sta_track_get(struct hostapd_iface *iface,
 }
 
 
-void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal)
+static struct hostapd_sta_info *
+sta_track_add_internal(struct hostapd_iface *iface, const u8 *addr,
+		       int ssi_signal, unsigned int max_entries)
 {
 	struct hostapd_sta_info *info;
 
@@ -1326,18 +1327,18 @@ void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal)
 		dl_list_add_tail(&iface->sta_seen, &info->list);
 		os_get_reltime(&info->last_seen);
 		info->ssi_signal = ssi_signal;
-		return;
+		return info;
 	}
 
 	/* Add a new entry */
 	info = os_zalloc(sizeof(*info));
 	if (info == NULL)
-		return;
+		return NULL;
 	os_memcpy(info->addr, addr, ETH_ALEN);
 	os_get_reltime(&info->last_seen);
 	info->ssi_signal = ssi_signal;
 
-	if (iface->num_sta_seen >= iface->conf->track_sta_max_num) {
+	if (max_entries && iface->num_sta_seen >= max_entries) {
 		/* Expire oldest entry to make room for a new one */
 		sta_track_expire(iface, 1);
 	}
@@ -1346,6 +1347,14 @@ void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal)
 		   MACSTR, iface->bss[0]->conf->iface, MAC2STR(addr));
 	dl_list_add_tail(&iface->sta_seen, &info->list);
 	iface->num_sta_seen++;
+	return info;
+}
+
+
+void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal)
+{
+	sta_track_add_internal(iface, addr, ssi_signal,
+			       iface->conf->track_sta_max_num);
 }
 
 
@@ -1780,7 +1789,6 @@ void handle_probe_req(struct hostapd_data *hapd,
 	params.known_bss_len = elems.mbssid_known_bss_len;
 
 	hostapd_gen_probe_resp(hapd, &params);
-
 	hostapd_free_probe_resp_params(&params);
 
 	if (!params.resp)
@@ -2630,6 +2638,12 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 			     hapd->conf->rsn_override_key_mgmt |
 			     hapd->conf->rsn_override_key_mgmt_2) &&
 	    (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP)) {
+		if (hapd->conf->decoyauth == SAE_DECOYAUTH_REQUIRED) {
+			wpa_printf(MSG_ERROR,
+				   "Required DecoyAuth not supported with SAE offload");
+			goto error;
+		}
+
 		if (hostapd_sae_pk_in_use(hapd->conf)) {
 			wpa_printf(MSG_ERROR,
 				   "SAE PK not supported with SAE offload");
diff --git a/src/ap/beacon.h b/src/ap/beacon.h
index e381542a8..c46d28c20 100644
--- a/src/ap/beacon.h
+++ b/src/ap/beacon.h
@@ -30,7 +30,6 @@ sta_track_seen_on(struct hostapd_iface *iface, const u8 *addr,
 		  const char *ifname);
 void sta_track_claim_taxonomy_info(struct hostapd_iface *iface, const u8 *addr,
 				   struct wpabuf **probe_ie_taxonomy);
-
 const u8 * hostapd_wpa_ie(struct hostapd_data *hapd, u8 eid);
 
 u8 * hostapd_unsol_bcast_probe_resp(struct hostapd_data *hapd,
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index a017e7fe3..18d3b76fd 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1232,13 +1232,216 @@ found:
 	return password;
 }
 
+int sae_get_matching_passwords(struct hostapd_data *hapd,
+			       struct sta_info *sta,
+			       const u8 *rx_id, size_t rx_id_len,
+			       const u8 ***passwords,
+			       size_t **password_lens,
+			       int *num_passwords)
+{
+	struct sae_password_entry *pw;
+	struct hostapd_sta_wpa_psk_short *psk;
+	const char *password;
+	int count = 0, i = 0;
+	int use_sta_psks = 0;
+
+	*passwords = NULL;
+	*password_lens = NULL;
+	*num_passwords = 0;
+
+	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
+		if (!is_broadcast_ether_addr(pw->peer_addr) &&
+		    (!sta || !ether_addr_equal(pw->peer_addr, sta->addr)))
+			continue;
+		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
+			continue;
+		if (rx_id && pw->identifier &&
+		    (rx_id_len != os_strlen(pw->identifier) ||
+		     os_memcmp(rx_id, pw->identifier, rx_id_len) != 0))
+			continue;
+		count++;
+	}
+	if (count == 0 && sta && !rx_id) {
+		for (psk = sta->psk; psk; psk = psk->next) {
+			if (psk->is_passphrase)
+				count++;
+		}
+		use_sta_psks = count > 0;
+	}
+	if (count > SAE_MAX_PASSWORDS)
+		return -1;
+
+	if (count == 0) {
+		password = sae_get_password(hapd, sta, rx_id, rx_id_len,
+					    NULL, NULL, NULL);
+		if (!password)
+			return -1;
+
+		*passwords = os_calloc(1, sizeof(**passwords));
+		*password_lens = os_calloc(1, sizeof(**password_lens));
+		if (!*passwords || !*password_lens) {
+			os_free(*passwords);
+			os_free(*password_lens);
+			*passwords = NULL;
+			*password_lens = NULL;
+			return -1;
+		}
+		(*passwords)[0] = (const u8 *) password;
+		(*password_lens)[0] = os_strlen(password);
+		*num_passwords = 1;
+		return 0;
+	}
+
+	*passwords = os_calloc(count, sizeof(**passwords));
+	*password_lens = os_calloc(count, sizeof(**password_lens));
+	if (!*passwords || !*password_lens) {
+		os_free(*passwords);
+		os_free(*password_lens);
+		*passwords = NULL;
+		*password_lens = NULL;
+		return -1;
+	}
+
+	if (use_sta_psks) {
+		for (psk = sta->psk; psk; psk = psk->next) {
+			if (!psk->is_passphrase)
+				continue;
+			(*passwords)[i] = (const u8 *) psk->passphrase;
+			(*password_lens)[i] = os_strlen(psk->passphrase);
+			i++;
+		}
+	} else {
+		for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
+			if (!is_broadcast_ether_addr(pw->peer_addr) &&
+			    (!sta || !ether_addr_equal(pw->peer_addr, sta->addr)))
+				continue;
+			if ((rx_id && !pw->identifier) ||
+			    (!rx_id && pw->identifier))
+				continue;
+			if (rx_id && pw->identifier &&
+			    (rx_id_len != os_strlen(pw->identifier) ||
+			     os_memcmp(rx_id, pw->identifier, rx_id_len) != 0))
+				continue;
+			(*passwords)[i] = (const u8 *) pw->password;
+			(*password_lens)[i] = os_strlen(pw->password);
+			i++;
+		}
+	}
+	*num_passwords = count;
+	return 0;
+}
+
+
+static struct sae_password_entry *
+sae_get_matching_password_entry(struct hostapd_data *hapd,
+				struct sta_info *sta, int password_idx)
+{
+	struct sae_password_entry *pw;
+	const u8 *rx_id = NULL;
+	size_t rx_id_len = 0;
+
+	if (sta->sae && sta->sae->tmp) {
+		rx_id = sta->sae->tmp->parsed_pw_id ?
+			sta->sae->tmp->parsed_pw_id : sta->sae->tmp->pw_id;
+		rx_id_len = sta->sae->tmp->parsed_pw_id ?
+			sta->sae->tmp->parsed_pw_id_len :
+			sta->sae->tmp->pw_id_len;
+	}
+
+	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)
+			continue;
+		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
+			continue;
+		if (rx_id &&
+		    (rx_id_len != os_strlen(pw->identifier) ||
+		     os_memcmp(rx_id, pw->identifier, rx_id_len) != 0))
+			continue;
+		if (password_idx-- == 0)
+			return pw;
+	}
+
+	return NULL;
+}
+
+
+static size_t hostapd_sae_commit_len(struct sae_data *sae,
+				     const struct wpabuf *token,
+				     const u8 *identifier,
+				     size_t identifier_len,
+				     int include_values)
+{
+	size_t len, values_len, values_frags;
+
+	if (!sae || !sae->tmp)
+		return 0;
+
+	len = 2 + sae->tmp->prime_len;
+	len += sae->tmp->ec ? 2 * sae->tmp->prime_len : sae->tmp->prime_len;
+	if (!sae->h2e && token)
+		len += wpabuf_len(token);
+	if (identifier)
+		len += 3 + identifier_len;
+	if (sae->h2e && sae->tmp->own_rejected_groups)
+		len += 3 + wpabuf_len(sae->tmp->own_rejected_groups);
+	if (sae->h2e && token)
+		len += 3 + wpabuf_len(token);
+	if (wpa_key_mgmt_sae_ext_key(sae->akmp))
+		len += 3 + RSN_SELECTOR_LEN;
+	if (!include_values)
+		return len;
+
+	values_len = sae_write_values_len(sae);
+	values_frags =
+		(values_len + SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN - 1) /
+		SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN;
+	return len + values_len +
+		SAE_DECOYAUTH_VALUES_ELEM_OVERHEAD * values_frags;
+}
+
+
+static const struct sae_pt **
+sae_get_cached_password_pts(struct hostapd_data *hapd,
+			    const u8 **passwords, int num_passwords)
+{
+	const struct sae_pt **pts;
+	struct sae_password_entry *pw;
+
+	pts = os_calloc(num_passwords, sizeof(*pts));
+	if (!pts)
+		return NULL;
+	for (int i = 0; i < num_passwords; i++) {
+		if (hapd->conf->ssid.wpa_passphrase &&
+		    passwords[i] ==
+		    (const u8 *) hapd->conf->ssid.wpa_passphrase) {
+			pts[i] = hapd->conf->ssid.pt;
+			continue;
+		}
+		for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
+			if (passwords[i] == (const u8 *) pw->password) {
+				pts[i] = pw->pt;
+				break;
+			}
+		}
+	}
+	return pts;
+}
+
 
 static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 					     struct sta_info *sta, int update,
-					     int status_code)
+					     int status_code, u16 *reply_status)
 {
 	struct wpabuf *buf;
 	const char *password = NULL;
+	const u8 **passwords = NULL;
+	const struct sae_pt **password_pts = NULL;
+	size_t *password_lens = NULL;
+	size_t commit_len;
+	int num_passwords = 0;
+	int use_decoyauth = 0;
+	int use_mld_addr = 0;
 	struct sae_password_entry *pw;
 	const u8 *rx_id = NULL;
 	size_t rx_id_len = 0;
@@ -1248,8 +1451,10 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 	const u8 *own_addr = hapd->own_addr;
 
 #ifdef CONFIG_IEEE80211BE
-	if (ap_sta_is_mld(hapd, sta))
+	if (ap_sta_is_mld(hapd, sta)) {
 		own_addr = hapd->mld->mld_addr;
+		use_mld_addr = 1;
+	}
 #endif /* CONFIG_IEEE80211BE */
 
 	if (sta->sae->tmp) {
@@ -1259,6 +1464,7 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 			sta->sae->tmp->parsed_pw_id_len :
 			sta->sae->tmp->pw_id_len;
 		use_pt = sta->sae->h2e;
+		use_decoyauth = sta->sae->tmp->decoyauth;
 #ifdef CONFIG_SAE_PK
 		os_memcpy(sta->sae->tmp->own_addr, own_addr, ETH_ALEN);
 		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
@@ -1279,7 +1485,7 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 		return NULL;
 	}
 
-	if (use_pt) {
+	if (use_pt && !use_decoyauth) {
 		struct sae_pt *tmp_pt = NULL;
 		bool failed = false;
 
@@ -1316,15 +1522,60 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 		if (failed)
 			return NULL;
 	}
-
-	if (update && !use_pt &&
-	    sae_prepare_commit(own_addr, sta->addr,
-			       (u8 *) password, os_strlen(password),
-			       sta->sae) < 0) {
+	if (update && use_decoyauth &&
+	    (sae_get_matching_passwords(hapd, sta, rx_id, rx_id_len,
+					&passwords, &password_lens,
+					&num_passwords) < 0)) {
+		os_free(password_lens);
+		os_free(passwords);
 		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
 		return NULL;
 	}
-
+	if (update && use_pt && use_decoyauth) {
+		password_pts = sae_get_cached_password_pts(
+			hapd, passwords, num_passwords);
+		if (!password_pts) {
+			os_free(password_lens);
+			os_free(passwords);
+			wpa_printf(MSG_DEBUG, "SAE: Could not load cached PTs");
+			return NULL;
+		}
+	}
+	if (update && use_decoyauth && sta->sae->tmp) {
+		sta->sae->tmp->decoyauth_cache_dir =
+			hapd->conf->decoyauth_cache_dir;
+		sta->sae->tmp->decoyauth_cache_entries =
+			hapd->conf->decoyauth_cache_entries;
+	}
+	if (update && use_decoyauth)
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth AP exchange h2e=%d mlo=%d group=%d link=" MACSTR
+			   " own=" MACSTR " peer=" MACSTR " passwords=%d",
+			   use_pt, use_mld_addr, sta->sae->group,
+			   MAC2STR(hapd->own_addr), MAC2STR(own_addr),
+			   MAC2STR(sta->addr), num_passwords);
+	if (update && use_decoyauth &&
+	    sae_ap_prepare_commit_h2e_with_pt(
+		    own_addr, sta->addr, hapd->conf->ssid.ssid,
+		    hapd->conf->ssid.ssid_len, passwords, password_lens,
+		    password_pts, num_passwords, sta->sae) < 0) {
+		os_free(password_pts);
+		os_free(password_lens);
+		os_free(passwords);
+		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
+		return NULL;
+	}
+	if (update && !use_pt && !use_decoyauth &&
+	    sae_prepare_commit(own_addr, sta->addr, (const u8 *) password,
+			       os_strlen(password), sta->sae) < 0) {
+		os_free(password_lens);
+		os_free(passwords);
+		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
+		return NULL;
+	}
+	os_free(password_pts);
+	os_free(password_lens);
+	os_free(passwords);
 	if (pw && sta->sae->tmp)
 		sta->sae->tmp->used_pw = pw;
 
@@ -1337,12 +1588,57 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
 		sta->sae->tmp->vlan_id = pw->vlan_id;
 	}
 
-	buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN +
-			   (rx_id ? 3 + rx_id_len : 0));
+	if (!use_decoyauth) {
+		buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN +
+				   (rx_id ? 3 + rx_id_len : 0));
+		if (buf &&
+		    sae_write_commit(sta->sae, buf, sta->sae->tmp ?
+				     sta->sae->tmp->anti_clogging_token : NULL,
+				     rx_id, rx_id_len) < 0) {
+			wpabuf_free(buf);
+			buf = NULL;
+		}
+		return buf;
+	}
+
+	commit_len = hostapd_sae_commit_len(sta->sae,
+					    sta->sae->tmp ?
+					    sta->sae->tmp->anti_clogging_token :
+					    NULL,
+					    rx_id, rx_id_len, 1);
+	if (!commit_len)
+		return NULL;
+	if (commit_len > SAE_DECOYAUTH_MAX_AUTH_BODY_LEN) {
+		size_t count = sae_decoyauth_fragment_count(sta->sae);
+		u16 fragment = sta->sae->tmp->decoyauth_fragment_request;
+
+		if (!count || count > UINT16_MAX || fragment > count)
+			return NULL;
+		sta->sae->tmp->decoyauth_fragmented = 1;
+		if (fragment < count) {
+			buf = wpabuf_alloc(SAE_DECOYAUTH_MAX_AUTH_BODY_LEN);
+			if (!buf ||
+			    sae_write_decoyauth_fragment(sta->sae, buf,
+						     fragment) < 0) {
+				wpabuf_free(buf);
+				return NULL;
+			}
+			*reply_status = SAE_DECOYAUTH_FRAGMENT_STATUS;
+			return buf;
+		}
+		commit_len = hostapd_sae_commit_len(
+			sta->sae,
+			sta->sae->tmp->anti_clogging_token,
+			rx_id, rx_id_len, 0);
+	}
+	if (commit_len > SAE_DECOYAUTH_MAX_AUTH_BODY_LEN)
+		return NULL;
+	buf = wpabuf_alloc(commit_len);
 	if (buf &&
-	    sae_write_commit(sta->sae, buf, sta->sae->tmp ?
+	    sae_ap_write_commit(sta->sae, buf, sta->sae->tmp ?
 			     sta->sae->tmp->anti_clogging_token : NULL,
-			     rx_id, rx_id_len) < 0) {
+			     rx_id, rx_id_len,
+			     !sta->sae->tmp->decoyauth_fragmented) < 0) {
 		wpabuf_free(buf);
 		buf = NULL;
 	}
@@ -1376,6 +1672,38 @@ static struct wpabuf * auth_build_sae_confirm(struct hostapd_data *hapd,
 }
 
 
+static int auth_sae_process_commit_data(struct sae_data *sae)
+{
+	if (sae->tmp && sae->tmp->decoyauth)
+		return sae_ap_process_commit(sae);
+	return sae_process_commit(sae);
+}
+
+
+static int auth_sae_check_confirm(struct hostapd_data *hapd,
+				  struct sta_info *sta, const u8 *data,
+				  size_t len)
+{
+	struct sae_data *sae = sta->sae;
+	struct sae_password_entry *pw;
+	int password_idx;
+	int ret;
+
+	if (!sae->tmp || !sae->tmp->decoyauth)
+		return sae_check_confirm(sae, data, len, NULL);
+
+	ret = sae_ap_check_confirm(sae, data, len, NULL, &password_idx);
+	if (ret < 0)
+		return ret;
+	pw = sae_get_matching_password_entry(hapd, sta, password_idx);
+	if (pw) {
+		sae->tmp->used_pw = pw;
+		sae->tmp->vlan_id = pw->vlan_id;
+	}
+	return 0;
+}
+
+
 static int auth_sae_send_commit(struct hostapd_data *hapd,
 				struct sta_info *sta,
 				int update, int status_code)
@@ -1384,19 +1712,18 @@ static int auth_sae_send_commit(struct hostapd_data *hapd,
 	int reply_res;
 	u16 status;
 
-	data = auth_build_sae_commit(hapd, sta, update, status_code);
-	if (!data && sta->sae->tmp &&
-	    (sta->sae->tmp->pw_id || sta->sae->tmp->parsed_pw_id))
-		return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;
-	if (data == NULL)
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
-
 	if (sta->sae->tmp && sta->sae->pk)
 		status = WLAN_STATUS_SAE_PK;
 	else if (sta->sae->tmp && sta->sae->h2e)
 		status = WLAN_STATUS_SAE_HASH_TO_ELEMENT;
 	else
 		status = WLAN_STATUS_SUCCESS;
+	data = auth_build_sae_commit(hapd, sta, update, status_code, &status);
+	if (!data && sta->sae->tmp &&
+	    (sta->sae->tmp->pw_id || sta->sae->tmp->parsed_pw_id))
+		return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;
+	if (data == NULL)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 #ifdef CONFIG_TESTING_OPTIONS
 	if (hapd->conf->sae_commit_status >= 0 &&
 	    hapd->conf->sae_commit_status != status) {
@@ -1735,8 +2062,13 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
 
 			sae_set_state(sta, SAE_COMMITTED, "Sent Commit");
 
-			if (sae_process_commit(sta->sae) < 0)
-				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			if (!sta->sae->tmp->decoyauth ||
+			    !sta->sae->tmp->decoyauth_commit_processed) {
+				if (auth_sae_process_commit_data(sta->sae) < 0)
+					return WLAN_STATUS_UNSPECIFIED_FAILURE;
+				if (sta->sae->tmp->decoyauth)
+					sta->sae->tmp->decoyauth_commit_processed = 1;
+			}
 
 			/*
 			 * In mesh case, both Commit and Confirm are sent
@@ -1748,8 +2080,9 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
 			 * overridden with explicit configuration so that the
 			 * infrastructure BSS case sends both frames together.
 			 */
-			immediate_confirm = (hapd->conf->mesh & MESH_ENABLED) ||
-				hapd->conf->sae_confirm_immediate;
+			immediate_confirm = !sta->sae->tmp->decoyauth_fragmented &&
+				((hapd->conf->mesh & MESH_ENABLED) ||
+				 hapd->conf->sae_confirm_immediate);
 
 			/* If sae_track_password is enabled and the STA has not
 			 * yet been tracked to having successfully completed
@@ -1796,7 +2129,15 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
 	case SAE_COMMITTED:
 		sae_clear_retransmit_timer(hapd, sta);
 		if (auth_transaction == WLAN_AUTH_TR_SEQ_SAE_COMMIT) {
-			if (sae_process_commit(sta->sae) < 0)
+			if (sta->sae->tmp->decoyauth_fragmented) {
+				ret = auth_sae_send_commit(hapd, sta, 0,
+						   status_code);
+				if (ret)
+					return ret;
+				sae_set_retransmit_timer(hapd, sta);
+				return WLAN_STATUS_SUCCESS;
+			}
+			if (auth_sae_process_commit_data(sta->sae) < 0)
 				return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 			ret = auth_sae_send_confirm(hapd, sta);
@@ -1851,7 +2192,7 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
 			if (ret)
 				return ret;
 
-			if (sae_process_commit(sta->sae) < 0)
+			if (auth_sae_process_commit_data(sta->sae) < 0)
 				return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 			ret = auth_sae_send_confirm(hapd, sta);
@@ -1880,7 +2221,7 @@ static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta,
 				return ret;
 			sae_set_state(sta, SAE_COMMITTED, "Sent Commit");
 
-			if (sae_process_commit(sta->sae) < 0)
+			if (auth_sae_process_commit_data(sta->sae) < 0)
 				return WLAN_STATUS_UNSPECIFIED_FAILURE;
 			sta->sae->sync = 0;
 			sae_set_retransmit_timer(hapd, sta);
@@ -2046,6 +2387,28 @@ static int check_sae_rejected_groups(struct hostapd_data *hapd,
 }
 
 
+static int sae_get_peer_decoyauth_mode(const u8 *ies, size_t ies_len,
+				       u16 *fragment_request)
+{
+	const u8 *ie;
+	int mode;
+
+	ie = get_vendor_ie(ies, ies_len, SAE_DECOYAUTH_VENDOR_TYPE);
+	if (fragment_request)
+		*fragment_request = 0;
+	if (!ie || (ie[1] != 6 && ie[1] != 8) ||
+	    ie[6] != SAE_DECOYAUTH_VENDOR_VERSION)
+		return SAE_DECOYAUTH_DISABLED;
+	mode = ie[7];
+	if (mode != SAE_DECOYAUTH_OPTIONAL &&
+	    mode != SAE_DECOYAUTH_REQUIRED)
+		return SAE_DECOYAUTH_DISABLED;
+	if (fragment_request && ie[1] == 8)
+		*fragment_request = WPA_GET_LE16(ie + 8);
+	return mode;
+}
+
+
 static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			    const struct ieee80211_mgmt *mgmt, size_t len,
 			    u16 auth_transaction, u16 status_code)
@@ -2267,6 +2630,40 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 		if (resp != WLAN_STATUS_SUCCESS)
 			goto reply;
 
+		if (sta->sae->tmp) {
+			const u8 *ies = mgmt->u.auth.variable +
+				ie_offset;
+			size_t ies_len = ((const u8 *) mgmt) + len - ies;
+			u16 fragment_request;
+			int peer_mode = sae_get_peer_decoyauth_mode(
+				ies, ies_len, &fragment_request);
+			int compatible = peer_mode != SAE_DECOYAUTH_DISABLED &&
+				status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT &&
+				!(conf->mesh & MESH_ENABLED) &&
+				sta->sae->tmp->ec &&
+				!sta->sae->tmp->pw_id &&
+				!sta->sae->tmp->parsed_pw_id;
+
+			/* A zero fragment request starts a new SAE exchange. */
+			if (!fragment_request) {
+				sta->sae->tmp->decoyauth_fragmented = 0;
+				sta->sae->tmp->decoyauth_commit_processed = 0;
+			}
+
+			sta->sae->tmp->decoyauth =
+				conf->decoyauth != SAE_DECOYAUTH_DISABLED &&
+				compatible;
+			sta->sae->tmp->decoyauth_fragment_request =
+				fragment_request;
+			if (conf->decoyauth == SAE_DECOYAUTH_REQUIRED &&
+			    !sta->sae->tmp->decoyauth) {
+				wpa_printf(MSG_INFO,
+					   "SAE: Peer does not support required DecoyAuth exchange");
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto reply;
+			}
+		}
+
 		if (check_sae_rejected_groups(hapd, sta->sae)) {
 			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto reply;
@@ -2355,8 +2752,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 				return;
 			}
 
-			if (sae_check_confirm(sta->sae, var, var_len,
-					      NULL) < 0) {
+			if (auth_sae_check_confirm(hapd, sta, var, var_len) < 0) {
 				if (sae_password_track_fail(hapd, sta)) {
 					wpa_printf(MSG_DEBUG,
 						   "SAE: Reject mismatching Confirm so that another password can be attempted by "
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index da87f0b29..bd8ac15ff 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -304,6 +304,12 @@ const char * sae_get_password(struct hostapd_data *hapd,
 			      size_t rx_id_len,
 			      struct sae_password_entry **pw_entry,
 			      struct sae_pt **s_pt, const struct sae_pk **s_pk);
+int sae_get_matching_passwords(struct hostapd_data *hapd,
+			       struct sta_info *sta,
+			       const u8 *rx_id, size_t rx_id_len,
+			       const u8 ***passwords,
+			       size_t **password_lens,
+			       int *num_passwords);
 struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd,
 					   struct sta_info *sta,
 					   struct hostapd_data **assoc_hapd);
-- 
2.53.0




More information about the Hostap mailing list