[RFC PATCH v1 06/13] SAE: Add DecoyAuth multi-password primitives

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


Add bounded multi-password candidate preparation, registered
vendor-specific value encoding, and selected-password key derivation
for SAE.

Signed-off-by: Jeff Hansen <x at jeffhansen.com>
---
 src/common/sae.c | 1335 +++++++++++++++++++++++++++++++++++++++++++++-
 src/common/sae.h |   52 ++
 2 files changed, 1362 insertions(+), 25 deletions(-)

diff --git a/src/common/sae.c b/src/common/sae.c
index f82a9e4fc..e42f2e9a2 100644
--- a/src/common/sae.c
+++ b/src/common/sae.c
@@ -96,9 +96,58 @@ int sae_set_group(struct sae_data *sae, int group)
 void sae_clear_temp_data(struct sae_data *sae)
 {
 	struct sae_temporary_data *tmp;
+#ifdef CONFIG_SAE_DECOYAUTH
+	int i;
+#endif /* CONFIG_SAE_DECOYAUTH */
 	if (sae == NULL || sae->tmp == NULL)
 		return;
 	tmp = sae->tmp;
+
+#ifdef CONFIG_SAE_DECOYAUTH
+	if (tmp->kcks) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			bin_clear_free(tmp->kcks[i], SAE_MAX_HASH_LEN);
+		os_free(tmp->kcks);
+	}
+	if (tmp->pmks) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			bin_clear_free(tmp->pmks[i], SAE_PMK_LEN_MAX);
+		os_free(tmp->pmks);
+	}
+	if (tmp->pmkids) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			bin_clear_free(tmp->pmkids[i], SAE_PMKID_LEN);
+		os_free(tmp->pmkids);
+	}
+	os_free(tmp->kck_lens);
+	os_free(tmp->pmk_lens);
+	if (tmp->u_coefficients) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_bignum_deinit(tmp->u_coefficients[i], 1);
+		os_free(tmp->u_coefficients);
+	}
+	if (tmp->v_coefficients) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_bignum_deinit(tmp->v_coefficients[i], 1);
+		os_free(tmp->v_coefficients);
+	}
+	if (tmp->scalar_coefficients) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_bignum_deinit(tmp->scalar_coefficients[i], 1);
+		os_free(tmp->scalar_coefficients);
+	}
+	if (tmp->pwe_eccs) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_ec_point_deinit(tmp->pwe_eccs[i], 1);
+		os_free(tmp->pwe_eccs);
+	}
+	if (tmp->own_commit_element_eccs) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_ec_point_deinit(tmp->own_commit_element_eccs[i], 1);
+		os_free(tmp->own_commit_element_eccs);
+	}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
 	crypto_ec_deinit(tmp->ec);
 	crypto_bignum_deinit(tmp->prime_buf, 0);
 	crypto_bignum_deinit(tmp->order_buf, 0);
@@ -136,6 +185,46 @@ void sae_clear_data(struct sae_data *sae)
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static void sae_free_bignum_array(struct crypto_bignum ***vals, int count,
+				  int clear)
+{
+	if (!vals || !*vals)
+		return;
+	for (int i = 0; i < count; i++)
+		crypto_bignum_deinit((*vals)[i], clear);
+	os_free(*vals);
+	*vals = NULL;
+}
+
+
+static void sae_reset_ap_password_state(struct sae_temporary_data *tmp)
+{
+	int i;
+
+	if (!tmp)
+		return;
+	sae_free_bignum_array(&tmp->u_coefficients, tmp->num_passwords, 1);
+	sae_free_bignum_array(&tmp->v_coefficients, tmp->num_passwords, 1);
+	if (tmp->pwe_eccs) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_ec_point_deinit(tmp->pwe_eccs[i], 1);
+		os_free(tmp->pwe_eccs);
+		tmp->pwe_eccs = NULL;
+	}
+	if (tmp->own_commit_element_eccs) {
+		for (i = 0; i < tmp->num_passwords; i++)
+			crypto_ec_point_deinit(tmp->own_commit_element_eccs[i], 1);
+		os_free(tmp->own_commit_element_eccs);
+		tmp->own_commit_element_eccs = NULL;
+	}
+	tmp->num_passwords = 0;
+}
+
+
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 static void sae_pwd_seed_key(const u8 *addr1, const u8 *addr2, u8 *key)
 {
 	wpa_printf(MSG_DEBUG, "SAE: PWE derivation - addr1=" MACSTR
@@ -1381,6 +1470,265 @@ int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static int sae_ap_prepare_commit_internal(const u8 *addr1, const u8 *addr2,
+					  const u8 *ssid, size_t ssid_len,
+					  const u8 **passwords,
+					  const size_t *password_lens,
+					  const struct sae_pt * const *password_pts,
+					  int num_passwords,
+					  struct sae_data *sae)
+{
+	struct crypto_bignum *mask = NULL;
+	struct crypto_bignum **hash_values = NULL;
+	struct crypto_bignum **u_values = NULL;
+	struct crypto_bignum **v_values = NULL;
+	int ret = -1;
+	const char *fail_stage = "validate arguments";
+	int candidate = -1;
+	int candidates_ready = 0;
+	struct os_reltime interpolation_start, interpolation_end;
+	struct os_reltime interpolation_elapsed;
+	int interpolation_timed;
+#ifdef CONFIG_SAE_DECOYAUTH_CACHE
+	struct crypto_interpolation_cache_stats cache_stats;
+	int cache_used = 0;
+#endif /* CONFIG_SAE_DECOYAUTH_CACHE */
+
+	if (!sae || !sae->tmp || !passwords || !password_lens ||
+	    !sae->tmp->ec || num_passwords <= 0 ||
+	    num_passwords > SAE_MAX_PASSWORDS) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth AP prepare rejected invalid state passwords=%d",
+			   num_passwords);
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "SAE: DecoyAuth AP prepare begin group=%d own=" MACSTR
+		   " peer=" MACSTR " passwords=%d ssid_len=%zu",
+		   sae->group, MAC2STR(addr1), MAC2STR(addr2),
+		   num_passwords, ssid_len);
+
+	fail_stage = "allocate coefficient state";
+	sae_reset_ap_password_state(sae->tmp);
+	sae->tmp->num_passwords = num_passwords;
+	sae->tmp->pwe_eccs = os_calloc(num_passwords,
+					 sizeof(*sae->tmp->pwe_eccs));
+	sae->tmp->own_commit_element_eccs =
+		os_calloc(num_passwords,
+			  sizeof(*sae->tmp->own_commit_element_eccs));
+	mask = crypto_bignum_init();
+	hash_values = os_calloc(num_passwords, sizeof(*hash_values));
+	u_values = os_calloc(num_passwords, sizeof(*u_values));
+	v_values = os_calloc(num_passwords, sizeof(*v_values));
+	if (!sae->tmp->pwe_eccs || !sae->tmp->own_commit_element_eccs ||
+	    !mask || !hash_values || !u_values || !v_values)
+		goto fail;
+
+	if (!sae->tmp->sae_rand)
+		sae->tmp->sae_rand = crypto_bignum_init();
+	if (!sae->tmp->own_commit_scalar)
+		sae->tmp->own_commit_scalar = crypto_bignum_init();
+	if (!sae->tmp->sae_rand || !sae->tmp->own_commit_scalar)
+		goto fail;
+
+	fail_stage = "generate commit scalar";
+	if (dragonfly_generate_scalar(sae->tmp->order, sae->tmp->sae_rand,
+				      mask, sae->tmp->own_commit_scalar) < 0) {
+		goto fail;
+	}
+
+
+	for (int i = 0; !candidates_ready && i < num_passwords; i++) {
+		struct crypto_bignum **encoded_point;
+		const struct sae_pt *pt = NULL;
+		struct sae_pt *derived_pt = NULL;
+		u8 hash[32];
+
+		candidate = i;
+		fail_stage = "derive H2E PWE";
+		if (password_pts)
+			pt = password_pts[i];
+		while (pt && pt->group != sae->group)
+			pt = pt->next;
+		if (!pt) {
+			int groups[] = { sae->group, 0 };
+
+			derived_pt = sae_derive_pt(groups, ssid, ssid_len,
+						   passwords[i], password_lens[i],
+						   NULL, 0);
+			pt = derived_pt;
+		}
+		crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1);
+		sae->tmp->pwe_ecc = pt && pt->ec ?
+			sae_derive_pwe_from_pt_ecc(pt, addr1, addr2) : NULL;
+		sae_deinit_pt(derived_pt);
+		if (!sae->tmp->pwe_ecc)
+			goto fail;
+		fail_stage = "derive commit element";
+		if (sae_derive_commit_element_ecc(sae, mask) < 0)
+			goto fail;
+
+		sae->h2e = 1;
+		sae->pk = 0;
+		sae->tmp->pwe_eccs[i] = crypto_ec_point_init(sae->tmp->ec);
+		sae->tmp->own_commit_element_eccs[i] =
+			crypto_ec_point_init(sae->tmp->ec);
+		fail_stage = "clone candidate points";
+		if (!sae->tmp->pwe_eccs[i] ||
+		    !sae->tmp->own_commit_element_eccs[i] ||
+		    crypto_ec_point_copy(sae->tmp->ec, sae->tmp->pwe_ecc,
+					  sae->tmp->pwe_eccs[i]) < 0 ||
+		    crypto_ec_point_copy(sae->tmp->ec,
+					  sae->tmp->own_commit_element_ecc,
+					  sae->tmp->own_commit_element_eccs[i]) < 0)
+			goto fail;
+
+		fail_stage = "hash candidate password";
+		if (sha256_vector(1, &passwords[i], &password_lens[i], hash) < 0)
+			goto fail;
+		hash_values[i] = crypto_bignum_init_set(hash, sizeof(hash));
+		if (!hash_values[i])
+			goto fail;
+
+		fail_stage = "encode candidate point";
+		encoded_point = crypto_point_to_values(
+			sae->tmp->own_commit_element_eccs[i], sae->tmp->ec);
+		if (!encoded_point || !encoded_point[0] || !encoded_point[1]) {
+			sae_free_bignum_array(&encoded_point, 2, 1);
+			goto fail;
+		}
+		u_values[i] = encoded_point[0];
+		v_values[i] = encoded_point[1];
+		os_free(encoded_point);
+	}
+	sae->h2e = 1;
+	sae->pk = 0;
+
+	candidate = -1;
+	fail_stage = "interpolate coefficient polynomials";
+	interpolation_timed = os_get_reltime(&interpolation_start) == 0;
+#ifdef CONFIG_SAE_DECOYAUTH_CACHE
+	if (sae->tmp->decoyauth_cache_dir &&
+	    sae->tmp->decoyauth_cache_entries) {
+		if (crypto_interpolate_pair_cached(
+			    hash_values, u_values, v_values, num_passwords,
+			    sae->tmp->ec, sae->tmp->decoyauth_cache_dir,
+			    sae->tmp->decoyauth_cache_entries,
+			    &sae->tmp->u_coefficients,
+			    &sae->tmp->v_coefficients, &cache_stats) == 0) {
+			cache_used = 1;
+			wpa_printf(MSG_DEBUG,
+				   "SAE: DecoyAuth matrix cache %s passwords=%d "
+				   "bytes=%zu prepare=%ld ms weave=%ld ms",
+				   cache_stats.hit ? "hit" : "miss",
+				   num_passwords, cache_stats.bytes,
+				   cache_stats.prepare_ms, cache_stats.weave_ms);
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "SAE: DecoyAuth matrix cache unavailable; using direct interpolation");
+		}
+	}
+	if (!cache_used &&
+#else /* CONFIG_SAE_DECOYAUTH_CACHE */
+	if (
+#endif /* CONFIG_SAE_DECOYAUTH_CACHE */
+	    crypto_interpolate_pair(hash_values, u_values, v_values,
+				    num_passwords, sae->tmp->ec,
+				    &sae->tmp->u_coefficients,
+				    &sae->tmp->v_coefficients) < 0)
+		goto fail;
+	if (interpolation_timed && os_get_reltime(&interpolation_end) == 0) {
+		os_reltime_sub(&interpolation_end, &interpolation_start,
+			       &interpolation_elapsed);
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth interpolated %d candidates in %ld ms",
+			   num_passwords,
+			   interpolation_elapsed.sec * 1000L +
+			   interpolation_elapsed.usec / 1000);
+	}
+
+	ret = 0;
+	wpa_printf(MSG_DEBUG,
+		   "SAE: DecoyAuth AP prepare complete group=%d values_len=%zu",
+		   sae->group, sae_write_values_len(sae));
+fail:
+	sae_free_bignum_array(&hash_values, num_passwords, 1);
+	sae_free_bignum_array(&u_values, num_passwords, 1);
+	sae_free_bignum_array(&v_values, num_passwords, 1);
+	crypto_bignum_deinit(mask, 1);
+	if (ret < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth AP prepare failed group=%d stage=%s candidate=%d",
+			   sae->group, fail_stage, candidate);
+		sae_reset_ap_password_state(sae->tmp);
+	}
+	return ret;
+}
+
+
+int sae_ap_prepare_commit_h2e(const u8 *addr1, const u8 *addr2,
+			       const u8 *ssid, size_t ssid_len,
+			       const u8 **passwords,
+			       const size_t *password_lens,
+			       int num_passwords, struct sae_data *sae)
+{
+	return sae_ap_prepare_commit_internal(addr1, addr2, ssid, ssid_len,
+					      passwords, password_lens, NULL,
+					      num_passwords, sae);
+}
+
+
+int sae_ap_prepare_commit_h2e_with_pt(
+	const u8 *addr1, const u8 *addr2, const u8 *ssid, size_t ssid_len,
+	const u8 **passwords, const size_t *password_lens,
+	const struct sae_pt * const *password_pts, int num_passwords,
+	struct sae_data *sae)
+{
+	return sae_ap_prepare_commit_internal(
+		addr1, addr2, ssid, ssid_len, passwords, password_lens,
+		password_pts, num_passwords, sae);
+}
+
+
+size_t sae_write_values_len(const struct sae_data *sae)
+{
+	if (!sae || !sae->tmp || sae->tmp->num_passwords <= 0)
+		return 0;
+	return 2 * (size_t) sae->tmp->num_passwords * sae->tmp->prime_len;
+}
+
+
+int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
+{
+	u8 *pos;
+
+	if (!sae || !sae->tmp || !buf || !sae->tmp->u_coefficients ||
+	    !sae->tmp->v_coefficients)
+		return -1;
+
+	for (int i = 0; i < sae->tmp->num_passwords; i++) {
+		pos = wpabuf_put(buf, sae->tmp->prime_len);
+		if (crypto_bignum_to_bin(sae->tmp->u_coefficients[i], pos,
+					 sae->tmp->prime_len,
+					 sae->tmp->prime_len) < 0)
+			return -1;
+	}
+	for (int i = 0; i < sae->tmp->num_passwords; i++) {
+		pos = wpabuf_put(buf, sae->tmp->prime_len);
+		if (crypto_bignum_to_bin(sae->tmp->v_coefficients[i], pos,
+					 sae->tmp->prime_len,
+					 sae->tmp->prime_len) < 0)
+			return -1;
+	}
+	return 0;
+}
+
+
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt,
 			  const u8 *addr1, const u8 *addr2,
 			  int *rejected_groups, const struct sae_pk *pk)
@@ -1477,6 +1825,44 @@ fail:
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static int sae_ap_derive_k_ecc(struct sae_data *sae, u8 *k, int index)
+{
+	struct crypto_ec_point *K;
+	int ret = -1;
+
+	K = crypto_ec_point_init(sae->tmp->ec);
+	if (K == NULL)
+		goto fail;
+
+	/*
+	 * K = scalar-op(rand, (elem-op(scalar-op(peer-commit-scalar, PWE),
+	 *                                        PEER-COMMIT-ELEMENT)))
+	 * If K is identity element (point-at-infinity), reject
+	 * k = F(K) (= x coordinate)
+	 */
+
+	if (crypto_ec_point_mul(sae->tmp->ec, sae->tmp->pwe_eccs[index],
+				sae->peer_commit_scalar, K) < 0 ||
+	    crypto_ec_point_add(sae->tmp->ec, K,
+				sae->tmp->peer_commit_element_ecc, K) < 0 ||
+	    crypto_ec_point_mul(sae->tmp->ec, K, sae->tmp->sae_rand, K) < 0 ||
+	    crypto_ec_point_is_at_infinity(sae->tmp->ec, K) ||
+	    crypto_ec_point_to_bin(sae->tmp->ec, K, k, NULL) < 0) {
+		wpa_printf(MSG_DEBUG, "SAE: Failed to calculate K and k");
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "SAE: k", k, sae->tmp->prime_len);
+
+	ret = 0;
+fail:
+	crypto_ec_point_deinit(K, 1);
+	return ret;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 static int sae_derive_k_ffc(struct sae_data *sae, u8 *k)
 {
 	struct crypto_bignum *K;
@@ -1678,37 +2064,258 @@ fail:
 }
 
 
-int sae_process_commit(struct sae_data *sae)
+#ifdef CONFIG_SAE_DECOYAUTH
+static int sae_ap_derive_keys(struct sae_data *sae, const u8 *k, int index)
 {
-	u8 k[SAE_MAX_PRIME_LEN];
-	int ret = 0;
-
-	if (sae->tmp == NULL ||
-	    (sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||
-	    (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
-	    sae_derive_keys(sae, k) < 0)
-		ret = -1;
-
-	forced_memzero(k, SAE_MAX_PRIME_LEN);
+	u8 zero[SAE_MAX_HASH_LEN], val[SAE_MAX_PRIME_LEN];
+	const u8 *salt;
+	struct wpabuf *rejected_groups = NULL;
+	u8 keyseed[SAE_MAX_HASH_LEN];
+	u8 keys[2 * SAE_MAX_HASH_LEN + SAE_PMK_LEN_MAX];
+	struct crypto_bignum *tmp;
+	int ret = -1;
+	int i;
+	size_t hash_len, salt_len, prime_len = sae->tmp->prime_len;
+	size_t pmk_len;
+	const u8 *addr[1];
+	size_t len[1];
 
-	return ret;
-}
+	wpa_printf(MSG_DEBUG, "SAE: Deriving keys for index %d", index);
+	tmp = crypto_bignum_init();
+	if (!tmp)
+		goto fail;
 
+	/* keyseed = H(salt, k)
+	 * KCK || PMK = KDF-Hash-Length(keyseed, "SAE KCK and PMK",
+	 *                      (commit-scalar + peer-commit-scalar) modulo r)
+	 * PMKID = L((commit-scalar + peer-commit-scalar) modulo r, 0, 128)
+	 *
+	 * When SAE-PK is used,
+	 * KCK || PMK || KEK = KDF-Hash-Length(keyseed, "SAE-PK keys", context)
+	 */
+	if (!sae->h2e)
+		hash_len = SHA256_MAC_LEN;
+	else if (sae->tmp->dh)
+		hash_len = sae_ffc_prime_len_2_hash_len(prime_len);
+	else
+		hash_len = sae_ecc_prime_len_2_hash_len(prime_len);
+	if (wpa_key_mgmt_sae_ext_key(sae->akmp))
+		pmk_len = hash_len;
+	else
+		pmk_len = SAE_PMK_LEN;
+	wpa_printf(MSG_DEBUG, "SAE: Derive keys - H2E=%d AKMP=0x%x = %08x (%s)",
+		   sae->h2e, sae->akmp,
+		   wpa_akm_to_suite(sae->akmp),
+		   wpa_key_mgmt_txt(sae->akmp, WPA_PROTO_RSN));
+	if (sae->h2e && (sae->tmp->own_rejected_groups ||
+			 sae->tmp->peer_rejected_groups)) {
+		struct wpabuf *own, *peer;
 
-int sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
-		     const struct wpabuf *token, const u8 *identifier,
-		     size_t identifier_len)
-{
-	u8 *pos;
+		own = sae->tmp->own_rejected_groups;
+		peer = sae->tmp->peer_rejected_groups;
+		salt_len = 0;
+		if (own)
+			salt_len += wpabuf_len(own);
+		if (peer)
+			salt_len += wpabuf_len(peer);
+		rejected_groups = wpabuf_alloc(salt_len);
+		if (!rejected_groups)
+			goto fail;
+		if (sae->tmp->own_addr_higher) {
+			if (own)
+				wpabuf_put_buf(rejected_groups, own);
+			if (peer)
+				wpabuf_put_buf(rejected_groups, peer);
+		} else {
+			if (peer)
+				wpabuf_put_buf(rejected_groups, peer);
+			if (own)
+				wpabuf_put_buf(rejected_groups, own);
+		}
+		salt = wpabuf_head(rejected_groups);
+		salt_len = wpabuf_len(rejected_groups);
+	} else {
+		os_memset(zero, 0, hash_len);
+		salt = zero;
+		salt_len = hash_len;
+	}
+	wpa_hexdump(MSG_DEBUG, "SAE: salt for keyseed derivation",
+		    salt, salt_len);
+	addr[0] = k;
+	len[0] = prime_len;
+	if (hkdf_extract(hash_len, salt, salt_len, 1, addr, len, keyseed) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG, "SAE: keyseed", keyseed, hash_len);
 
-	if (sae->tmp == NULL)
-		return -1;
+	if (crypto_bignum_add(sae->tmp->own_commit_scalar,
+			      sae->peer_commit_scalar, tmp) < 0 ||
+	    crypto_bignum_mod(tmp, sae->tmp->order, tmp) < 0)
+		goto fail;
+	/* IEEE Std 802.11-2016 is not exactly clear on the encoding of the bit
+	 * string that is needed for KCK, PMK, and PMKID derivation, but it
+	 * seems to make most sense to encode the
+	 * (commit-scalar + peer-commit-scalar) mod r part as a bit string by
+	 * zero padding it from left to the length of the order (in full
+	 * octets). */
+	if (crypto_bignum_to_bin(tmp, val, sizeof(val),
+				 sae->tmp->order_len) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "SAE: PMKID", val, SAE_PMKID_LEN);
 
-	wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */
-	if (!sae->h2e && token) {
-		wpabuf_put_buf(buf, token);
-		wpa_hexdump(MSG_DEBUG, "SAE: Anti-clogging token",
-			    wpabuf_head(token), wpabuf_len(token));
+#ifdef CONFIG_SAE_PK
+	if (sae->pk) {
+		if (sae_kdf_hash(hash_len, keyseed, "SAE-PK keys",
+				 val, sae->tmp->order_len,
+				 keys, 2 * hash_len + pmk_len) < 0)
+			goto fail;
+	} else {
+		if (sae_kdf_hash(hash_len, keyseed, "SAE KCK and PMK",
+				 val, sae->tmp->order_len,
+				 keys, hash_len + pmk_len) < 0)
+			goto fail;
+	}
+#else /* CONFIG_SAE_PK */
+	if (sae_kdf_hash(hash_len, keyseed, "SAE KCK and PMK",
+			 val, sae->tmp->order_len,
+			 keys, hash_len + pmk_len) < 0)
+		goto fail;
+#endif /* !CONFIG_SAE_PK */
+
+	if (!sae->tmp->kcks) {
+		sae->tmp->kcks = os_calloc(sae->tmp->num_passwords,
+					   sizeof(*sae->tmp->kcks));
+		if (!sae->tmp->kcks)
+			goto fail;
+		for (i = 0; i < sae->tmp->num_passwords; i++) {
+			sae->tmp->kcks[i] = os_malloc(SAE_MAX_HASH_LEN);
+			if (!sae->tmp->kcks[i])
+				goto fail;
+		}
+	}
+
+	if (!sae->tmp->pmks) {
+		sae->tmp->pmks = os_calloc(sae->tmp->num_passwords,
+					   sizeof(*sae->tmp->pmks));
+		if (!sae->tmp->pmks)
+			goto fail;
+		for (i = 0; i < sae->tmp->num_passwords; i++) {
+			sae->tmp->pmks[i] = os_malloc(SAE_PMK_LEN_MAX);
+			if (!sae->tmp->pmks[i])
+				goto fail;
+		}
+	}
+
+	if (!sae->tmp->pmkids) {
+		sae->tmp->pmkids = os_calloc(sae->tmp->num_passwords,
+					     sizeof(*sae->tmp->pmkids));
+		if (!sae->tmp->pmkids)
+			goto fail;
+		for (i = 0; i < sae->tmp->num_passwords; i++) {
+			sae->tmp->pmkids[i] = os_malloc(SAE_PMKID_LEN);
+			if (!sae->tmp->pmkids[i])
+				goto fail;
+		}
+	}
+
+	if (!sae->tmp->kck_lens) {
+		sae->tmp->kck_lens = os_calloc(sae->tmp->num_passwords,
+					       sizeof(*sae->tmp->kck_lens));
+		if (!sae->tmp->kck_lens)
+			goto fail;
+	}
+
+	if (!sae->tmp->pmk_lens) {
+		sae->tmp->pmk_lens = os_calloc(sae->tmp->num_passwords,
+					       sizeof(*sae->tmp->pmk_lens));
+		if (!sae->tmp->pmk_lens)
+			goto fail;
+	}
+
+	os_memcpy(sae->tmp->kcks[index], keys, hash_len);
+	sae->tmp->kck_lens[index] = hash_len;
+	os_memcpy(sae->tmp->pmks[index], keys + hash_len, pmk_len);
+	sae->tmp->pmk_lens[index] = pmk_len;
+	os_memcpy(sae->tmp->pmkids[index], val, SAE_PMKID_LEN);
+#ifdef CONFIG_SAE_PK
+	if (sae->pk) {
+		os_memcpy(sae->tmp->kek, keys + hash_len + SAE_PMK_LEN,
+			  hash_len);
+		sae->tmp->kek_len = hash_len;
+		wpa_hexdump_key(MSG_DEBUG, "SAE: KEK for SAE-PK",
+				sae->tmp->kek, sae->tmp->kek_len);
+	}
+#endif /* CONFIG_SAE_PK */
+	forced_memzero(keys, sizeof(keys));
+	wpa_hexdump_key(MSG_DEBUG, "SAE: KCK",
+			sae->tmp->kcks[index], sae->tmp->kck_lens[index]);
+	wpa_hexdump_key(MSG_DEBUG, "SAE: PMK",
+			sae->tmp->pmks[index], sae->tmp->pmk_lens[index]);
+
+	ret = 0;
+fail:
+	forced_memzero(keyseed, sizeof(keyseed));
+	forced_memzero(keys, sizeof(keys));
+	wpabuf_free(rejected_groups);
+	crypto_bignum_deinit(tmp, 0);
+	return ret;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
+int sae_process_commit(struct sae_data *sae)
+{
+	u8 k[SAE_MAX_PRIME_LEN];
+	int ret = 0;
+
+	if (sae->tmp == NULL ||
+	    (sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||
+	    (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
+	    sae_derive_keys(sae, k) < 0)
+		ret = -1;
+
+	forced_memzero(k, SAE_MAX_PRIME_LEN);
+
+	return ret;
+}
+
+
+#ifdef CONFIG_SAE_DECOYAUTH
+int sae_ap_process_commit(struct sae_data *sae)
+{
+	u8 k[SAE_MAX_PRIME_LEN];
+	int i;
+
+	if (!sae->tmp)
+		return -1;
+	for (i = 0; i < sae->tmp->num_passwords; i++) {
+		if ((sae->tmp->ec && sae_ap_derive_k_ecc(sae, k, i) < 0) ||
+		    (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
+		    sae_ap_derive_keys(sae, k, i) < 0)
+			goto fail;
+	}
+	forced_memzero(k, sizeof(k));
+	return 0;
+fail:
+	forced_memzero(k, sizeof(k));
+	return -1;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
+int sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
+		     const struct wpabuf *token, const u8 *identifier,
+		     size_t identifier_len)
+{
+	u8 *pos;
+
+	if (sae->tmp == NULL)
+		return -1;
+
+	wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */
+	if (!sae->h2e && token) {
+		wpabuf_put_buf(buf, token);
+		wpa_hexdump(MSG_DEBUG, "SAE: Anti-clogging token",
+			    wpabuf_head(token), wpabuf_len(token));
 	}
 	pos = wpabuf_put(buf, sae->tmp->prime_len);
 	if (crypto_bignum_to_bin(sae->tmp->own_commit_scalar, pos,
@@ -1781,6 +2388,123 @@ int sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static int sae_write_decoyauth_value_elems(struct sae_data *sae,
+					   struct wpabuf *buf)
+{
+	struct wpabuf *values;
+	const u8 *pos;
+	size_t remaining;
+
+	values = wpabuf_alloc(sae_write_values_len(sae));
+	if (!values)
+		return -1;
+	if (sae_write_values(sae, values) < 0) {
+		wpabuf_free(values);
+		return -1;
+	}
+
+	pos = wpabuf_head_u8(values);
+	remaining = wpabuf_len(values);
+	while (remaining) {
+		size_t frag_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, frag_len + 4);
+		WPA_PUT_BE32(wpabuf_put(buf, 4),
+			     SAE_DECOYAUTH_VALUES_VENDOR_TYPE);
+		wpabuf_put_data(buf, pos, frag_len);
+		pos += frag_len;
+		remaining -= frag_len;
+	}
+
+	wpabuf_free(values);
+	return 0;
+}
+
+
+int sae_ap_write_commit(struct sae_data *sae, struct wpabuf *buf,
+			const struct wpabuf *token, const u8 *identifier,
+			size_t identifier_len, int include_values)
+{
+	u8 *pos;
+	size_t commit_elem_len;
+	int ret = -1;
+
+	if (sae->tmp == NULL)
+		return -1;
+
+	wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */
+	if (!sae->h2e && token) {
+		wpabuf_put_buf(buf, token);
+		wpa_hexdump(MSG_DEBUG, "SAE: Anti-clogging token",
+			    wpabuf_head(token), wpabuf_len(token));
+	}
+	pos = wpabuf_put(buf, sae->tmp->prime_len);
+	if (crypto_bignum_to_bin(sae->tmp->own_commit_scalar, pos,
+				 sae->tmp->prime_len, sae->tmp->prime_len) < 0)
+		return -1;
+	wpa_hexdump(MSG_DEBUG, "SAE: own commit-scalar",
+		    pos, sae->tmp->prime_len);
+	commit_elem_len = sae->tmp->ec ? 2 * sae->tmp->prime_len :
+		sae->tmp->prime_len;
+	pos = wpabuf_put(buf, commit_elem_len);
+	os_memset(pos, 0, commit_elem_len);
+
+	if (identifier) {
+		/* Password Identifier element */
+		wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+		wpabuf_put_u8(buf, 1 + identifier_len);
+		wpabuf_put_u8(buf, WLAN_EID_EXT_PASSWORD_IDENTIFIER);
+		wpabuf_put_data(buf, identifier, identifier_len);
+		wpa_hexdump_ascii(MSG_DEBUG, "SAE: own Password Identifier",
+				  identifier, identifier_len);
+	}
+
+	if (sae->h2e && sae->tmp->own_rejected_groups) {
+		wpa_hexdump_buf(MSG_DEBUG, "SAE: own Rejected Groups",
+				sae->tmp->own_rejected_groups);
+		wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+		wpabuf_put_u8(buf,
+			      1 + wpabuf_len(sae->tmp->own_rejected_groups));
+		wpabuf_put_u8(buf, WLAN_EID_EXT_REJECTED_GROUPS);
+		wpabuf_put_buf(buf, sae->tmp->own_rejected_groups);
+	}
+
+	if (sae->h2e && token) {
+		wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+		wpabuf_put_u8(buf, 1 + wpabuf_len(token));
+		wpabuf_put_u8(buf, WLAN_EID_EXT_ANTI_CLOGGING_TOKEN);
+		wpabuf_put_buf(buf, token);
+		wpa_hexdump_buf(MSG_DEBUG,
+				"SAE: Anti-clogging token (in container)",
+				token);
+	}
+
+	if (wpa_key_mgmt_sae_ext_key(sae->akmp)) {
+		u32 suite = wpa_akm_to_suite(sae->akmp);
+
+		wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+		wpabuf_put_u8(buf, 1 + RSN_SELECTOR_LEN);
+		wpabuf_put_u8(buf, WLAN_EID_EXT_AKM_SUITE_SELECTOR);
+		RSN_SELECTOR_PUT(wpabuf_put(buf, RSN_SELECTOR_LEN), suite);
+		wpa_printf(MSG_DEBUG, "SAE: AKM Suite Selector: %08x", suite);
+		sae->own_akm_suite_selector = suite;
+	}
+
+	if (include_values && sae_write_decoyauth_value_elems(sae, buf) < 0)
+		goto out;
+
+	wpa_hexdump(MSG_DEBUG, "SAE: data", wpabuf_head(buf), wpabuf_len(buf));
+	ret = 0;
+out:
+	return ret;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group)
 {
 	if (allowed_groups) {
@@ -1863,6 +2587,58 @@ static int sae_is_akm_suite_selector_elem(const u8 *pos, const u8 *end)
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static int sae_is_decoyauth_values_elem(const u8 *pos, const u8 *end)
+{
+	return end - pos >= 6 &&
+		pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
+		pos[1] >= 4 &&
+		end - pos - 2 >= pos[1] &&
+		WPA_GET_BE32(pos + 2) == SAE_DECOYAUTH_VALUES_VENDOR_TYPE;
+}
+
+
+static int sae_commit_elem_is_zero(const u8 *commit_elem, size_t len)
+{
+	for (size_t i = 0; i < len; i++) {
+		if (commit_elem[i])
+			return 0;
+	}
+
+	return 1;
+}
+
+
+static struct wpabuf *sae_parse_decoyauth_ie_values(const u8 *pos,
+						     const u8 *end,
+						     const u8 **next)
+{
+	struct wpabuf *values;
+
+	if (!sae_is_decoyauth_values_elem(pos, end))
+		return NULL;
+
+	values = wpabuf_alloc(end - pos);
+	if (!values)
+		return NULL;
+
+	while (sae_is_decoyauth_values_elem(pos, end)) {
+		size_t frag_len = pos[1] - 4;
+
+		wpabuf_put_data(values, pos + 6, frag_len);
+		pos += 2 + pos[1];
+	}
+	if (next)
+		*next = pos;
+
+	/* Other authentication elements, such as an MLO element, may follow. */
+	return values;
+}
+
+
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 static void sae_parse_commit_token(struct sae_data *sae, const u8 **pos,
 				   const u8 *end, const u8 **token,
 				   size_t *token_len, int h2e)
@@ -1914,6 +2690,151 @@ static void sae_parse_token_container(struct sae_data *sae,
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+static u16 sae_parse_coefficient(struct sae_data *sae, const u8 **pos,
+				 const u8 *end,
+				 struct crypto_bignum **coefficient)
+{
+	if (sae->tmp->prime_len > end - *pos) {
+		wpa_printf(MSG_DEBUG, "SAE: Not enough data for coefficient");
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	*coefficient = crypto_bignum_init_set(*pos, sae->tmp->prime_len);
+	if (!*coefficient)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+	if (crypto_bignum_cmp(*coefficient, sae->tmp->prime) >= 0) {
+		wpa_printf(MSG_DEBUG, "SAE: Invalid coefficient");
+		crypto_bignum_deinit(*coefficient, 0);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	*pos += sae->tmp->prime_len;
+	return WLAN_STATUS_SUCCESS;
+}
+
+
+static u16 sae_parse_decoyauth_values(struct sae_data *sae,
+				       const u8 *password,
+				       size_t password_len,
+				       const u8 *vals,
+				       size_t vals_len)
+{
+	const u8 *pos = vals;
+	const u8 *end = vals + vals_len;
+	size_t num_passwords;
+	u8 hash[32];
+	struct crypto_bignum *hash_bn = NULL;
+	struct crypto_bignum **encoded_point = NULL;
+	struct crypto_ec_point *peer_commit_element_ecc = NULL;
+	u16 ret = WLAN_STATUS_UNSPECIFIED_FAILURE;
+	const char *fail_stage = "validate values";
+
+	if (!sae || !sae->tmp || !sae->tmp->ec || !vals || !vals_len) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA values rejected invalid state vals_len=%zu",
+			   vals_len);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+	if (vals_len % (2 * (size_t) sae->tmp->prime_len) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA values rejected length=%zu prime_len=%d",
+			   vals_len, sae->tmp->prime_len);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	num_passwords = vals_len / (2 * (size_t) sae->tmp->prime_len);
+	if (num_passwords == 0 || num_passwords > SAE_MAX_PASSWORDS) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA values rejected password count=%zu",
+			   num_passwords);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+	wpa_printf(MSG_DEBUG,
+		   "SAE: DecoyAuth STA values begin group=%d h2e=%d "
+		   "values_len=%zu prime_len=%d passwords=%zu",
+		   sae->group, sae->h2e, vals_len, sae->tmp->prime_len,
+		   num_passwords);
+
+	fail_stage = "allocate coefficient state";
+	sae_free_bignum_array(&sae->tmp->u_coefficients,
+			      sae->tmp->num_passwords, 1);
+	sae_free_bignum_array(&sae->tmp->v_coefficients,
+			      sae->tmp->num_passwords, 1);
+	sae->tmp->num_passwords = num_passwords;
+	sae->tmp->u_coefficients =
+		os_calloc(num_passwords, sizeof(*sae->tmp->u_coefficients));
+	sae->tmp->v_coefficients =
+		os_calloc(num_passwords, sizeof(*sae->tmp->v_coefficients));
+	encoded_point = os_calloc(2, sizeof(*encoded_point));
+	if (!sae->tmp->u_coefficients || !sae->tmp->v_coefficients ||
+	    !encoded_point)
+		goto fail;
+
+	fail_stage = "parse u coefficients";
+	for (size_t i = 0; i < num_passwords; i++) {
+		if (sae_parse_coefficient(sae, &pos, end,
+					  &sae->tmp->u_coefficients[i]) !=
+		    WLAN_STATUS_SUCCESS)
+			goto fail;
+	}
+	fail_stage = "parse v coefficients";
+	for (size_t i = 0; i < num_passwords; i++) {
+		if (sae_parse_coefficient(sae, &pos, end,
+					  &sae->tmp->v_coefficients[i]) !=
+		    WLAN_STATUS_SUCCESS)
+			goto fail;
+	}
+
+	fail_stage = "hash selected password";
+	if (sha256_vector(1, &password, &password_len, hash) < 0)
+		goto fail;
+	hash_bn = crypto_bignum_init_set(hash, sizeof(hash));
+	if (!hash_bn)
+		goto fail;
+
+	fail_stage = "evaluate coefficient polynomials";
+	encoded_point[0] = crypto_evaluate(sae->tmp->u_coefficients, hash_bn,
+					   num_passwords, sae->tmp->ec);
+	encoded_point[1] = crypto_evaluate(sae->tmp->v_coefficients, hash_bn,
+					   num_passwords, sae->tmp->ec);
+	if (!encoded_point[0] || !encoded_point[1])
+		goto fail;
+
+	fail_stage = "decode evaluated EC point";
+	peer_commit_element_ecc = crypto_values_to_point(encoded_point,
+							 sae->tmp->ec);
+	if (!peer_commit_element_ecc)
+		goto fail;
+
+	crypto_ec_point_deinit(sae->tmp->peer_commit_element_ecc, 0);
+	sae->tmp->peer_commit_element_ecc = peer_commit_element_ecc;
+	peer_commit_element_ecc = NULL;
+	ret = WLAN_STATUS_SUCCESS;
+	wpa_printf(MSG_DEBUG,
+		   "SAE: DecoyAuth STA values complete group=%d h2e=%d passwords=%zu",
+		   sae->group, sae->h2e, num_passwords);
+fail:
+	if (ret != WLAN_STATUS_SUCCESS)
+		wpa_printf(MSG_DEBUG,
+			   "SAE: DecoyAuth STA values failed group=%d h2e=%d "
+			   "stage=%s values_len=%zu passwords=%zu",
+			   sae->group, sae->h2e, fail_stage, vals_len,
+			   num_passwords);
+	crypto_bignum_deinit(hash_bn, 1);
+	if (encoded_point) {
+		crypto_bignum_deinit(encoded_point[0], 1);
+		crypto_bignum_deinit(encoded_point[1], 1);
+		os_free(encoded_point);
+	}
+	crypto_ec_point_deinit(peer_commit_element_ecc, 0);
+	return ret;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
+
 static u16 sae_parse_commit_scalar(struct sae_data *sae, const u8 **pos,
 				   const u8 *end)
 {
@@ -2310,6 +3231,145 @@ u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
 	return SAE_SILENTLY_DISCARD;
 }
 
+#ifdef CONFIG_SAE_DECOYAUTH
+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,
+			int *allowed_groups, int *ie_offset,
+			int *decoyauth_used)
+{
+	struct wpabuf *values = NULL;
+	const u8 *pos = data, *end = data + len;
+	const u8 *commit_elem;
+	const u8 *value_data = NULL;
+	size_t value_len = 0;
+	size_t commit_elem_len;
+	u16 res;
+
+	if (decoyauth_used)
+		*decoyauth_used = 0;
+
+	wpa_hexdump(MSG_DEBUG, "SAE: data", data, len);
+
+	/* Check Finite Cyclic Group */
+	if (end - pos < 2)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	res = sae_group_allowed(sae, allowed_groups, WPA_GET_LE16(pos));
+	if (res != WLAN_STATUS_SUCCESS)
+		return res;
+	pos += 2;
+
+	/* Optional Anti-Clogging Token */
+	sae_parse_commit_token(sae, &pos, end, token, token_len, 1);
+
+	/* commit-scalar */
+	res = sae_parse_commit_scalar(sae, &pos, end);
+	if (res != WLAN_STATUS_SUCCESS)
+		return res;
+
+	/*
+	 * DecoyAuth carries the real payload in the extension element/vendor IE
+	 * and leaves the fixed commit-element field as a placeholder.
+	 */
+	commit_elem = pos;
+	commit_elem_len = sae->tmp->dh ? sae->tmp->prime_len :
+		2 * sae->tmp->prime_len;
+	if ((size_t) (end - pos) < commit_elem_len)
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	pos += commit_elem_len;
+
+	if (ie_offset)
+		*ie_offset = pos - data;
+
+	/* Optional Password Identifier element */
+	res = sae_parse_password_identifier(sae, 1, &pos, end);
+	if (res != WLAN_STATUS_SUCCESS)
+		return res;
+
+	/* Conditional Rejected Groups element */
+	res = sae_parse_rejected_groups(sae, &pos, end);
+	if (res != WLAN_STATUS_SUCCESS)
+		return res;
+
+	/* Optional Anti-Clogging Token Container element */
+	sae_parse_token_container(sae, pos, end, token, token_len);
+
+	/* Conditional AKM Suite Selector element */
+	res = sae_parse_akm_suite_selector(sae, &pos, end);
+	if (res != WLAN_STATUS_SUCCESS)
+		return res;
+
+	if (sae->own_akm_suite_selector &&
+	    sae->own_akm_suite_selector != sae->peer_akm_suite_selector) {
+		wpa_printf(MSG_DEBUG,
+			   "SAE: AKM suite selector mismatch: own=%08x peer=%08x",
+			   sae->own_akm_suite_selector,
+			   sae->peer_akm_suite_selector);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	if (!sae->akmp) {
+		if (sae->peer_akm_suite_selector ==
+		    RSN_AUTH_KEY_MGMT_SAE_EXT_KEY)
+			sae->akmp = WPA_KEY_MGMT_SAE_EXT_KEY;
+		else if (sae->peer_akm_suite_selector ==
+			 RSN_AUTH_KEY_MGMT_FT_SAE_EXT_KEY)
+			sae->akmp = WPA_KEY_MGMT_FT_SAE_EXT_KEY;
+	}
+
+	if (end > pos && sae_is_decoyauth_values_elem(pos, end)) {
+		values = sae_parse_decoyauth_ie_values(pos, end, NULL);
+		if (!values)
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		value_data = wpabuf_head_u8(values);
+		value_len = wpabuf_len(values);
+	}
+
+	if (value_data) {
+		if (decoyauth_used)
+			*decoyauth_used = 1;
+		res = sae_parse_decoyauth_values(sae, password, password_len,
+						 value_data, value_len);
+		wpabuf_free(values);
+		if (res != WLAN_STATUS_SUCCESS)
+			return res;
+	} else {
+		const u8 *elem_pos = commit_elem;
+
+		wpabuf_free(values);
+		res = sae_parse_commit_element(sae, &elem_pos,
+					       commit_elem + commit_elem_len);
+		if (res != WLAN_STATUS_SUCCESS)
+			return res;
+	}
+
+	/*
+	 * Check whether peer-commit-scalar and PEER-COMMIT-ELEMENT are same as
+	 * the values we sent which would be evidence of a reflection attack.
+	 */
+	if (!sae->tmp->own_commit_scalar ||
+	    crypto_bignum_cmp(sae->tmp->own_commit_scalar,
+			      sae->peer_commit_scalar) != 0 ||
+	    (sae->tmp->dh &&
+	     (!sae->tmp->own_commit_element_ffc ||
+	      crypto_bignum_cmp(sae->tmp->own_commit_element_ffc,
+				sae->tmp->peer_commit_element_ffc) != 0)) ||
+	    (sae->tmp->ec &&
+	     (!sae->tmp->own_commit_element_ecc ||
+	      crypto_ec_point_cmp(sae->tmp->ec,
+				  sae->tmp->own_commit_element_ecc,
+				  sae->tmp->peer_commit_element_ecc) != 0)))
+		return WLAN_STATUS_SUCCESS; /* scalars/elements are different */
+
+	/*
+	 * This is a reflection attack - return special value to trigger caller
+	 * to silently discard the frame instead of replying with a specific
+	 * status code.
+	 */
+	return SAE_SILENTLY_DISCARD;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
 
 static int sae_cn_confirm(struct sae_data *sae, const u8 *sc,
 			  const struct crypto_bignum *scalar1,
@@ -2501,6 +3561,231 @@ int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
 }
 
 
+#ifdef CONFIG_SAE_DECOYAUTH
+int sae_ap_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
+			      int *ie_offset, int *password_idx)
+{
+	size_t hash_len;
+	size_t matched_hash_len = 0;
+	int i, j, matched_idx = -1;
+
+	if (password_idx)
+		*password_idx = -1;
+	if (!sae->tmp)
+		return -1;
+
+	for (i = 0; i < sae->tmp->num_passwords; i++) {
+		u8 verifier[SAE_MAX_HASH_LEN];
+
+		hash_len = sae->tmp->kck_lens[i];
+		sae->tmp->kck_len = hash_len;
+		os_memcpy(sae->tmp->kck, sae->tmp->kcks[i],
+			  hash_len);
+
+		if (len < 2 + hash_len) {
+			wpa_printf(MSG_DEBUG, "SAE: Too short confirm message");
+			continue;
+		}
+
+		if (sae->tmp->ec) {
+			crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1);
+			sae->tmp->pwe_ecc =
+				crypto_ec_point_init(sae->tmp->ec);
+			if (!sae->tmp->pwe_ecc)
+				continue;
+			if (crypto_ec_point_copy(sae->tmp->ec,
+						  sae->tmp->pwe_eccs[i],
+						  sae->tmp->pwe_ecc) < 0)
+				continue;
+			crypto_ec_point_deinit(
+				sae->tmp->own_commit_element_ecc, 0);
+			sae->tmp->own_commit_element_ecc =
+				crypto_ec_point_init(sae->tmp->ec);
+			if (!sae->tmp->own_commit_element_ecc)
+				continue;
+			if (crypto_ec_point_copy(
+				    sae->tmp->ec,
+				    sae->tmp->own_commit_element_eccs[i],
+				    sae->tmp->own_commit_element_ecc) < 0)
+				continue;
+		}
+
+		wpa_printf(MSG_DEBUG, "SAE: peer-send-confirm %u",
+			   WPA_GET_LE16(data));
+
+		if (!sae->peer_commit_scalar || !sae->tmp->own_commit_scalar) {
+			wpa_printf(MSG_DEBUG,
+				   "SAE: Temporary data not yet available");
+			continue;
+		}
+
+		if (sae->tmp->ec) {
+			if (!sae->tmp->peer_commit_element_ecc ||
+			    !sae->tmp->own_commit_element_ecc ||
+			    sae_cn_confirm_ecc(
+				    sae, data, sae->peer_commit_scalar,
+				    sae->tmp->peer_commit_element_ecc,
+				    sae->tmp->own_commit_scalar,
+				    sae->tmp->own_commit_element_ecc,
+				    verifier) < 0)
+				continue;
+		} else {
+			if (!sae->tmp->peer_commit_element_ffc ||
+			    !sae->tmp->own_commit_element_ffc ||
+			    sae_cn_confirm_ffc(
+				    sae, data, sae->peer_commit_scalar,
+				    sae->tmp->peer_commit_element_ffc,
+				    sae->tmp->own_commit_scalar,
+				    sae->tmp->own_commit_element_ffc,
+				    verifier) < 0)
+				return -1;
+		}
+
+		if (os_memcmp_const(verifier, data + 2, hash_len) != 0)
+			continue;
+		if (matched_idx < 0) {
+			matched_idx = i;
+			matched_hash_len = hash_len;
+		}
+	}
+
+	if (matched_idx < 0) {
+		wpa_printf(MSG_DEBUG, "SAE: Confirmation failed");
+		return -1;
+	}
+	if (password_idx)
+		*password_idx = matched_idx;
+
+	sae->tmp->kck_len = sae->tmp->kck_lens[matched_idx];
+	os_memcpy(sae->tmp->kck, sae->tmp->kcks[matched_idx],
+		  sae->tmp->kck_len);
+	sae->pmk_len = sae->tmp->pmk_lens[matched_idx];
+	os_memcpy(sae->pmk, sae->tmp->pmks[matched_idx], sae->pmk_len);
+	os_memcpy(sae->pmkid, sae->tmp->pmkids[matched_idx], SAE_PMKID_LEN);
+	if (sae->tmp->ec) {
+		crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1);
+		sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
+		if (!sae->tmp->pwe_ecc ||
+		    crypto_ec_point_copy(sae->tmp->ec,
+					  sae->tmp->pwe_eccs[matched_idx],
+					  sae->tmp->pwe_ecc) < 0)
+			return -1;
+		crypto_ec_point_deinit(sae->tmp->own_commit_element_ecc, 0);
+		sae->tmp->own_commit_element_ecc =
+			crypto_ec_point_init(sae->tmp->ec);
+		if (!sae->tmp->own_commit_element_ecc ||
+		    crypto_ec_point_copy(sae->tmp->ec,
+					  sae->tmp->own_commit_element_eccs[matched_idx],
+					  sae->tmp->own_commit_element_ecc) < 0)
+			return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "SAE: Confirmation successful");
+
+	for (j = 0; j < sae->tmp->num_passwords; j++) {
+		bin_clear_free(sae->tmp->kcks[j], SAE_MAX_HASH_LEN);
+		bin_clear_free(sae->tmp->pmks[j], SAE_PMK_LEN_MAX);
+		bin_clear_free(sae->tmp->pmkids[j], SAE_PMKID_LEN);
+		crypto_ec_point_deinit(sae->tmp->pwe_eccs[j], 1);
+		crypto_ec_point_deinit(
+			sae->tmp->own_commit_element_eccs[j], 1);
+	}
+
+	os_free(sae->tmp->kck_lens);
+	os_free(sae->tmp->kcks);
+	os_free(sae->tmp->pmk_lens);
+	os_free(sae->tmp->pmks);
+	os_free(sae->tmp->pmkids);
+	os_free(sae->tmp->pwe_eccs);
+	os_free(sae->tmp->own_commit_element_eccs);
+	sae->tmp->kck_lens = NULL;
+	sae->tmp->kcks = NULL;
+	sae->tmp->pmk_lens = NULL;
+	sae->tmp->pmks = NULL;
+	sae->tmp->pmkids = NULL;
+	sae->tmp->pwe_eccs = NULL;
+	sae->tmp->own_commit_element_eccs = NULL;
+
+#ifdef CONFIG_SAE_PK
+	if (sae_check_confirm_pk(sae, data + 2 + matched_hash_len,
+				 len - 2 - matched_hash_len) < 0)
+		return -1;
+#endif /* CONFIG_SAE_PK */
+
+	/* 2 bytes are for send-confirm, then the hash, followed by IEs */
+	if (ie_offset)
+		*ie_offset = 2 + matched_hash_len;
+
+	return 0;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
+#ifndef CONFIG_SAE_DECOYAUTH
+int sae_ap_prepare_commit_h2e(const u8 *addr1, const u8 *addr2,
+			      const u8 *ssid, size_t ssid_len,
+			      const u8 **passwords,
+			      const size_t *password_lens,
+			      int num_passwords, struct sae_data *sae)
+{
+	return -1;
+}
+
+
+int sae_ap_prepare_commit_h2e_with_pt(
+	const u8 *addr1, const u8 *addr2, const u8 *ssid, size_t ssid_len,
+	const u8 **passwords, const size_t *password_lens,
+	const struct sae_pt * const *password_pts, int num_passwords,
+	struct sae_data *sae)
+{
+	return -1;
+}
+
+
+int sae_ap_process_commit(struct sae_data *sae)
+{
+	return -1;
+}
+
+
+int sae_ap_write_commit(struct sae_data *sae, struct wpabuf *buf,
+			const struct wpabuf *token, const u8 *identifier,
+			size_t identifier_len, int include_values)
+{
+	return -1;
+}
+
+
+size_t sae_write_values_len(const struct sae_data *sae)
+{
+	return 0;
+}
+
+
+int sae_write_values(struct sae_data *sae, struct wpabuf *buf)
+{
+	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,
+			int *allowed_groups, int *ie_offset,
+			int *decoyauth_used)
+{
+	return WLAN_STATUS_UNSPECIFIED_FAILURE;
+}
+
+
+int sae_ap_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
+			 int *ie_offset, int *password_idx)
+{
+	return -1;
+}
+#endif /* CONFIG_SAE_DECOYAUTH */
+
+
 const char * sae_state_txt(enum sae_state state)
 {
 	switch (state) {
diff --git a/src/common/sae.h b/src/common/sae.h
index afde296bf..35c51a43b 100644
--- a/src/common/sae.h
+++ b/src/common/sae.h
@@ -16,7 +16,19 @@
 #define SAE_MAX_PRIME_LEN 512
 #define SAE_MAX_ECC_PRIME_LEN 66
 #define SAE_MAX_HASH_LEN 64
+#define SAE_MAX_PASSWORDS 512
 #define SAE_COMMIT_MAX_LEN (2 + 3 * SAE_MAX_PRIME_LEN + 255)
+#define SAE_DECOYAUTH_VENDOR_TYPE 0xBCB92306
+#define SAE_DECOYAUTH_VALUES_VENDOR_TYPE 0xBCB92307
+#define SAE_DECOYAUTH_VENDOR_VERSION 1
+#define SAE_DECOYAUTH_VALUES_ELEM_DATA_LEN 251
+#define SAE_DECOYAUTH_VALUES_ELEM_OVERHEAD 6
+
+enum sae_decoyauth_mode {
+	SAE_DECOYAUTH_DISABLED = 0,
+	SAE_DECOYAUTH_OPTIONAL = 1,
+	SAE_DECOYAUTH_REQUIRED = 2
+};
 #ifdef CONFIG_SAE_PK
 #define SAE_CONFIRM_MAX_LEN ((2 + SAE_MAX_HASH_LEN) + 1500)
 #else /* CONFIG_SAE_PK */
@@ -40,13 +52,25 @@ struct sae_pk {
 
 struct sae_temporary_data {
 	u8 kck[SAE_MAX_HASH_LEN];
+	u8 **kcks;
+	u8 **pmks;
+	u8 **pmkids;
 	size_t kck_len;
+	size_t *kck_lens;
+	size_t *pmk_lens;
+	int num_passwords;
+	struct crypto_bignum **u_coefficients;
+	struct crypto_bignum **v_coefficients;
+	struct crypto_bignum **scalar_coefficients;
 	struct crypto_bignum *own_commit_scalar;
 	struct crypto_bignum *own_commit_element_ffc;
 	struct crypto_ec_point *own_commit_element_ecc;
+	struct crypto_ec_point **own_commit_element_eccs;
+	struct crypto_bignum *peer_commit_scalar;
 	struct crypto_bignum *peer_commit_element_ffc;
 	struct crypto_ec_point *peer_commit_element_ecc;
 	struct crypto_ec_point *pwe_ecc;
+	struct crypto_ec_point **pwe_eccs;
 	struct crypto_bignum *pwe_ffc;
 	struct crypto_bignum *sae_rand;
 	struct crypto_ec *ec;
@@ -58,6 +82,8 @@ struct sae_temporary_data {
 	struct crypto_bignum *prime_buf;
 	struct crypto_bignum *order_buf;
 	struct wpabuf *anti_clogging_token;
+	const char *decoyauth_cache_dir;
+	unsigned int decoyauth_cache_entries;
 	u8 *pw_id;
 	size_t pw_id_len;
 	u8 *parsed_pw_id;
@@ -71,6 +97,8 @@ struct sae_temporary_data {
 	struct wpabuf *peer_rejected_groups;
 	unsigned int own_addr_higher:1;
 	unsigned int try_other_password:1;
+	unsigned int decoyauth:1;
+	unsigned int decoyauth_commit_processed:1;
 
 #ifdef CONFIG_SAE_PK
 	u8 kek[SAE_MAX_HASH_LEN];
@@ -141,19 +169,43 @@ void sae_clear_data(struct sae_data *sae);
 int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
 		       const u8 *password, size_t password_len,
 		       struct sae_data *sae);
+int sae_ap_prepare_commit_h2e(const u8 *addr1, const u8 *addr2,
+			       const u8 *ssid, size_t ssid_len,
+			       const u8 **passwords,
+			       const size_t *password_lens,
+			       int num_passwords, struct sae_data *sae);
+int sae_ap_prepare_commit_h2e_with_pt(
+	const u8 *addr1, const u8 *addr2, const u8 *ssid, size_t ssid_len,
+	const u8 **passwords, const size_t *password_lens,
+	const struct sae_pt * const *password_pts, int num_passwords,
+	struct sae_data *sae);
 int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt,
 			  const u8 *addr1, const u8 *addr2,
 			  int *rejected_groups, const struct sae_pk *pk);
 int sae_process_commit(struct sae_data *sae);
+int sae_ap_process_commit(struct sae_data *sae);
 int sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
 		     const struct wpabuf *token, const u8 *identifier,
 		     size_t identifier_len);
+int sae_ap_write_commit(struct sae_data *sae, struct wpabuf *buf,
+			const struct wpabuf *token, const u8 *identifier,
+			size_t identifier_len, int include_values);
 u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
 		     const u8 **token, size_t *token_len, int *allowed_groups,
 		     int h2e, int *ie_offset);
+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,
+			int *allowed_groups, int *ie_offset,
+			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);
 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);
+int sae_ap_check_confirm(struct sae_data *sae, const u8 *data, size_t len,
+		      int *ie_offset, int *password_idx);
 u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group);
 const char * sae_state_txt(enum sae_state state);
 size_t sae_ecc_prime_len_2_hash_len(size_t prime_len);
-- 
2.53.0




More information about the Hostap mailing list