[RFC PATCH v1 07/13] SAE: Add DecoyAuth fragment handling
Jeff Hansen
x at jeffhansen.com
Fri Sep 11 12:20:02 PDT 2026
Add bounded serialization and ordered reassembly for DecoyAuth
value payloads that do not fit in a single SAE Authentication frame.
Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
src/common/sae.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++-
src/common/sae.h | 16 +++++
2 files changed, 191 insertions(+), 1 deletion(-)
diff --git a/src/common/sae.c b/src/common/sae.c
index e42f2e9a2..687407d58 100644
--- a/src/common/sae.c
+++ b/src/common/sae.c
@@ -160,6 +160,9 @@ void sae_clear_temp_data(struct sae_data *sae)
crypto_ec_point_deinit(tmp->own_commit_element_ecc, 0);
crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0);
wpabuf_free(tmp->anti_clogging_token);
+#ifdef CONFIG_SAE_DECOYAUTH
+ wpabuf_free(tmp->decoyauth_rx_values);
+#endif /* CONFIG_SAE_DECOYAUTH */
wpabuf_free(tmp->own_rejected_groups);
wpabuf_free(tmp->peer_rejected_groups);
os_free(tmp->pw_id);
@@ -1539,7 +1542,6 @@ static int sae_ap_prepare_commit_internal(const u8 *addr1, const u8 *addr2,
goto fail;
}
-
for (int i = 0; !candidates_ready && i < num_passwords; i++) {
struct crypto_bignum **encoded_point;
const struct sae_pt *pt = NULL;
@@ -1726,6 +1728,68 @@ int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
}
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae)
+{
+ size_t len = sae_write_values_len(sae);
+
+ return (len + SAE_DECOYAUTH_FRAGMENT_DATA_LEN - 1) /
+ SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+}
+
+
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+ u16 fragment)
+{
+ struct wpabuf *values = NULL, *payload = NULL;
+ const u8 *pos;
+ size_t total, offset, frag_len, remaining;
+ u16 count;
+ int ret = -1;
+
+ total = sae_write_values_len(sae);
+ count = sae_decoyauth_fragment_count(sae);
+ if (!total || !count || fragment >= count || total > UINT32_MAX)
+ return -1;
+ offset = (size_t) fragment * SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+ frag_len = total - offset;
+ if (frag_len > SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+ frag_len = SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+
+ values = wpabuf_alloc(total);
+ payload = wpabuf_alloc(SAE_DECOYAUTH_FRAGMENT_HEADER_LEN + frag_len);
+ if (!values || !payload || sae_write_values(sae, values) < 0)
+ goto out;
+
+ wpabuf_put_u8(payload, SAE_DECOYAUTH_VENDOR_VERSION);
+ wpabuf_put_le16(payload, fragment);
+ wpabuf_put_le16(payload, count);
+ WPA_PUT_LE32(wpabuf_put(payload, 4), total);
+ wpabuf_put_data(payload, wpabuf_head_u8(values) + offset, frag_len);
+
+ wpabuf_put_le16(buf, sae->group);
+ pos = wpabuf_head_u8(payload);
+ remaining = wpabuf_len(payload);
+ while (remaining) {
+ size_t elem_len =
+ remaining > SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN ?
+ SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN : remaining;
+
+ wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
+ wpabuf_put_u8(buf, elem_len + 4);
+ WPA_PUT_BE32(wpabuf_put(buf, 4),
+ SAE_DECOYAUTH_VALUES_VENDOR_TYPE);
+ wpabuf_put_data(buf, pos, elem_len);
+ pos += elem_len;
+ remaining -= elem_len;
+ }
+ ret = 0;
+out:
+ wpabuf_free(payload);
+ wpabuf_free(values);
+ return ret;
+}
+
+
#endif /* CONFIG_SAE_DECOYAUTH */
@@ -2636,6 +2700,83 @@ static struct wpabuf *sae_parse_decoyauth_ie_values(const u8 *pos,
}
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+ size_t len, int *ie_offset)
+{
+ struct sae_temporary_data *tmp;
+ struct wpabuf *fragment = NULL;
+ const u8 *pos, *end, *next, *payload;
+ size_t payload_len, expected_len, offset;
+ u32 total;
+ u16 index, count;
+ int ret = -1;
+
+ if (!sae || !sae->tmp || len < 2 ||
+ WPA_GET_LE16(data) != sae->group)
+ return -1;
+ tmp = sae->tmp;
+ pos = data + 2;
+ end = data + len;
+ fragment = sae_parse_decoyauth_ie_values(pos, end, &next);
+ if (!fragment || wpabuf_len(fragment) <
+ SAE_DECOYAUTH_FRAGMENT_HEADER_LEN)
+ goto out;
+ payload = wpabuf_head_u8(fragment);
+ payload_len = wpabuf_len(fragment);
+ if (payload[0] != SAE_DECOYAUTH_VENDOR_VERSION)
+ goto out;
+ index = WPA_GET_LE16(payload + 1);
+ count = WPA_GET_LE16(payload + 3);
+ total = WPA_GET_LE32(payload + 5);
+ payload += SAE_DECOYAUTH_FRAGMENT_HEADER_LEN;
+ payload_len -= SAE_DECOYAUTH_FRAGMENT_HEADER_LEN;
+ if (!count || index >= count || !total ||
+ total > 2U * SAE_MAX_PASSWORDS * SAE_MAX_ECC_PRIME_LEN ||
+ count != (total + SAE_DECOYAUTH_FRAGMENT_DATA_LEN - 1) /
+ SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+ goto out;
+ offset = (size_t) index * SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+ expected_len = total - offset;
+ if (expected_len > SAE_DECOYAUTH_FRAGMENT_DATA_LEN)
+ expected_len = SAE_DECOYAUTH_FRAGMENT_DATA_LEN;
+ if (payload_len != expected_len)
+ goto out;
+
+ if (index < tmp->decoyauth_rx_next) {
+ if (!tmp->decoyauth_rx_values ||
+ tmp->decoyauth_rx_total != total ||
+ tmp->decoyauth_rx_count != count ||
+ wpabuf_len(tmp->decoyauth_rx_values) < offset + payload_len ||
+ os_memcmp(wpabuf_head_u8(tmp->decoyauth_rx_values) + offset,
+ payload, payload_len) != 0)
+ goto out;
+ } else {
+ if (index != tmp->decoyauth_rx_next)
+ goto out;
+ if (index == 0) {
+ wpabuf_free(tmp->decoyauth_rx_values);
+ tmp->decoyauth_rx_values = wpabuf_alloc(total);
+ tmp->decoyauth_rx_total = total;
+ tmp->decoyauth_rx_count = count;
+ if (!tmp->decoyauth_rx_values)
+ goto out;
+ } else if (!tmp->decoyauth_rx_values ||
+ tmp->decoyauth_rx_total != total ||
+ tmp->decoyauth_rx_count != count) {
+ goto out;
+ }
+ wpabuf_put_data(tmp->decoyauth_rx_values, payload, payload_len);
+ tmp->decoyauth_rx_next++;
+ }
+ if (ie_offset)
+ *ie_offset = next - data;
+ ret = 0;
+out:
+ wpabuf_free(fragment);
+ return ret;
+}
+
+
#endif /* CONFIG_SAE_DECOYAUTH */
@@ -3323,6 +3464,14 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
return WLAN_STATUS_UNSPECIFIED_FAILURE;
value_data = wpabuf_head_u8(values);
value_len = wpabuf_len(values);
+ } else if (sae_commit_elem_is_zero(commit_elem, commit_elem_len)) {
+ if (sae->tmp->decoyauth_rx_values &&
+ sae->tmp->decoyauth_rx_next == sae->tmp->decoyauth_rx_count &&
+ wpabuf_len(sae->tmp->decoyauth_rx_values) ==
+ sae->tmp->decoyauth_rx_total) {
+ value_data = wpabuf_head_u8(sae->tmp->decoyauth_rx_values);
+ value_len = wpabuf_len(sae->tmp->decoyauth_rx_values);
+ }
}
if (value_data) {
@@ -3331,6 +3480,11 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
res = sae_parse_decoyauth_values(sae, password, password_len,
value_data, value_len);
wpabuf_free(values);
+ wpabuf_free(sae->tmp->decoyauth_rx_values);
+ sae->tmp->decoyauth_rx_values = NULL;
+ sae->tmp->decoyauth_rx_total = 0;
+ sae->tmp->decoyauth_rx_count = 0;
+ sae->tmp->decoyauth_rx_next = 0;
if (res != WLAN_STATUS_SUCCESS)
return res;
} else {
@@ -3768,6 +3922,26 @@ int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
}
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae)
+{
+ return 0;
+}
+
+
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+ u16 fragment)
+{
+ return -1;
+}
+
+
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+ size_t len, int *ie_offset)
+{
+ return -1;
+}
+
+
u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
size_t password_len, const u8 *data, size_t len,
const u8 **token, size_t *token_len,
diff --git a/src/common/sae.h b/src/common/sae.h
index 35c51a43b..4a6641db5 100644
--- a/src/common/sae.h
+++ b/src/common/sae.h
@@ -23,6 +23,11 @@
#define SAE_DECOYAUTH_VENDOR_VERSION 1
#define SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN 251
#define SAE_DECOYAUTH_VALUES_ELEM_OVERHEAD 6
+#define SAE_DECOYAUTH_MAX_AUTH_BODY_LEN 2300
+#define SAE_DECOYAUTH_CAPABILITY_IE_LEN 8
+#define SAE_DECOYAUTH_FRAGMENT_DATA_LEN 1900
+#define SAE_DECOYAUTH_FRAGMENT_HEADER_LEN 9
+#define SAE_DECOYAUTH_FRAGMENT_STATUS 65534
enum sae_decoyauth_mode {
SAE_DECOYAUTH_DISABLED = 0,
@@ -82,6 +87,11 @@ struct sae_temporary_data {
struct crypto_bignum *prime_buf;
struct crypto_bignum *order_buf;
struct wpabuf *anti_clogging_token;
+ struct wpabuf *decoyauth_rx_values;
+ u32 decoyauth_rx_total;
+ u16 decoyauth_rx_count;
+ u16 decoyauth_rx_next;
+ u16 decoyauth_fragment_request;
const char *decoyauth_cache_dir;
unsigned int decoyauth_cache_entries;
u8 *pw_id;
@@ -98,6 +108,7 @@ struct sae_temporary_data {
unsigned int own_addr_higher:1;
unsigned int try_other_password:1;
unsigned int decoyauth:1;
+ unsigned int decoyauth_fragmented:1;
unsigned int decoyauth_commit_processed:1;
#ifdef CONFIG_SAE_PK
@@ -201,6 +212,11 @@ u16 sae_ap_parse_commit(struct sae_data *sae, const u8 *password,
int *decoyauth_used);
size_t sae_write_values_len(const struct sae_data *sae);
int sae_write_values(struct sae_data *sae, struct wpabuf *buf);
+size_t sae_decoyauth_fragment_count(const struct sae_data *sae);
+int sae_write_decoyauth_fragment(struct sae_data *sae, struct wpabuf *buf,
+ u16 fragment);
+int sae_parse_decoyauth_fragment(struct sae_data *sae, const u8 *data,
+ size_t len, int *ie_offset);
int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf);
int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
int *ie_offset);
--
2.53.0
More information about the Hostap
mailing list