Skip to content

Commit

Permalink
ble: allow configuring device privacy
Browse files Browse the repository at this point in the history
This allows using a random private resolvable address to prevent being tracked.
  • Loading branch information
ssievert42 committed Aug 14, 2023
1 parent d58609f commit 8b3c42c
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 3 deletions.
2 changes: 2 additions & 0 deletions libs/bluetooth/bluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ uint32_t jsble_central_send_passkey(uint16_t central_conn_handle, char *passkey)
void jsble_central_setWhitelist(bool whitelist);
/// Erase any saved bonding info for peers
void jsble_central_eraseBonds();
JsVar *jsble_getPrivacy();
void jsble_setPrivacy(JsVar *options);
#endif

#endif // BLUETOOTH_H
125 changes: 125 additions & 0 deletions libs/bluetooth/bluetooth_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,131 @@ const char *bleVarToUUIDAndUnLock(ble_uuid_t *uuid, JsVar *v) {
return r;
}

#if PEER_MANAGER_ENABLED
bool bleVarToPrivacy(JsVar *options, pm_privacy_params_t *privacy) {
if (jsvIsObject(options)) {
bool invalidOption = false;
memset(privacy, 0, sizeof(pm_privacy_params_t));
privacy->privacy_mode = BLE_GAP_PRIVACY_MODE_OFF;
privacy->private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE;
privacy->private_addr_cycle_s = 0; // use default address change cycle
privacy->p_device_irk = NULL; // use device default irk
// privacy mode
{
JsVar *privacyModeVar = jsvObjectGetChildIfExists(options, "privacy_mode");
if (privacyModeVar && jsvIsString(privacyModeVar)) {
if (jsvIsStringEqual(privacyModeVar, "off")) {
privacy->privacy_mode = BLE_GAP_PRIVACY_MODE_OFF;
} else if (jsvIsStringEqual(privacyModeVar, "device_privacy")) {
privacy->privacy_mode = BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY;
} else {
invalidOption = true;
}
} else {
invalidOption = true;
}
jsvUnLock(privacyModeVar);
}
// other options are only relevant if privacy_mode is something other than off
if (privacy->privacy_mode != BLE_GAP_PRIVACY_MODE_OFF) {
// private addr type
{
JsVar *privacyAddrTypeVar = jsvObjectGetChildIfExists(options, "private_addr_type");
if (privacyAddrTypeVar && jsvIsString(privacyAddrTypeVar)) {
if (jsvIsStringEqual(privacyAddrTypeVar, "random_private_resolvable")) {
privacy->private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE;
} else if (jsvIsStringEqual(privacyAddrTypeVar, "random_private_non_resolvable")) {
privacy->private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE;
} else {
invalidOption = true;
}
} else {
invalidOption = true;
}
jsvUnLock(privacyAddrTypeVar);
}
// private addr cycle s
{
JsVar *privateAddrCycleSVar = jsvObjectGetChildIfExists(options, "private_addr_cycle_s");
if (privateAddrCycleSVar && jsvIsInt(privateAddrCycleSVar)) {
privacy->private_addr_cycle_s = jsvGetInteger(privateAddrCycleSVar);
} else {
invalidOption = true;
}
jsvUnLock(privateAddrCycleSVar);
}
}
return !invalidOption;
}
return false;
}

JsVar *blePrivacyToVar(pm_privacy_params_t *privacy) {
JsVar *result = jsvNewObject();
if (!result) return 0;
if (privacy) {
char *privacy_mode_str = "";
switch (privacy->privacy_mode) {
case BLE_GAP_PRIVACY_MODE_OFF:
privacy_mode_str = "off";
break;
case BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY:
privacy_mode_str = "device_privacy";
break;
}
char *addr_type_str = "";
switch (privacy->private_addr_type) {
case BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE:
addr_type_str = "random_private_resolvable";
break;
case BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE:
addr_type_str = "random_private_non_resolvable";
break;
}
jsvObjectSetChildAndUnLock(result, "privacy_mode", jsvNewFromString(privacy_mode_str));
// other options are only relevant if privacy_mode is something other than off
if (privacy->privacy_mode != BLE_GAP_PRIVACY_MODE_OFF) {
jsvObjectSetChildAndUnLock(result, "private_addr_type", jsvNewFromString(addr_type_str));
jsvObjectSetChildAndUnLock(result, "private_addr_cycle_s", jsvNewFromInteger(privacy->private_addr_cycle_s));
}
return result;
}
jsvUnLock(result);
return 0;
}

bool blePrivacyVarEqual(JsVar *a, JsVar *b) {
if (jsvIsUndefined(a) && jsvIsUndefined(b)) {
return true;
}
if (jsvIsObject(a) && jsvIsObject(b)) {
bool privacyModeEqual = false;
{
JsVar *privacyModeA = jsvObjectGetChildIfExists(a, "privacy_mode");
JsVar *privacyModeB = jsvObjectGetChildIfExists(b, "privacy_mode");
privacyModeEqual = jsvIsEqual(privacyModeA, privacyModeB);
jsvUnLock2(privacyModeA, privacyModeB);
}
bool privateAddrTypeEqual = false;
{
JsVar *privateAddrTypeA = jsvObjectGetChildIfExists(a, "private_addr_type");
JsVar *privateAddrTypeB = jsvObjectGetChildIfExists(b, "private_addr_type");
privateAddrTypeEqual = jsvIsEqual(privateAddrTypeA, privateAddrTypeB);
jsvUnLock2(privateAddrTypeA, privateAddrTypeB);
}
bool privateAddrCycleSEqual = false;
{
JsVar *privateAddrCycleSA = jsvObjectGetChildIfExists(a, "private_addr_cycle_s");
JsVar *privateAddrCycleSB = jsvObjectGetChildIfExists(b, "private_addr_cycle_s");
privateAddrCycleSEqual = jsvIsEqual(privateAddrCycleSA, privateAddrCycleSB);
jsvUnLock2(privateAddrCycleSA, privateAddrCycleSB);
}
return privacyModeEqual && privateAddrTypeEqual && privateAddrCycleSEqual;
}
return false;
}
#endif // PEER_MANAGER_ENABLED

/// Queue an event on the 'NRF' object. Also calls jshHadEvent()
void bleQueueEventAndUnLock(const char *name, JsVar *data) {
//jsiConsolePrintf("[%s] %j\n", name, data);
Expand Down
10 changes: 10 additions & 0 deletions libs/bluetooth/bluetooth_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

#include "jsvar.h"
#include "bluetooth.h"
#if PEER_MANAGER_ENABLED
#include "peer_manager_types.h"
#endif

#define BLE_SCAN_EVENT JS_EVENT_PREFIX"blescan"
#define BLE_WRITE_EVENT JS_EVENT_PREFIX"blew"
Expand All @@ -33,6 +36,7 @@
#define BLE_NAME_GATT_SERVER_LEN 11 // include null terminator
#define BLE_NAME_SECURITY "BLE_SEC"
#define BLE_NAME_MAC_ADDRESS "BLE_MAC"
#define BLE_NAME_PRIVACY "BLE_PRIV"
#if ESPR_BLUETOOTH_ANCS
#define BLE_NAME_ANCS "BLE_ANCS"
#define BLE_NAME_AMS "BLE_AMS"
Expand Down Expand Up @@ -70,6 +74,12 @@ const char *bleVarToUUID(ble_uuid_t *uuid, JsVar *v);
/// Same as bleVarToUUID, but unlocks v
const char *bleVarToUUIDAndUnLock(ble_uuid_t *uuid, JsVar *v);

#if PEER_MANAGER_ENABLED
bool bleVarToPrivacy(JsVar *options, pm_privacy_params_t *privacy);
JsVar *blePrivacyToVar(pm_privacy_params_t *privacy);
bool blePrivacyVarEqual(JsVar *a, JsVar *b);
#endif // PEER_MANAGER_ENABLED

/// Queue an event on the 'NRF' object. Also calls jshHadEvent()
void bleQueueEventAndUnLock(const char *name, JsVar *data);

Expand Down
90 changes: 90 additions & 0 deletions libs/bluetooth/jswrap_bluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,96 @@ void jswrap_ble_setAddress(JsVar *address) {
#endif
}

/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "getPrivacy",
"#if" : "defined(NRF52_SERIES)",
"generate" : "jswrap_ble_getPrivacy",
"return" : ["JsVar", "Current privacy settings" ]
}
Get this device's Bluetooth privacy settings:
```
{
privacy_mode // The current privacy mode. Either "off" or "device_privacy".
private_addr_type // The type of address we are using. Either "random_private_resolvable" or "random_private_non_resolvable".
private_addr_cycle_s // How often the address changes, in seconds.
}
```
If privacy_mode is "off", only `{privacy_mode: "off"}` will be returned.
See `NRF.setPrivacy` for more info on the meaning of the different values.
*/
JsVar *jswrap_ble_getPrivacy() {
#if PEER_MANAGER_ENABLED
return jsble_getPrivacy();
#else
jsExceptionHere(JSET_ERROR, "Not implemented");
return 0;
#endif
}

/*JSON{
"type" : "staticmethod",
"class" : "NRF",
"name" : "setPrivacy",
"#if" : "defined(NRF52_SERIES)",
"generate" : "jswrap_ble_setPrivacy",
"params" : [
["options" ,"JsVar", "The privacy settings that should be applied."]
]
}
Set this device's Bluetooth privacy settings.
The privacy feature provides a way to avoid being tracked over a period of time.
This works by hiding the local device identity address, and replacing it with a random private address,
that automatically changes at a specified interval.
If a `"random_private_resolvable"` address is used, that address is generated with the help
of an identity resolving key (IRK), that is exchanged during bonding.
This allows a bonded device to still identify another device that is using a random private resolvable address.
Note that, while this can help against being tracked, there are other ways a Bluetooth device can reveal its identity.
For example, the name or services it advertises may be unique enough.
If it is possible for anyone to connect, the services the device provides,
and how it responds to trying to read from or write to their characteristics could be used as well.
```
NRF.setPrivacy({
privacy_mode // The privacy mode that should be used.
private_addr_type // The type of address to use.
private_addr_cycle_s // How often the address should change, in seconds.
});
```
`privacy_mode` can be one of:
* `"off"` - Disable the privacy feature.
* `"device_privacy"` - The device will send and accept only private addresses for its own address.
If `privacy_mode` is `"off"`, all other fields are ignored and become optional.
`private_addr_type` can be one of:
* `"random_private_resolvable"` - Address that can be resolved by a bonded peer that knows our IRK.
* `"random_private_non_resolvable"` - Address that cannot be resolved.
`private_addr_cycle_s` must be an integer. Pass `0` to use the default address change interval.
The default is usually to change the address every 15 minutes (or 900 seconds).
*/
void jswrap_ble_setPrivacy(JsVar *options) {
#if PEER_MANAGER_ENABLED
jsble_setPrivacy(options);
// privacy settings are only applied when the softdevice (re)starts
if (bleStatus & BLE_NEEDS_SOFTDEVICE_RESTART)
jswrap_ble_restart(NULL);
#else
jsExceptionHere(JSET_ERROR, "Not implemented");
#endif
}

/*JSON{
"type" : "staticmethod",
"class" : "NRF",
Expand Down
2 changes: 2 additions & 0 deletions libs/bluetooth/jswrap_bluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ void jswrap_ble_restart(JsVar *callback);
void jswrap_ble_eraseBonds();
JsVar *jswrap_ble_getAddress();
void jswrap_ble_setAddress(JsVar *address);
JsVar *jswrap_ble_getPrivacy();
void jswrap_ble_setPrivacy(JsVar *options);

/// Used by bluetooth.c internally when it needs to set up advertising at first
JsVar *jswrap_ble_getCurrentAdvertisingData();
Expand Down
66 changes: 63 additions & 3 deletions targets/nrf5x/bluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -2731,18 +2731,43 @@ void jsble_advertising_stop() {
NRF_RADIO_NOTIFICATION_DISTANCE_NONE);
APP_ERROR_CHECK(err_code);

#if PEER_MANAGER_ENABLED
peer_manager_init(false /*don't erase_bonds*/);
pm_privacy_params_t privacy_params = {
.privacy_mode = BLE_GAP_PRIVACY_MODE_OFF,
.private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE,
.private_addr_cycle_s = BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S,
.p_device_irk = NULL
};
{
JsVar *options = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_PRIVACY);
pm_privacy_params_t privacy;
if (options && bleVarToPrivacy(options, &privacy)) {
privacy_params = privacy;
}
jsvUnLock(options);
}
err_code = pm_privacy_set(&privacy_params);
if (err_code) jsiConsolePrintf("pm_privacy_set failed: 0x%x\n", err_code);
#endif

#ifdef NRF52_SERIES
// Set MAC address
JsVar *v = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_MAC_ADDRESS);
if (v) {
ble_gap_addr_t p_addr;
if (bleVarToAddr(v, &p_addr)) {
#if PEER_MANAGER_ENABLED
err_code = pm_id_addr_set(&p_addr);
if (err_code) jsiConsolePrintf("pm_id_addr_set failed: 0x%x\n", err_code);
#else
#if NRF_SD_BLE_API_VERSION < 3
err_code = sd_ble_gap_address_set(BLE_GAP_ADDR_CYCLE_MODE_NONE,&p_addr);
#else
err_code = sd_ble_gap_addr_set(&p_addr);
#endif
if (err_code) jsiConsolePrintf("sd_ble_gap_addr_set failed: 0x%x\n", err_code);
#endif // PEER_MANAGER_ENABLED
}
}
jsvUnLock(v);
Expand All @@ -2763,9 +2788,6 @@ void jsble_advertising_stop() {
*/
#endif

#if PEER_MANAGER_ENABLED
peer_manager_init(false /*don't erase_bonds*/);
#endif
gap_params_init();
services_init();
conn_params_init();
Expand Down Expand Up @@ -3531,6 +3553,44 @@ void jsble_central_eraseBonds() {
#endif
}

#if PEER_MANAGER_ENABLED
JsVar *jsble_getPrivacy() {
pm_privacy_params_t privacy;
memset(&privacy, 0, sizeof(pm_privacy_params_t));
ble_gap_irk_t irk;
memset(&irk, 0, sizeof(ble_gap_irk_t));
privacy.p_device_irk = &irk;
uint32_t err_code = pm_privacy_get(&privacy);
if (!jsble_check_error(err_code)) {
return blePrivacyToVar(&privacy);
}
return 0;
}
#endif // PEER_MANAGER_ENABLED

#if PEER_MANAGER_ENABLED
void jsble_setPrivacy(JsVar *options) {
if (!jsvIsObject(options) && !jsvIsUndefined(options)) {
jsExceptionHere(JSET_TYPEERROR, "Expecting an object or undefined, got %t", options);
} else {
pm_privacy_params_t privacy;
if (jsvIsObject(options) && !bleVarToPrivacy(options, &privacy)) {
jsExceptionHere(JSET_TYPEERROR, "Invalid or missing parameters, got %t", options);
} else {
// only update parameters if they are different
JsVar *currentPrivacy = jsvObjectGetChildIfExists(execInfo.hiddenRoot, BLE_NAME_PRIVACY);
if (!blePrivacyVarEqual(currentPrivacy, options)) {
jsvObjectSetOrRemoveChild(execInfo.hiddenRoot, BLE_NAME_PRIVACY, options);
// privacy settings can only be applied while not advertising, scanning or in a connection
// we only apply them when the softdevice (re)starts
bleStatus |= BLE_NEEDS_SOFTDEVICE_RESTART;
}
jsvUnLock(currentPrivacy);
}
}
}
#endif // PEER_MANAGER_ENABLED

#endif // BLUETOOTH


0 comments on commit 8b3c42c

Please sign in to comment.