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; }