Skip to content

Commit 0924171

Browse files
Deomid Ryabkovcesantabot
authored andcommitted
BT: Add API for managing pairing; improve security
PUBLISHED_FROM=df72de0a2747f731ba3ced6f8e846a95df90d217
1 parent ba7a956 commit 0924171

File tree

7 files changed

+171
-34
lines changed

7 files changed

+171
-34
lines changed

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@ Currently contains only GATT server implmenetation for ESP32.
77
`bt-common` library adds a `bt` configuration section with the following
88
settings:
99

10-
```json
10+
```
1111
"bt": {
12-
"enable": true, // Enabled by default. Disabled on first reboot with WiFi on
13-
"dev_name": "", // Device name. If empty, value equals to device.id
14-
"adv_enable": true, // Advertise our Bluetooth services
15-
"keep_enabled": true // Keep enabled after successful boot with WiFi on
12+
"enable": true, // Enabled by default. Disabled on first reboot with WiFi on
13+
"dev_name": "", // Device name. If empty, value equals to device.id
14+
"adv_enable": true, // Advertise our Bluetooth services
15+
"keep_enabled": true, // Keep enabled after successful boot with WiFi on
16+
"scan_rsp_data_hex": "", // Custom scan response data, as hex string (e.g. `48656c6c6f` for `Hello`)
17+
"allow_pairing": true, // Allow pairingbonding with other devices
18+
"max_paired_devices": 10, // Allow pairing with up to this many devices; -1 - no limit
19+
"gatts": {
20+
"min_sec_level": 0, // Minimum security level for all attributes of all services.
21+
// 0 - no auth required, 1 - encryption reqd, 2 - encryption + MITM reqd
22+
"require_pairing": false // Require taht device is paired before accessing services
23+
}
1624
}
1725
```
26+
27+
## Security
28+
29+
Default settings allow for unrestricted access: anyone can pair with a device and access the services.
30+
A better idea is to set `bt.gatts.require_pairing` to true, `bt.allow_pairing` to false and only enable it for a limited time via `mgos_bt_gap_set_pairing_enable` when user performs some action, e.g. presses a button.
31+
Raising `bt.gatts.min_sec_level` to at least 1 is also advisable.
32+
_Note_: At present, level 2 (MITM protection) is not usable as it requires device to have at least output capability during pairing, and there's no API for displaying the pairing code yet.

include/esp32/esp32_bt.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,16 @@ void mgos_bt_ble_set_scan_rsp_data(const struct mg_str scan_rsp_data);
7676
bool mgos_bt_gap_get_adv_enable(void);
7777
bool mgos_bt_gap_set_adv_enable(bool adv_enable);
7878

79+
bool mgos_bt_gap_get_pairing_enable(void);
80+
bool mgos_bt_gap_set_pairing_enable(bool pairing_enable);
81+
7982
int mgos_bt_ble_get_num_paired_devices(void);
83+
/*
84+
* These are actually async. TODO(rojer): Add callbacks to the API.
85+
* For now, just allow some time for the calls to complete.
86+
*/
87+
void mgos_bt_ble_remove_paired_device(const esp_bd_addr_t addr);
88+
void mgos_bt_ble_remove_all_paired_devices(void);
8089

8190
#ifdef __cplusplus
8291
}

include/esp32/esp32_bt_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bool esp32_bt_gattc_init(void);
2222
bool esp32_bt_init(void);
2323
bool esp32_bt_gap_init(void);
2424
bool esp32_bt_gatts_init(void);
25+
void esp32_bt_gatts_auth_cmpl(const esp_bd_addr_t addr);
2526

2627
void esp32_bt_set_is_advertising(bool is_advertising);
2728

mos.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ config_schema:
1717
- ["bt.adv_enable", "b", true, {title: "Advertise services"}]
1818
- ["bt.scan_rsp_data_hex", "s", "", {title: "Scan response data, hex-encoded"}]
1919
- ["bt.keep_enabled", "b", false, {title: "By default, BT will be disabled once WiFi is configured and connects. Set this to true to keep BT enabled."}]
20-
- ["bt.gatts_min_sec_level", "i", 0, {title: "0 - no auth required, 1 - encryption reqd, 2 - encryption + MITM reqd"}]
20+
- ["bt.allow_pairing", "b", true, {title: "Allow pairing/bonding with other devices"}]
21+
- ["bt.max_paired_devices", "i", -1, {title: "Max number of paired devices; -1 - no limit"}]
22+
- ["bt.gatts", "o", {title: "GATTS settings"}]
23+
- ["bt.gatts.min_sec_level", "i", 0, {title: "0 - no auth required, 1 - encryption reqd, 2 - encryption + MITM reqd"}]
24+
- ["bt.gatts.require_pairing", "b", false, {title: "Require device to be paired before accessing services"}]
2125

2226
tags:
2327
- bt

src/esp32/esp32_bt.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ bool mgos_bt_common_init(void) {
223223
mgos_net_add_event_handler(mgos_bt_net_ev, NULL);
224224
}
225225

226-
LOG(LL_INFO, ("Bluetooth init ok, %d paired devices",
226+
LOG(LL_INFO, ("Bluetooth init ok, pairing %s, %d paired devices",
227+
(mgos_bt_gap_get_pairing_enable() ? "enabled" : "disabled"),
227228
mgos_bt_ble_get_num_paired_devices()));
228229
ret = true;
229230

src/esp32/esp32_bt_gap.c

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "mgos_sys_config.h"
2121

2222
#include "esp32_bt_internal.h"
23+
#include "esp32_bt_gatts.h"
2324

2425
struct scan_cb_info {
2526
esp_bd_addr_t target_addr;
@@ -64,6 +65,8 @@ static esp_ble_adv_params_t s_adv_params = {
6465
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
6566
};
6667

68+
static bool s_pairing_enable = false;
69+
6770
static bool start_advertising(void) {
6871
if (s_advertising) return true;
6972
if (!s_adv_enable || is_scanning()) return false;
@@ -246,6 +249,7 @@ static void esp32_bt_gap_ev(esp_gap_ble_cb_event_t ev,
246249
LOG(ll, ("AUTH_CMPL peer %s at %d dt %d success %d (fr %d) kp %d kt %d",
247250
mgos_bt_addr_to_str(p->bd_addr, buf), p->addr_type, p->dev_type,
248251
p->success, p->fail_reason, p->key_present, p->key_type));
252+
if (p->success) esp32_bt_gatts_auth_cmpl(p->bd_addr);
249253
break;
250254
}
251255
case ESP_GAP_BLE_KEY_EVT: {
@@ -492,6 +496,45 @@ void esp32_bt_set_is_advertising(bool is_advertising) {
492496
s_advertising = is_advertising;
493497
}
494498

499+
bool mgos_bt_gap_get_pairing_enable(void) {
500+
return s_pairing_enable;
501+
}
502+
503+
bool mgos_bt_gap_set_pairing_enable(bool pairing_enable) {
504+
esp_ble_auth_req_t auth_req =
505+
(pairing_enable ? ESP_LE_AUTH_BOND : ESP_LE_AUTH_NO_BOND);
506+
if (esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req,
507+
sizeof(auth_req)) == ESP_OK) {
508+
s_pairing_enable = pairing_enable;
509+
return true;
510+
} else {
511+
return false;
512+
}
513+
}
514+
515+
void mgos_bt_ble_remove_paired_device(const esp_bd_addr_t addr) {
516+
/* Workaround for https://github.com/espressif/esp-idf/issues/1365 */
517+
if (mgos_bt_gatts_get_num_connections() > 0) {
518+
esp_ble_gap_disconnect((uint8_t *) addr);
519+
/* After disconnecting, some time is required before
520+
* esp_ble_remove_bond_device can succeed. */
521+
mgos_msleep(200);
522+
}
523+
524+
esp_ble_remove_bond_device((uint8_t *) addr);
525+
}
526+
527+
void mgos_bt_ble_remove_all_paired_devices(void) {
528+
int num = esp_ble_get_bond_device_num();
529+
esp_ble_bond_dev_t *list = (esp_ble_bond_dev_t *) calloc(num, sizeof(*list));
530+
if (list != NULL && esp_ble_get_bond_device_list(&num, list) == ESP_OK) {
531+
for (int i = 0; i < num; i++) {
532+
mgos_bt_ble_remove_paired_device(list[i].bd_addr);
533+
}
534+
}
535+
free(list);
536+
}
537+
495538
bool esp32_bt_gap_init(void) {
496539
if (esp_ble_gap_register_callback(esp32_bt_gap_ev) != ESP_OK) {
497540
return false;
@@ -515,9 +558,8 @@ bool esp32_bt_gap_init(void) {
515558
}
516559
}
517560

518-
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND;
519-
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req,
520-
sizeof(auth_req));
561+
mgos_bt_gap_set_pairing_enable(mgos_sys_config_get_bt_allow_pairing());
562+
521563
esp_ble_io_cap_t io_cap = ESP_IO_CAP_NONE;
522564
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &io_cap,
523565
sizeof(uint8_t));

src/esp32/esp32_bt_gatts.c

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ struct esp32_gatts_session_entry {
5454

5555
struct esp32_gatts_connection_entry {
5656
struct esp32_bt_connection bc;
57+
bool need_auth;
5758
SLIST_HEAD(sessions, esp32_gatts_session_entry) sessions;
5859
SLIST_ENTRY(esp32_gatts_connection_entry) next;
5960
};
@@ -267,6 +268,55 @@ static void run_on_mgos_task(esp_gatt_if_t gatts_if,
267268
mgos_invoke_cb(gatts_ev_mgos, ei, false /* from_isr */);
268269
}
269270

271+
static bool is_paired(const esp_bd_addr_t addr) {
272+
bool result = false;
273+
int num = esp_ble_get_bond_device_num();
274+
esp_ble_bond_dev_t *list = (esp_ble_bond_dev_t *) calloc(num, sizeof(*list));
275+
if (list != NULL && esp_ble_get_bond_device_list(&num, list) == ESP_OK) {
276+
for (int i = 0; i < num; i++) {
277+
if (mgos_bt_addr_cmp(addr, list[i].bd_addr) == 0) {
278+
result = true;
279+
break;
280+
}
281+
}
282+
}
283+
free(list);
284+
return result;
285+
}
286+
287+
static void create_sessions(struct esp32_gatts_connection_entry *ce) {
288+
/* Create a session for each of the currently registered services. */
289+
struct esp32_bt_service_entry *se;
290+
esp_ble_gatts_cb_param_t ep;
291+
ep.connect.conn_id = ce->bc.conn_id;
292+
memcpy(ep.connect.remote_bda, ce->bc.peer_addr, ESP_BD_ADDR_LEN);
293+
SLIST_FOREACH(se, &s_svcs, next) {
294+
struct esp32_gatts_session_entry *sse =
295+
(struct esp32_gatts_session_entry *) calloc(1, sizeof(*sse));
296+
sse->se = se;
297+
sse->bs.bc = &ce->bc;
298+
SLIST_INSERT_HEAD(&ce->sessions, sse, next);
299+
run_on_mgos_task(ce->bc.gatt_if, sse, sse->se, ESP_GATTS_CONNECT_EVT, &ep);
300+
}
301+
esp_ble_conn_update_params_t conn_params = {0};
302+
memcpy(conn_params.bda, ce->bc.peer_addr, ESP_BD_ADDR_LEN);
303+
conn_params.latency = 0;
304+
conn_params.max_int = 0x50; /* max_int = 0x50*1.25ms = 100ms */
305+
conn_params.min_int = 0x30; /* min_int = 0x30*1.25ms = 60ms */
306+
conn_params.timeout = 400; /* timeout = 400*10ms = 4000ms */
307+
esp_ble_gap_update_conn_params(&conn_params);
308+
}
309+
310+
void esp32_bt_gatts_auth_cmpl(const esp_bd_addr_t addr) {
311+
struct esp32_gatts_connection_entry *ce, *ct;
312+
SLIST_FOREACH_SAFE(ce, &s_conns, next, ct) {
313+
if (mgos_bt_addr_cmp(ce->bc.peer_addr, addr) == 0 && ce->need_auth) {
314+
ce->need_auth = false;
315+
create_sessions(ce);
316+
}
317+
}
318+
}
319+
270320
static void esp32_bt_gatts_ev(esp_gatts_cb_event_t ev, esp_gatt_if_t gatts_if,
271321
esp_ble_gatts_cb_param_t *ep) {
272322
char buf[BT_UUID_STR_LEN];
@@ -436,47 +486,62 @@ static void esp32_bt_gatts_ev(esp_gatts_cb_event_t ev, esp_gatt_if_t gatts_if,
436486
const struct gatts_connect_evt_param *p = &ep->connect;
437487
LOG(LL_INFO, ("CONNECT cid %d addr %s", p->conn_id,
438488
mgos_bt_addr_to_str(p->remote_bda, buf)));
439-
esp_ble_conn_update_params_t conn_params = {0};
440-
memcpy(conn_params.bda, p->remote_bda, ESP_BD_ADDR_LEN);
441-
conn_params.latency = 0;
442-
conn_params.max_int = 0x50; /* max_int = 0x50*1.25ms = 100ms */
443-
conn_params.min_int = 0x30; /* min_int = 0x30*1.25ms = 60ms */
444-
conn_params.timeout = 400; /* timeout = 400*10ms = 4000ms */
445-
esp_ble_gap_update_conn_params(&conn_params);
446489
/* Connect disables advertising. Resume, if it's enabled. */
447490
esp32_bt_set_is_advertising(false);
448491
mgos_bt_gap_set_adv_enable(mgos_bt_gap_get_adv_enable());
492+
bool disconnect = false;
493+
esp_ble_sec_act_t sec = 0;
449494
switch (mgos_sys_config_get_bt_gatts_min_sec_level()) {
450495
case MGOS_BT_GATT_PERM_LEVEL_NONE:
451496
break;
452497
case MGOS_BT_GATT_PERM_LEVEL_ENCR:
453-
LOG(LL_DEBUG, ("%s: Requesting encryption",
454-
mgos_bt_addr_to_str(p->remote_bda, buf)));
455-
esp_ble_set_encryption((uint8_t *) p->remote_bda,
456-
ESP_BLE_SEC_ENCRYPT_NO_MITM);
498+
sec = ESP_BLE_SEC_ENCRYPT_NO_MITM;
457499
break;
458500
case MGOS_BT_GATT_PERM_LEVEL_ENCR_MITM:
459-
LOG(LL_DEBUG, ("%s: Requesting encryption + MITM protection",
460-
mgos_bt_addr_to_str(p->remote_bda, buf)));
461-
esp_ble_set_encryption((uint8_t *) p->remote_bda,
462-
ESP_BLE_SEC_ENCRYPT_MITM);
501+
sec = ESP_BLE_SEC_ENCRYPT_MITM;
463502
break;
464503
}
504+
if (mgos_sys_config_get_bt_gatts_require_pairing()) {
505+
mgos_bt_addr_to_str(p->remote_bda, buf);
506+
int max_devices = mgos_sys_config_get_bt_max_paired_devices();
507+
if (is_paired(p->remote_bda)) {
508+
LOG(LL_INFO, ("%s: Already paired", buf));
509+
} else if (!mgos_bt_gap_get_pairing_enable()) {
510+
LOG(LL_ERROR, ("%s: pairing required but is not allowed", buf));
511+
disconnect = true;
512+
} else if (max_devices >= 0 &&
513+
mgos_bt_ble_get_num_paired_devices() >= max_devices) {
514+
LOG(LL_ERROR,
515+
("%s: pairing required but max num devices (%d) reached", buf,
516+
max_devices));
517+
disconnect = true;
518+
} else {
519+
LOG(LL_INFO, ("%s: Begin pairing", buf));
520+
if (sec == 0) sec = ESP_BLE_SEC_ENCRYPT_NO_MITM;
521+
}
522+
}
523+
if (disconnect) {
524+
LOG(LL_ERROR, ("%s: dropping connection",
525+
mgos_bt_addr_to_str(p->remote_bda, buf)));
526+
esp_ble_gap_disconnect((uint8_t *) p->remote_bda);
527+
break;
528+
}
465529
struct esp32_gatts_connection_entry *ce =
466530
(struct esp32_gatts_connection_entry *) calloc(1, sizeof(*ce));
467531
ce->bc.gatt_if = gatts_if;
468532
ce->bc.conn_id = p->conn_id;
469533
ce->bc.mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
470534
memcpy(ce->bc.peer_addr, p->remote_bda, ESP_BD_ADDR_LEN);
471-
/* Create a session for each of the currently registered services. */
472-
struct esp32_bt_service_entry *se;
473-
SLIST_FOREACH(se, &s_svcs, next) {
474-
struct esp32_gatts_session_entry *sse =
475-
(struct esp32_gatts_session_entry *) calloc(1, sizeof(*sse));
476-
sse->se = se;
477-
sse->bs.bc = &ce->bc;
478-
SLIST_INSERT_HEAD(&ce->sessions, sse, next);
479-
run_on_mgos_task(gatts_if, sse, sse->se, ev, ep);
535+
if (sec != 0) {
536+
LOG(LL_DEBUG,
537+
("%s: Requesting encryption%s",
538+
mgos_bt_addr_to_str(p->remote_bda, buf),
539+
(sec == ESP_BLE_SEC_ENCRYPT_MITM ? " + MITM protection" : "")));
540+
esp_ble_set_encryption((uint8_t *) p->remote_bda, sec);
541+
ce->need_auth = true;
542+
/* Wait for AUTH_CMPL */
543+
} else {
544+
create_sessions(ce);
480545
}
481546
SLIST_INSERT_HEAD(&s_conns, ce, next);
482547
break;

0 commit comments

Comments
 (0)