]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
SAE: Password identifier changing (STA)
authorJouni Malinen <jouni.malinen@oss.qualcomm.com>
Mon, 27 Oct 2025 11:44:27 +0000 (13:44 +0200)
committerJouni Malinen <j@w1.fi>
Mon, 27 Oct 2025 19:21:24 +0000 (21:21 +0200)
Add support for changing the SAE password identifier value that is sent
in SAE commit messages for privacy protection in cases where random MAC
addresses are used with per-STA (or per-user) SAE password identifiers.
At least for now, this functionality is disabled by default and needs to
be enabled with sae_password_id_change=1 in a network profile that uses
an SAE password identifier (sae_password_id=..). This mechanism might
get enabled by default in the future once the protocol specification
becomes more mature and there has been interoperability testing between
different implementations.

The implemented functionality is for the definition that were added in
IEEE P802.11bi/D2.1.

Signed-off-by: Jouni Malinen <jouni.malinen@oss.qualcomm.com>
17 files changed:
src/common/sae.c
src/common/sae.h
src/common/wpa_common.c
src/common/wpa_common.h
src/rsn_supp/wpa.c
src/rsn_supp/wpa.h
src/rsn_supp/wpa_i.h
src/rsn_supp/wpa_ie.c
src/utils/common.h
wpa_supplicant/Android.mk
wpa_supplicant/Makefile
wpa_supplicant/config.c
wpa_supplicant/config_file.c
wpa_supplicant/config_ssid.h
wpa_supplicant/sme.c
wpa_supplicant/wpa_supplicant.c
wpa_supplicant/wpas_glue.c

index 2e519676b1252ec606306d91cf12a76b25024ee6..32552772eed30aaaf7110e4cc734eb7843f16656 100644 (file)
@@ -1065,6 +1065,12 @@ sae_derive_pt_group(int group, const u8 *ssid, size_t ssid_len,
        if (!pt)
                return NULL;
 
+       if (identifier) {
+               pt->password_id = wpabuf_alloc_copy(identifier, identifier_len);
+               if (!pt->password_id)
+                       goto fail;
+       }
+
 #ifdef CONFIG_SAE_PK
        os_memcpy(pt->ssid, ssid, ssid_len);
        pt->ssid_len = ssid_len;
@@ -1281,6 +1287,7 @@ void sae_deinit_pt(struct sae_pt *pt)
                crypto_ec_point_deinit(pt->ecc_pt, 1);
                crypto_bignum_deinit(pt->ffc_pt, 1);
                crypto_ec_deinit(pt->ec);
+               wpabuf_free(pt->password_id);
                prev = pt;
                pt = pt->next;
                os_free(prev);
index 96d7f115f8d8fef14bdeeddb9d8b0e0ff4f5684c..afde296bfe76004b4657772b3b843a302d49902b 100644 (file)
@@ -103,6 +103,7 @@ struct sae_pt {
 
        const struct dh_group *dh;
        struct crypto_bignum *ffc_pt;
+       struct wpabuf *password_id;
 #ifdef CONFIG_SAE_PK
        u8 ssid[32];
        size_t ssid_len;
index 982e997a566b7d8ffc9ed77483b4d57748de4ff4..ee8084c4210e50bd0f397b5aaffffa63953001c4 100644 (file)
@@ -3523,6 +3523,15 @@ static int wpa_parse_generic(const u8 *pos, struct wpa_eapol_ie_parse *ie)
                return 0;
        }
 
+       if (left > 2 && selector == RSN_KEY_DATA_SAE_PW_IDS) {
+               ie->sae_pw_ids = p;
+               ie->sae_pw_ids_len = left;
+               wpa_hexdump_key(MSG_DEBUG,
+                               "RSN: SAE Password Identifiers in EAPOL-Key",
+                               pos, dlen);
+               return 0;
+       }
+
        if (left >= 1 && selector == WFA_KEY_DATA_IP_ADDR_REQ) {
                ie->ip_addr_req = p;
                wpa_hexdump(MSG_DEBUG, "WPA: IP Address Request in EAPOL-Key",
index 165853d104141805bce61fde5c839daaafcbadf2..92f5d5df650614f94a422127f2c80b8f36c01e9a 100644 (file)
@@ -673,6 +673,8 @@ struct wpa_eapol_ie_parse {
        size_t igtk_len;
        const u8 *bigtk;
        size_t bigtk_len;
+       const u8 *sae_pw_ids;
+       size_t sae_pw_ids_len;
        const u8 *mdie;
        size_t mdie_len;
        const u8 *ftie;
index e82421047fc24ae242c27ca462fab9bfd55d3522..f08a22aac1cdb17b1320181709ef7cff026a099b 100644 (file)
@@ -2717,6 +2717,52 @@ failed:
 }
 
 
+static void wpa_sm_sae_pw_id_change(struct wpa_sm *sm, const u8 *kde,
+                                   size_t kde_len)
+{
+       const u8 *pos = kde, *end = kde + kde_len;
+       u8 flags;
+       struct wpabuf_array *wa;
+
+       wpa_hexdump(MSG_DEBUG, "RSN: Received SAE Password Identifiers KDE",
+                   kde, kde_len);
+
+       if (end - pos < 1)
+               return;
+       flags = *pos++;
+       wpa_printf(MSG_DEBUG, "RSN: SAE Password Identifiers Flags: 0x%x",
+                  flags);
+
+       if (!sm->ctx->sae_pw_id_change)
+               return;
+
+       wa = wpabuf_array_alloc();
+       if (!wa)
+               return;
+
+       while (end > pos) {
+               u8 len;
+
+               len = *pos++;
+               if (end - pos < len) {
+                       wpa_printf(MSG_INFO,
+                                  "RSN: Truncated SAE Password Identifier Tuple");
+                       wpabuf_array_free(wa);
+                       return;
+               }
+               wpa_hexdump(MSG_DEBUG, "RSN: SAE Password Identifier",
+                           pos, len);
+               if (wpabuf_array_add(wa, wpabuf_alloc_copy(pos, len))) {
+                       wpabuf_array_free(wa);
+                       return;
+               }
+               pos += len;
+       }
+
+       sm->ctx->sae_pw_id_change(sm->ctx->ctx, wa);
+}
+
+
 static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
                                          const struct wpa_eapol_key *key,
                                          u16 ver, const u8 *key_data,
@@ -2997,6 +3043,9 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 
        if (ie.transition_disable)
                wpa_sm_transition_disable(sm, ie.transition_disable[0]);
+       if (ie.sae_pw_ids && wpa_key_mgmt_sae(sm->key_mgmt) &&
+           sm->sae_pw_id_change)
+               wpa_sm_sae_pw_id_change(sm, ie.sae_pw_ids, ie.sae_pw_ids_len);
        sm->msg_3_of_4_ok = 1;
        return;
 
@@ -5071,6 +5120,9 @@ int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param,
        case WPA_PARAM_RSN_OVERRIDE_SUPPORT:
                sm->rsn_override_support = value;
                break;
+       case WPA_PARAM_SAE_PW_ID_CHANGE:
+               sm->sae_pw_id_change = !!value;
+               break;
        default:
                break;
        }
index a33f3679c95942ceb7dd73cfcdea23082bfef2a1..acd905b71baf78d9ba6a2ec6552ea69995dd4866 100644 (file)
@@ -105,6 +105,7 @@ struct wpa_sm_ctx {
        void (*notify_pmksa_cache_entry)(void *ctx,
                                         struct rsn_pmksa_cache_entry *entry);
        void (*ssid_verified)(void *ctx);
+       void (*sae_pw_id_change)(void *ctx, struct wpabuf_array *wa);
 };
 
 
@@ -143,6 +144,7 @@ enum wpa_sm_conf_params {
        WPA_PARAM_SPP_AMSDU,
        WPA_PARAM_URNM_MFPR,
        WPA_PARAM_URNM_MFPR_X20,
+       WPA_PARAM_SAE_PW_ID_CHANGE,
 };
 
 enum wpa_rsn_override {
index 4f75e3cd7c187ea5178488b053fd81869860cb1c..9f9a032f15421d26256006d8e9367b9c16719dec 100644 (file)
@@ -115,6 +115,7 @@ struct wpa_sm {
        unsigned int prot_range_neg_x20:1;
        unsigned int ssid_protection:1;
        unsigned int spp_amsdu:1;
+       unsigned int sae_pw_id_change:1;
 
        u8 *assoc_wpa_ie; /* Own WPA/RSN IE from (Re)AssocReq */
        size_t assoc_wpa_ie_len;
index d00196743ef153955e801fb510e94881fafdb049..75349a05ec42d2e4e976b3f60193b0fc1876cc35 100644 (file)
@@ -301,7 +301,7 @@ int wpa_gen_wpa_ie(struct wpa_sm *sm, u8 *wpa_ie, size_t wpa_ie_len)
 int wpa_gen_rsnxe(struct wpa_sm *sm, u8 *rsnxe, size_t rsnxe_len)
 {
        u8 *pos = rsnxe;
-       u32 capab = 0, tmp;
+       u64 capab = 0, tmp;
        size_t flen;
 
        if (wpa_key_mgmt_sae(sm->key_mgmt) &&
@@ -326,6 +326,8 @@ int wpa_gen_rsnxe(struct wpa_sm *sm, u8 *rsnxe, size_t rsnxe_len)
                capab |= BIT(WLAN_RSNX_CAPAB_SSID_PROTECTION);
        if (sm->spp_amsdu)
                capab |= BIT(WLAN_RSNX_CAPAB_SPP_A_MSDU);
+       if (sm->sae_pw_id_change)
+               capab |= BIT_ULL(WLAN_RSNX_CAPAB_SAE_PW_ID_CHANGE);
 
        if (!capab)
                return 0; /* no supported extended RSN capabilities */
index 835818dfb9d224ae186f9ca925d599c806a0f427..52fd6cf8bc95b3ce8fb738e84a3ce734ce9e832a 100644 (file)
@@ -458,6 +458,10 @@ void perror(const char *s);
 #define BIT(x) (1U << (x))
 #endif
 
+#ifndef BIT_ULL
+#define BIT_ULL(x) (1ULL << (x))
+#endif
+
 #ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
index e09768a9d9c78c36af94e870c2b60532ec0b7372..3fa733180a9114d6b429c0ec744804738df08c13 100644 (file)
@@ -242,9 +242,9 @@ L_CFLAGS += -DCONFIG_SAE
 OBJS += src/common/sae.c
 ifdef CONFIG_SAE_PK
 L_CFLAGS += -DCONFIG_SAE_PK
-NEED_AES_SIV=y
 OBJS += src/common/sae_pk.c
 endif
+NEED_AES_SIV=y
 NEED_ECC=y
 NEED_DH_GROUPS=y
 NEED_HMAC_SHA256_KDF=y
index b72d9399e17ee0d9f37adf1ccc606a0c62acec4f..a7e15658e59e4ed2da3e0a2ab6afd3c1706a7be9 100644 (file)
@@ -276,9 +276,9 @@ CFLAGS += -DCONFIG_SAE
 OBJS += ../src/common/sae.o
 ifdef CONFIG_SAE_PK
 CFLAGS += -DCONFIG_SAE_PK
-NEED_AES_SIV=y
 OBJS += ../src/common/sae_pk.o
 endif
+NEED_AES_SIV=y
 NEED_ECC=y
 NEED_DH_GROUPS=y
 NEED_HMAC_SHA256_KDF=y
index 37381f5974054b4bc99c58469f084432940cac9e..5d1aa9697b0d980c0f8b2bbd896bcc16f03d2008 100644 (file)
@@ -666,6 +666,42 @@ static char * wpa_config_write_psk(const struct parse_data *data,
 #endif /* NO_CONFIG_WRITE */
 
 
+static int wpa_config_parse_alt_sae_password_ids(const struct parse_data *data,
+                                                struct wpa_ssid *ssid,
+                                                int line, const char *value)
+{
+       struct wpabuf *tmp;
+
+       if (!value[0]) {
+               wpabuf_array_free(ssid->alt_sae_password_ids);
+               return 0;
+       }
+
+       tmp = wpabuf_parse_bin(value);
+       if (!tmp) {
+               wpa_printf(MSG_ERROR, "Line %d: invalid alt_sae_password_ids",
+                          line);
+               return -1;
+       }
+
+       if (!ssid->alt_sae_password_ids)
+               ssid->alt_sae_password_ids = wpabuf_array_alloc();
+       if (wpabuf_array_add(ssid->alt_sae_password_ids, tmp) < 0)
+               return -1;
+       return 0;
+}
+
+
+#ifndef NO_CONFIG_WRITE
+static char *
+wpa_config_write_alt_sae_password_ids(const struct parse_data *data,
+                                     struct wpa_ssid *ssid)
+{
+       return NULL;
+}
+#endif /* NO_CONFIG_WRITE */
+
+
 static int wpa_config_parse_proto(const struct parse_data *data,
                                  struct wpa_ssid *ssid, int line,
                                  const char *value)
@@ -2554,6 +2590,7 @@ static const struct parse_data ssid_fields[] = {
        { INT(mem_only_psk) },
        { STR_KEY(sae_password) },
        { STR(sae_password_id) },
+       { FUNC(alt_sae_password_ids) },
        { INT(sae_pwe) },
        { FUNC(proto) },
        { FUNC(key_mgmt) },
@@ -2786,6 +2823,7 @@ static const struct parse_data ssid_fields[] = {
        { INT_RANGE(max_idle, 0, 65535)},
        { INT_RANGE(ssid_protection, 0, 1)},
        { INT_RANGE(rsn_overriding, 0, 2)},
+       { INT_RANGE(sae_password_id_change, 0, 1)},
 };
 
 #undef OFFSET
@@ -2958,6 +2996,7 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid)
        os_free(ssid->ext_psk);
        str_clear_free(ssid->sae_password);
        os_free(ssid->sae_password_id);
+       wpabuf_array_free(ssid->alt_sae_password_ids);
 #ifdef IEEE8021X_EAPOL
        eap_peer_config_free(&ssid->eap);
 #endif /* IEEE8021X_EAPOL */
index d2ccfa1a127f9ff1034f6fcc944018d17244bb0b..001250301788506731a64ade52db271b2ecfc336 100644 (file)
@@ -787,6 +787,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
        STR(sae_password);
        STR(sae_password_id);
        write_int(f, "sae_pwe", ssid->sae_pwe, DEFAULT_SAE_PWE);
+       INT(sae_password_id_change);
        write_proto(f, ssid);
        write_key_mgmt(f, ssid);
        INT_DEF(bg_scan_period, DEFAULT_BG_SCAN_PERIOD);
@@ -1004,7 +1005,20 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
        INT(max_idle);
        INT(ssid_protection);
        INT_DEF(rsn_overriding, RSN_OVERRIDING_NOT_SET);
-
+#ifdef CONFIG_SAE
+       if (ssid->alt_sae_password_ids) {
+               struct wpabuf_array *ids = ssid->alt_sae_password_ids;
+               unsigned int idx;
+               char hex[255 * 2 + 1];
+
+               for (idx = 0; idx < ids->num; idx++) {
+                       wpa_snprintf_hex(hex, sizeof(hex),
+                                        wpabuf_head(ids->buf[idx]),
+                                        wpabuf_len(ids->buf[idx]));
+                       fprintf(f, "\talt_sae_password_ids=%s\n", hex);
+               }
+       }
+#endif /* CONFIG_SAE */
 #undef STR
 #undef INT
 #undef INT_DEF
index 530b5e96385b7bbd69c976b9e47ce7dce832c696..3f22c44d70f8729fa7105d1529d5fa68d3909a57 100644 (file)
@@ -269,6 +269,10 @@ struct wpa_ssid {
         */
        char *sae_password_id;
 
+       struct wpabuf_array *alt_sae_password_ids;
+       unsigned int alt_sae_passwords_ids_idx;
+       bool alt_sae_passwords_ids_used;
+
        struct sae_pt *pt;
 
        /**
@@ -1336,6 +1340,10 @@ struct wpa_ssid {
         */
        int go_dik_id;
 
+       /**
+        * sae_password_id_change - Whether to use changing SAE password IDs
+        */
+       bool sae_password_id_change;
 };
 
 #endif /* CONFIG_SSID_H */
index b6e86f9c5decd81e13464fc3ba2a8c01b7fb177c..f9f5fd4a5bb6a71081a08682fdcee3e34940718d 100644 (file)
@@ -288,6 +288,11 @@ reuse_data:
                else
                        wpabuf_put_le16(buf,WLAN_STATUS_SUCCESS);
        }
+
+       if (use_pt && ssid->pt && ssid->pt->password_id) {
+               password_id = wpabuf_head(ssid->pt->password_id);
+               password_id_len = wpabuf_len(ssid->pt->password_id);
+       }
        if (sae_write_commit(&wpa_s->sme.sae, buf, wpa_s->sme.sae_token,
                             password_id, password_id_len) < 0) {
                wpabuf_free(buf);
@@ -1820,6 +1825,23 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
        if (auth_transaction == 1 &&
            status_code == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER) {
                const u8 *bssid = sa ? sa : wpa_s->pending_bssid;
+               struct wpa_ssid *ssid = wpa_s->current_ssid;
+
+               if (ssid && ssid->alt_sae_password_ids &&
+                   ssid->alt_sae_passwords_ids_used) {
+                       wpa_printf(MSG_DEBUG,
+                                  "SAE: Remove alternative password identifier (idx=%u) due to rejection",
+                                  ssid->alt_sae_passwords_ids_idx);
+                       wpabuf_array_remove(ssid->alt_sae_password_ids,
+                                           ssid->alt_sae_passwords_ids_idx);
+
+#ifndef CONFIG_NO_CONFIG_WRITE
+                       if (wpa_s->conf->update_config &&
+                           wpa_config_write(wpa_s->confname, wpa_s->conf))
+                               wpa_printf(MSG_DEBUG,
+                                          "SAE: Failed to update configuration");
+#endif /* CONFIG_NO_CONFIG_WRITE */
+               }
 
                wpa_msg(wpa_s, MSG_INFO,
                        WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER MACSTR,
index 33814273b2a51398980c5f9ce5f458d5603e8700..0bf9d91f55d467b8fdc5d6726647e30218c8e3ab 100644 (file)
@@ -2290,6 +2290,9 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s,
                                                   WPA_CIPHER_GCMP) &&
                         (wpa_s->wpa_proto & WPA_PROTO_RSN));
 
+       wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_SAE_PW_ID_CHANGE,
+                        ssid->sae_password_id && ssid->sae_password_id_change);
+
        if (!skip_default_rsne) {
                if (wpa_sm_set_assoc_wpa_ie_default(wpa_s->wpa, wpa_ie,
                                                    wpa_ie_len)) {
@@ -2677,6 +2680,7 @@ void wpa_s_setup_sae_pt(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
        const u8 *password_id = (const u8 *) ssid->sae_password_id;
        size_t password_id_len = ssid->sae_password_id ?
                os_strlen(ssid->sae_password_id) : 0;
+       struct wpabuf_array *ids;
 
        if (!groups || groups[0] <= 0)
                groups = default_groups;
@@ -2700,8 +2704,33 @@ void wpa_s_setup_sae_pt(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
                return;
        }
 
-       if (ssid->pt)
-               return; /* PT already derived */
+       ids = ssid->alt_sae_password_ids;
+       if (ids && ids->num) {
+               unsigned int idx = os_random() % ids->num;
+               struct wpabuf *id = ids->buf[idx];
+
+               password_id = wpabuf_head(id);
+               password_id_len = wpabuf_len(id);
+               wpa_hexdump(MSG_DEBUG,
+                           "SAE: Prepare PT for alternative password ID",
+                           password_id, password_id_len);
+               ssid->alt_sae_passwords_ids_idx = idx;
+               ssid->alt_sae_passwords_ids_used = true;
+       }
+
+       if (ssid->pt) {
+               if (!password_id && !ssid->pt->password_id)
+                       return; /* PT already derived for no PW ID */
+               if (password_id && ssid->pt->password_id &&
+                   password_id_len == wpabuf_len(ssid->pt->password_id) &&
+                   os_memcmp(password_id, wpabuf_head(ssid->pt->password_id),
+                             password_id_len == 0))
+                       return; /* PT already derived for same PW ID */
+
+               /* PT was derived for another password identifier */
+               sae_deinit_pt(ssid->pt);
+               ssid->pt = NULL;
+       }
        ssid->pt = sae_derive_pt(groups, ssid->ssid, ssid->ssid_len,
                                 (const u8 *) password, os_strlen(password),
                                 password_id, password_id_len);
index a1927374c7c1f2f3c43966f1693e12af9e328fc2..247dc1d7d741cf013fc95349992affeb5fd35b0f 100644 (file)
@@ -1449,6 +1449,30 @@ static void wpa_supplicant_ssid_verified(void *_wpa_s)
        wpa_msg(wpa_s, MSG_INFO, "RSN: SSID matched expected value");
 }
 
+
+static void wpa_supplicant_sae_pw_id_change(void *_wpa_s,
+                                           struct wpabuf_array *wa)
+{
+       struct wpa_supplicant *wpa_s = _wpa_s;
+       struct wpa_ssid *ssid = wpa_s->current_ssid;
+
+       wpa_msg(wpa_s, MSG_INFO, "RSN: Received %u SAE Password Identifier(s)",
+               wa->num);
+       if (!ssid) {
+               wpabuf_array_free(wa);
+               return;
+       }
+
+       wpabuf_array_free(ssid->alt_sae_password_ids);
+       ssid->alt_sae_password_ids = wa;
+
+#ifndef CONFIG_NO_CONFIG_WRITE
+       if (wpa_s->conf->update_config &&
+           wpa_config_write(wpa_s->confname, wpa_s->conf))
+               wpa_printf(MSG_DEBUG, "SAE: Failed to update configuration");
+#endif /* CONFIG_NO_CONFIG_WRITE */
+}
+
 #endif /* CONFIG_NO_WPA */
 
 
@@ -1519,6 +1543,7 @@ int wpa_supplicant_init_wpa(struct wpa_supplicant *wpa_s)
 #endif /* CONFIG_PASN */
        ctx->notify_pmksa_cache_entry = wpa_supplicant_notify_pmksa_cache_entry;
        ctx->ssid_verified = wpa_supplicant_ssid_verified;
+       ctx->sae_pw_id_change = wpa_supplicant_sae_pw_id_change;
 
        wpa_s->wpa = wpa_sm_init(ctx);
        if (wpa_s->wpa == NULL) {