[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