From c8060ced96e3d5609d1106bacadcfcb555242587 Mon Sep 17 00:00:00 2001 From: James Rich Date: Sat, 19 Sep 2026 18:48:19 -0500 Subject: [PATCH] nRF52 dial: encrypt a dialled link with the phone-API bond, since a refused security request drops it Declining the security request (86bddc0bd) was untested against a bonded phone. Measured 2026-09-19 with a Pixel bonded to this radio's phone API and its monitor peripheral-only: every dial went "declined the security request" -> "characteristic not found" -> disconnected 0x05 within a second, 26 times in a row. Android drops a link whose security request is refused; it does not carry on unencrypted. So honour the bond instead. The pairing that made it had this radio as the peripheral, so the keys live in the peripheral bond store whatever role this link has, and bond_load_keys resolves the phone's private address by IRK itself. The key to start encryption with as the central is the one the phone distributed (peer_enc); sd_ble_gap_encrypt with it gives "BLE connection secured" one second after the dial, then the mesh peer, the HELLO and a link that held through the liveness probes. A peer with no bond is still declined. Also a bench-only env, rak4631_blemesh_uuid, which adds BLE_GATT_MESH_DIAL_UUID on top of rak4631_blemesh so a dual-role phone can be dialled on purpose - the way this was measured. PLATFORMIO_BUILD_FLAGS replaces an extended env's flags, so it cannot be used for this. --- src/platform/nrf52/NRF52BLEGattMesh.cpp | 18 ++++++++++++++++-- variants/nrf52840/rak4631/platformio.ini | 7 +++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/platform/nrf52/NRF52BLEGattMesh.cpp b/src/platform/nrf52/NRF52BLEGattMesh.cpp index ceba20e132..fa9de8df93 100644 --- a/src/platform/nrf52/NRF52BLEGattMesh.cpp +++ b/src/platform/nrf52/NRF52BLEGattMesh.cpp @@ -10,6 +10,7 @@ #include "mesh/Throttle.h" #include #include +#include namespace { @@ -558,11 +559,24 @@ void NRF52BLEGattMesh::onSecurityRequest(uint16_t conn) #if BLE_GATT_MESH_DIAL // A phone bonded to this node's phone API asks the link be encrypted the moment it is dialled. // Bluefruit answers nothing on a central link, the phone's SMP timer runs out at 30 s and it drops - // the link (0x05). The mesh-peer link is open by design - the channel key is the security - so - // decline, which the SoftDevice does for a NULL parameter set. + // the link (0x05) - and declining is no better: Android drops a link whose security request is + // refused within a second (measured 2026-09-19, 0x05 every dial). So encrypt it, with the key the + // phone handed this node when it paired with the phone API. That pairing had this node as the + // peripheral, so the bond lives in the peripheral store whatever role this link has, and the + // key to start encryption with as the central is the one the phone distributed (peer_enc). + // The mesh characteristic needs no encryption; this only keeps a bonded phone on the link. BLEConnection *c = Bluefruit.Connection(conn); if (!c || c->getRole() != BLE_GAP_ROLE_CENTRAL) return; + ble_gap_addr_t addr = c->getPeerAddr(); // bond_load_keys resolves a private address by IRK itself + bond_keys_t bkeys = {}; + if (bond_load_keys(BLE_GAP_ROLE_PERIPH, &addr, &bkeys) && bkeys.peer_enc.enc_info.ltk_len > 0) { + const uint32_t err = sd_ble_gap_encrypt(conn, &bkeys.peer_enc.master_id, &bkeys.peer_enc.enc_info); + LOG_INFO("BLE GATT mesh: encrypting dialled conn %u with its phone-API bond (0x%x)", conn, (unsigned)err); + if (err == NRF_SUCCESS) + return; + } + // No bond to honour: decline, which the SoftDevice does for a NULL parameter set. const uint32_t err = sd_ble_gap_authenticate(conn, NULL); LOG_INFO("BLE GATT mesh: declined the security request on dialled conn %u (0x%x)", conn, (unsigned)err); #else diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini index de11b6fefe..dfdccc5595 100644 --- a/variants/nrf52840/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -112,3 +112,10 @@ build_flags = ${env:rak4631.build_flags} -DBLE_MESH_NRF52_CENTRAL=1 -DHAS_BLE_GATT_MESH=1 -DBLE_GATT_MESH_DIAL=1 + +; Bench-only: the UUID dial as well as the overflow bit, for exercising a dialled link against a +; phone that is bonded to this radio's phone API. Not a shipping env. +[env:rak4631_blemesh_uuid] +extends = env:rak4631_blemesh +build_flags = ${env:rak4631_blemesh.build_flags} + -DBLE_GATT_MESH_DIAL_UUID=1