From: Jouni Malinen Date: Mon, 27 Oct 2025 11:44:27 +0000 (+0200) Subject: SAE: Password identifier changing (STA) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ef228583e33187d0384ce51453c1a2b8ca3afc24;p=thirdparty%2Fhostap.git SAE: Password identifier changing (STA) 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 --- diff --git a/src/common/sae.c b/src/common/sae.c index 2e519676b..32552772e 100644 --- a/src/common/sae.c +++ b/src/common/sae.c @@ -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); diff --git a/src/common/sae.h b/src/common/sae.h index 96d7f115f..afde296bf 100644 --- a/src/common/sae.h +++ b/src/common/sae.h @@ -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; diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c index 982e997a5..ee8084c42 100644 --- a/src/common/wpa_common.c +++ b/src/common/wpa_common.c @@ -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", diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h index 165853d10..92f5d5df6 100644 --- a/src/common/wpa_common.h +++ b/src/common/wpa_common.h @@ -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; diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index e82421047..f08a22aac 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -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; } diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index a33f3679c..acd905b71 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -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 { diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index 4f75e3cd7..9f9a032f1 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -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; diff --git a/src/rsn_supp/wpa_ie.c b/src/rsn_supp/wpa_ie.c index d00196743..75349a05e 100644 --- a/src/rsn_supp/wpa_ie.c +++ b/src/rsn_supp/wpa_ie.c @@ -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 */ diff --git a/src/utils/common.h b/src/utils/common.h index 835818dfb..52fd6cf8b 100644 --- a/src/utils/common.h +++ b/src/utils/common.h @@ -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 diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index e09768a9d..3fa733180 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -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 diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index b72d9399e..a7e15658e 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -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 diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 37381f597..5d1aa9697 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -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 */ diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index d2ccfa1a1..001250301 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -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 diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 530b5e963..3f22c44d7 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -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 */ diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index b6e86f9c5..f9f5fd4a5 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -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, diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 33814273b..0bf9d91f5 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -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); diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index a1927374c..247dc1d7d 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -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) {