diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming b/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming index dd95bee9e4..221dff7355 100644 --- a/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming +++ b/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming @@ -219,4 +219,13 @@ menu "Blacklist Configuration" default 5 help Maximum number of roaming candidates to consider. This also defines the size of the blacklist. + + config ESP_WIFI_ROAMING_PREVENT_DOWNGRADE + bool "Prevent security downgrade when roaming" + default n + help + If the currently connected AP sends a "transition disable" bit, + this option will make the roaming logic ignore less secure APs. + This helps prevent security downgrades when roaming in a mixed + security environment (e.g., WPA2/WPA3). endmenu # "Blacklist Configuration" diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c index 3bff2d2ae5..4d4265ddbf 100644 --- a/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c +++ b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c @@ -605,8 +605,40 @@ static bool candidate_security_match(wifi_ap_record_t candidate) return false; } +#include "esp_wpas_glue.h" + static bool candidate_profile_match(wifi_ap_record_t candidate) { + u8 transition_disable = wpa_supplicant_get_transition_disable(); + +#if CONFIG_ESP_WIFI_ROAMING_PREVENT_DOWNGRADE + if (transition_disable & TRANSITION_DISABLE_WPA3_PERSONAL) { + if (candidate.authmode == WIFI_AUTH_WPA2_PSK) { + return false; + } + } + if (transition_disable & TRANSITION_DISABLE_ENHANCED_OPEN) { + if (candidate.authmode == WIFI_AUTH_OPEN) { + return false; + } + } + if (transition_disable & TRANSITION_DISABLE_WPA3_ENTERPRISE) { + if (candidate.authmode == WIFI_AUTH_WPA2_ENTERPRISE) { + return false; + } + } +#if TODO // application doesn't have a way to know SAE-PK enabled AP atm + if (transition_disable & TRANSITION_DISABLE_SAE_PK) { + /* This is a simplification. A more accurate check would involve + * parsing the candidate's RSN IE to see if it supports SAE-PK. + * For now, we reject all SAE APs if SAE-PK is enforced. */ + if (candidate.authmode == WIFI_AUTH_WPA3_PSK) { + return false; + } + } +#endif +#endif + return candidate_security_match(candidate); } diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.c b/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.c index 948f271397..42cbbe92d8 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.c @@ -189,4 +189,9 @@ void wpa_sm_disassociate(struct wpa_sm *sm, int reason_code) { /*check if need clear internal state and data value*/ } + +u8 wpa_supplicant_get_transition_disable(void) +{ + return wpa_sm_get_transition_disable(&gWpaSm); +} #endif diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.h b/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.h index 48ff0a6379..775c5b29f0 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.h +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpas_glue.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,4 +35,6 @@ void wpa_supplicant_transition_disable(struct wpa_sm *sm, u8 bitmap); int hostapd_send_eapol(const u8 *source, const u8 *sta_addr, const u8 *data, size_t data_len); + +u8 wpa_supplicant_get_transition_disable(void); #endif /* WPAS_GLUE_H */ diff --git a/components/wpa_supplicant/src/rsn_supp/wpa.c b/components/wpa_supplicant/src/rsn_supp/wpa.c index 75fe1f5094..4dfcd00a41 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa.c +++ b/components/wpa_supplicant/src/rsn_supp/wpa.c @@ -1340,6 +1340,7 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm, if (ie.transition_disable) { wpa_supplicant_transition_disable(sm, ie.transition_disable[0]); + sm->transition_disable = ie.transition_disable[0]; } if (sm->key_install && sm->key_info & WPA_KEY_INFO_INSTALL && sm->use_ext_key_id) { @@ -1357,6 +1358,11 @@ failed: wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED); } +u8 wpa_sm_get_transition_disable(struct wpa_sm *sm) +{ + return sm->transition_disable; +} + static int wpa_supplicant_activate_ptk(struct wpa_sm *sm) { int keylen; diff --git a/components/wpa_supplicant/src/rsn_supp/wpa_i.h b/components/wpa_supplicant/src/rsn_supp/wpa_i.h index b31aeb1e1a..b07015eede 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa_i.h +++ b/components/wpa_supplicant/src/rsn_supp/wpa_i.h @@ -86,6 +86,7 @@ struct wpa_sm { struct wpa_gtk_data gd; //used for calllback save param u16 key_info; //used for txcallback param u16 txcb_flags; + u8 transition_disable; bool ap_notify_completed_rsne; wifi_pmf_config_t pmf_cfg; u8 eapol1_count; @@ -208,6 +209,8 @@ int wpa_set_bss(char *macddr, char * bssid, u8 pairwise_cipher, u8 group_cipher, int wpa_sm_rx_eapol(u8 *src_addr, u8 *buf, u32 len); +u8 wpa_sm_get_transition_disable(struct wpa_sm *sm); + int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr, const struct wpa_eapol_key *key, struct wpa_ptk *ptk); #endif /* WPA_I_H */