|
| 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 | +} |
0 commit comments