[RFC PATCH v2 09/13] hostapd: Add DecoyAuth configuration
Jeff Hansen
x at jeffhansen.com
Thu Oct 22 15:59:46 PDT 2026
Add the DecoyAuth build option and optional-by-default runtime mode.
Require H2E for DecoyAuth exchanges. Expose cache and worker support as
independent build options and include interpolation cache configuration
for SAE access points.
Support plaintext password identifiers selecting independent candidate
sets. Treat empty configured identifiers as absent and bound their length
to the SAE Password Identifier element capacity.
Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
hostapd/Makefile | 13 +++++++++++++
hostapd/config_file.c | 42 ++++++++++++++++++++++++++++++++++++++++--
hostapd/defconfig | 11 +++++++++++
hostapd/hostapd.conf | 25 +++++++++++++++++++++++++
src/ap/ap_config.c | 10 ++++++++++
src/ap/ap_config.h | 3 +++
6 files changed, 102 insertions(+), 2 deletions(-)
diff --git a/hostapd/Makefile b/hostapd/Makefile
index 1ecafdc55..9fa6a46c7 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -296,6 +296,19 @@ endif
ifdef CONFIG_SAE
CFLAGS += -DCONFIG_SAE
OBJS += ../src/common/sae.o
+ifdef CONFIG_SAE_DECOYAUTH
+CFLAGS += -DCONFIG_SAE_DECOYAUTH
+ifndef CONFIG_NATIVE_WINDOWS
+ifdef CONFIG_SAE_DECOYAUTH_THREADS
+CFLAGS += -DCONFIG_SAE_DECOYAUTH_THREADS
+LIBS += -pthread
+LIBS_s += -pthread
+endif
+ifdef CONFIG_SAE_DECOYAUTH_CACHE
+CFLAGS += -DCONFIG_SAE_DECOYAUTH_CACHE
+endif
+endif
+endif
ifdef CONFIG_SAE_PK
CFLAGS += -DCONFIG_SAE_PK
NEED_BASE64=y
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 2717adc55..5d604ee22 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2214,9 +2214,13 @@ static int parse_sae_password(struct hostapd_bss_config *bss, const char *val)
if (!end)
end = pos2;
pos2 += 4;
- pw->identifier = os_strdup(pos2);
- if (!pw->identifier)
+ if (os_strlen(pos2) > 254)
goto fail;
+ if (*pos2) {
+ pw->identifier = os_strdup(pos2);
+ if (!pw->identifier)
+ goto fail;
+ }
}
if (!end) {
@@ -4684,6 +4688,40 @@ static int hostapd_config_fill(struct hostapd_config *conf,
bss->sae_confirm_immediate = atoi(pos);
} else if (os_strcmp(buf, "sae_pwe") == 0) {
bss->sae_pwe = atoi(pos);
+#ifdef CONFIG_SAE_DECOYAUTH
+ } else if (os_strcmp(buf, "decoyauth") == 0) {
+ bss->decoyauth = atoi(pos);
+ if (bss->decoyauth < SAE_DECOYAUTH_DISABLED ||
+ bss->decoyauth > SAE_DECOYAUTH_REQUIRED) {
+ wpa_printf(MSG_ERROR,
+ "Line %d: Invalid decoyauth value %d (expected 0..2)",
+ line, bss->decoyauth);
+ return 1;
+ }
+#ifdef CONFIG_SAE_DECOYAUTH_CACHE
+ } else if (os_strcmp(buf, "decoyauth_cache_dir") == 0) {
+ if (pos[0] != '/') {
+ wpa_printf(MSG_ERROR,
+ "Line %d: decoyauth_cache_dir must be an absolute path",
+ line);
+ return 1;
+ }
+ os_free(bss->decoyauth_cache_dir);
+ bss->decoyauth_cache_dir = os_strdup(pos);
+ if (!bss->decoyauth_cache_dir)
+ return 1;
+ } else if (os_strcmp(buf, "decoyauth_cache_entries") == 0) {
+ int entries = atoi(pos);
+
+ if (entries < 0 || entries > SAE_MAX_PASSWORDS) {
+ wpa_printf(MSG_ERROR,
+ "Line %d: Invalid decoyauth_cache_entries value %d (expected 0..%d)",
+ line, entries, SAE_MAX_PASSWORDS);
+ return 1;
+ }
+ bss->decoyauth_cache_entries = entries;
+#endif /* CONFIG_SAE_DECOYAUTH_CACHE */
+#endif /* CONFIG_SAE_DECOYAUTH */
} else if (os_strcmp(buf, "sae_accept_h2e_without_use") == 0) {
bss->sae_accept_h2e_without_use = atoi(pos);
} else if (os_strcmp(buf, "sae_pw_id_num") == 0) {
diff --git a/hostapd/defconfig b/hostapd/defconfig
index cecfc7c8a..d04859a65 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -164,6 +164,17 @@ CONFIG_IPV6=y
# Simultaneous Authentication of Equals (SAE), WPA3-Personal
#CONFIG_SAE=y
+# Experimental DecoyAuth extension for SAE multi-password authentication
+#CONFIG_SAE_DECOYAUTH=y
+
+# Cache DecoyAuth interpolation matrices on persistent storage. This requires
+# CONFIG_SAE_DECOYAUTH and is not supported on native Windows builds.
+#CONFIG_SAE_DECOYAUTH_CACHE=y
+
+# Use worker threads for DecoyAuth candidate and interpolation preparation.
+# This requires CONFIG_SAE_DECOYAUTH and is not supported on native Windows.
+#CONFIG_SAE_DECOYAUTH_THREADS=y
+
# SAE Public Key, WPA3-Personal
#CONFIG_SAE_PK=y
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 8f902b753..ae5dabc04 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -2314,6 +2314,31 @@ own_ip_addr=127.0.0.1
# regardless of the sae_pwe parameter value.
#sae_pwe=0
+# DecoyAuth mode for SAE authentication
+# 0 = disabled (use standard SAE)
+# 1 = optional (default; use DecoyAuth with capable stations and standard SAE
+# with the first matching password for legacy stations)
+# 2 = required (reject stations that do not negotiate DecoyAuth)
+# DecoyAuth requires H2E and supports MLO and plaintext password identifiers.
+# Only entries with the requested identifier form the candidate password set;
+# without an identifier, only entries without an identifier are considered.
+# Empty configured identifiers are treated as absent. Unknown identifiers fail.
+# SAE-PK, mesh, and SAE offload are not supported. Optional mode uses standard
+# SAE fallback when H2E or DecoyAuth is unavailable.
+#decoyauth=1
+
+# Optional persistent cache for the password-derived DecoyAuth interpolation
+# matrix. The directory and its files are restricted to the hostapd user because
+# the matrix is sensitive credential-derived material. Matrix entries use a
+# fixed-width binary representation and are read directly from the filesystem;
+# they are not expanded into a resident BIGNUM matrix.
+# The default is /tmp/hostapd-decoyauth.
+#decoyauth_cache_dir=/tmp/hostapd-decoyauth
+
+# Number of most-recent interpolation matrices retained in the directory.
+# 0 disables the cache. The default is 10.
+#decoyauth_cache_entries=10
+
# Accept SAE association when the STA advertises SAE H2E support in the RSNXE
# but authenticated using the hunting-and-pecking loop (i.e., did not use H2E).
# By default (0), hostapd rejects such an association as a potential downgrade
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index fd778d107..dd91b4e3f 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -136,6 +136,15 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
bss->anti_clogging_threshold = 5;
bss->sae_sync = 3;
+#ifdef CONFIG_SAE_DECOYAUTH
+ bss->decoyauth = SAE_DECOYAUTH_OPTIONAL;
+#ifdef CONFIG_SAE_DECOYAUTH_CACHE
+ bss->decoyauth_cache_dir = os_strdup("/tmp/hostapd-decoyauth");
+ bss->decoyauth_cache_entries = 10;
+#endif /* CONFIG_SAE_DECOYAUTH_CACHE */
+#else /* CONFIG_SAE_DECOYAUTH */
+ bss->decoyauth = SAE_DECOYAUTH_DISABLED;
+#endif /* CONFIG_SAE_DECOYAUTH */
bss->gas_frag_limit = 1400;
@@ -867,6 +876,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
os_free(conf->rsn_preauth_interfaces);
os_free(conf->ctrl_interface);
os_free(conf->config_id);
+ os_free(conf->decoyauth_cache_dir);
os_free(conf->ca_cert);
os_free(conf->server_cert);
os_free(conf->server_cert2);
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index a863eb1d6..b94dcdcf1 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -681,6 +681,9 @@ struct hostapd_bss_config {
int sae_require_mfp;
int sae_confirm_immediate;
enum sae_pwe sae_pwe;
+ int decoyauth;
+ char *decoyauth_cache_dir;
+ unsigned int decoyauth_cache_entries;
/* Accept an SAE association even when the STA advertises SAE H2E
* support in the RSNXE but authenticated using hunting-and-pecking.
* This is a workaround for WPA3-Personal Compatibility Mode and
--
2.56.0
More information about the Hostap
mailing list