From 9068fd7313754b822a528fe806a189652d805561 Mon Sep 17 00:00:00 2001 From: SteWers <42718143+SteWers@users.noreply.github.com> Date: Thu, 10 Sep 2026 13:24:41 +0200 Subject: [PATCH] Store BLE security bonds in UfsJsonSettings (#25015) * Store BLE security bonds in UfsJsonSettings Store BLE security bonds in `UfsJsonSettings`, as promised in PR #25008 * Optimize LTK storage --- .../tasmota_xdrv_driver/xdrv_79_esp32_ble.ino | 204 ++++++++++++------ 1 file changed, 136 insertions(+), 68 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_79_esp32_ble.ino b/tasmota/tasmota_xdrv_driver/xdrv_79_esp32_ble.ino index 464c1dfbb..1b71f420b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_79_esp32_ble.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_79_esp32_ble.ino @@ -34,6 +34,7 @@ #ifdef ESP32 // ESP32 family only. Use define USE_HM10 for ESP8266 support #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 #ifdef USE_BLE_ESP32 +#ifdef USE_UFILESYS /* xdrv_79: @@ -151,6 +152,9 @@ i.e. the Bluetooth of the ESP can be shared without conflict. #define XDRV_79 79 +#define XDRV_79_KEY "drvset79" // Unique driver key for central filesystem +#define MAX_BOND_DEVICES 16 + #include #include #include @@ -230,8 +234,6 @@ namespace BLE_ESP32 { #define BLE_ESP32_MAXNAMELEN 32 #define BLE_ESP32_MAXALIASLEN 32 -#define BLE_STORE_DIR "/.blestore" - #define MAX_BLE_DATA_LEN 100 struct generic_sensor_t { int16_t state; @@ -652,64 +654,6 @@ const char *BLE_RESTART_BLE_REASON_CONN_LIMIT = PSTR("connect failed with connec const char *BLE_RESTART_BLE_REASON_CONN_EXISTS = PSTR("connect failed with connection exists"); const char *BLERestartBLEReason = nullptr; -// Global native hook: Filepath -static void ble_get_bond_filepath(const uint8_t* val, int type, char* out_buf, size_t buf_len) { - // Format: BLE_STORE_DIR/AABBCCDDEEFF.TYPE - snprintf(out_buf, buf_len, BLE_STORE_DIR "/%02X%02X%02X%02X%02X%02X.%03d", - val[5], val[4], val[3], val[2], val[1], val[0], type); -} - -// Global native hook: Read stored BLE data -int ble_local_store_read(int type, const union ble_store_key* key, union ble_store_value* value) { - if (type != BLE_STORE_OBJ_TYPE_PEER_SEC) return BLE_HS_ENOENT; // Only that is needed at the moment - char filepath[sizeof(BLE_STORE_DIR) + 20]; - - // Pass the raw 6-byte MAC array pointer from the internal union struct - ble_get_bond_filepath(key->sec.peer_addr.val, type, filepath, sizeof(filepath)); - - FILE* file = fopen(filepath, "r"); - if (file) { - if (fread(&value->sec, 1, sizeof(ble_store_value_sec), file) == sizeof(ble_store_value_sec)) { - fclose(file); - AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: BLE data loaded from %s", filepath); - return 0; // 0 = Success for the NimBLE controller - } - fclose(file); - } - return BLE_HS_ENOENT; // Key not found, falls back to normal pairing workflow -} - -// Global native hook: Write BLE data -int ble_local_store_write(int type, const union ble_store_value* value) { - if (type != BLE_STORE_OBJ_TYPE_PEER_SEC) return BLE_HS_ENOMEM; // Only that is needed at the moment - char filepath[sizeof(BLE_STORE_DIR) + 20]; - - // Pass the raw 6-byte MAC array pointer from the internal union struct - ble_get_bond_filepath(value->sec.peer_addr.val, type, filepath, sizeof(filepath)); - - FILE* file = fopen(filepath, "w"); - if (file) { - size_t written = fwrite(&value->sec, 1, sizeof(ble_store_value_sec), file); - fclose(file); - if (written == sizeof(ble_store_value_sec)) { - AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: BLE data saved in %s", filepath); - return 0; // 0 = Success - } - } - AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: Save BLE data failed"); - return BLE_HS_ENOMEM; // Write failed -} - -// Global API: Delete dynamic storage file -void ble_local_store_delete(const uint8_t* mac_addr) { - if (!mac_addr) return; - char filepath[sizeof(BLE_STORE_DIR) + 20]; - - // Only delete files of BLE_STORE_OBJ_TYPE_PEER_SEC as no others are needed at the moment - ble_get_bond_filepath(mac_addr, BLE_STORE_OBJ_TYPE_PEER_SEC, filepath, sizeof(filepath)); - unlink(filepath); - AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: Deleted BLE data file %s", filepath); -} /*********************************************************************************************\ * log of all devices present @@ -1249,6 +1193,132 @@ bool isDeviceInFilter(const String& deviceName) { } #endif +/*********************************************************************************************\ + * BLE storage functions +\*********************************************************************************************/ + +int BLEStoreModify(const uint8_t* peerMAC, const char* peerKeyStr) { + char xdrv_key[] = XDRV_79_KEY "ltk"; + bool first = true; + uint8_t peerAddr[6]; + memcpy(peerAddr, peerMAC, sizeof(peerAddr)); + ReverseMAC(peerAddr); + char peerAddrStr[13]; + dump(peerAddrStr, sizeof(peerAddrStr), peerAddr, sizeof(peerAddr)); + +// Read and write entries from/to settings file + String json = UfsJsonSettingsRead(xdrv_key); + String jsonOutput = "{\""; + jsonOutput += xdrv_key; + jsonOutput += "\":{\"ltkBondKeys\":["; + if (json.length()) { + JsonParser parser((char*)json.c_str()); + JsonParserObject root = parser.getRootObject(); + if (root) { + JsonParserArray ltkBondKeys = root["ltkBondKeys"]; + if (ltkBondKeys) { + int8_t i = -1; + while(ltkBondKeys[++i]) { + if (strncmp(ltkBondKeys[i].getStr(), peerAddrStr, 12)) { // not ours, just copy + if (!first) jsonOutput += ','; + jsonOutput += '"'; + jsonOutput += ltkBondKeys[i].getStr(); + jsonOutput += '"'; + first = false; + } else { + continue; // Skip when found; means remove the entry at this point + } + } + } + } + } + + // Add entry + if (peerKeyStr) { + if (!first) jsonOutput += ','; + jsonOutput += '"'; + jsonOutput += peerAddrStr; + jsonOutput += ':'; + jsonOutput += peerKeyStr; + jsonOutput += '"'; + } + + jsonOutput += "]}}"; + + AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: ltk-setting: %s", jsonOutput.c_str()); + + if (jsonOutput.indexOf("[]") != -1) { // No pair present, so no setting is needed and deleted + UfsJsonSettingsDelete(xdrv_key); + return 0; // 0 = Success for the NimBLE controller + } + + if (UfsJsonSettingsWrite(jsonOutput.c_str())) return 0; // 0 = Success for the NimBLE controller + AddLog(LOG_LEVEL_ERROR, "BLE: Saving bond keys failed"); + return BLE_HS_ENOMEM; +} + +// Global Hook: Read the 16-byte raw LTK using clean native C String slicing +int ble_local_store_read(int type, const union ble_store_key* key, union ble_store_value* value) { + AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: ble_local_store_read called"); + if (type != BLE_STORE_OBJ_TYPE_PEER_SEC) return BLE_HS_ENOENT; + if (!key->sec.peer_addr.val) return BLE_HS_ENOENT; + + char xdrv_key[] = XDRV_79_KEY "ltk"; + uint8_t peerAddr[6]; + memcpy(peerAddr, key->sec.peer_addr.val, sizeof(peerAddr)); + ReverseMAC(peerAddr); + char peerAddrStr[13]; + dump(peerAddrStr, sizeof(peerAddrStr), peerAddr, sizeof(peerAddr)); + +// Read entries from settings file + String json = UfsJsonSettingsRead(xdrv_key); + if (json.length()) { + JsonParser parser((char*)json.c_str()); + JsonParserObject root = parser.getRootObject(); + if (root) { + JsonParserArray ltkBondKeys = root["ltkBondKeys"]; + if (ltkBondKeys) { + int8_t i = -1; + while(ltkBondKeys[++i]) { + if (!strncmp(ltkBondKeys[i].getStr(), peerAddrStr, 12)) { // Entry found + const char* peerKeyStr = ltkBondKeys[i].getStr() + 13; + if (strlen(peerKeyStr) != 32 ) return BLE_HS_ENOENT; + HexToBytes(peerKeyStr, value->sec.ltk, 16); + value->sec.peer_addr.type = key->sec.peer_addr.type; + memcpy(value->sec.peer_addr.val, key->sec.peer_addr.val, 6); + value->sec.authenticated = 1; + value->sec.ltk_present = 1; + AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: Loaded Bond Key Idx: %d, MAC: %s, Key: %s", i, peerAddrStr, peerKeyStr); + return 0; // 0 = Success for the NimBLE controller + } + } + } + } + } + return BLE_HS_ENOENT; +} + +// Global Hook: Write LTK +int ble_local_store_write(int type, const union ble_store_value* value) { + AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: ble_local_store_write called"); + if (type != BLE_STORE_OBJ_TYPE_PEER_SEC) return BLE_HS_ENOMEM; // Only that is needed at the moment + if (!value->sec.peer_addr.val) return BLE_HS_ENOMEM; + + char peerKeyStr[33]; + dump(peerKeyStr, 33, value->sec.ltk, 16); + + return BLEStoreModify(value->sec.peer_addr.val, peerKeyStr); +} + +// Global Hook: Delete LTK +int ble_local_store_delete(int type, const union ble_store_key* key) { + AddLog(BLELogLevel[LOG_LEVEL_DEBUG], "BLE: ble_local_store_delete called"); + if (type != BLE_STORE_OBJ_TYPE_PEER_SEC) return BLE_HS_ENOMEM; // Only that is needed at the moment + if (!key->sec.peer_addr.val) return BLE_HS_ENOMEM; + + return BLEStoreModify(key->sec.peer_addr.val, nullptr); +} + /*********************************************************************************************\ * Advertisment details \*********************************************************************************************/ @@ -1399,7 +1469,6 @@ void postAdvertismentDetails(){ * Classes \*********************************************************************************************/ -// does not really take any action class BLESensorCallback : public NimBLEClientCallbacks { void onConnect(NimBLEClient* pClient) { #ifdef BLE_ESP32_DEBUG @@ -1842,11 +1911,10 @@ static void BLETaskStopStartNimBLE(NimBLEClient **ppClient, bool start = true){ NimBLEDevice::init("BLE_ESP32"); // --- NEUTRAL ADVANCED STORAGE ROUTING LAYER --- - // Create the physical folder BLE_STORE_DIR in the flash memory - mkdir(BLE_STORE_DIR, 0777); // Hook the dynamic directory filesystem into the native Apache MyNewT config ble_hs_cfg.store_read_cb = ble_local_store_read; ble_hs_cfg.store_write_cb = ble_local_store_write; + ble_hs_cfg.store_delete_cb = ble_local_store_delete; // Set default global security capabilities for the Bluetooth stack NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); @@ -1926,8 +1994,8 @@ static void BLEDoPairing(NimBLEClient **ppClient) { BLERunningScan = 0; // Delete old bonding - ble_local_store_delete(pairingAddress.getBase()->val); - NimBLEDevice::deleteBond(pairingAddress); // Must be executed after ble_local_store_delete + BLEStoreModify(pairingAddress.getBase()->val, nullptr); + NimBLEDevice::deleteBond(pairingAddress); // Must be executed after BLEStoreModify if (pClient->connect(pairingAddress, false, false, false)) { AddLog(LOG_LEVEL_DEBUG, "BLE: Connected for pairing. Starting crypto handshake..."); @@ -1939,7 +2007,7 @@ static void BLEDoPairing(NimBLEClient **ppClient) { pairingState = PAIRING_NONE; } } else { - AddLog(LOG_LEVEL_ERROR, "BLE: Connect for pairing failed."); + AddLog(LOG_LEVEL_ERROR, "BLE: Connect for pairing failed. Please try again."); pairingState = PAIRING_NONE; } } @@ -3990,7 +4058,7 @@ void sendExample(){ // end #ifdef BLE_ESP32_EXAMPLES #endif - -#endif +#endif // USE_UFILESYS +#endif // USE_BLE_ESP32 #endif // CONFIG_IDF_TARGET_ESP32 or CONFIG_IDF_TARGET_ESP32C3 #endif // ESP32