Skip to content

Commit 631216a

Browse files
authored
Merge pull request #11812 from lucasssvaz/fix/ble_bugfixes
fix(BLE): Fix double callback and secure examples
2 parents ad18f5f + 82f64a8 commit 631216a

File tree

11 files changed

+690
-26
lines changed

11 files changed

+690
-26
lines changed

libraries/BLE/README.md

Lines changed: 461 additions & 6 deletions
Large diffs are not rendered by default.

libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@
1212
This means that in NimBLE you can read the insecure characteristic without entering
1313
the passkey. This is not possible in Bluedroid.
1414
15-
Also, the SoC stores the authentication info in the NVS memory. After a successful
16-
connection it is possible that a passkey change will be ineffective.
17-
To avoid this, clear the memory of the SoC's between security tests.
15+
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
16+
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
17+
"Just Works" pairing method (with encryption if secure connection is enabled).
1818
1919
Based on examples from Neil Kolban and h2zero.
2020
Created by lucasssvaz.
2121
*/
2222

2323
#include "BLEDevice.h"
2424
#include "BLESecurity.h"
25+
#include "nvs_flash.h"
2526

2627
// The remote service we wish to connect to.
2728
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
@@ -116,7 +117,7 @@ bool connectToServer() {
116117

117118
// For Bluedroid, we need to set the authentication request type for the secure characteristic
118119
// This is not needed for NimBLE and will be ignored.
119-
pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_NO_MITM);
120+
pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_MITM);
120121

121122
// Try to read the secure characteristic (this will trigger security negotiation in NimBLE)
122123
if (pRemoteSecureCharacteristic->canRead()) {
@@ -167,6 +168,12 @@ void setup() {
167168
Serial.begin(115200);
168169
Serial.println("Starting Secure BLE Client application...");
169170

171+
// Clear NVS to remove any cached pairing information
172+
// This ensures fresh authentication for testing
173+
Serial.println("Clearing NVS pairing data...");
174+
nvs_flash_erase();
175+
nvs_flash_init();
176+
170177
BLEDevice::init("Secure BLE Client");
171178

172179
// Set up security with the same passkey as the server
@@ -177,16 +184,16 @@ void setup() {
177184
// - IO capability is set to NONE
178185
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
179186
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
180-
// - Max key size is set to 16 bytes
187+
// - Key size is set to 16 bytes
181188

182189
// Set the same static passkey as the server
183190
// The first argument defines if the passkey is static or random.
184191
// The second argument is the passkey (ignored when using a random passkey).
185192
pSecurity->setPassKey(true, CLIENT_PIN);
186193

187194
// Set authentication mode to match server requirements
188-
// Enable secure connection only for this example
189-
pSecurity->setAuthenticationMode(false, false, true);
195+
// Enable secure connection and MITM (for password prompts) for this example
196+
pSecurity->setAuthenticationMode(false, true, true);
190197

191198
// Retrieve a Scanner and set the callback we want to use to be informed when we
192199
// have detected a new device. Specify that we want active scanning and start the
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
Simple BLE Server Authorization Example
3+
4+
This example demonstrates how to create a BLE server with authorization
5+
requirements. It shows the essential setup for:
6+
- Authorization with static passkey
7+
- Secure connection
8+
- MITM (Man-In-The-Middle) protection
9+
10+
The server creates a single characteristic that requires authorization
11+
to access. Clients must provide the correct passkey (123456) to read
12+
or write to the characteristic.
13+
14+
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
15+
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
16+
17+
Due to a bug in ESP-IDF's Bluedroid, this example will currently not work with ESP32.
18+
19+
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
20+
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
21+
"Just Works" pairing method (with encryption if secure connection is enabled).
22+
23+
Created by lucasssvaz.
24+
*/
25+
26+
#include <BLEDevice.h>
27+
#include <BLEUtils.h>
28+
#include <BLEServer.h>
29+
#include <BLESecurity.h>
30+
#include <nvs_flash.h>
31+
32+
// See the following for generating UUIDs:
33+
// https://www.uuidgenerator.net/
34+
35+
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
36+
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
37+
38+
// Example passkey - change this for production use
39+
#define AUTH_PASSKEY 123456
40+
41+
static int s_readCount = 0;
42+
static BLECharacteristic *s_pCharacteristic;
43+
44+
class MySecurityCallbacks : public BLESecurityCallbacks {
45+
bool onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) {
46+
Serial.println("Authorization request received");
47+
if (isRead) {
48+
s_readCount++;
49+
// Keep value length <= (MTU - 1) to avoid a follow-up read request
50+
uint16_t maxLen = BLEDevice::getServer()->getPeerMTU(connHandle) - 1;
51+
String msg = "Authorized #" + String(s_readCount);
52+
if (msg.length() > maxLen) {
53+
msg = msg.substring(0, maxLen);
54+
}
55+
s_pCharacteristic->setValue(msg);
56+
// Grant authorization to the first 3 reads
57+
if (s_readCount <= 3) {
58+
Serial.println("Authorization granted");
59+
return true;
60+
} else {
61+
Serial.println("Authorization denied, read count exceeded");
62+
Serial.println("Please reset the read counter to continue");
63+
return false;
64+
}
65+
}
66+
// Fallback to deny
67+
Serial.println("Authorization denied");
68+
return false;
69+
}
70+
};
71+
72+
void setup() {
73+
Serial.begin(115200);
74+
Serial.println("Starting BLE Authorization Example!");
75+
76+
// Initialize the BOOT pin for resetting the read count
77+
pinMode(BOOT_PIN, INPUT_PULLUP);
78+
79+
// Clear NVS to remove any cached pairing information
80+
// This ensures fresh authentication for testing
81+
Serial.println("Clearing NVS pairing data...");
82+
nvs_flash_erase();
83+
nvs_flash_init();
84+
85+
Serial.print("Using BLE stack: ");
86+
Serial.println(BLEDevice::getBLEStackString());
87+
88+
BLEDevice::init("BLE Auth Server");
89+
90+
// Set MTU to 517 to avoid a follow-up read request
91+
BLEDevice::setMTU(517);
92+
93+
// Configure BLE Security
94+
BLESecurity *pSecurity = new BLESecurity();
95+
96+
// Set static passkey for authentication
97+
pSecurity->setPassKey(true, AUTH_PASSKEY);
98+
99+
// Set IO capability to DisplayOnly for MITM authentication
100+
pSecurity->setCapability(ESP_IO_CAP_OUT);
101+
102+
// Enable authorization requirements:
103+
// - bonding: true (for persistent storage of the keys)
104+
// - MITM: true (enables Man-In-The-Middle protection for password prompts)
105+
// - secure connection: true (enables secure connection for encryption)
106+
pSecurity->setAuthenticationMode(true, true, true);
107+
108+
// Set the security callbacks
109+
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());
110+
111+
// Create BLE Server
112+
BLEServer *pServer = BLEDevice::createServer();
113+
pServer->advertiseOnDisconnect(true);
114+
115+
// Create BLE Service
116+
BLEService *pService = pServer->createService(SERVICE_UUID);
117+
118+
// Create characteristic with read and write properties
119+
uint32_t properties = BLECharacteristic::PROPERTY_READ;
120+
121+
// For NimBLE: Add authentication properties
122+
// These properties ensure the characteristic requires authorization
123+
// (ignored by Bluedroid but harmless)
124+
properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_AUTHOR;
125+
126+
s_pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, properties);
127+
128+
// For Bluedroid: Set access permissions that require encryption and MITM
129+
// This ensures authorization is required (ignored by NimBLE)
130+
s_pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_READ_AUTHORIZATION);
131+
132+
// Set initial value
133+
s_pCharacteristic->setValue("Hello! You needed authorization to read this!");
134+
135+
// Start the service
136+
pService->start();
137+
138+
// Configure and start advertising
139+
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
140+
pAdvertising->addServiceUUID(SERVICE_UUID);
141+
pAdvertising->setScanResponse(true);
142+
pAdvertising->setMinPreferred(0x06); // helps with iPhone connections
143+
pAdvertising->setMinPreferred(0x12);
144+
145+
BLEDevice::startAdvertising();
146+
147+
Serial.println("BLE Server is running!");
148+
Serial.println("Authorization is required to access the characteristic.");
149+
Serial.printf("Use passkey: %d when prompted\n", AUTH_PASSKEY);
150+
}
151+
152+
void loop() {
153+
// Reset the read count if the BOOT pin is pressed
154+
if (digitalRead(BOOT_PIN) == LOW) {
155+
s_readCount = 0;
156+
Serial.println("Read count reset");
157+
}
158+
159+
delay(100);
160+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false
4+
},
5+
"fqbn_append": "PartitionScheme=huge_app",
6+
"requires": [
7+
"CONFIG_SOC_BLE_SUPPORTED=y"
8+
]
9+
}

libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
This means that in NimBLE you can read the insecure characteristic without entering
1616
the passkey. This is not possible in Bluedroid.
1717
18-
Also, the SoC stores the authentication info in the NVS memory. After a successful
19-
connection it is possible that a passkey change will be ineffective.
20-
To avoid this, clear the memory of the SoC's between security tests.
18+
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
19+
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
20+
"Just Works" pairing method (with encryption if secure connection is enabled).
2121
2222
Based on examples from Neil Kolban and h2zero.
2323
Created by lucasssvaz.
@@ -27,13 +27,14 @@
2727
#include <BLEUtils.h>
2828
#include <BLEServer.h>
2929
#include <BLESecurity.h>
30+
#include <nvs_flash.h>
3031
#include <string>
3132

3233
// See the following for generating UUIDs:
3334
// https://www.uuidgenerator.net/
3435

3536
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
36-
#define Insecure_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
37+
#define INSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
3738
#define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8"
3839

3940
// This is an example passkey. You should use a different or random passkey.
@@ -43,6 +44,12 @@ void setup() {
4344
Serial.begin(115200);
4445
Serial.println("Starting BLE work!");
4546

47+
// Clear NVS to remove any cached pairing information
48+
// This ensures fresh authentication for testing
49+
Serial.println("Clearing NVS pairing data...");
50+
nvs_flash_erase();
51+
nvs_flash_init();
52+
4653
Serial.print("Using BLE stack: ");
4754
Serial.println(BLEDevice::getBLEStackString());
4855

@@ -55,16 +62,22 @@ void setup() {
5562
// - IO capability is set to NONE
5663
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
5764
// - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it.
58-
// - Max key size is set to 16 bytes
65+
// - Key size is set to 16 bytes
5966

6067
// Set static passkey
6168
// The first argument defines if the passkey is static or random.
6269
// The second argument is the passkey (ignored when using a random passkey).
6370
pSecurity->setPassKey(true, SERVER_PIN);
6471

72+
// Set IO capability to DisplayOnly
73+
// We need the proper IO capability for MITM authentication even
74+
// if the passkey is static and won't be shown to the user
75+
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
76+
pSecurity->setCapability(ESP_IO_CAP_OUT);
77+
6578
// Set authentication mode
66-
// Require secure connection only for this example
67-
pSecurity->setAuthenticationMode(false, false, true);
79+
// Require secure connection and MITM (for password prompts) for this example
80+
pSecurity->setAuthenticationMode(false, true, true);
6881

6982
BLEServer *pServer = BLEDevice::createServer();
7083
pServer->advertiseOnDisconnect(true);
@@ -78,16 +91,16 @@ void setup() {
7891
// These special permission properties are not supported by Bluedroid and will be ignored.
7992
// This can be removed if only using Bluedroid (ESP32).
8093
// Check the BLECharacteristic.h file for more information.
81-
secure_properties |= BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_WRITE_ENC;
94+
secure_properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN;
8295

8396
BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties);
84-
BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(Insecure_CHARACTERISTIC_UUID, insecure_properties);
97+
BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(INSECURE_CHARACTERISTIC_UUID, insecure_properties);
8598

8699
// Bluedroid uses permissions to secure characteristics.
87100
// This is the same as using the properties above.
88101
// NimBLE does not use permissions and will ignore these calls.
89102
// This can be removed if only using NimBLE (any SoC except ESP32).
90-
pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
103+
pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM);
91104
pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
92105

93106
// Set value for secure characteristic

libraries/BLE/src/BLECharacteristic.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,6 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
923923
if (ctxt->om->om_pkthdr_len > 8) {
924924
rc = ble_gap_conn_find(conn_handle, &desc);
925925
assert(rc == 0);
926-
pCharacteristic->m_pCallbacks->onRead(pCharacteristic);
927926
pCharacteristic->m_pCallbacks->onRead(pCharacteristic, &desc);
928927
}
929928

@@ -957,7 +956,6 @@ int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr
957956
rc = ble_gap_conn_find(conn_handle, &desc);
958957
assert(rc == 0);
959958
pCharacteristic->setValue(buf, len);
960-
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic);
961959
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc);
962960

963961
return 0;

libraries/BLE/src/BLEClient.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ void BLEClient::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t
536536
m_semaphoreRssiCmplEvt.give();
537537
m_semaphoreSearchCmplEvt.give(1);
538538
BLEDevice::removePeerDevice(m_appId, true);
539+
// Reset security state on disconnect
540+
BLESecurity::resetSecurity();
539541
if (m_wasConnected && m_pClientCallbacks != nullptr) {
540542
m_pClientCallbacks->onDisconnect(this);
541543
}
@@ -981,6 +983,8 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) {
981983

982984
BLEDevice::removePeerDevice(client->m_appId, true);
983985
client->m_isConnected = false;
986+
// Reset security state on disconnect
987+
BLESecurity::resetSecurity();
984988
if (client->m_pClientCallbacks != nullptr) {
985989
client->m_pClientCallbacks->onDisconnect(client);
986990
}

libraries/BLE/src/BLESecurity.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ bool BLESecurity::m_securityStarted = false;
4747
bool BLESecurity::m_passkeySet = false;
4848
bool BLESecurity::m_staticPasskey = true;
4949
bool BLESecurity::m_regenOnConnect = false;
50-
uint8_t BLESecurity::m_iocap = 0;
50+
uint8_t BLESecurity::m_iocap = ESP_IO_CAP_NONE;
5151
uint8_t BLESecurity::m_authReq = 0;
5252
uint8_t BLESecurity::m_initKey = 0;
5353
uint8_t BLESecurity::m_respKey = 0;
@@ -188,6 +188,12 @@ void BLESecurity::regenPassKeyOnConnect(bool enable) {
188188
m_regenOnConnect = enable;
189189
}
190190

191+
// This function resets the security state on disconnect.
192+
void BLESecurity::resetSecurity() {
193+
log_d("resetSecurity: Resetting security state");
194+
m_securityStarted = false;
195+
}
196+
191197
// This function sets the authentication mode with bonding, MITM, and secure connection options.
192198
void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) {
193199
log_d("setAuthenticationMode: bonding=%d, mitm=%d, sc=%d", bonding, mitm, sc);
@@ -270,6 +276,7 @@ void BLESecurity::setEncryptionLevel(esp_ble_sec_act_t level) {
270276

271277
bool BLESecurity::startSecurity(esp_bd_addr_t bd_addr, int *rcPtr) {
272278
#ifdef CONFIG_BLE_SMP_ENABLE
279+
log_d("startSecurity: bd_addr=%s", BLEAddress(bd_addr).toString().c_str());
273280
if (m_securityStarted) {
274281
log_w("Security already started for bd_addr=%s", BLEAddress(bd_addr).toString().c_str());
275282
if (rcPtr) {
@@ -333,6 +340,7 @@ void BLESecurityCallbacks::onAuthenticationComplete(esp_ble_auth_cmpl_t param) {
333340
#if defined(CONFIG_NIMBLE_ENABLED)
334341
// This function initiates security for a given connection handle.
335342
bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) {
343+
log_d("startSecurity: connHandle=%d", connHandle);
336344
if (m_securityStarted) {
337345
log_w("Security already started for connHandle=%d", connHandle);
338346
if (rcPtr) {

0 commit comments

Comments
 (0)