1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

BLE: improved pairing security (#4240)

* ble: use unique root security keys for new pairings after pairing reset; added migrations for existing pairing data; unit_tests: added migration tests

* bt: lower logging level

* hal: bt: updated doxygen strings

* hal: ble: Added checks for root_keys ptr

* service: ble: bt_keys_storage minor cleanup
This commit is contained in:
hedger
2025-09-24 19:36:45 +01:00
committed by GitHub
parent 30077dd512
commit 0d5beedb01
11 changed files with 391 additions and 86 deletions

View File

@@ -4,10 +4,23 @@
#include <bt/bt_service/bt_keys_storage.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
#define BT_TEST_MIGRATION_FILE_PATH EXT_PATH("unit_tests/bt_migration_test.keys")
#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
// Identity root key
static const uint8_t gap_legacy_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
// Encryption root key
static const uint8_t gap_legacy_erk[16] =
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
// Test constants for migration (matching bt_keys_storage.c)
#define BT_KEYS_STORAGE_MAGIC_TEST (0x18)
#define BT_KEYS_STORAGE_VERSION_1_TEST (1)
typedef struct {
Storage* storage;
BtKeysStorage* bt_keys_storage;
@@ -88,6 +101,134 @@ static void bt_test_keys_remove_test_file(void) {
"Can't remove test file");
}
// Helper function to create a version 0 file manually
static bool
bt_test_create_v0_file(const char* file_path, const uint8_t* nvm_data, size_t nvm_size) {
// Version 0 files use saved_struct format with magic 0x18, version 0, containing only BLE pairing data
return saved_struct_save(
file_path,
nvm_data,
nvm_size,
BT_KEYS_STORAGE_MAGIC_TEST,
0); // Version 0
}
// Helper function to verify file format version
static bool bt_test_verify_file_version(const char* file_path, uint32_t expected_version) {
uint8_t magic, version;
size_t size;
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
return false;
}
return (magic == BT_KEYS_STORAGE_MAGIC_TEST && version == expected_version);
}
// Test migration from version 0 to version 1, including root key preservation
static void bt_test_migration_v0_to_v1(void) {
// Create test NVM data
const size_t test_nvm_size = 100;
uint8_t test_nvm_data[test_nvm_size];
for(size_t i = 0; i < test_nvm_size; i++) {
test_nvm_data[i] = (uint8_t)(i & 0xFF);
}
// Create a version 0 file
mu_assert(
bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, test_nvm_data, test_nvm_size),
"Failed to create version 0 test file");
// Create BT keys storage and load the v0 file (should trigger migration)
BtKeysStorage* migration_storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
uint8_t loaded_buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
memset(loaded_buffer, 0, sizeof(loaded_buffer));
bt_keys_storage_set_ram_params(migration_storage, loaded_buffer, sizeof(loaded_buffer));
// Load should succeed and migrate v0 to v1
mu_assert(bt_keys_storage_load(migration_storage), "Failed to load and migrate v0 file");
// Verify the file is now version 1
mu_assert(
bt_test_verify_file_version(BT_TEST_MIGRATION_FILE_PATH, BT_KEYS_STORAGE_VERSION_1_TEST),
"File was not migrated to version 1");
// Verify the NVM data was preserved during migration
mu_assert(
memcmp(test_nvm_data, loaded_buffer, test_nvm_size) == 0,
"NVM data was corrupted during migration");
// Verify that legacy root keys are used after migration
const GapRootSecurityKeys* migrated_keys = bt_keys_storage_get_root_keys(migration_storage);
mu_assert(
memcmp(migrated_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) == 0,
"IRK not set to legacy after migration");
mu_assert(
memcmp(migrated_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) == 0,
"ERK not set to legacy after migration");
bt_keys_storage_free(migration_storage);
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
}
// Test that migration preserves existing pairing data and root keys are not changed on reload
static void bt_test_migration_preserves_pairings_and_keys(void) {
const size_t pairing_data_size = 200;
uint8_t pairing_data[pairing_data_size];
for(size_t i = 0; i < pairing_data_size; i++) {
pairing_data[i] = (uint8_t)((i * 7 + 42) & 0xFF);
}
mu_assert(
bt_test_create_v0_file(BT_TEST_MIGRATION_FILE_PATH, pairing_data, pairing_data_size),
"Failed to create v0 file with pairing data");
GapRootSecurityKeys keys_after_first_load;
for(int iteration = 0; iteration < 2; iteration++) {
BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
memset(buffer, 0, sizeof(buffer));
bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer));
mu_assert(bt_keys_storage_load(storage), "Failed to load on iteration");
mu_assert(
memcmp(pairing_data, buffer, pairing_data_size) == 0,
"Pairing data corrupted on iteration");
const GapRootSecurityKeys* keys = bt_keys_storage_get_root_keys(storage);
if(iteration == 0)
memcpy(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys));
else
mu_assert(
memcmp(&keys_after_first_load, keys, sizeof(GapRootSecurityKeys)) == 0,
"Root keys changed after reload");
bt_keys_storage_free(storage);
}
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
}
// Test that delete operation generates new secure keys in v1 and does not match legacy
static void bt_test_delete_generates_new_keys_and_not_legacy(void) {
BtKeysStorage* storage = bt_keys_storage_alloc(BT_TEST_MIGRATION_FILE_PATH);
uint8_t buffer[BT_TEST_NVM_RAM_BUFF_SIZE];
memset(buffer, 0x55, sizeof(buffer));
bt_keys_storage_set_ram_params(storage, buffer, sizeof(buffer));
mu_assert(bt_keys_storage_update(storage, buffer, 100), "Failed to create initial v1 file");
const GapRootSecurityKeys* original_keys = bt_keys_storage_get_root_keys(storage);
uint8_t original_keys_copy[sizeof(GapRootSecurityKeys)];
memcpy(original_keys_copy, original_keys, sizeof(original_keys_copy));
bt_keys_storage_delete(storage);
const GapRootSecurityKeys* new_keys = bt_keys_storage_get_root_keys(storage);
mu_assert(
memcmp(original_keys_copy, new_keys, sizeof(original_keys_copy)) != 0,
"Root keys were not regenerated after delete");
mu_assert(
memcmp(new_keys->irk, gap_legacy_irk, sizeof(gap_legacy_irk)) != 0,
"IRK after delete should not match legacy");
mu_assert(
memcmp(new_keys->erk, gap_legacy_erk, sizeof(gap_legacy_erk)) != 0,
"ERK after delete should not match legacy");
bt_keys_storage_free(storage);
storage_simply_remove(bt_test->storage, BT_TEST_MIGRATION_FILE_PATH);
}
MU_TEST(bt_test_keys_storage_serial_profile) {
furi_check(bt_test);
@@ -96,10 +237,28 @@ MU_TEST(bt_test_keys_storage_serial_profile) {
bt_test_keys_remove_test_file();
}
MU_TEST(bt_test_migration_v0_to_v1_test) {
furi_check(bt_test);
bt_test_migration_v0_to_v1();
}
MU_TEST(bt_test_migration_preserves_pairings_and_keys_test) {
furi_check(bt_test);
bt_test_migration_preserves_pairings_and_keys();
}
MU_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test) {
furi_check(bt_test);
bt_test_delete_generates_new_keys_and_not_legacy();
}
MU_TEST_SUITE(test_bt) {
bt_test_alloc();
MU_RUN_TEST(bt_test_keys_storage_serial_profile);
MU_RUN_TEST(bt_test_migration_v0_to_v1_test);
MU_RUN_TEST(bt_test_migration_preserves_pairings_and_keys_test);
MU_RUN_TEST(bt_test_delete_generates_new_keys_and_not_legacy_test);
bt_test_free();
}

View File

@@ -403,6 +403,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
bt->current_profile = furi_hal_bt_change_app(
message->data.profile.template,
message->data.profile.params,
bt_keys_storage_get_root_keys(bt->keys_storage),
bt_on_gap_event_callback,
bt);
if(bt->current_profile) {
@@ -458,7 +459,6 @@ static void bt_load_keys(Bt* bt) {
bt_keys_storage_load(bt->keys_storage);
bt->current_profile = NULL;
} else {
FURI_LOG_I(TAG, "Keys unchanged");
}
@@ -466,8 +466,12 @@ static void bt_load_keys(Bt* bt) {
static void bt_start_application(Bt* bt) {
if(!bt->current_profile) {
bt->current_profile =
furi_hal_bt_change_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt);
bt->current_profile = furi_hal_bt_change_app(
ble_profile_serial,
NULL,
bt_keys_storage_get_root_keys(bt->keys_storage),
bt_on_gap_event_callback,
bt);
if(!bt->current_profile) {
FURI_LOG_E(TAG, "BLE App start failed");

View File

@@ -2,21 +2,92 @@
#include <furi.h>
#include <furi_hal_bt.h>
#include <lib/toolbox/saved_struct.h>
#include <storage/storage.h>
#include <furi_hal_random.h>
#include <gap.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
#define BT_KEYS_STORAGE_VERSION (0)
#define BT_KEYS_STORAGE_MAGIC (0x18)
#define BT_KEYS_STORAGE_VERSION (1)
#define BT_KEYS_STORAGE_LEGACY_VERSION (0) // Legacy version with no root keys
#define TAG "BtKeyStorage"
// Identity root key
static const uint8_t gap_legacy_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
// Encryption root key
static const uint8_t gap_legacy_erk[16] =
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
struct BtKeysStorage {
uint8_t* nvm_sram_buff;
uint16_t nvm_sram_buff_size;
uint16_t current_size;
FuriString* file_path;
GapRootSecurityKeys root_keys;
};
typedef struct {
GapRootSecurityKeys root_keys;
uint8_t pairing_data[];
} BtKeysStorageFile;
static bool bt_keys_storage_save(BtKeysStorage* instance) {
furi_assert(instance);
size_t total_size = sizeof(BtKeysStorageFile) + instance->current_size;
BtKeysStorageFile* save_data = malloc(total_size);
memcpy(&save_data->root_keys, &instance->root_keys, sizeof(GapRootSecurityKeys));
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(save_data->pairing_data, instance->nvm_sram_buff, instance->current_size);
furi_hal_bt_nvm_sram_sem_release();
bool saved = saved_struct_save(
furi_string_get_cstr(instance->file_path),
save_data,
sizeof(GapRootSecurityKeys) + instance->current_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
free(save_data);
return saved;
}
static bool bt_keys_storage_load_keys_and_pairings(
BtKeysStorage* instance,
const uint8_t* file_data,
size_t data_size) {
if(data_size < sizeof(GapRootSecurityKeys)) {
return false;
}
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)file_data;
memcpy(&instance->root_keys, &loaded->root_keys, sizeof(GapRootSecurityKeys));
size_t ble_data_size = data_size - sizeof(GapRootSecurityKeys);
if(ble_data_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "BLE data too large for SRAM buffer");
return false;
}
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(instance->nvm_sram_buff, loaded->pairing_data, ble_data_size);
instance->current_size = ble_data_size;
furi_hal_bt_nvm_sram_sem_release();
return true;
}
static void bt_keys_storage_regenerate_root_keys(BtKeysStorage* instance) {
furi_hal_random_fill_buf(instance->root_keys.erk, sizeof(instance->root_keys.erk));
furi_hal_random_fill_buf(instance->root_keys.irk, sizeof(instance->root_keys.irk));
}
bool bt_keys_storage_delete(BtKeysStorage* instance) {
furi_assert(instance);
@@ -25,6 +96,15 @@ bool bt_keys_storage_delete(BtKeysStorage* instance) {
furi_hal_bt_stop_advertising();
delete_succeed = furi_hal_bt_clear_white_list();
FURI_LOG_I(TAG, "Root keys regen");
bt_keys_storage_regenerate_root_keys(instance);
instance->current_size = 0;
if(!bt_keys_storage_save(instance)) {
FURI_LOG_E(TAG, "Save after delete failed");
}
if(bt_is_active) {
furi_hal_bt_start_advertising();
}
@@ -42,6 +122,7 @@ BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) {
instance->file_path = furi_string_alloc();
furi_string_set_str(instance->file_path, keys_storage_path);
bt_keys_storage_regenerate_root_keys(instance);
return instance;
}
@@ -76,20 +157,31 @@ static bool bt_keys_storage_file_exists(const char* file_path) {
return ret;
}
static bool bt_keys_storage_validate_file(const char* file_path, size_t* payload_size) {
static bool bt_keys_storage_validate_file(
const char* file_path,
size_t* payload_size,
uint8_t* file_version) {
uint8_t magic, version;
size_t size;
if(!saved_struct_get_metadata(file_path, &magic, &version, &size)) {
FURI_LOG_E(TAG, "Failed to get metadata");
FURI_LOG_W(TAG, "Failed to get metadata");
return false;
} else if(magic != BT_KEYS_STORAGE_MAGIC || version != BT_KEYS_STORAGE_VERSION) {
FURI_LOG_E(TAG, "File version mismatch");
} else if(magic != BT_KEYS_STORAGE_MAGIC) {
FURI_LOG_W(TAG, "File magic mismatch");
return false;
} else if(version > BT_KEYS_STORAGE_VERSION) {
FURI_LOG_E(
TAG,
"File version %d is newer than supported version %d",
version,
BT_KEYS_STORAGE_VERSION);
return false;
}
*payload_size = size;
*file_version = version;
return true;
}
@@ -102,32 +194,45 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
do {
const char* file_path = furi_string_get_cstr(instance->file_path);
size_t payload_size;
uint8_t file_version;
if(!bt_keys_storage_file_exists(file_path)) {
FURI_LOG_W(TAG, "Missing or empty file");
is_changed = true;
break;
}
} else if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
FURI_LOG_W(TAG, "Invalid or corrupted file");
is_changed = true;
break;
}
// Early check for legacy version: always considered changed, no need to load
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
is_changed = true;
break;
}
data_buffer = malloc(payload_size);
const bool data_loaded = saved_struct_load(
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, BT_KEYS_STORAGE_VERSION);
file_path, data_buffer, payload_size, BT_KEYS_STORAGE_MAGIC, file_version);
if(!data_loaded) {
FURI_LOG_E(TAG, "Failed to load file");
break;
}
} else if(payload_size == instance->current_size) {
// At this point, it's version 1 file we have
const BtKeysStorageFile* loaded = (const BtKeysStorageFile*)data_buffer;
size_t expected_file_size = sizeof(GapRootSecurityKeys) + instance->current_size;
if(payload_size == expected_file_size) {
furi_hal_bt_nvm_sram_sem_acquire();
is_changed = memcmp(data_buffer, instance->nvm_sram_buff, payload_size);
is_changed =
memcmp(loaded->pairing_data, instance->nvm_sram_buff, instance->current_size);
furi_hal_bt_nvm_sram_sem_release();
} else {
FURI_LOG_D(TAG, "Size mismatch");
FURI_LOG_D(TAG, "NVRAM sz mismatch (v1)");
is_changed = true;
}
} while(false);
@@ -139,45 +244,59 @@ bool bt_keys_storage_is_changed(BtKeysStorage* instance) {
return is_changed;
}
static bool bt_keys_storage_load_legacy_pairings(
BtKeysStorage* instance,
const uint8_t* file_data,
size_t payload_size) {
FURI_LOG_I(TAG, "Loaded v0, upgrading to v1");
memcpy(instance->root_keys.irk, gap_legacy_irk, sizeof(instance->root_keys.irk));
memcpy(instance->root_keys.erk, gap_legacy_erk, sizeof(instance->root_keys.erk));
if(payload_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "Pairing too large");
return false;
}
furi_hal_bt_nvm_sram_sem_acquire();
memcpy(instance->nvm_sram_buff, file_data, payload_size);
instance->current_size = payload_size;
furi_hal_bt_nvm_sram_sem_release();
if(!bt_keys_storage_save(instance)) {
FURI_LOG_W(TAG, "Upgrade to v1 failed");
} else {
FURI_LOG_I(TAG, "Upgraded to v1");
}
return true;
}
bool bt_keys_storage_load(BtKeysStorage* instance) {
furi_assert(instance);
const char* file_path = furi_string_get_cstr(instance->file_path);
size_t payload_size;
uint8_t file_version;
if(!bt_keys_storage_validate_file(file_path, &payload_size, &file_version)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
return false;
}
bool loaded = false;
uint8_t* file_data = malloc(payload_size);
do {
const char* file_path = furi_string_get_cstr(instance->file_path);
// Get payload size
size_t payload_size;
if(!bt_keys_storage_validate_file(file_path, &payload_size)) {
FURI_LOG_E(TAG, "Invalid or corrupted file");
break;
} else if(payload_size > instance->nvm_sram_buff_size) {
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
if(!saved_struct_load(
file_path, file_data, payload_size, BT_KEYS_STORAGE_MAGIC, file_version)) {
FURI_LOG_E(TAG, "Failed to load");
break;
}
// Load saved data to ram
furi_hal_bt_nvm_sram_sem_acquire();
const bool data_loaded = saved_struct_load(
file_path,
instance->nvm_sram_buff,
payload_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
if(!data_loaded) {
FURI_LOG_E(TAG, "Failed to load file");
if(file_version == BT_KEYS_STORAGE_LEGACY_VERSION) {
loaded = bt_keys_storage_load_legacy_pairings(instance, file_data, payload_size);
break;
}
instance->current_size = payload_size;
loaded = true;
// Only v1 left
loaded = bt_keys_storage_load_keys_and_pairings(instance, file_data, payload_size);
} while(false);
free(file_data);
return loaded;
}
@@ -203,14 +322,8 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
instance->current_size = new_size;
furi_hal_bt_nvm_sram_sem_acquire();
bool data_updated = saved_struct_save(
furi_string_get_cstr(instance->file_path),
instance->nvm_sram_buff,
new_size,
BT_KEYS_STORAGE_MAGIC,
BT_KEYS_STORAGE_VERSION);
furi_hal_bt_nvm_sram_sem_release();
// Save using version 1 format with embedded root keys
bool data_updated = bt_keys_storage_save(instance);
if(!data_updated) {
FURI_LOG_E(TAG, "Failed to update key storage");
@@ -222,3 +335,9 @@ bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32
return updated;
}
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance) {
furi_assert(instance);
return &instance->root_keys;
}

View File

@@ -3,6 +3,8 @@
#include <stdint.h>
#include <stdbool.h>
#include <gap.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -19,6 +21,8 @@ void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint
bool bt_keys_storage_is_changed(BtKeysStorage* instance);
const GapRootSecurityKeys* bt_keys_storage_get_root_keys(BtKeysStorage* instance);
bool bt_keys_storage_load(BtKeysStorage* instance);
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);

View File

@@ -2,6 +2,7 @@
#include "bt_settings_filename.h"
#include <furi.h>
#include <storage/storage.h>
#include <toolbox/saved_struct.h>
@@ -14,12 +15,13 @@
void bt_settings_load(BtSettings* bt_settings) {
furi_assert(bt_settings);
const bool success = saved_struct_load(
const bool load_success = saved_struct_load(
BT_SETTINGS_PATH, bt_settings, sizeof(BtSettings), BT_SETTINGS_MAGIC, BT_SETTINGS_VERSION);
if(!success) {
if(!load_success) {
FURI_LOG_W(TAG, "Failed to load settings, using defaults");
memset(bt_settings, 0, sizeof(BtSettings));
bt_settings->enabled = false;
bt_settings_save(bt_settings);
}
}

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,86.1,,
Version,+,87.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
@@ -703,6 +703,7 @@ Function,+,bt_forget_bonded_devices,void,Bt*
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_free,void,BtKeysStorage*
Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage*
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_set_default_path,void,Bt*
@@ -1173,7 +1174,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool,
Function,+,furi_hal_bt_dump_state,void,FuriString*
@@ -1200,7 +1201,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void,
Function,+,furi_hal_bt_reinit,void,
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
Function,+,furi_hal_bt_start_advertising,void,
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
Function,+,furi_hal_bt_start_radio_stack,_Bool,
@@ -1740,7 +1741,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t"
Function,-,gap_extra_beacon_start,_Bool,
Function,-,gap_extra_beacon_stop,_Bool,
Function,-,gap_get_state,GapState,
Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*"
Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,-,gap_start_advertising,void,
Function,-,gap_stop_advertising,void,
Function,-,gap_thread_stop,void,
1 entry status name type params
2 Version + 86.1 87.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
703 Function + bt_keys_storage_alloc BtKeysStorage* const char*
704 Function + bt_keys_storage_delete _Bool BtKeysStorage*
705 Function + bt_keys_storage_free void BtKeysStorage*
706 Function + bt_keys_storage_get_root_keys const GapRootSecurityKeys* BtKeysStorage*
707 Function + bt_keys_storage_is_changed _Bool BtKeysStorage*
708 Function + bt_keys_storage_load _Bool BtKeysStorage*
709 Function + bt_keys_storage_set_default_path void Bt*
1174 Function + furi_hal_adc_init void
1175 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1176 Function + furi_hal_adc_release void FuriHalAdcHandle*
1177 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*
1178 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1179 Function + furi_hal_bt_clear_white_list _Bool
1180 Function + furi_hal_bt_dump_state void FuriString*
1201 Function + furi_hal_bt_reinit void
1202 Function + furi_hal_bt_set_key_storage_change_callback void BleGlueKeyStorageChangedCallback, void*
1203 Function + furi_hal_bt_start_advertising void
1204 Function + furi_hal_bt_start_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*
1205 Function + furi_hal_bt_start_packet_rx void uint8_t, uint8_t
1206 Function + furi_hal_bt_start_packet_tx void uint8_t, uint8_t, uint8_t
1207 Function + furi_hal_bt_start_radio_stack _Bool
1741 Function - gap_extra_beacon_start _Bool
1742 Function - gap_extra_beacon_stop _Bool
1743 Function - gap_get_state GapState
1744 Function - gap_init _Bool GapConfig*, GapEventCallback, void* GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*
1745 Function - gap_start_advertising void
1746 Function - gap_stop_advertising void
1747 Function - gap_thread_stop void

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,86.1,,
Version,+,87.0,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
@@ -780,6 +780,7 @@ Function,+,bt_forget_bonded_devices,void,Bt*
Function,+,bt_keys_storage_alloc,BtKeysStorage*,const char*
Function,+,bt_keys_storage_delete,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_free,void,BtKeysStorage*
Function,+,bt_keys_storage_get_root_keys,const GapRootSecurityKeys*,BtKeysStorage*
Function,+,bt_keys_storage_is_changed,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_load,_Bool,BtKeysStorage*
Function,+,bt_keys_storage_set_default_path,void,Bt*
@@ -1285,7 +1286,7 @@ Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool,
Function,+,furi_hal_bt_dump_state,void,FuriString*
@@ -1312,7 +1313,7 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void,
Function,+,furi_hal_bt_reinit,void,
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
Function,+,furi_hal_bt_start_advertising,void,
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t"
Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t"
Function,+,furi_hal_bt_start_radio_stack,_Bool,
@@ -1961,7 +1962,7 @@ Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t"
Function,-,gap_extra_beacon_start,_Bool,
Function,-,gap_extra_beacon_stop,_Bool,
Function,-,gap_get_state,GapState,
Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*"
Function,-,gap_init,_Bool,"GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*"
Function,-,gap_start_advertising,void,
Function,-,gap_stop_advertising,void,
Function,-,gap_thread_stop,void,
1 entry status name type params
2 Version + 86.1 87.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
780 Function + bt_keys_storage_alloc BtKeysStorage* const char*
781 Function + bt_keys_storage_delete _Bool BtKeysStorage*
782 Function + bt_keys_storage_free void BtKeysStorage*
783 Function + bt_keys_storage_get_root_keys const GapRootSecurityKeys* BtKeysStorage*
784 Function + bt_keys_storage_is_changed _Bool BtKeysStorage*
785 Function + bt_keys_storage_load _Bool BtKeysStorage*
786 Function + bt_keys_storage_set_default_path void Bt*
1286 Function + furi_hal_adc_init void
1287 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1288 Function + furi_hal_adc_release void FuriHalAdcHandle*
1289 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*
1290 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1291 Function + furi_hal_bt_clear_white_list _Bool
1292 Function + furi_hal_bt_dump_state void FuriString*
1313 Function + furi_hal_bt_reinit void
1314 Function + furi_hal_bt_set_key_storage_change_callback void BleGlueKeyStorageChangedCallback, void*
1315 Function + furi_hal_bt_start_advertising void
1316 Function + furi_hal_bt_start_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, const GapRootSecurityKeys*, GapEventCallback, void*
1317 Function + furi_hal_bt_start_packet_rx void uint8_t, uint8_t
1318 Function + furi_hal_bt_start_packet_tx void uint8_t, uint8_t, uint8_t
1319 Function + furi_hal_bt_start_radio_stack _Bool
1962 Function - gap_extra_beacon_start _Bool
1963 Function - gap_extra_beacon_stop _Bool
1964 Function - gap_get_state GapState
1965 Function - gap_init _Bool GapConfig*, GapEventCallback, void* GapConfig*, const GapRootSecurityKeys*, GapEventCallback, void*
1966 Function - gap_start_advertising void
1967 Function - gap_stop_advertising void
1968 Function - gap_thread_stop void

View File

@@ -51,13 +51,6 @@ typedef enum {
GapCommandKillThread,
} GapCommand;
// Identity root key
static const uint8_t gap_irk[16] =
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
// Encryption root key
static const uint8_t gap_erk[16] =
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
static Gap* gap = NULL;
static void gap_advertise_start(GapState new_state);
@@ -333,7 +326,9 @@ static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) {
gap->service.mfg_data_len += mfg_data_len;
}
static void gap_init_svc(Gap* gap) {
static void gap_init_svc(Gap* gap, const GapRootSecurityKeys* root_keys) {
furi_check(root_keys);
tBleStatus status;
uint32_t srd_bd_addr[2];
@@ -351,9 +346,9 @@ static void gap_init_svc(Gap* gap) {
aci_hal_write_config_data(
CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr);
// Set Identity root key used to derive LTK and CSRK
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk);
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, root_keys->irk);
// Set Encryption root key used to derive LTK and CSRK
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk);
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, root_keys->erk);
// Set TX Power to 0 dBm
aci_hal_set_tx_power_level(1, 0x19);
// Initialize GATT interface
@@ -535,7 +530,11 @@ static void gap_advetise_timer_callback(void* context) {
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
}
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
bool gap_init(
GapConfig* config,
const GapRootSecurityKeys* root_keys,
GapEventCallback on_event_cb,
void* context) {
if(!ble_glue_is_radio_stack_ready()) {
return false;
}
@@ -548,7 +547,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL);
// Initialization of GATT & GAP layer
gap->service.adv_name = config->adv_name;
gap_init_svc(gap);
gap_init_svc(gap, root_keys);
ble_event_dispatcher_init();
// Initialization of the GAP state
gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
@@ -573,14 +572,13 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len);
}
gap->service.adv_svc_uuid_len = 1;
if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) {
uint8_t adv_service_uid[2];
gap->service.adv_svc_uuid_len = 1;
adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff;
adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8;
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
} else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) {
gap->service.adv_svc_uuid_len = 1;
set_advertisment_service_uid(
gap->config->adv_service.Service_UUID_128,
sizeof(gap->config->adv_service.Service_UUID_128));

View File

@@ -6,6 +6,7 @@
#include <furi_hal_version.h>
#define GAP_MAC_ADDR_SIZE (6)
#define GAP_KEY_SIZE (0x10)
/*
* GAP helpers - background thread that handles BLE GAP events and advertising.
@@ -83,7 +84,18 @@ typedef struct {
GapConnectionParamsRequest conn_param;
} GapConfig;
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
typedef struct {
// Encryption Root key. Must be unique per-device (or app)
uint8_t erk[GAP_KEY_SIZE];
// Identity Root key. Used for resolving RPAs, if configured
uint8_t irk[GAP_KEY_SIZE];
} GapRootSecurityKeys;
bool gap_init(
GapConfig* config,
const GapRootSecurityKeys* root_keys,
GapEventCallback on_event_cb,
void* context);
void gap_start_advertising(void);

View File

@@ -36,6 +36,9 @@ static FuriHalBt furi_hal_bt = {
.stack = FuriHalBtStackUnknown,
};
static FuriHalBleProfileBase* current_profile = NULL;
static GapConfig current_config = {0};
void furi_hal_bt_init(void) {
FURI_LOG_I(TAG, "Start BT initialization");
furi_hal_bus_enable(FuriHalBusHSEM);
@@ -149,9 +152,6 @@ bool furi_hal_bt_is_testing_supported(void) {
}
}
static FuriHalBleProfileBase* current_profile = NULL;
static GapConfig current_config = {0};
bool furi_hal_bt_check_profile_type(
FuriHalBleProfileBase* profile,
const FuriHalBleProfileTemplate* profile_template) {
@@ -165,10 +165,12 @@ bool furi_hal_bt_check_profile_type(
FuriHalBleProfileBase* furi_hal_bt_start_app(
const FuriHalBleProfileTemplate* profile_template,
FuriHalBleProfileParams params,
const GapRootSecurityKeys* root_keys,
GapEventCallback event_cb,
void* context) {
furi_check(event_cb);
furi_check(profile_template);
furi_check(root_keys);
furi_check(current_profile == NULL);
do {
@@ -183,7 +185,7 @@ FuriHalBleProfileBase* furi_hal_bt_start_app(
profile_template->get_gap_config(&current_config, params);
if(!gap_init(&current_config, event_cb, context)) {
if(!gap_init(&current_config, root_keys, event_cb, context)) {
gap_thread_stop();
FURI_LOG_E(TAG, "Failed to init GAP");
break;
@@ -239,12 +241,11 @@ void furi_hal_bt_reinit(void) {
FuriHalBleProfileBase* furi_hal_bt_change_app(
const FuriHalBleProfileTemplate* profile_template,
FuriHalBleProfileParams profile_params,
const GapRootSecurityKeys* root_keys,
GapEventCallback event_cb,
void* context) {
furi_check(event_cb);
furi_hal_bt_reinit();
return furi_hal_bt_start_app(profile_template, profile_params, event_cb, context);
return furi_hal_bt_start_app(profile_template, profile_params, root_keys, event_cb, context);
}
bool furi_hal_bt_is_active(void) {

View File

@@ -77,6 +77,7 @@ bool furi_hal_bt_check_profile_type(
*
* @param profile_template FuriHalBleProfileTemplate instance
* @param params Parameters to pass to the profile. Can be NULL
* @param root_keys pointer to root keys
* @param event_cb GapEventCallback instance
* @param context pointer to context
*
@@ -85,6 +86,7 @@ bool furi_hal_bt_check_profile_type(
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app(
const FuriHalBleProfileTemplate* profile_template,
FuriHalBleProfileParams params,
const GapRootSecurityKeys* root_keys,
GapEventCallback event_cb,
void* context);
@@ -100,6 +102,7 @@ void furi_hal_bt_reinit(void);
* @param profile_template FuriHalBleProfileTemplate instance
* @param profile_params Parameters to pass to the profile. Can be NULL
* @param event_cb GapEventCallback instance
* @param root_keys pointer to root keys
* @param context pointer to context
*
* @return instance of profile, NULL on failure
@@ -107,6 +110,7 @@ void furi_hal_bt_reinit(void);
FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app(
const FuriHalBleProfileTemplate* profile_template,
FuriHalBleProfileParams profile_params,
const GapRootSecurityKeys* root_keys,
GapEventCallback event_cb,
void* context);