From f2ae75667379c25a376d60d7e516b97dfd176fa4 Mon Sep 17 00:00:00 2001 From: Davis Mosenkovs Date: Thu, 3 Sep 2026 23:56:48 +0300 Subject: [PATCH] Add option for Bluetooth security Under settings - Bluetooth add BT Security with settings: * Secured - enable Bluetooth security (watch is rebooted on change); * Pairable - temporary make watch discoverable and accept new pairings. The Secured setting: * requests encryption and authentication for BLE characteristics; * allows only encrypted, authenticated (by PIN) and bonded pairing; * allows new pairing only when Pairable setting is enabled; * enables Bluetooth MAC address randomization (BLE RPA with random IRK); * removes all data fields from advertisements after pairing; * uses cryptographically strong RNG for pairing/bonding PIN; * explicitly verifies connection security for sensitive-services; * ensures the watch is bonded with no more than 1 single device; * watch gets rebooted when the Secured setting is changed. The Pairable setting: * temporary sends full advertisements and allows new pairing/bonding; * gets automatically disabled when leaving BT Security screen. This PR maintains compatibility with insecure companion apps. When the Secured setting is disabled the only behavior change is verification by the sensitive-services that Bluetooth is Enabled. The sensitive-services are: * DfuService (allows to compromise watch) * FSService (allows to compromise watch) * HeartRateService (provides data about watch user) * MotionService (provides data about watch user) --- src/CMakeLists.txt | 3 + .../ble/AlertNotificationService.cpp | 3 + .../ble/BatteryInformationService.cpp | 6 +- .../ble/BatteryInformationService.h | 4 +- src/components/ble/CurrentTimeService.cpp | 8 +- src/components/ble/CurrentTimeService.h | 5 +- src/components/ble/DfuService.cpp | 4 + src/components/ble/FSService.cpp | 4 + src/components/ble/HeartRateService.cpp | 7 +- src/components/ble/ImmediateAlertService.cpp | 2 + src/components/ble/MotionService.cpp | 9 +- src/components/ble/MusicService.cpp | 2 + src/components/ble/NimbleController.cpp | 224 ++++++++++++++---- src/components/ble/NimbleController.h | 5 + src/components/ble/SimpleWeatherService.cpp | 5 +- src/components/ble/SimpleWeatherService.h | 16 +- src/components/settings/Settings.h | 30 +++ src/displayapp/DisplayApp.cpp | 3 + src/displayapp/Messages.h | 1 + .../screens/settings/SettingBluetooth.cpp | 72 +++--- .../screens/settings/SettingBluetooth.h | 11 +- .../screens/settings/SettingBluetoothMain.cpp | 59 +++++ .../screens/settings/SettingBluetoothMain.h | 28 +++ .../settings/SettingBluetoothSecurity.cpp | 119 ++++++++++ .../settings/SettingBluetoothSecurity.h | 37 +++ src/systemtask/Messages.h | 3 +- src/systemtask/SystemTask.cpp | 12 +- 27 files changed, 571 insertions(+), 111 deletions(-) create mode 100644 src/displayapp/screens/settings/SettingBluetoothMain.cpp create mode 100644 src/displayapp/screens/settings/SettingBluetoothMain.h create mode 100644 src/displayapp/screens/settings/SettingBluetoothSecurity.cpp create mode 100644 src/displayapp/screens/settings/SettingBluetoothSecurity.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index faef4160da..c4db1de0e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -420,6 +420,8 @@ list(APPEND SOURCE_FILES displayapp/screens/settings/SettingChimes.cpp displayapp/screens/settings/SettingShakeThreshold.cpp displayapp/screens/settings/SettingBluetooth.cpp + displayapp/screens/settings/SettingBluetoothMain.cpp + displayapp/screens/settings/SettingBluetoothSecurity.cpp displayapp/screens/settings/SettingOTA.cpp ## Watch faces @@ -795,6 +797,7 @@ add_definitions(-DFREERTOS) add_definitions(-D__STACK_SIZE=1024) add_definitions(-D__HEAP_SIZE=0) add_definitions(-DMYNEWT_VAL_BLE_LL_RFMGMT_ENABLE_TIME=1500) +add_definitions(-DMYNEWT_VAL_BLE_RPA_TIMEOUT=900) add_definitions(-DLFS_CONFIG=libs/lfs_config.h) # _sbrk is purposefully not implemented so that builds fail when it is used diff --git a/src/components/ble/AlertNotificationService.cpp b/src/components/ble/AlertNotificationService.cpp index d9f28698bc..61f2cc5e4c 100644 --- a/src/components/ble/AlertNotificationService.cpp +++ b/src/components/ble/AlertNotificationService.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "components/ble/NimbleController.h" #include "components/ble/NotificationManager.h" #include "systemtask/SystemTask.h" @@ -17,6 +18,8 @@ int AlertNotificationCallback(uint16_t /*conn_handle*/, uint16_t /*attr_handle*/ } void AlertNotificationService::Init() { + systemTask.nimble().AddCharacteristicSecurity(serviceDefinition); + int res; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); diff --git a/src/components/ble/BatteryInformationService.cpp b/src/components/ble/BatteryInformationService.cpp index db7c856622..420ad41d3a 100644 --- a/src/components/ble/BatteryInformationService.cpp +++ b/src/components/ble/BatteryInformationService.cpp @@ -1,6 +1,7 @@ #include "components/ble/BatteryInformationService.h" #include #include "components/battery/BatteryController.h" +#include "components/ble/NimbleController.h" using namespace Pinetime::Controllers; @@ -12,8 +13,9 @@ int BatteryInformationServiceCallback(uint16_t /*conn_handle*/, uint16_t attr_ha return batteryInformationService->OnBatteryServiceRequested(attr_handle, ctxt); } -BatteryInformationService::BatteryInformationService(Controllers::Battery& batteryController) +BatteryInformationService::BatteryInformationService(Controllers::NimbleController& nimble, Controllers::Battery& batteryController) : batteryController {batteryController}, + nimble {nimble}, characteristicDefinition {{.uuid = &batteryLevelUuid.u, .access_cb = BatteryInformationServiceCallback, .arg = this, @@ -30,6 +32,8 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte } void BatteryInformationService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); + int res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); diff --git a/src/components/ble/BatteryInformationService.h b/src/components/ble/BatteryInformationService.h index 7f0a89adcf..8228b867a3 100644 --- a/src/components/ble/BatteryInformationService.h +++ b/src/components/ble/BatteryInformationService.h @@ -12,10 +12,11 @@ namespace Pinetime { namespace Controllers { class Battery; + class NimbleController; class BatteryInformationService { public: - BatteryInformationService(Controllers::Battery& batteryController); + BatteryInformationService(Controllers::NimbleController& nimble, Controllers::Battery& batteryController); void Init(); int OnBatteryServiceRequested(uint16_t attributeHandle, ble_gatt_access_ctxt* context); @@ -23,6 +24,7 @@ namespace Pinetime { private: Controllers::Battery& batteryController; + Controllers::NimbleController& nimble; static constexpr uint16_t batteryInformationServiceId {0x180F}; static constexpr uint16_t batteryLevelId {0x2A19}; diff --git a/src/components/ble/CurrentTimeService.cpp b/src/components/ble/CurrentTimeService.cpp index 012951cbae..0164e00284 100644 --- a/src/components/ble/CurrentTimeService.cpp +++ b/src/components/ble/CurrentTimeService.cpp @@ -1,4 +1,5 @@ #include "components/ble/CurrentTimeService.h" +#include "components/ble/NimbleController.h" #include using namespace Pinetime::Controllers; @@ -24,6 +25,8 @@ int CurrentTimeService::OnCurrentTimeServiceAccessed(struct ble_gatt_access_ctxt } void CurrentTimeService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); + int res; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); @@ -97,7 +100,7 @@ int CurrentTimeService::OnLocalTimeAccessed(struct ble_gatt_access_ctxt* ctxt) { return 0; } -CurrentTimeService::CurrentTimeService(DateTime& dateTimeController) +CurrentTimeService::CurrentTimeService(NimbleController& nimble, DateTime& dateTimeController) : characteristicDefinition { {.uuid = &ctsLtChrUuid.u, @@ -118,5 +121,6 @@ CurrentTimeService::CurrentTimeService(DateTime& dateTimeController) .characteristics = characteristicDefinition}, {0}, }, - m_dateTimeController {dateTimeController} { + m_dateTimeController {dateTimeController}, + nimble{nimble} { } diff --git a/src/components/ble/CurrentTimeService.h b/src/components/ble/CurrentTimeService.h index bec75a2bd4..9cc7f87ade 100644 --- a/src/components/ble/CurrentTimeService.h +++ b/src/components/ble/CurrentTimeService.h @@ -11,9 +11,11 @@ namespace Pinetime { namespace Controllers { + class NimbleController; + class CurrentTimeService { public: - CurrentTimeService(DateTime& dateTimeController); + CurrentTimeService(NimbleController& nimble, DateTime& dateTimeController); void Init(); int OnCurrentTimeServiceAccessed(struct ble_gatt_access_ctxt* ctxt); @@ -52,6 +54,7 @@ namespace Pinetime { } CtsLocalTimeData; DateTime& m_dateTimeController; + NimbleController& nimble; }; } } diff --git a/src/components/ble/DfuService.cpp b/src/components/ble/DfuService.cpp index ad9c99e9e0..cdbf898e34 100644 --- a/src/components/ble/DfuService.cpp +++ b/src/components/ble/DfuService.cpp @@ -71,6 +71,8 @@ DfuService::DfuService(Pinetime::System::SystemTask& systemTask, } void DfuService::Init() { + systemTask.nimble().AddCharacteristicSecurity(serviceDefinition); + int res; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); @@ -90,6 +92,8 @@ int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandl systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification); return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; } + if (!systemTask.nimble().IsConnSecurityOK()) + return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; #endif if (bleController.IsFirmwareUpdating()) { diff --git a/src/components/ble/FSService.cpp b/src/components/ble/FSService.cpp index 721ed297c5..2585ac42eb 100644 --- a/src/components/ble/FSService.cpp +++ b/src/components/ble/FSService.cpp @@ -42,6 +42,8 @@ FSService::FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Control } void FSService::Init() { + systemTask.nimble().AddCharacteristicSecurity(serviceDefinition); + int res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); @@ -61,6 +63,8 @@ int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attribut systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification); return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; } + if (!systemTask.nimble().IsConnSecurityOK()) + return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; #endif if (attributeHandle == versionCharacteristicHandle) { diff --git a/src/components/ble/HeartRateService.cpp b/src/components/ble/HeartRateService.cpp index d34dbf83eb..a0360b5ea3 100644 --- a/src/components/ble/HeartRateService.cpp +++ b/src/components/ble/HeartRateService.cpp @@ -37,6 +37,8 @@ HeartRateService::HeartRateService(NimbleController& nimble, Controllers::HeartR } void HeartRateService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); + int res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); @@ -46,6 +48,9 @@ void HeartRateService::Init() { } int HeartRateService::OnHeartRateRequested(uint16_t attributeHandle, ble_gatt_access_ctxt* context) { + if (!nimble.IsConnSecurityOK()) + return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; + if (attributeHandle == heartRateMeasurementHandle) { NRF_LOG_INFO("HEARTRATE : handle = %d", heartRateMeasurementHandle); uint8_t buffer[2] = {0, heartRateController.HeartRate()}; // [0] = flags, [1] = hr value @@ -65,7 +70,7 @@ void HeartRateService::OnNewHeartRateValue(uint8_t heartRateValue) { uint16_t connectionHandle = nimble.connHandle(); - if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) { + if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE || !nimble.IsConnSecurityOK()) { return; } diff --git a/src/components/ble/ImmediateAlertService.cpp b/src/components/ble/ImmediateAlertService.cpp index e25e018f92..eaedf366fa 100644 --- a/src/components/ble/ImmediateAlertService.cpp +++ b/src/components/ble/ImmediateAlertService.cpp @@ -48,6 +48,8 @@ ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask& syste } void ImmediateAlertService::Init() { + systemTask.nimble().AddCharacteristicSecurity(serviceDefinition); + int res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); diff --git a/src/components/ble/MotionService.cpp b/src/components/ble/MotionService.cpp index 44ad755f10..924b3100b3 100644 --- a/src/components/ble/MotionService.cpp +++ b/src/components/ble/MotionService.cpp @@ -51,6 +51,8 @@ MotionService::MotionService(NimbleController& nimble, Controllers::MotionContro } void MotionService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); + int res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); @@ -60,6 +62,9 @@ void MotionService::Init() { } int MotionService::OnStepCountRequested(uint16_t attributeHandle, ble_gatt_access_ctxt* context) { + if (!nimble.IsConnSecurityOK()) + return BLE_ATT_ERR_INSUFFICIENT_AUTHEN; + if (attributeHandle == stepCountHandle) { NRF_LOG_INFO("Motion-stepcount : handle = %d", stepCountHandle); uint32_t buffer = motionController.NbSteps(); @@ -86,7 +91,7 @@ void MotionService::OnNewStepCountValue(uint32_t stepCount) { uint16_t connectionHandle = nimble.connHandle(); - if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) { + if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE || !nimble.IsConnSecurityOK()) { return; } @@ -103,7 +108,7 @@ void MotionService::OnNewMotionValues(int16_t x, int16_t y, int16_t z) { uint16_t connectionHandle = nimble.connHandle(); - if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) { + if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE || !nimble.IsConnSecurityOK()) { return; } diff --git a/src/components/ble/MusicService.cpp b/src/components/ble/MusicService.cpp index 43cbec70d6..3e8958029d 100644 --- a/src/components/ble/MusicService.cpp +++ b/src/components/ble/MusicService.cpp @@ -116,6 +116,8 @@ Pinetime::Controllers::MusicService::MusicService(Pinetime::Controllers::NimbleC } void Pinetime::Controllers::MusicService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); + uint8_t res = 0; res = ble_gatts_count_cfg(serviceDefinition); ASSERT(res == 0); diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index 5059007ab9..fabaf96909 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -7,6 +7,7 @@ #include #include #include +#include <../src/ble_hs_priv.h> #include #include #include @@ -41,10 +42,10 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, currentTimeClient {dateTimeController}, anService {systemTask, notificationManager}, alertNotificationClient {systemTask, notificationManager}, - currentTimeService {dateTimeController}, + currentTimeService {*this, dateTimeController}, musicService {*this}, - weatherService {dateTimeController}, - batteryInformationService {batteryController}, + weatherService {*this, dateTimeController}, + batteryInformationService {*this, batteryController}, immediateAlertService {systemTask, notificationManager}, heartRateService {*this, heartRateController}, motionService {*this, motionController}, @@ -77,6 +78,17 @@ void NimbleController::Init() { vTaskDelay(10); } + if (systemTask.GetSettings().GetBleSecured()) { + ble_hs_cfg.sm_sc = 1; + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_bonding = 1; + ble_hs_cfg.sm_io_cap = BLE_HS_IO_DISPLAY_ONLY; + ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; + ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; + + RandomizeOurIRK(); + } + nptr = this; ble_hs_cfg.reset_cb = nimble_on_reset; ble_hs_cfg.sync_cb = nimble_on_sync; @@ -102,7 +114,7 @@ void NimbleController::Init() { int rc; rc = ble_hs_util_ensure_addr(0); ASSERT(rc == 0); - rc = ble_hs_id_infer_auto(0, &addrType); + rc = ble_hs_id_infer_auto(systemTask.GetSettings().GetBleSecured() ? 1 : 0, &addrType); ASSERT(rc == 0); rc = ble_svc_gap_device_name_set(deviceName); ASSERT(rc == 0); @@ -136,17 +148,50 @@ void NimbleController::Init() { StartAdvertising(); } +void NimbleController::AddCharacteristicSecurity(const struct ble_gatt_svc_def* svcs) { + if (!systemTask.GetSettings().GetBleSecured()) + return; + + struct ble_gatt_chr_def* chrs; + for (int si = 0; svcs[si].type != 0; si++) { + chrs = const_cast(svcs[si].characteristics); + for (int ci = 0; chrs[ci].uuid != NULL; ci++) { + if (chrs[ci].flags & BLE_GATT_CHR_F_READ) { + chrs[ci].flags |= BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN; + } + if ((chrs[ci].flags & BLE_GATT_CHR_F_WRITE) || (chrs[ci].flags & BLE_GATT_CHR_F_WRITE_NO_RSP)) { + chrs[ci].flags |= BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_WRITE_AUTHEN; + } + chrs[ci].min_key_size = 16; + } + } +} + +bool NimbleController::IsConnSecurityOK() { +#ifndef PINETIME_IS_RECOVERY + if (!bleController.IsRadioEnabled() || !systemTask.GetSettings().GetBleRadioEnabled()) + return false; + + if (!systemTask.GetSettings().GetBleSecured()) + return true; + + if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) + return false; + + struct ble_gap_conn_desc desc; + return (ble_gap_conn_find(connectionHandle, &desc) == 0 && desc.sec_state.encrypted && desc.sec_state.authenticated && + desc.sec_state.bonded && desc.sec_state.key_size >= 16); +#else + return true; +#endif +} + void NimbleController::StartAdvertising() { struct ble_gap_adv_params adv_params; - struct ble_hs_adv_fields fields; - struct ble_hs_adv_fields rsp_fields; + int rc; memset(&adv_params, 0, sizeof(adv_params)); - memset(&fields, 0, sizeof(fields)); - memset(&rsp_fields, 0, sizeof(rsp_fields)); - adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; - adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; /* fast advertise for 30 sec */ if (fastAdvCount < 15) { adv_params.itvl_min = 32; @@ -157,25 +202,59 @@ void NimbleController::StartAdvertising() { adv_params.itvl_max = 1651; } - fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; - fields.uuids16 = &HeartRateService::heartRateServiceUuid; - fields.num_uuids16 = 1; - fields.uuids16_is_complete = 1; - fields.uuids128 = &DfuService::serviceUuid; - fields.num_uuids128 = 1; - fields.uuids128_is_complete = 1; - fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + if (systemTask.GetSettings().GetBlePairingAllowed()) { + struct ble_hs_adv_fields fields; + struct ble_hs_adv_fields rsp_fields; + memset(&fields, 0, sizeof(fields)); + memset(&rsp_fields, 0, sizeof(rsp_fields)); - rsp_fields.name = reinterpret_cast(deviceName); - rsp_fields.name_len = strlen(deviceName); - rsp_fields.name_is_complete = 1; + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; - int rc; - rc = ble_gap_adv_set_fields(&fields); - ASSERT(rc == 0); + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + fields.uuids16 = &HeartRateService::heartRateServiceUuid; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + fields.uuids128 = &DfuService::serviceUuid; + fields.num_uuids128 = 1; + fields.uuids128_is_complete = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; - rc = ble_gap_adv_rsp_set_fields(&rsp_fields); - ASSERT(rc == 0); + rsp_fields.name = reinterpret_cast(deviceName); + rsp_fields.name_len = strlen(deviceName); + rsp_fields.name_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + ASSERT(rc == 0); + + rc = ble_gap_adv_rsp_set_fields(&rsp_fields); + ASSERT(rc == 0); + + } else { + ble_addr_t peer_addrs[2]; + int num_peers; + + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_NON; + adv_params.filter_policy = BLE_HCI_ADV_FILT_BOTH; + + rc = ble_store_util_bonded_peers(peer_addrs, &num_peers, 2); + if (rc != 0 || num_peers != 1) { + NRF_LOG_INFO("No or multiple bonds, resetting"); + ClearBonds(); + RandomizeOurIRK(); + return; + } + + rc = ble_gap_adv_set_data(NULL, 0); + ASSERT(rc == 0); + + rc = ble_gap_adv_rsp_set_data(NULL, 0); + ASSERT(rc == 0); + + rc = ble_gap_wl_set(&peer_addrs[0], 1); + ASSERT(rc == 0); + } rc = ble_gap_adv_start(addrType, NULL, 2000, &adv_params, GAPEventCallback, this); ASSERT(rc == 0); @@ -205,6 +284,9 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { fastAdvCount = 0; StartAdvertising(); } else { + if (systemTask.GetSettings().GetBleSecured()) { + ble_gap_security_initiate(event->connect.conn_handle); + } connectionHandle = event->connect.conn_handle; bleController.Connect(); systemTask.PushMessage(Pinetime::System::Messages::BleConnected); @@ -217,7 +299,9 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { NRF_LOG_INFO("Disconnect event : BLE_GAP_EVENT_DISCONNECT"); NRF_LOG_INFO("disconnect reason=%d", event->disconnect.reason); - if (event->disconnect.conn.sec_state.bonded) { + if ((!systemTask.GetSettings().GetBleSecured() && event->disconnect.conn.sec_state.bonded) || + (event->disconnect.conn.sec_state.encrypted && event->disconnect.conn.sec_state.authenticated && + event->disconnect.conn.sec_state.bonded && event->disconnect.conn.sec_state.key_size >= 16)) { PersistBond(event->disconnect.conn); } @@ -252,8 +336,13 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { NRF_LOG_INFO("Security event : BLE_GAP_EVENT_ENC_CHANGE"); NRF_LOG_INFO("encryption change event; status=%0X ", event->enc_change.status); - if (event->enc_change.status == 0) { - struct ble_gap_conn_desc desc; + struct ble_gap_conn_desc desc; + if (systemTask.GetSettings().GetBleSecured() && + (event->enc_change.status != 0 || ble_gap_conn_find(event->enc_change.conn_handle, &desc) != 0 || !desc.sec_state.encrypted || + !desc.sec_state.authenticated || (!desc.sec_state.bonded && !systemTask.GetSettings().GetBlePairingAllowed()) || + desc.sec_state.key_size < 16)) { + ble_gap_terminate(event->enc_change.conn_handle, BLE_ERR_INSUFFICIENT_SEC); + } else if (event->enc_change.status == 0) { ble_gap_conn_find(event->enc_change.conn_handle, &desc); if (desc.sec_state.bonded) { PersistBond(desc); @@ -281,7 +370,15 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { * Use the tinycrypt prng here since rand() is predictable. */ NRF_LOG_INFO("Security event : BLE_GAP_EVENT_PASSKEY_ACTION"); - if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + if (!systemTask.GetSettings().GetBlePairingAllowed() || + (systemTask.GetSettings().GetBleSecured() && event->passkey.params.action != BLE_SM_IOACT_DISP)) { + ble_gap_terminate(event->passkey.conn_handle, BLE_ERR_NO_PAIRING); + } else if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + if (systemTask.GetSettings().GetBleSecured()) { + /* If BLE security enabled, delete all previous bonding data from RAM and flash, and generate new IRK */ + ClearBonds(); + RandomizeOurIRK(); + } struct ble_sm_io pkey = {0}; pkey.action = event->passkey.params.action; @@ -302,7 +399,14 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { */ uint32_t passkey_rand; do { - passkey_rand = ble_ll_rand(); + if (systemTask.GetSettings().GetBleSecured()) { + if (ble_ll_rand_data_get(reinterpret_cast(&passkey_rand), 4) != 0) { + NRF_LOG_INFO("Pairing RNG failure"); + return 0; + } + } else { + passkey_rand = ble_ll_rand(); + } } while (passkey_rand > 4293999999); pkey.passkey = passkey_rand % 1000000; @@ -338,23 +442,35 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { NRF_LOG_INFO("MTU Update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); break; - case BLE_GAP_EVENT_REPEAT_PAIRING: { + case BLE_GAP_EVENT_REPEAT_PAIRING: NRF_LOG_INFO("Pairing event : BLE_GAP_EVENT_REPEAT_PAIRING"); - /* We already have a bond with the peer, but it is attempting to - * establish a new secure link. This app sacrifices security for - * convenience: just throw away the old bond and accept the new link. - */ + if (!systemTask.GetSettings().GetBlePairingAllowed()) { + ble_gap_terminate(event->repeat_pairing.conn_handle, BLE_ERR_INSUFFICIENT_SEC); + } else if (systemTask.GetSettings().GetBleSecured()) { + /* Delete all previous bonding data from RAM and flash */ + ClearBonds(); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + } else { + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ - /* Delete the old bond. */ - struct ble_gap_conn_desc desc; - ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); - ble_store_util_delete_peer(&desc.peer_id_addr); + /* Delete the old bond. */ + struct ble_gap_conn_desc desc; + ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + ble_store_util_delete_peer(&desc.peer_id_addr); - /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should - * continue with the pairing operation. - */ - } - return BLE_GAP_REPEAT_PAIRING_RETRY; + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + } + break; case BLE_GAP_EVENT_NOTIFY_RX: { /* Peer sent us a notification or indication. */ @@ -489,6 +605,9 @@ void NimbleController::RestoreBond() { memset(&sec, 0, sizeof sec); fs.FileRead(&file_p, reinterpret_cast(&sec.sec), sizeof sec); ble_store_write_our_sec(&sec.sec); + if (systemTask.GetSettings().GetBleSecured() && sec.sec.irk_present == 1) { + ble_hs_pvcy_set_our_irk(sec.sec.irk); + } memset(&sec, 0, sizeof sec); fs.FileRead(&file_p, reinterpret_cast(&sec.sec), sizeof sec); @@ -504,3 +623,18 @@ void NimbleController::RestoreBond() { fs.FileDelete("/bond.dat"); } } + +void NimbleController::RandomizeOurIRK() { + uint8_t rand_irk[16]; + + if (ble_ll_rand_data_get(rand_irk, 16) == 0) { + ble_hs_pvcy_set_our_irk(rand_irk); + } else { + NRF_LOG_INFO("IRK RNG failure"); + } +} + +void NimbleController::ClearBonds() { + ble_store_clear(); + fs.FileDelete("/bond.dat"); +} diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index 597ef0cc34..c3468ba96d 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -81,9 +81,14 @@ namespace Pinetime { void EnableRadio(); void DisableRadio(); + void AddCharacteristicSecurity(const struct ble_gatt_svc_def* svcs); + bool IsConnSecurityOK(); + private: void PersistBond(struct ble_gap_conn_desc& desc); void RestoreBond(); + void RandomizeOurIRK(); + void ClearBonds(); static constexpr const char* deviceName = "InfiniTime"; Pinetime::System::SystemTask& systemTask; diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index c2da93055e..5e62327552 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -17,6 +17,7 @@ */ #include "components/ble/SimpleWeatherService.h" +#include "components/ble/NimbleController.h" #include #include @@ -116,10 +117,12 @@ int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble return static_cast(arg)->OnCommand(ctxt); } -SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) { +SimpleWeatherService::SimpleWeatherService(NimbleController& nimble, DateTime& dateTimeController) + : dateTimeController(dateTimeController), nimble {nimble} { } void SimpleWeatherService::Init() { + nimble.AddCharacteristicSecurity(serviceDefinition); ble_gatts_count_cfg(serviceDefinition); ble_gatts_add_svcs(serviceDefinition); } diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 96933b8522..ee1c2203e3 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -40,10 +40,11 @@ int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_ac namespace Pinetime { namespace Controllers { + class NimbleController; class SimpleWeatherService { public: - explicit SimpleWeatherService(DateTime& dateTimeController); + explicit SimpleWeatherService(NimbleController& nimble, DateTime& dateTimeController); void Init(); @@ -176,12 +177,12 @@ namespace Pinetime { ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)}; - const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u, - .access_cb = WeatherCallback, - .arg = this, - .flags = BLE_GATT_CHR_F_WRITE, - .val_handle = &eventHandle}, - {0}}; + struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u, + .access_cb = WeatherCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE, + .val_handle = &eventHandle}, + {0}}; const struct ble_gatt_svc_def serviceDefinition[2] = { {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition}, {0}}; @@ -189,6 +190,7 @@ namespace Pinetime { uint16_t eventHandle {}; Pinetime::Controllers::DateTime& dateTimeController; + NimbleController& nimble; std::optional currentWeather; std::optional forecast; diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h index 9133d3fea1..eca1a3e29d 100644 --- a/src/components/settings/Settings.h +++ b/src/components/settings/Settings.h @@ -313,6 +313,33 @@ namespace Pinetime { return bleRadioEnabled; }; + void SetBleSecured(bool secured) { + if (secured != settings.bleSecured) { + settingsChanged = true; + } + settings.bleSecured = secured; + }; + + bool GetBleSecured() const { +#ifndef PINETIME_IS_RECOVERY + return settings.bleSecured; +#else + return false; +#endif + }; + + void SetBlePairingAllowed(bool allowed) { + blePairingAllowed = allowed; + }; + + bool GetBlePairingAllowed() const { +#ifndef PINETIME_IS_RECOVERY + return blePairingAllowed || !settings.bleSecured; +#else + return true; +#endif + }; + void SetDfuAndFsMode(DfuAndFsMode mode) { if (mode == GetDfuAndFsMode()) { return; @@ -383,6 +410,8 @@ namespace Pinetime { bool dfuAndFsEnabledOnBoot = false; uint16_t heartRateBackgroundPeriod = std::numeric_limits::max(); // Disabled by default + + bool bleSecured = false; }; SettingsData settings; @@ -396,6 +425,7 @@ namespace Pinetime { */ bool bleRadioEnabled = true; bool dfuAndFsEnabledTillReboot = false; + bool blePairingAllowed = false; void LoadSettingsFromFile(); void SaveSettingsToFile(); diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 84fa603622..fe14575c4e 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -481,6 +481,9 @@ void DisplayApp::Refresh() { case Messages::BleRadioEnableToggle: PushMessageToSystemTask(System::Messages::BleRadioEnableToggle); break; + case Messages::BleAllowPairingToggle: + PushMessageToSystemTask(System::Messages::BleAllowPairingToggle); + break; case Messages::Chime: LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); motorController.RunForDuration(35); diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index 1fcd72d278..2eca36d748 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -24,6 +24,7 @@ namespace Pinetime { AlarmTriggered, Chime, BleRadioEnableToggle, + BleAllowPairingToggle, }; } } diff --git a/src/displayapp/screens/settings/SettingBluetooth.cpp b/src/displayapp/screens/settings/SettingBluetooth.cpp index e4dc695c94..82b5ea999d 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.cpp +++ b/src/displayapp/screens/settings/SettingBluetooth.cpp @@ -1,57 +1,41 @@ #include "displayapp/screens/settings/SettingBluetooth.h" -#include +#include "displayapp/screens/settings/SettingBluetoothMain.h" +#include "displayapp/screens/settings/SettingBluetoothSecurity.h" #include "displayapp/DisplayApp.h" -#include "displayapp/Messages.h" -#include "displayapp/screens/Styles.h" -#include "displayapp/screens/Screen.h" -#include "displayapp/screens/Symbols.h" +#include "displayapp/screens/ScreenList.h" +#include "components/settings/Settings.h" +#include "displayapp/widgets/DotIndicator.h" using namespace Pinetime::Applications::Screens; -namespace { - struct Option { - const char* name; - bool radioEnabled; - }; - - constexpr std::array options = {{ - {"Enabled", true}, - {"Disabled", false}, - }}; - - std::array CreateOptionArray() { - std::array optionArray; - for (size_t i = 0; i < CheckboxList::MaxItems; i++) { - if (i >= options.size()) { - optionArray[i].name = ""; - optionArray[i].enabled = false; - } else { - optionArray[i].name = options[i].name; - optionArray[i].enabled = true; - } - } - return optionArray; - }; +bool SettingBluetooth::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + return screens.OnTouchEvent(event); } SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) : app {app}, settings {settingsController}, - checkboxList( - 0, - 1, - "Bluetooth", - Symbols::bluetooth, - settingsController.GetBleRadioEnabled() ? 0 : 1, - [this](uint32_t index) { - const bool priorMode = settings.GetBleRadioEnabled(); - const bool newMode = options[index].radioEnabled; - if (newMode != priorMode) { - settings.SetBleRadioEnabled(newMode); - this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); - } - }, - CreateOptionArray()) { + screens {app, + 0, + {[this]() -> std::unique_ptr { + return screenBluetoothMain(); + }, + [this]() -> std::unique_ptr { + return screenBluetoothSecurity(); + }}, + Screens::ScreenListModes::UpDown} { +} + +std::unique_ptr SettingBluetooth::screenBluetoothMain() { + Widgets::DotIndicator dotIndicator(0, 2); + dotIndicator.Create(); + return std::make_unique(app, settings); +} + +std::unique_ptr SettingBluetooth::screenBluetoothSecurity() { + Widgets::DotIndicator dotIndicator(1, 2); + dotIndicator.Create(); + return std::make_unique(app, settings); } SettingBluetooth::~SettingBluetooth() { diff --git a/src/displayapp/screens/settings/SettingBluetooth.h b/src/displayapp/screens/settings/SettingBluetooth.h index 0cf014f5fa..65f798b858 100644 --- a/src/displayapp/screens/settings/SettingBluetooth.h +++ b/src/displayapp/screens/settings/SettingBluetooth.h @@ -1,12 +1,10 @@ #pragma once -#include #include #include -#include "components/settings/Settings.h" #include "displayapp/screens/Screen.h" -#include "displayapp/screens/CheckboxList.h" +#include "displayapp/screens/ScreenList.h" namespace Pinetime { @@ -18,10 +16,15 @@ namespace Pinetime { SettingBluetooth(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); ~SettingBluetooth() override; + bool OnTouchEvent(TouchEvents event) override; + private: DisplayApp* app; Pinetime::Controllers::Settings& settings; - CheckboxList checkboxList; + + ScreenList<2> screens; + std::unique_ptr screenBluetoothMain(); + std::unique_ptr screenBluetoothSecurity(); }; } } diff --git a/src/displayapp/screens/settings/SettingBluetoothMain.cpp b/src/displayapp/screens/settings/SettingBluetoothMain.cpp new file mode 100644 index 0000000000..e447e1907f --- /dev/null +++ b/src/displayapp/screens/settings/SettingBluetoothMain.cpp @@ -0,0 +1,59 @@ +#include "displayapp/screens/settings/SettingBluetoothMain.h" +#include +#include "displayapp/DisplayApp.h" +#include "displayapp/Messages.h" +#include "displayapp/screens/Styles.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + struct Option { + const char* name; + bool radioEnabled; + }; + + constexpr std::array options = {{ + {"Enabled", true}, + {"Disabled", false}, + }}; + + std::array CreateOptionArray() { + std::array optionArray; + for (size_t i = 0; i < CheckboxList::MaxItems; i++) { + if (i >= options.size()) { + optionArray[i].name = ""; + optionArray[i].enabled = false; + } else { + optionArray[i].name = options[i].name; + optionArray[i].enabled = true; + } + } + return optionArray; + }; +} + +SettingBluetoothMain::SettingBluetoothMain(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController) + : app {app}, + settings {settingsController}, + checkboxList( + 0, + 1, + "Bluetooth", + Symbols::bluetooth, + settingsController.GetBleRadioEnabled() ? 0 : 1, + [this](uint32_t index) { + const bool priorMode = settings.GetBleRadioEnabled(); + const bool newMode = options[index].radioEnabled; + if (newMode != priorMode) { + settings.SetBleRadioEnabled(newMode); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); + } + }, + CreateOptionArray()) { +} + +SettingBluetoothMain::~SettingBluetoothMain() { + lv_obj_clean(lv_scr_act()); +} diff --git a/src/displayapp/screens/settings/SettingBluetoothMain.h b/src/displayapp/screens/settings/SettingBluetoothMain.h new file mode 100644 index 0000000000..dda492003d --- /dev/null +++ b/src/displayapp/screens/settings/SettingBluetoothMain.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/CheckboxList.h" + +namespace Pinetime { + + namespace Applications { + namespace Screens { + + class SettingBluetoothMain : public Screen { + public: + SettingBluetoothMain(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + ~SettingBluetoothMain() override; + + private: + DisplayApp* app; + Pinetime::Controllers::Settings& settings; + CheckboxList checkboxList; + }; + } + } +} diff --git a/src/displayapp/screens/settings/SettingBluetoothSecurity.cpp b/src/displayapp/screens/settings/SettingBluetoothSecurity.cpp new file mode 100644 index 0000000000..901fbb4940 --- /dev/null +++ b/src/displayapp/screens/settings/SettingBluetoothSecurity.cpp @@ -0,0 +1,119 @@ +#include "displayapp/screens/settings/SettingBluetoothSecurity.h" +#include +#include +#include "displayapp/DisplayApp.h" +#include "displayapp/Messages.h" +#include "displayapp/screens/Styles.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/Symbols.h" +#include "systemtask/SystemTask.h" + +using namespace Pinetime::Applications::Screens; + +namespace { + void SecurityEventHandler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + auto* screen = static_cast(obj->user_data); + screen->ToggleSecurity(); + } + } + + void PairingEventHandler(lv_obj_t* obj, lv_event_t event) { + if (event == LV_EVENT_VALUE_CHANGED) { + auto* screen = static_cast(obj->user_data); + screen->TogglePairing(); + } + } +} + +SettingBluetoothSecurity::SettingBluetoothSecurity(Pinetime::Applications::DisplayApp* app, + Pinetime::Controllers::Settings& settingsController) + : app {app}, settings {settingsController} { + lv_obj_t* container1 = lv_cont_create(lv_scr_act(), nullptr); + + lv_obj_set_style_local_bg_opa(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10); + lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5); + lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0); + + lv_obj_set_pos(container1, 10, 60); + lv_obj_set_width(container1, LV_HOR_RES - 20); + lv_obj_set_height(container1, LV_VER_RES - 20); + lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT); + + lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(title, "BT security"); + lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); + lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 10, 15); + + lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE); + lv_label_set_text_static(icon, Symbols::bluetooth); + lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER); + lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0); + + secured = settings.GetBleSecured(); + securityCheckbox = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(securityCheckbox, "Secured"); + lv_checkbox_set_checked(securityCheckbox, secured); + lv_obj_add_state(securityCheckbox, LV_STATE_DEFAULT); + securityCheckbox->user_data = this; + lv_obj_set_event_cb(securityCheckbox, SecurityEventHandler); + + pairingCheckbox = lv_checkbox_create(container1, nullptr); + lv_checkbox_set_text(pairingCheckbox, "Pairable"); + lv_obj_add_state(pairingCheckbox, LV_STATE_DEFAULT); + pairingCheckbox->user_data = this; + lv_checkbox_set_checked(pairingCheckbox, settings.GetBlePairingAllowed() && settings.GetBleRadioEnabled()); + if (settings.GetBleSecured() && settings.GetBleRadioEnabled()) { + lv_obj_set_event_cb(pairingCheckbox, PairingEventHandler); + } else { + lv_checkbox_set_disabled(pairingCheckbox); + } + + statusLabel = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(statusLabel, ""); + lv_label_set_recolor(statusLabel, true); + lv_obj_set_auto_realign(statusLabel, true); + lv_obj_align(statusLabel, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 176); +} + +SettingBluetoothSecurity::~SettingBluetoothSecurity() { + lv_obj_clean(lv_scr_act()); + settings.SetBlePairingAllowed(false); + if (secured != settings.GetBleSecured()) { + settings.SetBleSecured(secured); + settings.SaveSettings(); + settings.SetBleRadioEnabled(false); + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle); + vTaskDelay(pdMS_TO_TICKS(1000)); + NVIC_SystemReset(); + } else if (settings.GetBleSecured() && settings.GetBleRadioEnabled()) { + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleAllowPairingToggle); + } +} + +void SettingBluetoothSecurity::ToggleSecurity() { + secured = !secured; + lv_checkbox_set_checked(securityCheckbox, secured); + UpdateRebootMessage(); +} + +void SettingBluetoothSecurity::UpdateRebootMessage() { + if (secured != settings.GetBleSecured()) { + lv_label_set_text_static(statusLabel, "#ffa500 Reboot needed.#"); + } else { + lv_label_set_text_static(statusLabel, ""); + } +} + +void SettingBluetoothSecurity::TogglePairing() { + settings.SetBlePairingAllowed(!settings.GetBlePairingAllowed()); + lv_checkbox_set_checked(pairingCheckbox, settings.GetBlePairingAllowed()); + if (settings.GetBlePairingAllowed()) { + lv_label_set_text_static(statusLabel, "#00ff00 Pair NOW!#"); + } else { + UpdateRebootMessage(); + } + this->app->PushMessage(Pinetime::Applications::Display::Messages::BleAllowPairingToggle); +} diff --git a/src/displayapp/screens/settings/SettingBluetoothSecurity.h b/src/displayapp/screens/settings/SettingBluetoothSecurity.h new file mode 100644 index 0000000000..1b953028d2 --- /dev/null +++ b/src/displayapp/screens/settings/SettingBluetoothSecurity.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include "components/settings/Settings.h" +#include "displayapp/screens/Screen.h" +#include "displayapp/screens/CheckboxList.h" + +namespace Pinetime { + + namespace Applications { + namespace Screens { + + class SettingBluetoothSecurity : public Screen { + public: + SettingBluetoothSecurity(DisplayApp* app, Pinetime::Controllers::Settings& settingsController); + ~SettingBluetoothSecurity() override; + + void ToggleSecurity(); + void TogglePairing(); + + private: + DisplayApp* app; + Pinetime::Controllers::Settings& settings; + + bool secured; + void UpdateRebootMessage(); + + lv_obj_t* securityCheckbox; + lv_obj_t* pairingCheckbox; + lv_obj_t* statusLabel; + }; + } + } +} diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index fee94bb747..2015e3c915 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -29,7 +29,8 @@ namespace Pinetime { BatteryPercentageUpdated, StartFileTransfer, StopFileTransfer, - BleRadioEnableToggle + BleRadioEnableToggle, + BleAllowPairingToggle }; } } diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 826474a068..26ec6844d5 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -114,6 +114,9 @@ void SystemTask::Work() { fs.Init(); + // Settings controller is needed by NimbleController + settingsController.Init(); + nimbleController.Init(); twiMaster.Init(); @@ -137,7 +140,6 @@ void SystemTask::Work() { motionSensor.Init(); motionController.Init(motionSensor.DeviceType()); - settingsController.Init(); displayApp.Register(this); displayApp.Register(&nimbleController.weather()); @@ -373,6 +375,14 @@ void SystemTask::Work() { nimbleController.DisableRadio(); } break; + case Messages::BleAllowPairingToggle: + if (settingsController.GetBleRadioEnabled() && settingsController.GetBleSecured() && + (!bleController.IsConnected() || settingsController.GetBlePairingAllowed())) { + nimbleController.DisableRadio(); + vTaskDelay(pdMS_TO_TICKS(250)); + nimbleController.EnableRadio(); + } + break; default: break; }