From 0d5beedb012f1a676b48106c7a21996d83906081 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 24 Sep 2025 19:36:45 +0100 Subject: [PATCH] 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 --- .../debug/unit_tests/tests/bt/bt_test.c | 159 +++++++++++++ applications/services/bt/bt_service/bt.c | 10 +- .../services/bt/bt_service/bt_keys_storage.c | 219 ++++++++++++++---- .../services/bt/bt_service/bt_keys_storage.h | 4 + applications/services/bt/bt_settings.c | 8 +- targets/f18/api_symbols.csv | 9 +- targets/f7/api_symbols.csv | 9 +- targets/f7/ble_glue/gap.c | 26 +-- targets/f7/ble_glue/gap.h | 14 +- targets/f7/furi_hal/furi_hal_bt.c | 15 +- targets/furi_hal_include/furi_hal_bt.h | 4 + 11 files changed, 391 insertions(+), 86 deletions(-) diff --git a/applications/debug/unit_tests/tests/bt/bt_test.c b/applications/debug/unit_tests/tests/bt/bt_test.c index a0f189fdb..be7e8f1ff 100644 --- a/applications/debug/unit_tests/tests/bt/bt_test.c +++ b/applications/debug/unit_tests/tests/bt/bt_test.c @@ -4,10 +4,23 @@ #include #include +#include #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(); } diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 2eeeab286..7554d87ef 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -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"); diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 57742e8e2..0735a52b7 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -2,21 +2,92 @@ #include #include -#include -#include +#include -#define BT_KEYS_STORAGE_VERSION (0) -#define BT_KEYS_STORAGE_MAGIC (0x18) +#include + +#include +#include + +#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; +} diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index b7a127035..2033b055b 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -3,6 +3,8 @@ #include #include +#include + #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); diff --git a/applications/services/bt/bt_settings.c b/applications/services/bt/bt_settings.c index abdc97f7e..da607e540 100644 --- a/applications/services/bt/bt_settings.c +++ b/applications/services/bt/bt_settings.c @@ -2,6 +2,7 @@ #include "bt_settings_filename.h" #include + #include #include @@ -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); } } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index fbbb5d13f..2bbf81e7a 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -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, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f2b13e4a9..2370133d6 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -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, diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 70c9d1c6f..38fc5e420 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -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)); diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index b2bf0ffc1..57a0bb79d 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -6,6 +6,7 @@ #include #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); diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index 2c1a9367b..f1881f94f 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -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(¤t_config, params); - if(!gap_init(¤t_config, event_cb, context)) { + if(!gap_init(¤t_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) { diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 6da723311..1711f703d 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -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);