From 65a303d48f92c1706dc6f464f88c41fbc9aa5c9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 11 Nov 2025 16:39:55 -0600 Subject: [PATCH] [wifi] Add min_auth_mode configuration option (#11814) --- esphome/components/wifi/__init__.py | 42 +++++++++++++++++++ esphome/components/wifi/wifi_component.h | 8 ++++ .../wifi/wifi_component_esp8266.cpp | 13 +++++- .../wifi/wifi_component_esp_idf.cpp | 15 +++++-- tests/components/wifi/test.esp32-idf.yaml | 1 + tests/components/wifi/test.esp8266-ard.yaml | 6 ++- 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 358f920c2c..c42af23252 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation from esphome.automation import Condition import esphome.codegen as cg @@ -42,6 +44,7 @@ from esphome.const import ( CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, + Platform, PlatformFramework, ) from esphome.core import CORE, CoroPriority, HexInt, coroutine_with_priority @@ -49,10 +52,13 @@ import esphome.final_validate as fv from . import wpa2_eap +_LOGGER = logging.getLogger(__name__) + AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" +CONF_MIN_AUTH_MODE = "min_auth_mode" # Maximum number of WiFi networks that can be configured # Limited to 127 because selected_sta_index_ is int8_t in C++ @@ -70,6 +76,14 @@ WIFI_POWER_SAVE_MODES = { "LIGHT": WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT, "HIGH": WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH, } + +WifiMinAuthMode = wifi_ns.enum("WifiMinAuthMode") +WIFI_MIN_AUTH_MODES = { + "WPA": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA, + "WPA2": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA2, + "WPA3": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA3, +} +VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True) WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) @@ -187,6 +201,27 @@ def validate_variant(_): raise cv.Invalid(f"WiFi requires component esp32_hosted on {variant}") +def _apply_min_auth_mode_default(config): + """Apply platform-specific default for min_auth_mode and warn ESP8266 users.""" + # Only apply defaults for platforms that support min_auth_mode + if CONF_MIN_AUTH_MODE not in config and (CORE.is_esp8266 or CORE.is_esp32): + if CORE.is_esp8266: + _LOGGER.warning( + "The minimum WiFi authentication mode (wifi -> min_auth_mode) is not set. " + "This controls the weakest encryption your device will accept when connecting to WiFi. " + "Currently defaults to WPA (less secure), but will change to WPA2 (more secure) in 2026.6.0. " + "WPA uses TKIP encryption which has known security vulnerabilities and should be avoided. " + "WPA2 uses AES encryption which is significantly more secure. " + "To silence this warning, explicitly set min_auth_mode under 'wifi:'. " + "If your router supports WPA2 or WPA3, set 'min_auth_mode: WPA2'. " + "If your router only supports WPA, set 'min_auth_mode: WPA'." + ) + config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA") + elif CORE.is_esp32: + config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA2") + return config + + def final_validate(config): has_sta = bool(config.get(CONF_NETWORKS, True)) has_ap = CONF_AP in config @@ -287,6 +322,10 @@ CONFIG_SCHEMA = cv.All( ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional(CONF_MIN_AUTH_MODE): cv.All( + VALIDATE_WIFI_MIN_AUTH_MODE, + cv.only_on([Platform.ESP32, Platform.ESP8266]), + ), cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=8.5, max=20.5) ), @@ -311,6 +350,7 @@ CONFIG_SCHEMA = cv.All( ), } ), + _apply_min_auth_mode_default, _validate, ) @@ -420,6 +460,8 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) + if CONF_MIN_AUTH_MODE in config: + cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE])) if config[CONF_FAST_CONNECT]: cg.add_define("USE_WIFI_FAST_CONNECT") cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN])) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index e786708b08..02d6d984f1 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -225,6 +225,12 @@ enum WiFiPowerSaveMode : uint8_t { WIFI_POWER_SAVE_HIGH, }; +enum WifiMinAuthMode : uint8_t { + WIFI_MIN_AUTH_MODE_WPA = 0, + WIFI_MIN_AUTH_MODE_WPA2, + WIFI_MIN_AUTH_MODE_WPA3, +}; + #ifdef USE_ESP32 struct IDFWiFiEvent; #endif @@ -274,6 +280,7 @@ class WiFiComponent : public Component { bool is_connected(); void set_power_save_mode(WiFiPowerSaveMode power_save); + void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; } void set_output_power(float output_power) { output_power_ = output_power; } void set_passive_scan(bool passive); @@ -490,6 +497,7 @@ class WiFiComponent : public Component { // Group all 8-bit values together WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF}; WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE}; + WifiMinAuthMode min_auth_mode_{WIFI_MIN_AUTH_MODE_WPA2}; WiFiRetryPhase retry_phase_{WiFiRetryPhase::INITIAL_CONNECT}; uint8_t num_retried_{0}; // Index into sta_ array for the currently selected AP configuration (-1 = none selected) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 4e17c42f41..56e071404b 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -258,8 +258,17 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { - // Only allow auth modes with at least WPA - conf.threshold.authmode = AUTH_WPA_PSK; + // Set threshold based on configured minimum auth mode + // Note: ESP8266 doesn't support WPA3 + switch (this->min_auth_mode_) { + case WIFI_MIN_AUTH_MODE_WPA: + conf.threshold.authmode = AUTH_WPA_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA2: + case WIFI_MIN_AUTH_MODE_WPA3: // Fall back to WPA2 for ESP8266 + conf.threshold.authmode = AUTH_WPA2_PSK; + break; + } } conf.threshold.rssi = -127; #endif diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 08ecba3598..d3088c9a10 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -308,7 +308,18 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (ap.get_password().empty()) { conf.sta.threshold.authmode = WIFI_AUTH_OPEN; } else { - conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; + // Set threshold based on configured minimum auth mode + switch (this->min_auth_mode_) { + case WIFI_MIN_AUTH_MODE_WPA: + conf.sta.threshold.authmode = WIFI_AUTH_WPA_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA2: + conf.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + break; + case WIFI_MIN_AUTH_MODE_WPA3: + conf.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK; + break; + } } #ifdef USE_WIFI_WPA2_EAP @@ -347,8 +358,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // The minimum rssi to accept in the fast scan mode conf.sta.threshold.rssi = -127; - conf.sta.threshold.authmode = WIFI_AUTH_OPEN; - wifi_config_t current_conf; esp_err_t err; err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf); diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 91e235b9ce..827e4b17f7 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -2,6 +2,7 @@ psram: wifi: use_psram: true + min_auth_mode: WPA packages: - !include common.yaml diff --git a/tests/components/wifi/test.esp8266-ard.yaml b/tests/components/wifi/test.esp8266-ard.yaml index dade44d145..9cb0e3cf48 100644 --- a/tests/components/wifi/test.esp8266-ard.yaml +++ b/tests/components/wifi/test.esp8266-ard.yaml @@ -1 +1,5 @@ -<<: !include common.yaml +wifi: + min_auth_mode: WPA2 + +packages: + - !include common.yaml