Skip to content

Commit 9a127fc

Browse files
authored
Merge pull request #11681 from lucasssvaz/fix/bt_component
fix(ble): Fix BLESecurity and add examples
2 parents 259c2ff + 586e497 commit 9a127fc

21 files changed

+1034
-166
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
Secure client with static passkey
3+
4+
This example demonstrates how to create a secure BLE client that connects to
5+
a secure BLE server using a static passkey without prompting the user.
6+
The client will automatically use the same passkey (123456) as the server.
7+
8+
This client is designed to work with the Server_secure_static_passkey example.
9+
10+
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
11+
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
12+
This means that in NimBLE you can read the insecure characteristic without entering
13+
the passkey. This is not possible in Bluedroid.
14+
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.
18+
19+
Based on examples from Neil Kolban and h2zero.
20+
Created by lucasssvaz.
21+
*/
22+
23+
#include "BLEDevice.h"
24+
#include "BLESecurity.h"
25+
26+
// The remote service we wish to connect to.
27+
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
28+
// The characteristics of the remote service we are interested in.
29+
static BLEUUID insecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
30+
static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8");
31+
32+
// This must match the server's passkey
33+
#define CLIENT_PIN 123456
34+
35+
static boolean doConnect = false;
36+
static boolean connected = false;
37+
static boolean doScan = false;
38+
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
39+
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
40+
static BLEAdvertisedDevice *myDevice;
41+
42+
// Callback function to handle notifications
43+
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
44+
Serial.print("Notify callback for characteristic ");
45+
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
46+
Serial.print(" of data length ");
47+
Serial.println(length);
48+
Serial.print("data: ");
49+
Serial.write(pData, length);
50+
Serial.println();
51+
}
52+
53+
class MyClientCallback : public BLEClientCallbacks {
54+
void onConnect(BLEClient *pclient) {
55+
Serial.println("Connected to secure server");
56+
}
57+
58+
void onDisconnect(BLEClient *pclient) {
59+
connected = false;
60+
Serial.println("Disconnected from server");
61+
}
62+
};
63+
64+
bool connectToServer() {
65+
Serial.print("Forming a secure connection to ");
66+
Serial.println(myDevice->getAddress().toString().c_str());
67+
68+
BLEClient *pClient = BLEDevice::createClient();
69+
Serial.println(" - Created client");
70+
71+
pClient->setClientCallbacks(new MyClientCallback());
72+
73+
// Connect to the remote BLE Server.
74+
pClient->connect(myDevice);
75+
Serial.println(" - Connected to server");
76+
77+
// Set MTU to maximum for better performance
78+
pClient->setMTU(517);
79+
80+
// Obtain a reference to the service we are after in the remote BLE server.
81+
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
82+
if (pRemoteService == nullptr) {
83+
Serial.print("Failed to find our service UUID: ");
84+
Serial.println(serviceUUID.toString().c_str());
85+
pClient->disconnect();
86+
return false;
87+
}
88+
Serial.println(" - Found our service");
89+
90+
// Obtain a reference to the insecure characteristic
91+
pRemoteInsecureCharacteristic = pRemoteService->getCharacteristic(insecureCharUUID);
92+
if (pRemoteInsecureCharacteristic == nullptr) {
93+
Serial.print("Failed to find insecure characteristic UUID: ");
94+
Serial.println(insecureCharUUID.toString().c_str());
95+
pClient->disconnect();
96+
return false;
97+
}
98+
Serial.println(" - Found insecure characteristic");
99+
100+
// Obtain a reference to the secure characteristic
101+
pRemoteSecureCharacteristic = pRemoteService->getCharacteristic(secureCharUUID);
102+
if (pRemoteSecureCharacteristic == nullptr) {
103+
Serial.print("Failed to find secure characteristic UUID: ");
104+
Serial.println(secureCharUUID.toString().c_str());
105+
pClient->disconnect();
106+
return false;
107+
}
108+
Serial.println(" - Found secure characteristic");
109+
110+
// Read the value of the insecure characteristic (should work without authentication)
111+
if (pRemoteInsecureCharacteristic->canRead()) {
112+
String value = pRemoteInsecureCharacteristic->readValue();
113+
Serial.print("Insecure characteristic value: ");
114+
Serial.println(value.c_str());
115+
}
116+
117+
// For Bluedroid, we need to set the authentication request type for the secure characteristic
118+
// This is not needed for NimBLE and will be ignored.
119+
pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_NO_MITM);
120+
121+
// Try to read the secure characteristic (this will trigger security negotiation in NimBLE)
122+
if (pRemoteSecureCharacteristic->canRead()) {
123+
Serial.println("Attempting to read secure characteristic...");
124+
String value = pRemoteSecureCharacteristic->readValue();
125+
Serial.print("Secure characteristic value: ");
126+
Serial.println(value.c_str());
127+
}
128+
129+
// Register for notifications on both characteristics if they support it
130+
if (pRemoteInsecureCharacteristic->canNotify()) {
131+
pRemoteInsecureCharacteristic->registerForNotify(notifyCallback);
132+
Serial.println(" - Registered for insecure characteristic notifications");
133+
}
134+
135+
if (pRemoteSecureCharacteristic->canNotify()) {
136+
pRemoteSecureCharacteristic->registerForNotify(notifyCallback);
137+
Serial.println(" - Registered for secure characteristic notifications");
138+
}
139+
140+
connected = true;
141+
return true;
142+
}
143+
144+
/**
145+
* Scan for BLE servers and find the first one that advertises the service we are looking for.
146+
*/
147+
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
148+
/**
149+
* Called for each advertising BLE server.
150+
*/
151+
void onResult(BLEAdvertisedDevice advertisedDevice) {
152+
Serial.print("BLE Advertised Device found: ");
153+
Serial.println(advertisedDevice.toString().c_str());
154+
155+
// We have found a device, let us now see if it contains the service we are looking for.
156+
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
157+
Serial.println("Found our secure server!");
158+
BLEDevice::getScan()->stop();
159+
myDevice = new BLEAdvertisedDevice(advertisedDevice);
160+
doConnect = true;
161+
doScan = true;
162+
}
163+
}
164+
};
165+
166+
void setup() {
167+
Serial.begin(115200);
168+
Serial.println("Starting Secure BLE Client application...");
169+
170+
BLEDevice::init("Secure BLE Client");
171+
172+
// Set up security with the same passkey as the server
173+
BLESecurity *pSecurity = new BLESecurity();
174+
175+
// Set security parameters
176+
// Default parameters:
177+
// - IO capability is set to NONE
178+
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
179+
// - 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
181+
182+
// Set the same static passkey as the server
183+
// The first argument defines if the passkey is static or random.
184+
// The second argument is the passkey (ignored when using a random passkey).
185+
pSecurity->setPassKey(true, CLIENT_PIN);
186+
187+
// Set authentication mode to match server requirements
188+
// Enable secure connection only for this example
189+
pSecurity->setAuthenticationMode(false, false, true);
190+
191+
// Retrieve a Scanner and set the callback we want to use to be informed when we
192+
// have detected a new device. Specify that we want active scanning and start the
193+
// scan to run for 5 seconds.
194+
BLEScan *pBLEScan = BLEDevice::getScan();
195+
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
196+
pBLEScan->setInterval(1349);
197+
pBLEScan->setWindow(449);
198+
pBLEScan->setActiveScan(true);
199+
pBLEScan->start(5, false);
200+
}
201+
202+
void loop() {
203+
// If the flag "doConnect" is true then we have scanned for and found the desired
204+
// BLE Server with which we wish to connect. Now we connect to it.
205+
if (doConnect == true) {
206+
if (connectToServer()) {
207+
Serial.println("We are now connected to the secure BLE Server.");
208+
} else {
209+
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
210+
}
211+
doConnect = false;
212+
}
213+
214+
// If we are connected to a peer BLE Server, demonstrate secure communication
215+
if (connected) {
216+
// Write to the insecure characteristic
217+
String insecureValue = "Client time: " + String(millis() / 1000);
218+
if (pRemoteInsecureCharacteristic->canWrite()) {
219+
pRemoteInsecureCharacteristic->writeValue(insecureValue.c_str(), insecureValue.length());
220+
Serial.println("Wrote to insecure characteristic: " + insecureValue);
221+
}
222+
223+
// Write to the secure characteristic
224+
String secureValue = "Secure client time: " + String(millis() / 1000);
225+
if (pRemoteSecureCharacteristic->canWrite()) {
226+
pRemoteSecureCharacteristic->writeValue(secureValue.c_str(), secureValue.length());
227+
Serial.println("Wrote to secure characteristic: " + secureValue);
228+
}
229+
} else if (doScan) {
230+
// Restart scanning if we're disconnected
231+
BLEDevice::getScan()->start(0);
232+
}
233+
234+
delay(2000); // Delay 2 seconds between loops
235+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"fqbn_append": "PartitionScheme=huge_app",
3+
"requires": [
4+
"CONFIG_SOC_BLE_SUPPORTED=y"
5+
]
6+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Secure server with static passkey
3+
4+
This example demonstrates how to create a secure BLE server with no
5+
IO capability using a static passkey.
6+
The server will accept connections from devices that have the same passkey set.
7+
The example passkey is set to 123456.
8+
The server will create a service and a secure and an insecure characteristic
9+
to be used as example.
10+
11+
This server is designed to be used with the Client_secure_static_passkey example.
12+
13+
Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE.
14+
Bluedroid initiates security on-connect, while NimBLE initiates security on-demand.
15+
This means that in NimBLE you can read the insecure characteristic without entering
16+
the passkey. This is not possible in Bluedroid.
17+
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.
21+
22+
Based on examples from Neil Kolban and h2zero.
23+
Created by lucasssvaz.
24+
*/
25+
26+
#include <BLEDevice.h>
27+
#include <BLEUtils.h>
28+
#include <BLEServer.h>
29+
#include <BLESecurity.h>
30+
#include <string>
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 Insecure_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
37+
#define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8"
38+
39+
// This is an example passkey. You should use a different or random passkey.
40+
#define SERVER_PIN 123456
41+
42+
void setup() {
43+
Serial.begin(115200);
44+
Serial.println("Starting BLE work!");
45+
46+
Serial.print("Using BLE stack: ");
47+
Serial.println(BLEDevice::getBLEStackString());
48+
49+
BLEDevice::init("Secure BLE Server");
50+
51+
BLESecurity *pSecurity = new BLESecurity();
52+
53+
// Set security parameters
54+
// Default parameters:
55+
// - IO capability is set to NONE
56+
// - Initiator and responder key distribution flags are set to both encryption and identity keys.
57+
// - 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
59+
60+
// Set static passkey
61+
// The first argument defines if the passkey is static or random.
62+
// The second argument is the passkey (ignored when using a random passkey).
63+
pSecurity->setPassKey(true, SERVER_PIN);
64+
65+
// Set authentication mode
66+
// Require secure connection only for this example
67+
pSecurity->setAuthenticationMode(false, false, true);
68+
69+
BLEServer *pServer = BLEDevice::createServer();
70+
pServer->advertiseOnDisconnect(true);
71+
72+
BLEService *pService = pServer->createService(SERVICE_UUID);
73+
74+
uint32_t insecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE;
75+
uint32_t secure_properties = insecure_properties;
76+
77+
// NimBLE uses properties to secure characteristics.
78+
// These special permission properties are not supported by Bluedroid and will be ignored.
79+
// This can be removed if only using Bluedroid (ESP32).
80+
// Check the BLECharacteristic.h file for more information.
81+
secure_properties |= BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_WRITE_ENC;
82+
83+
BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties);
84+
BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(Insecure_CHARACTERISTIC_UUID, insecure_properties);
85+
86+
// Bluedroid uses permissions to secure characteristics.
87+
// This is the same as using the properties above.
88+
// NimBLE does not use permissions and will ignore these calls.
89+
// 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);
91+
pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
92+
93+
// Set value for secure characteristic
94+
pSecureCharacteristic->setValue("Secure Hello World!");
95+
96+
// Set value for insecure characteristic
97+
// When using NimBLE you will be able to read this characteristic without entering the passkey.
98+
pInsecureCharacteristic->setValue("Insecure Hello World!");
99+
100+
pService->start();
101+
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
102+
pAdvertising->addServiceUUID(SERVICE_UUID);
103+
pAdvertising->setScanResponse(true);
104+
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
105+
pAdvertising->setMinPreferred(0x12);
106+
BLEDevice::startAdvertising();
107+
Serial.println("Characteristic defined! Now you can read it in your phone!");
108+
}
109+
110+
void loop() {
111+
delay(2000);
112+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"fqbn_append": "PartitionScheme=huge_app",
3+
"requires": [
4+
"CONFIG_SOC_BLE_SUPPORTED=y"
5+
]
6+
}

0 commit comments

Comments
 (0)