From bd3f8e006c40dfc5e64dc37f3f90e9bc2c4ec5ac Mon Sep 17 00:00:00 2001 From: whitty Date: Sat, 28 Feb 2026 03:02:29 +1100 Subject: [PATCH] [esp32_ble] allow setting of min/max key_size and auth_req_mode (#7138) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/components/esp32_ble/__init__.py | 52 +++++++++++++ esphome/components/esp32_ble/ble.cpp | 74 ++++++++++++++++++- esphome/components/esp32_ble/ble.h | 26 +++++++ esphome/core/defines.h | 1 + ...nded-auth-req-params-single.esp32-idf.yaml | 6 ++ ...st-extended-auth-req-params.esp32-idf.yaml | 5 ++ 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 tests/components/esp32_ble/test-extended-auth-req-params-single.esp32-idf.yaml create mode 100644 tests/components/esp32_ble/test-extended-auth-req-params.esp32-idf.yaml diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index c0e2f78bde..8b368afc2e 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -21,6 +21,7 @@ from esphome.const import ( ) from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority import esphome.final_validate as fv +from esphome.types import ConfigType DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] @@ -188,6 +189,9 @@ def register_bt_logger(*loggers: BTLoggers) -> None: CONF_BLE_ID = "ble_id" CONF_IO_CAPABILITY = "io_capability" +CONF_AUTH_REQ_MODE = "auth_req_mode" +CONF_MAX_KEY_SIZE = "max_key_size" +CONF_MIN_KEY_SIZE = "min_key_size" CONF_ADVERTISING = "advertising" CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time" CONF_DISABLE_BT_LOGS = "disable_bt_logs" @@ -238,6 +242,18 @@ IO_CAPABILITY = { "display_yes_no": IoCapability.IO_CAP_IO, } +AuthReqMode = esp32_ble_ns.enum("AuthReqMode") +AUTH_REQ_MODE = { + "no_bond": AuthReqMode.AUTH_REQ_NO_BOND, + "bond": AuthReqMode.AUTH_REQ_BOND, + "mitm": AuthReqMode.AUTH_REQ_MITM, + "bond_mitm": AuthReqMode.AUTH_REQ_BOND_MITM, + "sc_only": AuthReqMode.AUTH_REQ_SC_ONLY, + "sc_bond": AuthReqMode.AUTH_REQ_SC_BOND, + "sc_mitm": AuthReqMode.AUTH_REQ_SC_MITM, + "sc_mitm_bond": AuthReqMode.AUTH_REQ_SC_MITM_BOND, +} + esp_power_level_t = cg.global_ns.enum("esp_power_level_t") TX_POWER_LEVELS = { @@ -258,6 +274,10 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( IO_CAPABILITY, lower=True ), + # note: no defaults so we can action them not being present + cv.Optional(CONF_AUTH_REQ_MODE): cv.enum(AUTH_REQ_MODE, lower=True), + cv.Optional(CONF_MAX_KEY_SIZE): cv.int_range(min=7, max=16), + cv.Optional(CONF_MIN_KEY_SIZE): cv.int_range(min=7, max=16), cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_ADVERTISING, default=False): cv.boolean, cv.Optional( @@ -279,6 +299,23 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) +def _validate_key_sizes(config: ConfigType) -> ConfigType: + if ( + CONF_MIN_KEY_SIZE in config + and CONF_MAX_KEY_SIZE in config + and config[CONF_MIN_KEY_SIZE] > config[CONF_MAX_KEY_SIZE] + ): + raise cv.Invalid( + f"min_key_size ({config[CONF_MIN_KEY_SIZE]}) must be " + f"less than or equal to " + f"max_key_size ({config[CONF_MAX_KEY_SIZE]})" + ) + return config + + +CONFIG_SCHEMA = cv.All(CONFIG_SCHEMA, _validate_key_sizes) + + bt_uuid16_format = "XXXX" bt_uuid32_format = "XXXXXXXX" bt_uuid128_format = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" @@ -487,6 +524,21 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) + + if ( + CONF_AUTH_REQ_MODE in config + or CONF_MAX_KEY_SIZE in config + or CONF_MIN_KEY_SIZE in config + ): + cg.add_define("ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS", None) + + if CONF_AUTH_REQ_MODE in config: + cg.add(var.set_auth_req(config[CONF_AUTH_REQ_MODE])) + if CONF_MAX_KEY_SIZE in config: + cg.add(var.set_max_key_size(config[CONF_MAX_KEY_SIZE])) + if CONF_MIN_KEY_SIZE in config: + cg.add(var.set_min_key_size(config[CONF_MIN_KEY_SIZE])) + cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) if (name := config.get(CONF_NAME)) is not None: cg.add(var.set_name(name)) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index acbe9d88fc..9d26018800 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -296,12 +296,39 @@ bool ESP32BLE::ble_setup_() { return false; } - err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t)); + err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(esp_ble_io_cap_t)); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); + ESP_LOGE(TAG, "esp_ble_gap_set_security_param iocap_mode failed: %d", err); return false; } +#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + if (this->max_key_size_) { + err = esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &(this->max_key_size_), sizeof(uint8_t)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_set_security_param max_key_size failed: %d", err); + return false; + } + } + + if (this->min_key_size_) { + err = esp_ble_gap_set_security_param(ESP_BLE_SM_MIN_KEY_SIZE, &(this->min_key_size_), sizeof(uint8_t)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_set_security_param min_key_size failed: %d", err); + return false; + } + } + + if (this->auth_req_mode_) { + err = esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &(this->auth_req_mode_.value()), + sizeof(esp_ble_auth_req_t)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_set_security_param authen_req_mode failed: %d", err); + return false; + } + } +#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + // BLE takes some time to be fully set up, 200ms should be more than enough delay(200); // NOLINT @@ -645,6 +672,7 @@ void ESP32BLE::dump_config() { io_capability_s = "invalid"; break; } + char mac_s[18]; format_mac_addr_upper(mac_address, mac_s); ESP_LOGCONFIG(TAG, @@ -652,6 +680,48 @@ void ESP32BLE::dump_config() { " MAC address: %s\n" " IO Capability: %s", mac_s, io_capability_s); + +#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + const char *auth_req_mode_s = ""; + if (this->auth_req_mode_) { + switch (this->auth_req_mode_.value()) { + case AUTH_REQ_NO_BOND: + auth_req_mode_s = "no_bond"; + break; + case AUTH_REQ_BOND: + auth_req_mode_s = "bond"; + break; + case AUTH_REQ_MITM: + auth_req_mode_s = "mitm"; + break; + case AUTH_REQ_BOND_MITM: + auth_req_mode_s = "bond_mitm"; + break; + case AUTH_REQ_SC_ONLY: + auth_req_mode_s = "sc_only"; + break; + case AUTH_REQ_SC_BOND: + auth_req_mode_s = "sc_bond"; + break; + case AUTH_REQ_SC_MITM: + auth_req_mode_s = "sc_mitm"; + break; + case AUTH_REQ_SC_MITM_BOND: + auth_req_mode_s = "sc_mitm_bond"; + break; + } + } + + ESP_LOGCONFIG(TAG, " Auth Req Mode: %s", auth_req_mode_s); + if (this->max_key_size_ && this->min_key_size_) { + ESP_LOGCONFIG(TAG, " Key Size: %u - %u", this->min_key_size_, this->max_key_size_); + } else if (this->max_key_size_) { + ESP_LOGCONFIG(TAG, " Key Size: - %u", this->max_key_size_); + } else if (this->min_key_size_) { + ESP_LOGCONFIG(TAG, " Key Size: %u - ", this->min_key_size_); + } +#endif // ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + } else { ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); } diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index f1ab81b6dc..2ce17e97be 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -52,6 +52,19 @@ enum IoCapability { IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, }; +#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS +enum AuthReqMode { + AUTH_REQ_NO_BOND = ESP_LE_AUTH_NO_BOND, + AUTH_REQ_BOND = ESP_LE_AUTH_BOND, + AUTH_REQ_MITM = ESP_LE_AUTH_REQ_MITM, + AUTH_REQ_BOND_MITM = ESP_LE_AUTH_REQ_BOND_MITM, + AUTH_REQ_SC_ONLY = ESP_LE_AUTH_REQ_SC_ONLY, + AUTH_REQ_SC_BOND = ESP_LE_AUTH_REQ_SC_BOND, + AUTH_REQ_SC_MITM = ESP_LE_AUTH_REQ_SC_MITM, + AUTH_REQ_SC_MITM_BOND = ESP_LE_AUTH_REQ_SC_MITM_BOND, +}; +#endif + enum BLEComponentState : uint8_t { /** Nothing has been initialized yet. */ BLE_COMPONENT_STATE_OFF = 0, @@ -100,6 +113,12 @@ class ESP32BLE : public Component { public: void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } +#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + void set_max_key_size(uint8_t key_size) { this->max_key_size_ = key_size; } + void set_min_key_size(uint8_t key_size) { this->min_key_size_ = key_size; } + void set_auth_req(AuthReqMode req) { this->auth_req_mode_ = (esp_ble_auth_req_t) req; } +#endif + void set_advertising_cycle_time(uint32_t advertising_cycle_time) { this->advertising_cycle_time_ = advertising_cycle_time; } @@ -209,6 +228,13 @@ class ESP32BLE : public Component { // 1-byte aligned members (grouped together to minimize padding) BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum) bool enable_on_boot_{}; // 1 byte + +#ifdef ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS + optional auth_req_mode_; + + uint8_t max_key_size_{0}; // range is 7..16, 0 is unset + uint8_t min_key_size_{0}; // range is 7..16, 0 is unset +#endif }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 673aa246fe..9b55ae93b1 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -209,6 +209,7 @@ #define ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT 2 +#define ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS #define ESPHOME_LOOP_TASK_STACK_SIZE 8192 #define USE_ESP32_CAMERA_JPEG_ENCODER #define USE_HTTP_REQUEST_RESPONSE diff --git a/tests/components/esp32_ble/test-extended-auth-req-params-single.esp32-idf.yaml b/tests/components/esp32_ble/test-extended-auth-req-params-single.esp32-idf.yaml new file mode 100644 index 0000000000..6e191c132f --- /dev/null +++ b/tests/components/esp32_ble/test-extended-auth-req-params-single.esp32-idf.yaml @@ -0,0 +1,6 @@ +esp32_ble: + io_capability: keyboard_display + # Explicitly not setting some parameters to test ifdef selection + # max_key_size: 16 + # min_key_size: 7 + auth_req_mode: sc_mitm_bond diff --git a/tests/components/esp32_ble/test-extended-auth-req-params.esp32-idf.yaml b/tests/components/esp32_ble/test-extended-auth-req-params.esp32-idf.yaml new file mode 100644 index 0000000000..f05b9bac96 --- /dev/null +++ b/tests/components/esp32_ble/test-extended-auth-req-params.esp32-idf.yaml @@ -0,0 +1,5 @@ +esp32_ble: + io_capability: keyboard_display + max_key_size: 16 + min_key_size: 7 + auth_req_mode: sc_mitm_bond