[RFC PATCH v2 12/13] wpa_supplicant: Add DecoyAuth SAE initiator

Jeff Hansen x at jeffhansen.com
Thu Oct 22 15:59:46 PDT 2026


Negotiate H2E DecoyAuth in optional mode by default and evaluate
responder candidate data. Handle bounded fragments and extend the
authentication wait for large password sets.

Negotiate DecoyAuth with plaintext password identifiers while preserving
the no-identifier exchange. Keep opaque replacement identifiers on the
ordinary SAE path. Validate configured identifier length and NUL-free
content, and normalize explicitly empty identifiers to absent.

Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
 wpa_supplicant/Makefile            |   3 +
 wpa_supplicant/config.c            |  19 ++++
 wpa_supplicant/config_file.c       |   7 ++
 wpa_supplicant/config_ssid.h       |   6 +
 wpa_supplicant/defconfig           |   3 +
 wpa_supplicant/sme.c               | 173 ++++++++++++++++++++++++++---
 wpa_supplicant/wpa_supplicant.c    |   7 +-
 wpa_supplicant/wpa_supplicant.conf |  12 ++
 wpa_supplicant/wpa_supplicant_i.h  |   1 +
 9 files changed, 213 insertions(+), 18 deletions(-)

diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index 6de5ce571..4e96eb24e 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -284,6 +284,9 @@ endif
 ifdef CONFIG_SAE
 CFLAGS += -DCONFIG_SAE
 OBJS += ../src/common/sae.o
+ifdef CONFIG_SAE_DECOYAUTH
+CFLAGS += -DCONFIG_SAE_DECOYAUTH
+endif
 ifdef CONFIG_SAE_PK
 CFLAGS += -DCONFIG_SAE_PK
 OBJS += ../src/common/sae_pk.o
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index afe770b58..c1da56a58 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -84,6 +84,12 @@ static int wpa_config_parse_str(const struct parse_data *data,
 		return -1;
 	}
 
+	if (os_strcmp(data->name, "sae_password_id") == 0 &&
+	    (res_len > 254 || memchr(tmp, 0, res_len))) {
+		os_free(tmp);
+		return -1;
+	}
+
 	if (data->key_data) {
 		wpa_hexdump_ascii_key(MSG_MSGDUMP, data->name,
 				      (u8 *) tmp, res_len);
@@ -2770,6 +2776,9 @@ static const struct parse_data ssid_fields[] = {
 	{ STR(sae_password_id) },
 	{ FUNC(alt_sae_password_ids) },
 	{ INT(sae_pwe) },
+#ifdef CONFIG_SAE_DECOYAUTH
+	{ INT_RANGE(decoyauth, 0, 2) },
+#endif /* CONFIG_SAE_DECOYAUTH */
 	{ FUNC(proto) },
 	{ FUNC(key_mgmt) },
 	{ INT(bg_scan_period) },
@@ -3590,6 +3599,11 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid)
 	ssid->proactive_key_caching = -1;
 	ssid->ieee80211w = MGMT_FRAME_PROTECTION_DEFAULT;
 	ssid->sae_pwe = DEFAULT_SAE_PWE;
+#ifdef CONFIG_SAE_DECOYAUTH
+	ssid->decoyauth = SAE_DECOYAUTH_OPTIONAL;
+#else /* CONFIG_SAE_DECOYAUTH */
+	ssid->decoyauth = SAE_DECOYAUTH_DISABLED;
+#endif /* CONFIG_SAE_DECOYAUTH */
 #ifdef CONFIG_MACSEC
 	ssid->mka_priority = DEFAULT_PRIO_NOT_KEY_SERVER;
 #endif /* CONFIG_MACSEC */
@@ -3657,6 +3671,11 @@ int wpa_config_set(struct wpa_ssid *ssid, const char *var, const char *value,
 			ret = -1;
 		}
 #ifdef CONFIG_SAE
+		if (ret >= 0 && os_strcmp(var, "sae_password_id") == 0 &&
+		    ssid->sae_password_id && !ssid->sae_password_id[0]) {
+			os_free(ssid->sae_password_id);
+			ssid->sae_password_id = NULL;
+		}
 		if (os_strcmp(var, "ssid") == 0 ||
 		    os_strcmp(var, "psk") == 0 ||
 		    os_strcmp(var, "sae_password") == 0 ||
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index b01134945..bda242be9 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -19,6 +19,9 @@
 #include "base64.h"
 #include "uuid.h"
 #include "common/ieee802_1x_defs.h"
+#ifdef CONFIG_SAE_DECOYAUTH
+#include "common/sae.h"
+#endif /* CONFIG_SAE_DECOYAUTH */
 #include "p2p/p2p.h"
 #include "eap_peer/eap_methods.h"
 #include "eap_peer/eap.h"
@@ -815,6 +818,10 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid,
 	STR(sae_password);
 	STR(sae_password_id);
 	write_int(f, "sae_pwe", ssid->sae_pwe, DEFAULT_SAE_PWE);
+#ifdef CONFIG_SAE_DECOYAUTH
+	write_int(f, "decoyauth", ssid->decoyauth,
+		  SAE_DECOYAUTH_OPTIONAL);
+#endif /* CONFIG_SAE_DECOYAUTH */
 	INT(sae_password_id_change);
 	write_proto(f, ssid);
 	write_key_mgmt(f, ssid);
diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h
index 86bd8d617..35ac1699c 100644
--- a/wpa_supplicant/config_ssid.h
+++ b/wpa_supplicant/config_ssid.h
@@ -1292,6 +1292,12 @@ struct wpa_ssid {
 	 */
 	enum sae_pwe sae_pwe;
 
+	/**
+	 * decoyauth - DecoyAuth negotiation mode
+	 * 0 = disabled, 1 = optional, 2 = required
+	 */
+	int decoyauth;
+
 	/**
 	 * disable_eht - Disable EHT (IEEE 802.11be) for this network
 	 *
diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig
index 3ecd1e5f7..0ad46d14f 100644
--- a/wpa_supplicant/defconfig
+++ b/wpa_supplicant/defconfig
@@ -257,6 +257,9 @@ CONFIG_CTRL_IFACE=y
 # Simultaneous Authentication of Equals (SAE), WPA3-Personal
 CONFIG_SAE=y
 
+# Experimental DecoyAuth extension for SAE multi-password authentication
+#CONFIG_SAE_DECOYAUTH=y
+
 # SAE Public Key, WPA3-Personal
 #CONFIG_SAE_PK=y
 
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 75a70627c..cee67302c 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -42,6 +42,7 @@
 #include "hs20_supplicant.h"
 
 #define SME_AUTH_TIMEOUT 5
+#define SME_DECOYAUTH_AUTH_TIMEOUT 15
 #define SME_ASSOC_TIMEOUT 5
 #ifdef CONFIG_ENC_ASSOC
 static const int dot11RSNAConfigPMKLifetime = 43200;
@@ -65,6 +66,15 @@ static void sme_process_802_1x_auth_response(struct wpa_supplicant *wpa_s,
 					     bool external);
 #endif /* CONFIG_IEEE8021X_AUTH */
 
+static unsigned int sme_auth_timeout(const struct wpa_ssid *ssid)
+{
+#ifdef CONFIG_SAE
+	if (ssid && ssid->decoyauth != SAE_DECOYAUTH_DISABLED)
+		return SME_DECOYAUTH_AUTH_TIMEOUT;
+#endif /* CONFIG_SAE */
+	return SME_AUTH_TIMEOUT;
+}
+
 #if defined(CONFIG_IEEE8021X_AUTH) || defined(CONFIG_ENC_ASSOC)
 
 static const u8 * sme_get_peer_addr(struct wpa_supplicant *wpa_s, bool external)
@@ -248,6 +258,8 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s,
 	struct wpa_bss *bss;
 	int use_pt = 0;
 	bool use_pk = false;
+	bool advertise_decoyauth;
+	u16 fragment_request = 0;
 	u8 rsnxe_capa = 0;
 	int key_mgmt = external ? wpa_s->sme.ext_auth_key_mgmt :
 		wpa_s->key_mgmt;
@@ -317,6 +329,8 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s,
 		wpa_printf(MSG_DEBUG, "SAE: No password available");
 		goto fail;
 	}
+	str_clear_free(wpa_s->sme.sae_password);
+	wpa_s->sme.sae_password = NULL;
 
 	if (reuse && wpa_s->sme.sae.tmp &&
 	    ether_addr_equal(addr, wpa_s->sme.sae.tmp->bssid)) {
@@ -443,9 +457,42 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s,
 	}
 
 reuse_data:
+	if (use_pt && ssid->pt && ssid->pt->password_id) {
+		password_id = wpabuf_head(ssid->pt->password_id);
+		password_id_len = wpabuf_len(ssid->pt->password_id);
+	}
+	if (wpa_s->sme.sae.tmp)
+		fragment_request = wpa_s->sme.sae.tmp->decoyauth_rx_next;
+	advertise_decoyauth =
+		ssid->decoyauth != SAE_DECOYAUTH_DISABLED && use_pt && !use_pk &&
+		ssid->mode != WPAS_MODE_MESH &&
+		wpa_s->sme.sae.tmp && wpa_s->sme.sae.tmp->ec;
+	/* Opaque replacement identifiers still use ordinary SAE. */
+	if (password_id &&
+	    (!ssid->sae_password_id ||
+	     password_id_len != os_strlen(ssid->sae_password_id) ||
+	     os_memcmp(password_id, ssid->sae_password_id, password_id_len)))
+		advertise_decoyauth = false;
+	if (ssid->decoyauth == SAE_DECOYAUTH_REQUIRED &&
+	    !advertise_decoyauth) {
+		wpa_printf(MSG_INFO,
+			   "SAE: Required DecoyAuth is incompatible with this SAE exchange");
+		goto fail;
+	}
+	if (advertise_decoyauth && password_id &&
+	    !wpa_s->sme.sae.tmp->pw_id) {
+		wpa_s->sme.sae.tmp->pw_id = os_zalloc(password_id_len + 1);
+		if (!wpa_s->sme.sae.tmp->pw_id)
+			goto fail;
+		os_memcpy(wpa_s->sme.sae.tmp->pw_id, password_id, password_id_len);
+		wpa_s->sme.sae.tmp->pw_id_len = password_id_len;
+	}
 	len = wpa_s->sme.sae_token ? 3 + wpabuf_len(wpa_s->sme.sae_token) : 0;
 	if (ssid->sae_password_id)
 		len += 4 + os_strlen(ssid->sae_password_id);
+	if (advertise_decoyauth)
+		len += SAE_DECOYAUTH_CAPABILITY_IE_LEN +
+			(fragment_request ? 2 : 0);
 	buf = wpabuf_alloc(4 + SAE_COMMIT_MAX_LEN + len);
 	if (buf == NULL)
 		goto fail;
@@ -459,15 +506,26 @@ reuse_data:
 			wpabuf_put_le16(buf,WLAN_STATUS_SUCCESS);
 	}
 
-	if (use_pt && ssid->pt && ssid->pt->password_id) {
-		password_id = wpabuf_head(ssid->pt->password_id);
-		password_id_len = wpabuf_len(ssid->pt->password_id);
-	}
 	if (sae_write_commit(&wpa_s->sme.sae, buf, wpa_s->sme.sae_token,
 			     password_id, password_id_len) < 0) {
 		wpabuf_free(buf);
 		goto fail;
 	}
+	if (advertise_decoyauth) {
+		wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
+		wpabuf_put_u8(buf, fragment_request ? 8 : 6);
+		WPA_PUT_BE32(wpabuf_put(buf, 4), SAE_DECOYAUTH_VENDOR_TYPE);
+		wpabuf_put_u8(buf, SAE_DECOYAUTH_VENDOR_VERSION);
+		wpabuf_put_u8(buf, ssid->decoyauth);
+		if (fragment_request)
+			wpabuf_put_le16(buf, fragment_request);
+	}
+	str_clear_free(wpa_s->sme.sae_password);
+	wpa_s->sme.sae_password = os_strdup(password);
+	if (!wpa_s->sme.sae_password) {
+		wpabuf_free(buf);
+		goto fail;
+	}
 	if (ret_use_pt)
 		*ret_use_pt = use_pt;
 	if (ret_use_pk)
@@ -2812,8 +2870,8 @@ no_fils:
 		return;
 	}
 
-	eloop_register_timeout(SME_AUTH_TIMEOUT, 0, sme_auth_timer, wpa_s,
-			       NULL);
+	eloop_register_timeout(sme_auth_timeout(ssid), 0, sme_auth_timer,
+			       wpa_s, NULL);
 
 	/*
 	 * Association will be started based on the authentication event from
@@ -2976,7 +3034,7 @@ static int sme_external_auth_build_buf(struct wpabuf *buf,
 
 static int sme_external_auth_send_sae_commit(struct wpa_supplicant *wpa_s,
 					     const u8 *bssid,
-					     struct wpa_ssid *ssid)
+					     struct wpa_ssid *ssid, int reuse)
 {
 	struct wpabuf *resp, *buf;
 	int use_pt;
@@ -2986,7 +3044,7 @@ static int sme_external_auth_send_sae_commit(struct wpa_supplicant *wpa_s,
 	resp = sme_auth_build_sae_commit(wpa_s, ssid, bssid,
 					 wpa_s->sme.ext_ml_auth ?
 					 wpa_s->sme.ext_auth_ap_mld_addr : NULL,
-					 1, 0, &use_pt, &use_pk);
+					 1, reuse, &use_pt, &use_pk);
 	if (!resp) {
 		wpa_printf(MSG_DEBUG, "SAE: Failed to build SAE commit");
 		return -1;
@@ -3099,7 +3157,7 @@ static int sme_handle_external_auth_start(struct wpa_supplicant *wpa_s,
 	}
 	if (!ssid ||
 	    sme_external_auth_send_sae_commit(wpa_s, ext_auth->bssid,
-					      ssid) < 0)
+					      ssid, 0) < 0)
 		return -1;
 
 	return 0;
@@ -3712,7 +3770,38 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
 
 			sme_external_auth_send_sae_commit(
 				wpa_s, wpa_s->sme.ext_auth_bssid,
-				wpa_s->sme.ext_auth_wpa_ssid);
+				wpa_s->sme.ext_auth_wpa_ssid, 0);
+		}
+		return 0;
+	}
+
+	if (auth_transaction == WLAN_AUTH_TR_SEQ_SAE_COMMIT &&
+	    status_code == SAE_DECOYAUTH_FRAGMENT_STATUS &&
+	    wpa_s->sme.sae.state == SAE_COMMITTED &&
+	    ((external && wpa_s->sme.ext_auth_wpa_ssid) ||
+	     (!external && wpa_s->current_bss && wpa_s->current_ssid))) {
+		if (sae_parse_decoyauth_fragment(&wpa_s->sme.sae, data, len,
+						 ie_offset) < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "SAE: Invalid DecoyAuth fragment");
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "SAE: Request DecoyAuth fragment %u of %u",
+			   wpa_s->sme.sae.tmp->decoyauth_rx_next,
+			   wpa_s->sme.sae.tmp->decoyauth_rx_count);
+		if (!external) {
+			sme_send_authentication(wpa_s, wpa_s->current_bss,
+						wpa_s->current_ssid, 2);
+		} else {
+			if (wpa_s->sme.ext_ml_auth &&
+			    sme_external_ml_auth(wpa_s, data, len, *ie_offset,
+						 status_code))
+				return -1;
+			if (sme_external_auth_send_sae_commit(
+				    wpa_s, wpa_s->sme.ext_auth_bssid,
+				    wpa_s->sme.ext_auth_wpa_ssid, 1) < 0)
+				return -1;
 		}
 		return 0;
 	}
@@ -3740,7 +3829,7 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
 
 			sme_external_auth_send_sae_commit(
 				wpa_s, wpa_s->sme.ext_auth_bssid,
-				wpa_s->sme.ext_auth_wpa_ssid);
+				wpa_s->sme.ext_auth_wpa_ssid, 0);
 		}
 		return 0;
 	}
@@ -3785,7 +3874,10 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
 	}
 
 	if (auth_transaction == WLAN_AUTH_TR_SEQ_SAE_COMMIT) {
+		struct wpa_ssid *auth_ssid;
 		u16 res;
+		int decoyauth_used = 0;
+		const u8 *event_peer = sa ? sa : wpa_s->pending_bssid;
 
 		groups = wpa_s->conf->sae_groups;
 
@@ -3794,6 +3886,8 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
 		    (!external &&
 		     (!wpa_s->current_bss || !wpa_s->current_ssid)))
 			return -1;
+		auth_ssid = external ? wpa_s->sme.ext_auth_wpa_ssid :
+			wpa_s->current_ssid;
 		if (wpa_s->sme.sae.state != SAE_COMMITTED) {
 			wpa_printf(MSG_DEBUG,
 				   "SAE: Ignore commit message while waiting for confirm");
@@ -3819,18 +3913,53 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
 
 		if (groups && groups[0] <= 0)
 			groups = NULL;
-		res = sae_parse_commit(&wpa_s->sme.sae, data, len, NULL, NULL,
-				       groups, status_code ==
-				       WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
-				       status_code == WLAN_STATUS_SAE_PK,
-				       ie_offset);
+		if (!wpa_s->sme.sae_password) {
+			wpa_printf(MSG_DEBUG,
+				   "SAE: No retained password for peer commit");
+			return -1;
+		}
+
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA Commit parse begin h2e=%d mlo=%d group=%d own=" MACSTR
+			   " ap_link=" MACSTR " ap_mld=" MACSTR " event_peer=" MACSTR
+			   " body_len=%zu",
+			   status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT,
+			   wpa_s->valid_links != 0, wpa_s->sme.sae.group,
+			   MAC2STR(wpa_s->own_addr), MAC2STR(wpa_s->pending_bssid),
+			   MAC2STR(wpa_s->ap_mld_addr), MAC2STR(event_peer), len);
+		if (auth_ssid->decoyauth == SAE_DECOYAUTH_DISABLED ||
+		    status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT) {
+			res = sae_parse_commit(
+				&wpa_s->sme.sae, data, len, NULL, NULL, groups,
+				status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT ||
+				status_code == WLAN_STATUS_SAE_PK, ie_offset);
+		} else {
+			res = sae_ap_parse_commit(
+				&wpa_s->sme.sae,
+				(u8 *) wpa_s->sme.sae_password,
+				os_strlen(wpa_s->sme.sae_password), data, len,
+				NULL, NULL, groups, ie_offset,
+				&decoyauth_used);
+		}
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA Commit parse result=%u "
+			   "decoyauth_used=%d ie_offset=%d",
+			   res, decoyauth_used, ie_offset ? *ie_offset : -1);
 		if (res == SAE_SILENTLY_DISCARD) {
 			wpa_printf(MSG_DEBUG,
 				   "SAE: Drop commit message due to reflection attack");
 			return 0;
 		}
+		str_clear_free(wpa_s->sme.sae_password);
+		wpa_s->sme.sae_password = NULL;
 		if (res != WLAN_STATUS_SUCCESS)
 			return -1;
+		if (auth_ssid->decoyauth == SAE_DECOYAUTH_REQUIRED &&
+		    !decoyauth_used) {
+			wpa_printf(MSG_INFO,
+				   "SAE: AP did not use required DecoyAuth exchange");
+			return -1;
+		}
 
 		if (wpa_s->sme.sae.tmp &&
 		    sme_check_sae_rejected_groups(
@@ -6256,6 +6385,16 @@ void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
 void sme_event_auth_timed_out(struct wpa_supplicant *wpa_s,
 			      union wpa_event_data *data)
 {
+	if (wpa_s->wpa_state == WPA_AUTHENTICATING &&
+	    sme_auth_timeout(wpa_s->current_ssid) ==
+	    SME_DECOYAUTH_AUTH_TIMEOUT &&
+	    eloop_is_timeout_registered(sme_auth_timer, wpa_s, NULL)) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"SME: Defer driver authentication timeout until the "
+			"15-second DecoyAuth deadline");
+		return;
+	}
+
 	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Authentication timed out");
 	wpas_connection_failed(wpa_s, wpa_s->pending_bssid, NULL);
 	wpa_supplicant_mark_disassoc(wpa_s);
@@ -6362,6 +6501,8 @@ void sme_clear_on_disassoc(struct wpa_supplicant *wpa_s)
 #ifdef CONFIG_SAE
 	wpabuf_free(wpa_s->sme.sae_token);
 	wpa_s->sme.sae_token = NULL;
+	str_clear_free(wpa_s->sme.sae_password);
+	wpa_s->sme.sae_password = NULL;
 	sae_clear_data(&wpa_s->sme.sae);
 #endif /* CONFIG_SAE */
 #ifdef CONFIG_IEEE80211R
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index bf577e0d5..9c4d38a6c 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -2451,7 +2451,8 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
 	sel = ie.key_mgmt & ssid->key_mgmt;
 #ifdef CONFIG_SAE
 	if ((!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE) &&
-	     !(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA)) ||
+	     (!(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA) ||
+	      ssid->decoyauth == SAE_DECOYAUTH_REQUIRED)) ||
 	    wpas_is_sae_avoided(wpa_s, ssid, &ie))
 		sel &= ~(WPA_KEY_MGMT_SAE | WPA_KEY_MGMT_SAE_EXT_KEY |
 			 WPA_KEY_MGMT_FT_SAE | WPA_KEY_MGMT_FT_SAE_EXT_KEY);
@@ -5828,7 +5829,9 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit)
 	}
 
 	if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_STA) &&
-	    wpa_key_mgmt_sae(params.key_mgmt_suite)) {
+	    wpa_key_mgmt_sae(params.key_mgmt_suite) &&
+	    (ssid->decoyauth == SAE_DECOYAUTH_DISABLED ||
+	     !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE))) {
 		params.auth_alg = WPA_AUTH_ALG_SAE;
 		if (ssid->sae_password) {
 			params.sae_password = ssid->sae_password;
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index 4fc3cca52..fb18c1d54 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -509,6 +509,18 @@ fast_reauth=1
 # regardless of the sae_pwe parameter value.
 #sae_pwe=0
 
+# DecoyAuth mode for SAE authentication (network block parameter)
+# 0 = disabled (use standard SAE)
+# 1 = optional (default; negotiate when supported and accept standard SAE
+#     fallback)
+# 2 = required (require DecoyAuth and reject standard SAE fallback)
+# DecoyAuth requires H2E and supports MLO and plaintext password identifiers.
+# sae_password_id selects a password set; the password selects an entry in it.
+# Omit sae_password_id (or set it to "") to use the existing no-identifier set.
+# SAE-PK, mesh, and SAE offload are not supported. Optional mode uses standard
+# SAE fallback when H2E or DecoyAuth is unavailable.
+#decoyauth=1
+
 # Default value for DTIM period (if not overridden in network block)
 #dtim_period=2
 
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 1ff84a472..282202c85 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1151,6 +1151,7 @@ struct wpa_supplicant {
 #ifdef CONFIG_SAE
 		struct sae_data sae;
 		struct wpabuf *sae_token;
+		char *sae_password;
 		int sae_group_index;
 		unsigned int sae_pmksa_caching:1;
 		u16 seq_num;
-- 
2.56.0




More information about the Hostap mailing list