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.
This commit is contained in:
James Rich committed 2026-09-19 18:48:19 -05:00
1 parent 648b22fe8c
commit c8060ced96
2 files changed
+23 -2

No files matched your search

+16 -2
View File
@@ -10,6 +10,7 @@
#include "mesh/Throttle.h"
#include <array>
#include <bluefruit.h>
#include <utility/bonding.h>
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
+7
View File
@@ -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