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
This commit is contained in:
SteWers
2026-09-10 13:24:41 +02:00
committed by GitHub
parent d96c10dbad
commit 9068fd7313
+136 -68
View File
@@ -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 <vector>
#include <deque>
#include <string.h>
@@ -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