From 511c1d456006b19283268bc40a168e67194985e2 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 31 Jul 2025 08:51:38 -0300 Subject: [PATCH 1/3] fix(ble): Fix security for Bluedroid and add examples --- .../Client_secure_static_passkey.ino | 235 ++++++++++++++++++ .../Client_secure_static_passkey/ci.json | 6 + .../Server_secure_static_passkey.ino | 114 +++++++++ .../Server_secure_static_passkey/ci.json | 6 + libraries/BLE/src/BLECharacteristic.cpp | 57 ++++- libraries/BLE/src/BLECharacteristic.h | 14 +- libraries/BLE/src/BLEClient.cpp | 92 ++++--- libraries/BLE/src/BLEClient.h | 3 - libraries/BLE/src/BLEDescriptor.cpp | 2 +- libraries/BLE/src/BLEDescriptor.h | 7 +- libraries/BLE/src/BLEDevice.cpp | 106 ++++++-- libraries/BLE/src/BLEDevice.h | 16 +- libraries/BLE/src/BLERemoteCharacteristic.cpp | 6 +- libraries/BLE/src/BLERemoteCharacteristic.h | 7 + libraries/BLE/src/BLERemoteDescriptor.cpp | 4 +- libraries/BLE/src/BLESecurity.cpp | 234 +++++++++++++++-- libraries/BLE/src/BLESecurity.h | 99 ++++++-- libraries/BLE/src/BLEServer.cpp | 145 ++++++++--- libraries/BLE/src/BLEServer.h | 9 +- libraries/BLE/src/BLEUtils.cpp | 7 +- libraries/BLE/src/BLEUtils.h | 2 +- 21 files changed, 1017 insertions(+), 154 deletions(-) create mode 100644 libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino create mode 100644 libraries/BLE/examples/Client_secure_static_passkey/ci.json create mode 100644 libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino create mode 100644 libraries/BLE/examples/Server_secure_static_passkey/ci.json diff --git a/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino b/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino new file mode 100644 index 00000000000..d0870c7b2ad --- /dev/null +++ b/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino @@ -0,0 +1,235 @@ +/* + Secure client with static passkey + + This example demonstrates how to create a secure BLE client that connects to + a secure BLE server using a static passkey without prompting the user. + The client will automatically use the same passkey (123456) as the server. + + This client is designed to work with the Server_secure_static_passkey example. + + Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE. + Bluedroid initiates security on-connect, while NimBLE initiates security on-demand. + This means that in NimBLE you can read the unsecure characteristic without entering + the passkey. This is not possible in Bluedroid. + + Also, the SoC stores the authentication info in the NVS memory. After a successful + connection it is possible that a passkey change will be ineffective. + To avoid this, clear the memory of the SoC's between security tests. + + Based on examples from Neil Kolban and h2zero. + Created by lucasssvaz. +*/ + +#include "BLEDevice.h" +#include "BLESecurity.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); +// The characteristics of the remote service we are interested in. +static BLEUUID unsecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); +static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8"); + +// This must match the server's passkey +#define CLIENT_PIN 123456 + +static boolean doConnect = false; +static boolean connected = false; +static boolean doScan = false; +static BLERemoteCharacteristic *pRemoteUnsecureCharacteristic; +static BLERemoteCharacteristic *pRemoteSecureCharacteristic; +static BLEAdvertisedDevice *myDevice; + +// Callback function to handle notifications +static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); + Serial.print("data: "); + Serial.write(pData, length); + Serial.println(); +} + +class MyClientCallback : public BLEClientCallbacks { + void onConnect(BLEClient *pclient) { + Serial.println("Connected to secure server"); + } + + void onDisconnect(BLEClient *pclient) { + connected = false; + Serial.println("Disconnected from server"); + } +}; + +bool connectToServer() { + Serial.print("Forming a secure connection to "); + Serial.println(myDevice->getAddress().toString().c_str()); + + BLEClient *pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + pClient->setClientCallbacks(new MyClientCallback()); + + // Connect to the remote BLE Server. + pClient->connect(myDevice); + Serial.println(" - Connected to server"); + + // Set MTU to maximum for better performance + pClient->setMTU(517); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService *pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + Serial.println(" - Found our service"); + + // Obtain a reference to the unsecure characteristic + pRemoteUnsecureCharacteristic = pRemoteService->getCharacteristic(unsecureCharUUID); + if (pRemoteUnsecureCharacteristic == nullptr) { + Serial.print("Failed to find unsecure characteristic UUID: "); + Serial.println(unsecureCharUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + Serial.println(" - Found unsecure characteristic"); + + // Obtain a reference to the secure characteristic + pRemoteSecureCharacteristic = pRemoteService->getCharacteristic(secureCharUUID); + if (pRemoteSecureCharacteristic == nullptr) { + Serial.print("Failed to find secure characteristic UUID: "); + Serial.println(secureCharUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + Serial.println(" - Found secure characteristic"); + + // Read the value of the unsecure characteristic (should work without authentication) + if (pRemoteUnsecureCharacteristic->canRead()) { + String value = pRemoteUnsecureCharacteristic->readValue(); + Serial.print("Unsecure characteristic value: "); + Serial.println(value.c_str()); + } + + // For Bluedroid, we need to set the authentication request type for the secure characteristic + // This is not needed for NimBLE and will be ignored. + pRemoteSecureCharacteristic->setAuth(ESP_GATT_AUTH_REQ_NO_MITM); + + // Try to read the secure characteristic (this will trigger security negotiation in NimBLE) + if (pRemoteSecureCharacteristic->canRead()) { + Serial.println("Attempting to read secure characteristic..."); + String value = pRemoteSecureCharacteristic->readValue(); + Serial.print("Secure characteristic value: "); + Serial.println(value.c_str()); + } + + // Register for notifications on both characteristics if they support it + if (pRemoteUnsecureCharacteristic->canNotify()) { + pRemoteUnsecureCharacteristic->registerForNotify(notifyCallback); + Serial.println(" - Registered for unsecure characteristic notifications"); + } + + if (pRemoteSecureCharacteristic->canNotify()) { + pRemoteSecureCharacteristic->registerForNotify(notifyCallback); + Serial.println(" - Registered for secure characteristic notifications"); + } + + connected = true; + return true; +} + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + Serial.println("Found our secure server!"); + BLEDevice::getScan()->stop(); + myDevice = new BLEAdvertisedDevice(advertisedDevice); + doConnect = true; + doScan = true; + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting Secure BLE Client application..."); + + BLEDevice::init("Secure BLE Client"); + + // Set up security with the same passkey as the server + BLESecurity *pSecurity = new BLESecurity(); + + // Set security parameters + // Deafult parameters: + // - IO capability is set to NONE + // - Initiator and responder key distribution flags are set to both encryption and identity keys. + // - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it. + // - Max key size is set to 16 bytes + + // Set the same static passkey as the server + // The first argument defines if the passkey is static or random. + // The second argument is the passkey (ignored when using a random passkey). + pSecurity->setPassKey(true, CLIENT_PIN); + + // Set authentication mode to match server requirements + // Enable secure connection only for this example + pSecurity->setAuthenticationMode(false, false, true); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + pBLEScan->start(5, false); +} + +void loop() { + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. + if (doConnect == true) { + if (connectToServer()) { + Serial.println("We are now connected to the secure BLE Server."); + } else { + Serial.println("We have failed to connect to the server; there is nothing more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, demonstrate secure communication + if (connected) { + // Write to the unsecure characteristic + String unsecureValue = "Client time: " + String(millis() / 1000); + if (pRemoteUnsecureCharacteristic->canWrite()) { + pRemoteUnsecureCharacteristic->writeValue(unsecureValue.c_str(), unsecureValue.length()); + Serial.println("Wrote to unsecure characteristic: " + unsecureValue); + } + + // Write to the secure characteristic + String secureValue = "Secure client time: " + String(millis() / 1000); + if (pRemoteSecureCharacteristic->canWrite()) { + pRemoteSecureCharacteristic->writeValue(secureValue.c_str(), secureValue.length()); + Serial.println("Wrote to secure characteristic: " + secureValue); + } + } else if (doScan) { + // Restart scanning if we're disconnected + BLEDevice::getScan()->start(0); + } + + delay(2000); // Delay 2 seconds between loops +} diff --git a/libraries/BLE/examples/Client_secure_static_passkey/ci.json b/libraries/BLE/examples/Client_secure_static_passkey/ci.json new file mode 100644 index 00000000000..abe13a7ebbb --- /dev/null +++ b/libraries/BLE/examples/Client_secure_static_passkey/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_BLE_SUPPORTED=y" + ] +} diff --git a/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino new file mode 100644 index 00000000000..f23490156f0 --- /dev/null +++ b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino @@ -0,0 +1,114 @@ +/* + Secure server with static passkey + + This example demonstrates how to create a secure BLE server with no + IO capability using a static passkey. + The server will accept connections from devices that have the same passkey set. + The example passkey is set to 123456. + The server will create a service and a secure and an unsecure characteristic + to be used as example. + + This server is designed to be used with the Client_secure_static_passkey example. + + Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE. + Bluedroid initiates security on-connect, while NimBLE initiates security on-demand. + This means that in NimBLE you can read the unsecure characteristic without entering + the passkey. This is not possible in Bluedroid. + + Also, the SoC stores the authentication info in the NVS memory. After a successful + connection it is possible that a passkey change will be ineffective. + To avoid this, clear the memory of the SoC's between security tests. + + Based on examples from Neil Kolban and h2zero. + Created by lucasssvaz. +*/ + +#include +#include +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define UNSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8" + +// This is an example passkey. You should use a different or random passkey. +#define SERVER_PIN 123456 + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + Serial.print("Using BLE stack: "); + Serial.println(BLEDevice::getBLEStackString()); + + BLEDevice::init("Secure BLE Server"); + + BLESecurity *pSecurity = new BLESecurity(); + + // Set security parameters + // Deafult parameters: + // - IO capability is set to NONE + // - Initiator and responder key distribution flags are set to both encryption and identity keys. + // - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it. + // - Max key size is set to 16 bytes + + // Set static passkey + // The first argument defines if the passkey is static or random. + // The second argument is the passkey (ignored when using a random passkey). + pSecurity->setPassKey(true, SERVER_PIN); + + // Set authentication mode + // Require secure connection only for this example + pSecurity->setAuthenticationMode(false, false, true); + + BLEServer *pServer = BLEDevice::createServer(); + pServer->advertiseOnDisconnect(true); + + BLEService *pService = pServer->createService(SERVICE_UUID); + + uint32_t unsecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE; + uint32_t secure_properties = unsecure_properties; + + // NimBLE uses properties to secure characteristics. + // These special permission properties are not supported by Bluedroid and will be ignored. + // This can be removed if only using Bluedroid (ESP32). + // Check the BLECharacteristic.h file for more information. + secure_properties |= BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_WRITE_ENC; + + BLECharacteristic *pSecureCharacteristic = + pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties); + BLECharacteristic *pUnsecureCharacteristic = + pService->createCharacteristic(UNSECURE_CHARACTERISTIC_UUID, unsecure_properties); + + // Bluedroid uses permissions to secure characteristics. + // This is the same as using the properties above. + // NimBLE does not use permissions and will ignore these calls. + // This can be removed if only using NimBLE (any SoC except ESP32). + pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pUnsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); + + // Set value for secure characteristic + pSecureCharacteristic->setValue("Secure Hello World!"); + + // Set value for unsecure characteristic + // When using NimBLE you will be able to read this characteristic without entering the passkey. + pUnsecureCharacteristic->setValue("Unsecure Hello World!"); + + pService->start(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + delay(2000); +} diff --git a/libraries/BLE/examples/Server_secure_static_passkey/ci.json b/libraries/BLE/examples/Server_secure_static_passkey/ci.json new file mode 100644 index 00000000000..abe13a7ebbb --- /dev/null +++ b/libraries/BLE/examples/Server_secure_static_passkey/ci.json @@ -0,0 +1,6 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_BLE_SUPPORTED=y" + ] +} diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 3d218e6bd13..369324395be 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -145,7 +145,7 @@ uint16_t BLECharacteristic::getHandle() { return m_handle; } // getHandle -void BLECharacteristic::setAccessPermissions(uint8_t perm) { +void BLECharacteristic::setAccessPermissions(uint16_t perm) { #ifdef CONFIG_BLUEDROID_ENABLED m_permissions = perm; #endif @@ -567,6 +567,34 @@ void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_ga // we save the new value. Next we look at the need_rsp flag which indicates whether or not we need // to send a response. If we do, then we formulate a response and send it. if (param->write.handle == m_handle) { + + // Check for authorization requirement + if (m_permissions & ESP_GATT_PERM_WRITE_AUTHORIZATION) { + bool authorized = false; + + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("Authorization required for write operation. Checking authorization..."); + authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( + param->write.conn_id, m_handle, false + ); + } else { + log_w("onAuthorizationRequest not implemented. Rejecting write authorization request"); + } + + if (!authorized) { + log_i("Write authorization rejected"); + if (param->write.need_rsp) { + esp_err_t errRc = ::esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_INSUF_AUTHORIZATION, nullptr); + if (errRc != ESP_OK) { + log_e("esp_ble_gatts_send_response (authorization failed): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + return; // Exit early, don't process the write + } else { + log_i("Write authorization granted"); + } + } + if (param->write.is_prep) { m_value.addPart(param->write.value, param->write.len); m_writeEvt = true; @@ -622,6 +650,33 @@ void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_ga { if (param->read.handle == m_handle) { + // Check for authorization requirement + if (m_permissions & ESP_GATT_PERM_READ_AUTHORIZATION) { + bool authorized = false; + + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("Authorization required for read operation. Checking authorization..."); + authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( + param->read.conn_id, m_handle, true + ); + } else { + log_w("onAuthorizationRequest not implemented. Rejecting read authorization request"); + } + + if (!authorized) { + log_i("Read authorization rejected"); + if (param->read.need_rsp) { + esp_err_t errRc = ::esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_INSUF_AUTHORIZATION, nullptr); + if (errRc != ESP_OK) { + log_e("esp_ble_gatts_send_response (authorization failed): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + return; // Exit early, don't process the read + } else { + log_i("Read authorization granted"); + } + } + // Here's an interesting thing. The read request has the option of saying whether we need a response // or not. What would it "mean" to receive a read request and NOT send a response back? That feels like // a very strange read. diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index dc87177644f..ddd06cec29f 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -62,7 +62,7 @@ #if defined(CONFIG_NIMBLE_ENABLED) typedef uint16_t esp_gatt_char_prop_t; -typedef uint8_t esp_gatt_perm_t; +typedef uint16_t esp_gatt_perm_t; #endif /*************************************************************************** @@ -140,11 +140,17 @@ class BLECharacteristic { #if defined(CONFIG_BLUEDROID_ENABLED) static const uint32_t PROPERTY_READ = 1 << 0; + static const uint32_t PROPERTY_READ_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_READ_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_READ_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. static const uint32_t PROPERTY_WRITE = 1 << 1; + static const uint32_t PROPERTY_WRITE_NR = 1 << 5; + static const uint32_t PROPERTY_WRITE_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_WRITE_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_WRITE_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. static const uint32_t PROPERTY_NOTIFY = 1 << 2; static const uint32_t PROPERTY_BROADCAST = 1 << 3; static const uint32_t PROPERTY_INDICATE = 1 << 4; - static const uint32_t PROPERTY_WRITE_NR = 1 << 5; #endif /*************************************************************************** @@ -161,8 +167,8 @@ class BLECharacteristic { static const uint32_t PROPERTY_WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC; static const uint32_t PROPERTY_WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN; static const uint32_t PROPERTY_WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR; - static const uint32_t PROPERTY_BROADCAST = BLE_GATT_CHR_F_BROADCAST; static const uint32_t PROPERTY_NOTIFY = BLE_GATT_CHR_F_NOTIFY; + static const uint32_t PROPERTY_BROADCAST = BLE_GATT_CHR_F_BROADCAST; static const uint32_t PROPERTY_INDICATE = BLE_GATT_CHR_F_INDICATE; #endif @@ -193,7 +199,7 @@ class BLECharacteristic { void setValue(double data64); String toString(); uint16_t getHandle(); - void setAccessPermissions(uint8_t perm); + void setAccessPermissions(uint16_t perm); esp_gatt_char_prop_t getProperties(); void setReadProperty(bool value); void setWriteProperty(bool value); diff --git a/libraries/BLE/src/BLEClient.cpp b/libraries/BLE/src/BLEClient.cpp index c101993d002..7eeea3c1961 100644 --- a/libraries/BLE/src/BLEClient.cpp +++ b/libraries/BLE/src/BLEClient.cpp @@ -19,6 +19,7 @@ * Common includes * ***************************************************************************/ +#include "Arduino.h" #include #include "BLEClient.h" #include "BLEUtils.h" @@ -29,6 +30,7 @@ #include #include "BLEDevice.h" #include "esp32-hal-log.h" +#include "BLESecurity.h" /*************************************************************************** * Bluedroid includes * @@ -598,11 +600,11 @@ void BLEClient::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t if (errRc != ESP_OK) { log_e("esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { - esp_ble_set_encryption(evtParam->connect.remote_bda, BLEDevice::m_securityLevel); + // Set encryption on connect for BlueDroid when security is enabled + // This ensures security is established before any secure operations + if (BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { + BLESecurity::startSecurity(evtParam->connect.remote_bda); } -#endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTC_CONNECT_EVT @@ -1006,6 +1008,10 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { break; } + if(BLESecurity::m_securityEnabled) { + BLESecurity::startSecurity(client->m_conn_id); + } + // In the case of a multiconnecting device we ignore this device when // scanning since we are already connected to it // BLEDevice::addIgnored(client->m_peerAddress); @@ -1136,8 +1142,6 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { ble_store_util_delete_peer(&desc.peer_id_addr); } else if (BLEDevice::m_securityCallbacks != nullptr) { BLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc); - } else { - client->m_pClientCallbacks->onAuthenticationComplete(&desc); } } @@ -1164,27 +1168,52 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { } if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + // Display the passkey on this device + log_d("BLE_SM_IOACT_DISP"); + pkey.action = event->passkey.params.action; - pkey.passkey = BLESecurity::m_passkey; // This is the passkey to be entered on peer + pkey.passkey = BLESecurity::getPassKey(); // This is the passkey to be entered on peer + + if(!BLESecurity::m_passkeySet) { + log_w("No passkey set"); + } + + if (BLESecurity::m_staticPasskey && pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); + } else { + log_i("Passkey: %d", pkey.passkey); + } + + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onPassKeyNotify(pkey.passkey); + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + // Check if the passkey on the peer device is correct + log_d("BLE_SM_IOACT_NUMCMP"); + log_d("Passkey on device's display: %d", event->passkey.params.numcmp); pkey.action = event->passkey.params.action; - // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { pkey.numcmp_accept = BLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp); - //////////////////////////////////////////////////// } else { - pkey.numcmp_accept = client->m_pClientCallbacks->onConfirmPIN(event->passkey.params.numcmp); + log_e("onConfirmPIN not implemented. Rejecting connection"); + pkey.numcmp_accept = 0; } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("ble_sm_inject_io result: %d", rc); - //TODO: Handle out of band pairing } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + // Out of band pairing + // TODO: Handle out of band pairing + log_w("BLE_SM_IOACT_OOB: Not implemented"); + static uint8_t tem_oob[16] = {0}; pkey.action = event->passkey.params.action; for (int i = 0; i < 16; i++) { @@ -1192,24 +1221,35 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("ble_sm_inject_io result: %d", rc); - //////// } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { - log_d("Enter the passkey"); + // Input passkey from peer device + log_d("BLE_SM_IOACT_INPUT"); + pkey.action = event->passkey.params.action; + pkey.passkey = BLESecurity::getPassKey(); + + if (!BLESecurity::m_passkeySet) { + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("No passkey set, getting passkey from onPassKeyRequest"); + pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); + } else { + log_w("*ATTENTION* onPassKeyRequest not implemented and no static passkey set."); + } + } - // Compatibility only - Do not use, should be removed the in future - if (BLEDevice::m_securityCallbacks != nullptr) { - pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); - ///////////////////////////////////////////// + if (BLESecurity::m_staticPasskey && pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); } else { - pkey.passkey = client->m_pClientCallbacks->onPassKeyRequest(); + log_i("Passkey: %d", pkey.passkey); } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NONE) { - log_d("No passkey action required"); + log_d("BLE_SM_IOACT_NONE"); + log_i("No passkey action required"); } return 0; @@ -1255,20 +1295,6 @@ bool BLEClientCallbacks::onConnParamsUpdateRequest(BLEClient *pClient, const ble return true; } -uint32_t BLEClientCallbacks::onPassKeyRequest() { - log_d("onPassKeyRequest: default: 123456"); - return 123456; -} - -void BLEClientCallbacks::onAuthenticationComplete(ble_gap_conn_desc *desc) { - log_d("onAuthenticationComplete: default"); -} - -bool BLEClientCallbacks::onConfirmPIN(uint32_t pin) { - log_d("onConfirmPIN: default: true"); - return true; -} - #endif // CONFIG_NIMBLE_ENABLED #endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ diff --git a/libraries/BLE/src/BLEClient.h b/libraries/BLE/src/BLEClient.h index 97ef72f4917..f3823f72d3d 100644 --- a/libraries/BLE/src/BLEClient.h +++ b/libraries/BLE/src/BLEClient.h @@ -209,9 +209,6 @@ class BLEClientCallbacks { #if defined(CONFIG_NIMBLE_ENABLED) virtual bool onConnParamsUpdateRequest(BLEClient *pClient, const ble_gap_upd_params *params); - virtual uint32_t onPassKeyRequest(); - virtual void onAuthenticationComplete(ble_gap_conn_desc *desc); - virtual bool onConfirmPIN(uint32_t pin); #endif }; diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 2452f3cd45b..6e6df767dc4 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -207,7 +207,7 @@ void BLEDescriptor::setValue(const String &value) { setValue(reinterpret_cast(value.c_str()), value.length()); } // setValue -void BLEDescriptor::setAccessPermissions(uint8_t perm) { +void BLEDescriptor::setAccessPermissions(uint16_t perm) { m_permissions = perm; } diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index 83008743ae5..1e4f80f8ac5 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -42,6 +42,9 @@ #include #include "BLEConnInfo.h" +// Bluedroid compatibility +// NimBLE does not support signed reads and writes + #define ESP_GATT_PERM_READ BLE_ATT_F_READ #define ESP_GATT_PERM_WRITE BLE_ATT_F_WRITE #define ESP_GATT_PERM_READ_ENCRYPTED BLE_ATT_F_READ_ENC @@ -92,7 +95,7 @@ class BLEDescriptor { uint8_t *getValue(); // Get a pointer to the value of the descriptor. BLECharacteristic *getCharacteristic(); // Get the characteristic that this descriptor belongs to. - void setAccessPermissions(uint8_t perm); // Set the permissions of the descriptor. + void setAccessPermissions(uint16_t perm); // Set the permissions of the descriptor. void setCallbacks(BLEDescriptorCallbacks *pCallbacks); // Set callbacks to be invoked for the descriptor. void setValue(const uint8_t *data, size_t size); // Set the value of the descriptor as a pointer to data. void setValue(const String &value); // Set the value of the descriptor as a data buffer. @@ -140,7 +143,7 @@ class BLEDescriptor { #if defined(CONFIG_BLUEDROID_ENABLED) FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - uint8_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + uint16_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; #endif /*************************************************************************** diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 014806cc32b..d06a8277d50 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -35,6 +35,7 @@ #include "BLEClient.h" #include "BLEUtils.h" #include "GeneralUtils.h" +#include "BLESecurity.h" #if defined(ARDUINO_ARCH_ESP32) #include "esp32-hal-bt.h" @@ -97,7 +98,6 @@ gap_event_handler BLEDevice::m_customGapHandler = nullptr; ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; gattc_event_handler BLEDevice::m_customGattcHandler = nullptr; gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; #endif @@ -236,6 +236,8 @@ String BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID ch */ void BLEDevice::init(String deviceName) { if (!initialized) { + log_i("Initializing BLE stack: %s", getBLEStackString().c_str()); + esp_err_t errRc = ESP_OK; #if defined(CONFIG_BLUEDROID_ENABLED) #if defined(ARDUINO_ARCH_ESP32) @@ -709,6 +711,28 @@ void BLEDevice::setCustomGapHandler(gap_event_handler handler) { #endif } +BLEStack BLEDevice::getBLEStack() { +#if defined(CONFIG_BLUEDROID_ENABLED) + return BLEStack::BLUEDROID; +#elif defined(CONFIG_NIMBLE_ENABLED) + return BLEStack::NIMBLE; +#else + return BLEStack::UNKNOWN; +#endif +} + +String BLEDevice::getBLEStackString() { + switch (getBLEStack()) { + case BLEStack::BLUEDROID: + return "Bluedroid"; + case BLEStack::NIMBLE: + return "NimBLE"; + case BLEStack::UNKNOWN: + default: + return "Unknown"; + } +} + /*************************************************************************** * Bluedroid functions * ***************************************************************************/ @@ -730,11 +754,9 @@ void BLEDevice::gattServerEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t switch (event) { case ESP_GATTS_CONNECT_EVT: { -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + if (BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { + BLESecurity::startSecurity(param->connect.remote_bda); } -#endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTS_CONNECT_EVT @@ -771,11 +793,11 @@ void BLEDevice::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t switch (event) { case ESP_GATTC_CONNECT_EVT: { -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + // Set encryption on connect for BlueDroid when security is enabled + // This ensures security is established before any secure operations + if (BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { + BLESecurity::startSecurity(param->connect.remote_bda); } -#endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTS_CONNECT_EVT @@ -807,26 +829,51 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ log_i("ESP_GAP_BLE_OOB_REQ_EVT"); break; case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ log_i("ESP_GAP_BLE_LOCAL_IR_EVT"); break; case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ log_i("ESP_GAP_BLE_LOCAL_ER_EVT"); break; - case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ log_i("ESP_GAP_BLE_NC_REQ_EVT"); + case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ + { + log_i("ESP_GAP_BLE_NC_REQ_EVT"); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if (BLEDevice::m_securityCallbacks != nullptr) { esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } else { + log_e("onConfirmPIN not implemented. Rejecting connection"); + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, false); } #endif // CONFIG_BLE_SMP_ENABLE + } break; case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + { log_i("ESP_GAP_BLE_PASSKEY_REQ_EVT: "); // esp_log_buffer_hex(m_remote_bda, sizeof(m_remote_bda)); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); + uint32_t passkey = BLESecurity::getPassKey(); + + if (!BLESecurity::m_passkeySet) { + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("No passkey set, getting passkey from onPassKeyRequest"); + passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); + } else { + log_w("*ATTENTION* onPassKeyRequest not implemented and no static passkey set."); + } + } + + if (BLESecurity::m_staticPasskey && passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); + } else { + log_i("Passkey: %d", passkey); } + + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, passkey); #endif // CONFIG_BLE_SMP_ENABLE + } break; /* * TODO should we add white/black list comparison? */ case ESP_GAP_BLE_SEC_REQ_EVT: + { /* send the positive(true) security response to the peer device to accept the security request. If not accept the security request, should sent the security response with negative(false) accept value*/ log_i("ESP_GAP_BLE_SEC_REQ_EVT"); @@ -834,36 +881,57 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par if (BLEDevice::m_securityCallbacks != nullptr) { esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); } else { + log_w("onSecurityRequest not implemented. Accepting security request"); esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); } #endif // CONFIG_BLE_SMP_ENABLE + } break; /* * */ case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: //the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + { //display the passkey number to the user to input it in the peer device within 30 seconds log_i("ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - log_i("passKey = %d", param->ble_security.key_notif.passkey); + uint32_t passkey = param->ble_security.key_notif.passkey; + + if(!BLESecurity::m_passkeySet) { + log_w("No passkey set"); + } + + if (BLESecurity::m_staticPasskey && passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); + } else { + log_i("Passkey: %d", passkey); + } + if (BLEDevice::m_securityCallbacks != nullptr) { - BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + BLEDevice::m_securityCallbacks->onPassKeyNotify(passkey); } #endif // CONFIG_BLE_SMP_ENABLE + } break; case ESP_GAP_BLE_KEY_EVT: + { //shows the ble key type info share with peer device to the user. log_d("ESP_GAP_BLE_KEY_EVT"); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig log_i("key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); #endif // CONFIG_BLE_SMP_ENABLE + } break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: log_i("ESP_GAP_BLE_AUTH_CMPL_EVT"); + case ESP_GAP_BLE_AUTH_CMPL_EVT: + { + log_i("ESP_GAP_BLE_AUTH_CMPL_EVT"); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if (BLEDevice::m_securityCallbacks != nullptr) { BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); } #endif // CONFIG_BLE_SMP_ENABLE + } break; default: { @@ -896,13 +964,7 @@ void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) { m_customGattsHandler = handler; } -/* - * @brief Set encryption level that will be negotiated with peer device durng connection - * @param [in] level Requested encryption level - */ -void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { - BLEDevice::m_securityLevel = level; -} + #endif diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 66cfa2b371a..4b16bb9877c 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -22,8 +22,8 @@ ***************************************************************************/ #include -#include #include +#include "WString.h" #include "BLEServer.h" #include "BLEClient.h" #include "BLEUtils.h" @@ -33,6 +33,16 @@ #include "BLEAddress.h" #include "BLEUtils.h" +/*************************************************************************** + * Common definitions * + ***************************************************************************/ + +enum class BLEStack { + BLUEDROID, + NIMBLE, + UNKNOWN +}; + /*************************************************************************** * Bluedroid includes * ***************************************************************************/ @@ -141,7 +151,6 @@ class BLEDevice { ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) - static esp_ble_sec_act_t m_securityLevel; static gattc_event_handler m_customGattcHandler; static gatts_event_handler m_customGattsHandler; #endif @@ -179,13 +188,14 @@ class BLEDevice { static BLEClient *getClientByGattIf(uint16_t conn_id); static void setCustomGapHandler(gap_event_handler handler); static void deinit(bool release_memory = false); + static BLEStack getBLEStack(); + static String getBLEStackString(); /*************************************************************************** * Bluedroid public declarations * ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) - static void setEncryptionLevel(esp_ble_sec_act_t level); static void setCustomGattcHandler(gattc_event_handler handler); static void setCustomGattsHandler(gatts_event_handler handler); #endif diff --git a/libraries/BLE/src/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index aec9500d6f3..dd3aa313e83 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -846,7 +846,7 @@ String BLERemoteCharacteristic::readValue() { case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): - if (retryCount && pClient->secureConnection()) { + if (BLESecurity::m_securityEnabled && retryCount && pClient->secureConnection()) { break; } /* Else falls through. */ @@ -928,10 +928,10 @@ bool BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool resp case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): - if (retryCount && pClient->secureConnection()) { + if (BLESecurity::m_securityEnabled && retryCount && pClient->secureConnection()) { break; } - /* Else falls through. */ + /* Else falls through. */ default: goto exit; } } while (rc != 0 && retryCount--); diff --git a/libraries/BLE/src/BLERemoteCharacteristic.h b/libraries/BLE/src/BLERemoteCharacteristic.h index 81ad7b2f4f5..f45460a435e 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.h +++ b/libraries/BLE/src/BLERemoteCharacteristic.h @@ -45,6 +45,7 @@ #include #include +// Bluedroid Compatibility #define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN #define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ #define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE @@ -53,6 +54,12 @@ #define ESP_GATT_CHAR_PROP_BIT_NOTIFY BLE_GATT_CHR_PROP_NOTIFY #define ESP_GATT_CHAR_PROP_BIT_INDICATE BLE_GATT_CHR_PROP_INDICATE +#define ESP_GATT_AUTH_REQ_NONE 0 +#define ESP_GATT_AUTH_REQ_NO_MITM 1 +#define ESP_GATT_AUTH_REQ_MITM 2 +#define ESP_GATT_AUTH_REQ_SIGNED_NO_MITM 3 +#define ESP_GATT_AUTH_REQ_SIGNED_MITM 4 + #endif /*************************************************************************** diff --git a/libraries/BLE/src/BLERemoteDescriptor.cpp b/libraries/BLE/src/BLERemoteDescriptor.cpp index a142fe11880..75a608d3791 100644 --- a/libraries/BLE/src/BLERemoteDescriptor.cpp +++ b/libraries/BLE/src/BLERemoteDescriptor.cpp @@ -313,7 +313,7 @@ String BLERemoteDescriptor::readValue() { case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): - if (retryCount && pClient->secureConnection()) { + if (BLESecurity::m_securityEnabled && retryCount && pClient->secureConnection()) { break; } /* Else falls through. */ @@ -459,7 +459,7 @@ bool BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): - if (retryCount && pClient->secureConnection()) { + if (BLESecurity::m_securityEnabled && retryCount && pClient->secureConnection()) { break; } /* Else falls through. */ diff --git a/libraries/BLE/src/BLESecurity.cpp b/libraries/BLE/src/BLESecurity.cpp index 978026a3809..55388ecd891 100644 --- a/libraries/BLE/src/BLESecurity.cpp +++ b/libraries/BLE/src/BLESecurity.cpp @@ -19,8 +19,11 @@ * Common includes * ***************************************************************************/ +#include "Arduino.h" #include "BLESecurity.h" #include "BLEUtils.h" +#include "BLEDevice.h" +#include "GeneralUtils.h" #include "esp32-hal-log.h" /*************************************************************************** @@ -35,17 +38,46 @@ * Common properties * ***************************************************************************/ +// If true, the security will be enforced on connection even if no security is needed +// TODO: Make this configurable without breaking Bluedroid/NimBLE compatibility +bool BLESecurity::m_forceSecurity = true; + +bool BLESecurity::m_securityEnabled = false; +bool BLESecurity::m_securityStarted = false; +bool BLESecurity::m_passkeySet = false; +bool BLESecurity::m_staticPasskey = true; +bool BLESecurity::m_regenOnConnect = false; +uint8_t BLESecurity::m_iocap = 0; +uint8_t BLESecurity::m_authReq = 0; +uint8_t BLESecurity::m_initKey = 0; +uint8_t BLESecurity::m_respKey = 0; uint32_t BLESecurity::m_passkey = BLE_SM_DEFAULT_PASSKEY; /*************************************************************************** - * Common functions * + * Bluedroid properties * ***************************************************************************/ -BLESecurity::BLESecurity() {} +#if defined(CONFIG_BLUEDROID_ENABLED) +uint8_t BLESecurity::m_keySize = 16; +esp_ble_sec_act_t BLESecurity::m_securityLevel = (esp_ble_sec_act_t)0; +#endif -BLESecurity::~BLESecurity() {} +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +// This function initializes the BLESecurity class. +BLESecurity::BLESecurity() { + log_d("BLESecurity: Initializing"); + setKeySize(); + setInitEncryptionKey(); + setRespEncryptionKey(); + setCapability(ESP_IO_CAP_NONE); +} +// This function sets the authentication mode for the BLE security. void BLESecurity::setAuthenticationMode(uint8_t auth_req) { + log_d("setAuthenticationMode: auth_req=%d", auth_req); m_authReq = auth_req; #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); @@ -56,7 +88,9 @@ void BLESecurity::setAuthenticationMode(uint8_t auth_req) { #endif } +// This function sets the Input/Output capability for the BLE security. void BLESecurity::setCapability(uint8_t iocap) { + log_d("setCapability: iocap=%d", iocap); m_iocap = iocap; #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); @@ -65,7 +99,12 @@ void BLESecurity::setCapability(uint8_t iocap) { #endif } +// This sets the initiator key distribution flags. +// ESP_BLE_ENC_KEY_MASK indicates that the device should distribute the Encryption Key to the peer device. +// ESP_BLE_ID_KEY_MASK indicates that the device should distribute the Identity Key to the peer device. +// Both are set by default. void BLESecurity::setInitEncryptionKey(uint8_t init_key) { + log_d("setInitEncryptionKey: init_key=%d", init_key); m_initKey = init_key; #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); @@ -74,7 +113,12 @@ void BLESecurity::setInitEncryptionKey(uint8_t init_key) { #endif } +// This sets the responder key distribution flags. +// ESP_BLE_ENC_KEY_MASK indicates that the device should distribute the Encryption Key to the peer device. +// ESP_BLE_ID_KEY_MASK indicates that the device should distribute the Identity Key to the peer device. +// Both are set by default. void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { + log_d("setRespEncryptionKey: resp_key=%d", resp_key); m_respKey = resp_key; #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); @@ -83,34 +127,168 @@ void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { #endif } +// This function sets the key size for the BLE security. void BLESecurity::setKeySize(uint8_t key_size) { #if defined(CONFIG_BLUEDROID_ENABLED) + log_d("setKeySize: key_size=%d", key_size); m_keySize = key_size; esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); #endif } -void BLESecurity::setStaticPIN(uint32_t pin) { - m_passkey = pin; +// This function generates a random passkey between 000000 and 999999. +uint32_t BLESecurity::generateRandomPassKey() { + return random(0, 999999); +} + +// This function sets a passkey for the BLE security. +// The first argument defines if the passkey is static or random. +// The second argument is the passkey (ignored when using a random passkey). +// The function returns the passkey that was set. +uint32_t BLESecurity::setPassKey(bool staticPasskey, uint32_t passkey) { + log_d("setPassKey: staticPasskey=%d, passkey=%d", staticPasskey, passkey); + m_staticPasskey = staticPasskey; + + if(m_staticPasskey) { + m_passkey = passkey; + if(m_passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*WARNING* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*WARNING* Please use a random passkey or set a different static passkey"); + } + } else { + m_passkey = generateRandomPassKey(); + } + + m_passkeySet = true; + #if defined(CONFIG_BLUEDROID_ENABLED) + // Workaround for making Bluedroid and NimBLE manage the random passkey similarly. esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &m_passkey, sizeof(uint32_t)); - setCapability(ESP_IO_CAP_OUT); - setKeySize(); - setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); +#endif + + return m_passkey; +} + +// This function gets the passkey being used for the BLE security. +// If a static passkey is set, it will return the static passkey. +// If using a random passkey, it will generate a new random passkey if m_regenOnConnect is true. +// Otherwise, it will return the current passkey being used. +uint32_t BLESecurity::getPassKey() { + if(m_passkeySet && !m_staticPasskey && m_regenOnConnect) { + m_passkey = generateRandomPassKey(); +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &m_passkey, sizeof(uint32_t)); +#endif + } + return m_passkey; +} + +// This function sets if the passkey should be regenerated on each connection. +void BLESecurity::regenPassKeyOnConnect(bool enable) { + m_regenOnConnect = enable; +} + +// This function sets the authentication mode with bonding, MITM, and secure connection options. +void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) { + log_d("setAuthenticationMode: bonding=%d, mitm=%d, sc=%d", bonding, mitm, sc); + m_authReq = bonding ? ESP_LE_AUTH_BOND : 0; + m_authReq |= mitm ? ESP_LE_AUTH_REQ_MITM : 0; + m_authReq |= sc ? ESP_LE_AUTH_REQ_SC_ONLY : 0; + m_securityEnabled = (m_authReq != 0); +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); + if(sc) { + if(mitm) { + setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); + } else { + setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + } + } #elif defined(CONFIG_NIMBLE_ENABLED) - setCapability(BLE_HS_IO_DISPLAY_ONLY); - setKeySize(); - setAuthenticationMode(false, false, true); // No bonding, no MITM, secure connection only - setInitEncryptionKey(BLE_HS_KEY_DIST_ENC_KEY | BLE_HS_KEY_DIST_ID_KEY); + ble_hs_cfg.sm_bonding = bonding; + ble_hs_cfg.sm_mitm = mitm; + ble_hs_cfg.sm_sc = sc; #endif } +// This callback is called by the device that has Input capability when the peer device has Output capability +// It can also be called in NimBLE when there is no passkey set. +// It should return the passkey that the peer device is showing on its output. +// This might not be called if the client has a static passkey set. +uint32_t BLESecurityCallbacks::onPassKeyRequest() { + Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onPassKeyRequest."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onPassKeyRequest with a suitable passkey in your BLESecurityCallbacks class"); + Serial.printf("BLESecurityCallbacks: Default passkey: %06d\n", BLE_SM_DEFAULT_PASSKEY); + return BLE_SM_DEFAULT_PASSKEY; +} + +// This callback is called by the device that has Output capability when the peer device has Input capability +// It should display the passkey that will need to be entered on the peer device +void BLESecurityCallbacks::onPassKeyNotify(uint32_t passkey) { + Serial.printf("BLESecurityCallbacks: Using default onPassKeyNotify. Passkey: %06lu\n", passkey); +} + +// This callback is called when the peer device requests a secure connection. +// Usually the client accepts the server's security request. +// It should return true if the connection is accepted, false otherwise. +bool BLESecurityCallbacks::onSecurityRequest() { + Serial.println("BLESecurityCallbacks: Using default onSecurityRequest. It will accept any security request."); + return true; +} + +// This callback is called by both devices when both have the DisplayYesNo capability. +// It should return true if both devices display the same passkey. +bool BLESecurityCallbacks::onConfirmPIN(uint32_t pin) { + Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onConfirmPIN. It will accept any passkey."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onConfirmPIN with a suitable confirmation logic in your BLESecurityCallbacks class"); + return true; +} + +// This callback is called when the characteristic requires authorization. +// connHandle is the connection handle of the peer device. +// attrHandle is the handle of the characteristic. +// If isRead is true, the peer device is requesting to read the characteristic, +// otherwise it is requesting to write. +// It should return true if the authorization is granted, false otherwise. +bool BLESecurityCallbacks::onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) { + Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onAuthorizationRequest. It will accept any authorization request."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onAuthorizationRequest with a suitable authorization logic in your BLESecurityCallbacks class"); + return true; +} + /*************************************************************************** * Bluedroid functions * ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) +// This function sets the encryption level that will be negotiated with peer device during connection +void BLESecurity::setEncryptionLevel(esp_ble_sec_act_t level) { + m_securityLevel = level; +} + +bool BLESecurity::startSecurity(esp_bd_addr_t bd_addr, int *rcPtr) { +#ifdef CONFIG_BLE_SMP_ENABLE + if(m_securityStarted) { + log_w("Security already started for bd_addr=%s", BLEAddress(bd_addr).toString().c_str()); + return true; + } + + int rc = esp_ble_set_encryption(bd_addr, m_securityLevel); + if (rc != ESP_OK) { + log_e("esp_ble_set_encryption: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + } + if (rcPtr) { + *rcPtr = rc; + } + m_securityStarted = (rc == ESP_OK); + return m_securityStarted; +#else + log_e("Bluedroid SMP is not enabled. Can't start security."); + return false; +#endif +} + +// This function converts an ESP BLE key type to a string representation. char *BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { char *key_str = nullptr; switch (key_type) { @@ -127,6 +305,12 @@ char *BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { } return key_str; } + +// This function is called when authentication is complete. +void BLESecurityCallbacks::onAuthenticationComplete(esp_ble_auth_cmpl_t param) { + bool success = param.success; + Serial.printf("Using default onAuthenticationComplete. Authentication %s.\n", success ? "successful" : "failed"); +} #endif /*************************************************************************** @@ -134,16 +318,13 @@ char *BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { ***************************************************************************/ #if defined(CONFIG_NIMBLE_ENABLED) -void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) { - m_authReq = bonding ? BLE_SM_PAIR_AUTHREQ_BOND : 0; - m_authReq |= mitm ? BLE_SM_PAIR_AUTHREQ_MITM : 0; - m_authReq |= sc ? BLE_SM_PAIR_AUTHREQ_SC : 0; - ble_hs_cfg.sm_bonding = bonding; - ble_hs_cfg.sm_mitm = mitm; - ble_hs_cfg.sm_sc = sc; -} - +// This function initiates security for a given connection handle. bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) { + if(m_securityStarted) { + log_w("Security already started for connHandle=%d", connHandle); + return true; + } + int rc = ble_gap_security_initiate(connHandle); if (rc != 0) { log_e("ble_gap_security_initiate: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); @@ -151,7 +332,14 @@ bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) { if (rcPtr) { *rcPtr = rc; } - return rc == 0 || rc == BLE_HS_EALREADY; + m_securityStarted = (rc == 0 || rc == BLE_HS_EALREADY); + return m_securityStarted; +} + +// This function is called when authentication is complete for NimBLE. +void BLESecurityCallbacks::onAuthenticationComplete(ble_gap_conn_desc *desc) { + bool success = desc != nullptr; + Serial.printf("Using default onAuthenticationComplete. Authentication %s.\n", success ? "successful" : "failed"); } #endif diff --git a/libraries/BLE/src/BLESecurity.h b/libraries/BLE/src/BLESecurity.h index 574110e6118..79fb99f544b 100644 --- a/libraries/BLE/src/BLESecurity.h +++ b/libraries/BLE/src/BLESecurity.h @@ -23,6 +23,9 @@ ***************************************************************************/ #include "WString.h" +#include "BLEDevice.h" +#include "BLEClient.h" +#include "BLEServer.h" /*************************************************************************** * Bluedroid includes * @@ -41,15 +44,42 @@ #endif /*************************************************************************** - * Common definitions * + * Common definitions * ***************************************************************************/ #define BLE_SM_DEFAULT_PASSKEY 123456 +/*************************************************************************** + * NimBLE definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +// Compatibility with Bluedroid definitions + +#define ESP_IO_CAP_OUT BLE_HS_IO_DISPLAY_ONLY +#define ESP_IO_CAP_IO BLE_HS_IO_DISPLAY_YESNO +#define ESP_IO_CAP_IN BLE_HS_IO_KEYBOARD_ONLY +#define ESP_IO_CAP_NONE BLE_HS_IO_NO_INPUT_OUTPUT +#define ESP_IO_CAP_KBDISP BLE_HS_IO_KEYBOARD_DISPLAY + +#define ESP_LE_AUTH_NO_BOND 0x00 +#define ESP_LE_AUTH_BOND BLE_SM_PAIR_AUTHREQ_BOND +#define ESP_LE_AUTH_REQ_MITM BLE_SM_PAIR_AUTHREQ_MITM +#define ESP_LE_AUTH_REQ_SC_ONLY BLE_SM_PAIR_AUTHREQ_SC +#define ESP_LE_AUTH_REQ_BOND_MITM (BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM) +#define ESP_LE_AUTH_REQ_SC_BOND (BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_SC) +#define ESP_LE_AUTH_REQ_SC_MITM (BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC) +#define ESP_LE_AUTH_REQ_SC_MITM_BOND (BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC | BLE_SM_PAIR_AUTHREQ_BOND) + +#define ESP_BLE_ENC_KEY_MASK BLE_HS_KEY_DIST_ENC_KEY +#define ESP_BLE_ID_KEY_MASK BLE_HS_KEY_DIST_ID_KEY +#endif + /*************************************************************************** * Forward declarations * ***************************************************************************/ +class BLEDevice; class BLEServer; class BLEClient; @@ -63,13 +93,17 @@ class BLESecurity { ***************************************************************************/ BLESecurity(); - virtual ~BLESecurity(); + virtual ~BLESecurity() = default; static void setAuthenticationMode(uint8_t auth_req); static void setCapability(uint8_t iocap); - static void setInitEncryptionKey(uint8_t init_key); - static void setRespEncryptionKey(uint8_t resp_key); + static void setInitEncryptionKey(uint8_t init_key = (ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK)); + static void setRespEncryptionKey(uint8_t resp_key = (ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK)); static void setKeySize(uint8_t key_size = 16); - static void setStaticPIN(uint32_t pin); + static uint32_t setPassKey(bool staticPasskey = false, uint32_t passkey = BLE_SM_DEFAULT_PASSKEY); + static void setAuthenticationMode(bool bonding, bool mitm, bool sc); + static uint32_t getPassKey(); + static uint32_t generateRandomPassKey(); + static void regenPassKeyOnConnect(bool enable = false); /*************************************************************************** * Bluedroid public declarations * @@ -77,6 +111,8 @@ class BLESecurity { #if defined(CONFIG_BLUEDROID_ENABLED) static char *esp_key_type_to_str(esp_ble_key_type_t key_type); + static void setEncryptionLevel(esp_ble_sec_act_t level); + static bool startSecurity(esp_bd_addr_t bd_addr, int *rcPtr = nullptr); #endif /*************************************************************************** @@ -84,18 +120,26 @@ class BLESecurity { ***************************************************************************/ #if defined(CONFIG_NIMBLE_ENABLED) - static void setAuthenticationMode(bool bonding, bool mitm, bool sc); static bool startSecurity(uint16_t connHandle, int *rcPtr = nullptr); #endif private: + friend class BLEDevice; friend class BLEServer; friend class BLEClient; + friend class BLERemoteCharacteristic; + friend class BLERemoteDescriptor; /*************************************************************************** * Common private properties * ***************************************************************************/ + static bool m_securityEnabled; + static bool m_securityStarted; + static bool m_forceSecurity; + static bool m_passkeySet; + static bool m_staticPasskey; + static bool m_regenOnConnect; static uint8_t m_iocap; static uint8_t m_authReq; static uint8_t m_initKey; @@ -103,11 +147,12 @@ class BLESecurity { static uint32_t m_passkey; /*************************************************************************** - * Bluedroid private properties * + * Bluedroid private properties * ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) static uint8_t m_keySize; + static esp_ble_sec_act_t m_securityLevel; #endif }; // BLESecurity @@ -121,18 +166,42 @@ class BLESecurityCallbacks { * Common public declarations * ***************************************************************************/ - virtual ~BLESecurityCallbacks(){}; - virtual uint32_t onPassKeyRequest() = 0; - virtual void onPassKeyNotify(uint32_t pass_key) = 0; - virtual bool onSecurityRequest() = 0; - virtual bool onConfirmPIN(uint32_t pin) = 0; + BLESecurityCallbacks() = default; + virtual ~BLESecurityCallbacks() = default; + + // This callback is called by the device that has Input capability when the peer device has Output capability + // and the passkey is not set. + // It should return the passkey that the peer device is showing on its output. + // This MUST be replaced with a custom implementation when being used. + virtual uint32_t onPassKeyRequest(); + + // This callback is called by the device that has Output capability when the peer device has Input capability + // It should display the passkey that will need to be entered on the peer device + virtual void onPassKeyNotify(uint32_t pass_key); + + // This callback is called when the peer device requests a secure connection. + // Usually the client accepts the server's security request. + // It should return true if the connection is accepted, false otherwise. + virtual bool onSecurityRequest(); + + // This callback is called by both devices when both have the DisplayYesNo capability. + // It should return true if both devices display the same passkey. + // This MUST be replaced with a custom implementation when being used. + virtual bool onConfirmPIN(uint32_t pin); + + // This callback is called when the peer device requests authorization to read or write a characteristic. + // It should return true if the authorization is granted, false otherwise. + // This MUST be replaced with a custom implementation when being used. + virtual bool onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead); /*************************************************************************** * Bluedroid public declarations * ***************************************************************************/ #if defined(CONFIG_BLUEDROID_ENABLED) - virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; + // This callback is called when the authentication is complete. + // Status can be checked in the desc parameter. + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t desc); #endif /*************************************************************************** @@ -140,7 +209,9 @@ class BLESecurityCallbacks { ***************************************************************************/ #if defined(CONFIG_NIMBLE_ENABLED) - virtual void onAuthenticationComplete(ble_gap_conn_desc *) = 0; + // This callback is called when the authentication is complete. + // Status can be checked in the desc parameter. + virtual void onAuthenticationComplete(ble_gap_conn_desc *desc); #endif }; // BLESecurityCallbacks diff --git a/libraries/BLE/src/BLEServer.cpp b/libraries/BLE/src/BLEServer.cpp index 323237e965e..5ec962a8f71 100644 --- a/libraries/BLE/src/BLEServer.cpp +++ b/libraries/BLE/src/BLEServer.cpp @@ -67,6 +67,10 @@ BLEServer::BLEServer() { m_svcChanged = false; #endif +#if !defined(CONFIG_BT_NIMBLE_EXT_ADV) || defined(CONFIG_BLUEDROID_ENABLED) + m_advertiseOnDisconnect = false; +#endif + m_appId = ESP_GATT_IF_NONE; m_gattsStarted = false; m_connectedCount = 0; @@ -296,6 +300,12 @@ bool BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { return m_connectedServersMap.erase(conn_id) > 0; } +#if !defined(CONFIG_BT_NIMBLE_EXT_ADV) || defined(CONFIG_BLUEDROID_ENABLED) +void BLEServer::advertiseOnDisconnect(bool enable) { + m_advertiseOnDisconnect = enable; +} +#endif + void BLEServerCallbacks::onConnect(BLEServer *pServer) { log_d("BLEServerCallbacks", ">> onConnect(): Default"); log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); @@ -449,6 +459,13 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t if (removePeerDevice(param->disconnect.conn_id, false)) { m_connectedCount--; // Decrement the number of connected devices count. } + + // Start advertising again if enabled + if (m_advertiseOnDisconnect) { + log_i("Start advertising again after disconnect"); + startAdvertising(); + } + break; } // ESP_GATTS_DISCONNECT_EVT @@ -621,6 +638,10 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { server->m_pServerCallbacks->onConnect(server, &desc); } + if(BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { + BLESecurity::startSecurity(event->connect.conn_handle); + } + server->m_connectedCount++; } @@ -655,6 +676,13 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); } +#if !defined(CONFIG_BT_NIMBLE_EXT_ADV) + if (server->m_advertiseOnDisconnect) { + log_i("Start advertising again after disconnect"); + server->startAdvertising(); + } +#endif + return 0; } // BLE_GAP_EVENT_DISCONNECT @@ -784,8 +812,6 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { if (BLEDevice::m_securityCallbacks != nullptr) { BLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc); - } else if (server->m_pServerCallbacks != nullptr) { - server->m_pServerCallbacks->onAuthenticationComplete(&desc); } return 0; @@ -796,63 +822,126 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { struct ble_sm_io pkey = {0, 0}; if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + // Display the passkey on this device + log_d("BLE_SM_IOACT_DISP"); + pkey.action = event->passkey.params.action; - // backward compatibility - pkey.passkey = BLESecurity::m_passkey; - // if the (static)passkey is the default, check the callback for custom value - // both values default to the same. - if (pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { - if (server->m_pServerCallbacks != nullptr) { - pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); - } + pkey.passkey = BLESecurity::getPassKey(); + + if(!BLESecurity::m_passkeySet) { + log_w("No passkey set"); + } + + if (BLESecurity::m_staticPasskey && pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); + } else { + log_i("Passkey: %d", pkey.passkey); + } + + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onPassKeyNotify(pkey.passkey); } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + // Check if the passkey on the peer device is correct + log_d("BLE_SM_IOACT_NUMCMP"); + log_d("Passkey on device's display: %d", event->passkey.params.numcmp); pkey.action = event->passkey.params.action; - // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { pkey.numcmp_accept = BLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp); - } else if (server->m_pServerCallbacks != nullptr) { - pkey.numcmp_accept = server->m_pServerCallbacks->onConfirmPIN(event->passkey.params.numcmp); + } else { + log_e("onConfirmPIN not implemented. Rejecting connection"); + pkey.numcmp_accept = 0; } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: %d", rc); - //TODO: Handle out of band pairing } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + // Out of band pairing + // TODO: Handle out of band pairing + log_w("BLE_SM_IOACT_OOB: Not implemented"); + static uint8_t tem_oob[16] = {0}; pkey.action = event->passkey.params.action; for (int i = 0; i < 16; i++) { pkey.oob[i] = tem_oob[i]; } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("BLE_SM_IOACT_OOB; ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { - log_d("Enter the passkey"); + // Input passkey from peer device + log_d("BLE_SM_IOACT_INPUT"); + pkey.action = event->passkey.params.action; + pkey.passkey = BLESecurity::getPassKey(); - // Compatibility only - Do not use, should be removed the in future - if (BLEDevice::m_securityCallbacks != nullptr) { - pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); - } else if (server->m_pServerCallbacks != nullptr) { - pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); + if (!BLESecurity::m_passkeySet) { + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("No passkey set, getting passkey from onPassKeyRequest"); + pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); + } else { + log_w("*ATTENTION* onPassKeyRequest not implemented and no static passkey set."); + } + } + + if (BLESecurity::m_staticPasskey && pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { + log_w("*ATTENTION* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); + log_w("*ATTENTION* Please use a random passkey or set a different static passkey"); + } else { + log_i("Passkey: %d", pkey.passkey); } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("BLE_SM_IOACT_INPUT; ble_sm_inject_io result: %d", rc); } else if (event->passkey.params.action == BLE_SM_IOACT_NONE) { - log_d("No passkey action required"); + log_d("BLE_SM_IOACT_NONE"); + log_i("No passkey action required"); } log_d("<< handleGATTServerEvent"); return 0; } // BLE_GAP_EVENT_PASSKEY_ACTION + case BLE_GAP_EVENT_AUTHORIZE: + { + log_d("BLE_GAP_EVENT_AUTHORIZE"); + + log_i("Authorization request: conn_handle=%d attr_handle=%d is_read=%d", + event->authorize.conn_handle, + event->authorize.attr_handle, + event->authorize.is_read); + + bool authorized = false; + + if (BLEDevice::m_securityCallbacks != nullptr) { + log_i("Asking for authorization from onAuthorizationRequest"); + authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( + event->authorize.conn_handle, event->authorize.attr_handle, event->authorize.is_read + ); + } else { + log_w("onAuthorizationRequest not implemented. Rejecting authorization request"); + } + + if (authorized) { + log_i("Authorization granted"); + event->authorize.out_response = BLE_GAP_AUTHORIZE_ACCEPT; + } else { + log_i("Authorization rejected"); + event->authorize.out_response = BLE_GAP_AUTHORIZE_REJECT; + } + + return 0; + } // BLE_GAP_EVENT_AUTHORIZE + default: break; } @@ -951,20 +1040,6 @@ void BLEServerCallbacks::onMtuChanged(BLEServer *pServer, ble_gap_conn_desc *des log_d("BLEServerCallbacks", "<< onMtuChanged()"); } // onMtuChanged -uint32_t BLEServerCallbacks::onPassKeyRequest() { - log_d("BLEServerCallbacks", "onPassKeyRequest: default: 123456"); - return 123456; -} - -void BLEServerCallbacks::onAuthenticationComplete(ble_gap_conn_desc *) { - log_d("BLEServerCallbacks", "onAuthenticationComplete: default"); -} - -bool BLEServerCallbacks::onConfirmPIN(uint32_t pin) { - log_d("BLEServerCallbacks", "onConfirmPIN: default: true"); - return true; -} - #endif // CONFIG_NIMBLE_ENABLED #endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ diff --git a/libraries/BLE/src/BLEServer.h b/libraries/BLE/src/BLEServer.h index 865d1046312..9896e5d9367 100644 --- a/libraries/BLE/src/BLEServer.h +++ b/libraries/BLE/src/BLEServer.h @@ -129,6 +129,9 @@ class BLEServer { BLEService *getServiceByUUID(const char *uuid); BLEService *getServiceByUUID(BLEUUID uuid); void start(); +#if !defined(CONFIG_BT_NIMBLE_EXT_ADV) || defined(CONFIG_BLUEDROID_ENABLED) + void advertiseOnDisconnect(bool enable); +#endif // Connection management functions std::map getPeerDevices(bool client); @@ -174,6 +177,9 @@ class BLEServer { uint32_t m_connectedCount; bool m_gattsStarted; std::map m_connectedServersMap; +#if !defined(CONFIG_BT_NIMBLE_EXT_ADV) || defined(CONFIG_BLUEDROID_ENABLED) + bool m_advertiseOnDisconnect; +#endif FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); @@ -261,9 +267,6 @@ class BLEServerCallbacks { virtual void onConnect(BLEServer *pServer, ble_gap_conn_desc *desc); virtual void onDisconnect(BLEServer *pServer, ble_gap_conn_desc *desc); virtual void onMtuChanged(BLEServer *pServer, ble_gap_conn_desc *desc, uint16_t mtu); - virtual uint32_t onPassKeyRequest(); - virtual void onAuthenticationComplete(ble_gap_conn_desc *desc); - virtual bool onConfirmPIN(uint32_t pin); #endif }; // BLEServerCallbacks diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp index 4ca04e6e2b6..7d6f7a18e7d 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -656,7 +656,7 @@ static const gattService_t g_gattServices[] = { * @param [in] length The length of the data to convert. * @return A pointer to the formatted buffer. */ -char *BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { +char *BLEUtils::buildHexData(uint8_t *target, const uint8_t *source, uint8_t length) { // Guard against too much data. if (length > 100) { length = 100; @@ -672,8 +672,7 @@ char *BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { char *startOfData = (char *)target; for (int i = 0; i < length; i++) { - sprintf((char *)target, "%.2x", (char)*source); - source++; + sprintf((char *)target, "%.2x", (char)source[i]); target += 2; } @@ -1568,7 +1567,7 @@ void BLEUtils::dumpGattServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gat // - uint32_t trans_id // - esp_bd_addr_t bda // - uint8_t exec_write_flag -#ifdef ARDUHAL_LOG_LEVEL_VERBOSE +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE case ESP_GATTS_EXEC_WRITE_EVT: { char *pWriteFlagText; diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/BLEUtils.h index 2689706b7a1..91e15157a8d 100644 --- a/libraries/BLE/src/BLEUtils.h +++ b/libraries/BLE/src/BLEUtils.h @@ -96,7 +96,7 @@ class BLEUtils { * Common public declarations * ***************************************************************************/ - static char *buildHexData(uint8_t *target, uint8_t *source, uint8_t length); + static char *buildHexData(uint8_t *target, const uint8_t *source, uint8_t length); static String buildPrintData(uint8_t *source, size_t length); static const char *advDataTypeToString(uint8_t advType); static String characteristicPropertiesToString(uint8_t prop); From fe67c724c246b9c7a61fc5b0b90c8bc62ac33448 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:38:02 +0000 Subject: [PATCH 2/3] ci(pre-commit): Apply automatic fixes --- .../Server_secure_static_passkey.ino | 6 ++-- libraries/BLE/src/BLECharacteristic.cpp | 8 ++--- libraries/BLE/src/BLECharacteristic.h | 12 +++---- libraries/BLE/src/BLEClient.cpp | 4 +-- libraries/BLE/src/BLEDevice.cpp | 33 +++++++------------ libraries/BLE/src/BLESecurity.cpp | 18 +++++----- libraries/BLE/src/BLEServer.cpp | 16 ++++----- 7 files changed, 40 insertions(+), 57 deletions(-) diff --git a/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino index f23490156f0..f4fe3421849 100644 --- a/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino +++ b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino @@ -80,10 +80,8 @@ void setup() { // Check the BLECharacteristic.h file for more information. secure_properties |= BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_WRITE_ENC; - BLECharacteristic *pSecureCharacteristic = - pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties); - BLECharacteristic *pUnsecureCharacteristic = - pService->createCharacteristic(UNSECURE_CHARACTERISTIC_UUID, unsecure_properties); + BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties); + BLECharacteristic *pUnsecureCharacteristic = pService->createCharacteristic(UNSECURE_CHARACTERISTIC_UUID, unsecure_properties); // Bluedroid uses permissions to secure characteristics. // This is the same as using the properties above. diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 369324395be..a44f73294ba 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -574,9 +574,7 @@ void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_ga if (BLEDevice::m_securityCallbacks != nullptr) { log_i("Authorization required for write operation. Checking authorization..."); - authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( - param->write.conn_id, m_handle, false - ); + authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest(param->write.conn_id, m_handle, false); } else { log_w("onAuthorizationRequest not implemented. Rejecting write authorization request"); } @@ -656,9 +654,7 @@ void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_ga if (BLEDevice::m_securityCallbacks != nullptr) { log_i("Authorization required for read operation. Checking authorization..."); - authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( - param->read.conn_id, m_handle, true - ); + authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest(param->read.conn_id, m_handle, true); } else { log_w("onAuthorizationRequest not implemented. Rejecting read authorization request"); } diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index ddd06cec29f..52ca1898b55 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -140,14 +140,14 @@ class BLECharacteristic { #if defined(CONFIG_BLUEDROID_ENABLED) static const uint32_t PROPERTY_READ = 1 << 0; - static const uint32_t PROPERTY_READ_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. - static const uint32_t PROPERTY_READ_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. - static const uint32_t PROPERTY_READ_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_READ_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_READ_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_READ_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. static const uint32_t PROPERTY_WRITE = 1 << 1; static const uint32_t PROPERTY_WRITE_NR = 1 << 5; - static const uint32_t PROPERTY_WRITE_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. - static const uint32_t PROPERTY_WRITE_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. - static const uint32_t PROPERTY_WRITE_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_WRITE_ENC = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_WRITE_AUTHEN = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. + static const uint32_t PROPERTY_WRITE_AUTHOR = 0; // Not supported by Bluedroid. Use setAccessPermissions() instead. static const uint32_t PROPERTY_NOTIFY = 1 << 2; static const uint32_t PROPERTY_BROADCAST = 1 << 3; static const uint32_t PROPERTY_INDICATE = 1 << 4; diff --git a/libraries/BLE/src/BLEClient.cpp b/libraries/BLE/src/BLEClient.cpp index 7eeea3c1961..39c21a49584 100644 --- a/libraries/BLE/src/BLEClient.cpp +++ b/libraries/BLE/src/BLEClient.cpp @@ -1008,7 +1008,7 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { break; } - if(BLESecurity::m_securityEnabled) { + if (BLESecurity::m_securityEnabled) { BLESecurity::startSecurity(client->m_conn_id); } @@ -1174,7 +1174,7 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { pkey.action = event->passkey.params.action; pkey.passkey = BLESecurity::getPassKey(); // This is the passkey to be entered on peer - if(!BLESecurity::m_passkeySet) { + if (!BLESecurity::m_passkeySet) { log_w("No passkey set"); } diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index d06a8277d50..927553cd887 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -723,13 +723,10 @@ BLEStack BLEDevice::getBLEStack() { String BLEDevice::getBLEStackString() { switch (getBLEStack()) { - case BLEStack::BLUEDROID: - return "Bluedroid"; - case BLEStack::NIMBLE: - return "NimBLE"; + case BLEStack::BLUEDROID: return "Bluedroid"; + case BLEStack::NIMBLE: return "NimBLE"; case BLEStack::UNKNOWN: - default: - return "Unknown"; + default: return "Unknown"; } } @@ -829,7 +826,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ log_i("ESP_GAP_BLE_OOB_REQ_EVT"); break; case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ log_i("ESP_GAP_BLE_LOCAL_IR_EVT"); break; case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ log_i("ESP_GAP_BLE_LOCAL_ER_EVT"); break; - case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ + case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ { log_i("ESP_GAP_BLE_NC_REQ_EVT"); #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig @@ -840,8 +837,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, false); } #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ { log_i("ESP_GAP_BLE_PASSKEY_REQ_EVT: "); @@ -867,8 +863,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, passkey); #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; /* * TODO should we add white/black list comparison? */ @@ -885,8 +880,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); } #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; /* * */ @@ -897,7 +891,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig uint32_t passkey = param->ble_security.key_notif.passkey; - if(!BLESecurity::m_passkeySet) { + if (!BLESecurity::m_passkeySet) { log_w("No passkey set"); } @@ -912,8 +906,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par BLEDevice::m_securityCallbacks->onPassKeyNotify(passkey); } #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; case ESP_GAP_BLE_KEY_EVT: { //shows the ble key type info share with peer device to the user. @@ -921,8 +914,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig log_i("key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; case ESP_GAP_BLE_AUTH_CMPL_EVT: { log_i("ESP_GAP_BLE_AUTH_CMPL_EVT"); @@ -931,8 +923,7 @@ void BLEDevice::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_par BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); } #endif // CONFIG_BLE_SMP_ENABLE - } - break; + } break; default: { break; @@ -964,8 +955,6 @@ void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) { m_customGattsHandler = handler; } - - #endif /*************************************************************************** diff --git a/libraries/BLE/src/BLESecurity.cpp b/libraries/BLE/src/BLESecurity.cpp index 55388ecd891..3c60472e8f9 100644 --- a/libraries/BLE/src/BLESecurity.cpp +++ b/libraries/BLE/src/BLESecurity.cpp @@ -149,9 +149,9 @@ uint32_t BLESecurity::setPassKey(bool staticPasskey, uint32_t passkey) { log_d("setPassKey: staticPasskey=%d, passkey=%d", staticPasskey, passkey); m_staticPasskey = staticPasskey; - if(m_staticPasskey) { + if (m_staticPasskey) { m_passkey = passkey; - if(m_passkey == BLE_SM_DEFAULT_PASSKEY) { + if (m_passkey == BLE_SM_DEFAULT_PASSKEY) { log_w("*WARNING* Using default passkey: %06d", BLE_SM_DEFAULT_PASSKEY); log_w("*WARNING* Please use a random passkey or set a different static passkey"); } @@ -174,7 +174,7 @@ uint32_t BLESecurity::setPassKey(bool staticPasskey, uint32_t passkey) { // If using a random passkey, it will generate a new random passkey if m_regenOnConnect is true. // Otherwise, it will return the current passkey being used. uint32_t BLESecurity::getPassKey() { - if(m_passkeySet && !m_staticPasskey && m_regenOnConnect) { + if (m_passkeySet && !m_staticPasskey && m_regenOnConnect) { m_passkey = generateRandomPassKey(); #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &m_passkey, sizeof(uint32_t)); @@ -197,8 +197,8 @@ void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) { m_securityEnabled = (m_authReq != 0); #if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); - if(sc) { - if(mitm) { + if (sc) { + if (mitm) { setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); } else { setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); @@ -252,7 +252,9 @@ bool BLESecurityCallbacks::onConfirmPIN(uint32_t pin) { // It should return true if the authorization is granted, false otherwise. bool BLESecurityCallbacks::onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) { Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onAuthorizationRequest. It will accept any authorization request."); - Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onAuthorizationRequest with a suitable authorization logic in your BLESecurityCallbacks class"); + Serial.println( + "BLESecurityCallbacks: *ATTENTION* Please implement onAuthorizationRequest with a suitable authorization logic in your BLESecurityCallbacks class" + ); return true; } @@ -268,7 +270,7 @@ void BLESecurity::setEncryptionLevel(esp_ble_sec_act_t level) { bool BLESecurity::startSecurity(esp_bd_addr_t bd_addr, int *rcPtr) { #ifdef CONFIG_BLE_SMP_ENABLE - if(m_securityStarted) { + if (m_securityStarted) { log_w("Security already started for bd_addr=%s", BLEAddress(bd_addr).toString().c_str()); return true; } @@ -320,7 +322,7 @@ void BLESecurityCallbacks::onAuthenticationComplete(esp_ble_auth_cmpl_t param) { #if defined(CONFIG_NIMBLE_ENABLED) // This function initiates security for a given connection handle. bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) { - if(m_securityStarted) { + if (m_securityStarted) { log_w("Security already started for connHandle=%d", connHandle); return true; } diff --git a/libraries/BLE/src/BLEServer.cpp b/libraries/BLE/src/BLEServer.cpp index 5ec962a8f71..896e25f3ffc 100644 --- a/libraries/BLE/src/BLEServer.cpp +++ b/libraries/BLE/src/BLEServer.cpp @@ -638,7 +638,7 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { server->m_pServerCallbacks->onConnect(server, &desc); } - if(BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { + if (BLESecurity::m_securityEnabled && BLESecurity::m_forceSecurity) { BLESecurity::startSecurity(event->connect.conn_handle); } @@ -828,7 +828,7 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { pkey.action = event->passkey.params.action; pkey.passkey = BLESecurity::getPassKey(); - if(!BLESecurity::m_passkeySet) { + if (!BLESecurity::m_passkeySet) { log_w("No passkey set"); } @@ -915,18 +915,16 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { { log_d("BLE_GAP_EVENT_AUTHORIZE"); - log_i("Authorization request: conn_handle=%d attr_handle=%d is_read=%d", - event->authorize.conn_handle, - event->authorize.attr_handle, - event->authorize.is_read); + log_i( + "Authorization request: conn_handle=%d attr_handle=%d is_read=%d", event->authorize.conn_handle, event->authorize.attr_handle, event->authorize.is_read + ); bool authorized = false; if (BLEDevice::m_securityCallbacks != nullptr) { log_i("Asking for authorization from onAuthorizationRequest"); - authorized = BLEDevice::m_securityCallbacks->onAuthorizationRequest( - event->authorize.conn_handle, event->authorize.attr_handle, event->authorize.is_read - ); + authorized = + BLEDevice::m_securityCallbacks->onAuthorizationRequest(event->authorize.conn_handle, event->authorize.attr_handle, event->authorize.is_read); } else { log_w("onAuthorizationRequest not implemented. Rejecting authorization request"); } From 586e497eb06a70c8a4f3b55c1f7b602c7763878f Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:52:13 -0300 Subject: [PATCH 3/3] fix(ble): Fix typos and security start --- .../Client_secure_static_passkey.ino | 44 +++++++------- .../Server_secure_static_passkey.ino | 20 +++---- libraries/BLE/src/BLESecurity.cpp | 58 +++++++++++++------ 3 files changed, 72 insertions(+), 50 deletions(-) diff --git a/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino b/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino index d0870c7b2ad..c6e20b58bbb 100644 --- a/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino +++ b/libraries/BLE/examples/Client_secure_static_passkey/Client_secure_static_passkey.ino @@ -9,7 +9,7 @@ Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE. Bluedroid initiates security on-connect, while NimBLE initiates security on-demand. - This means that in NimBLE you can read the unsecure characteristic without entering + This means that in NimBLE you can read the insecure characteristic without entering the passkey. This is not possible in Bluedroid. Also, the SoC stores the authentication info in the NVS memory. After a successful @@ -26,7 +26,7 @@ // The remote service we wish to connect to. static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); // The characteristics of the remote service we are interested in. -static BLEUUID unsecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); +static BLEUUID insecureCharUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8"); // This must match the server's passkey @@ -35,7 +35,7 @@ static BLEUUID secureCharUUID("ff1d2614-e2d6-4c87-9154-6625d39ca7f8"); static boolean doConnect = false; static boolean connected = false; static boolean doScan = false; -static BLERemoteCharacteristic *pRemoteUnsecureCharacteristic; +static BLERemoteCharacteristic *pRemoteInsecureCharacteristic; static BLERemoteCharacteristic *pRemoteSecureCharacteristic; static BLEAdvertisedDevice *myDevice; @@ -87,15 +87,15 @@ bool connectToServer() { } Serial.println(" - Found our service"); - // Obtain a reference to the unsecure characteristic - pRemoteUnsecureCharacteristic = pRemoteService->getCharacteristic(unsecureCharUUID); - if (pRemoteUnsecureCharacteristic == nullptr) { - Serial.print("Failed to find unsecure characteristic UUID: "); - Serial.println(unsecureCharUUID.toString().c_str()); + // Obtain a reference to the insecure characteristic + pRemoteInsecureCharacteristic = pRemoteService->getCharacteristic(insecureCharUUID); + if (pRemoteInsecureCharacteristic == nullptr) { + Serial.print("Failed to find insecure characteristic UUID: "); + Serial.println(insecureCharUUID.toString().c_str()); pClient->disconnect(); return false; } - Serial.println(" - Found unsecure characteristic"); + Serial.println(" - Found insecure characteristic"); // Obtain a reference to the secure characteristic pRemoteSecureCharacteristic = pRemoteService->getCharacteristic(secureCharUUID); @@ -107,10 +107,10 @@ bool connectToServer() { } Serial.println(" - Found secure characteristic"); - // Read the value of the unsecure characteristic (should work without authentication) - if (pRemoteUnsecureCharacteristic->canRead()) { - String value = pRemoteUnsecureCharacteristic->readValue(); - Serial.print("Unsecure characteristic value: "); + // Read the value of the insecure characteristic (should work without authentication) + if (pRemoteInsecureCharacteristic->canRead()) { + String value = pRemoteInsecureCharacteristic->readValue(); + Serial.print("Insecure characteristic value: "); Serial.println(value.c_str()); } @@ -127,9 +127,9 @@ bool connectToServer() { } // Register for notifications on both characteristics if they support it - if (pRemoteUnsecureCharacteristic->canNotify()) { - pRemoteUnsecureCharacteristic->registerForNotify(notifyCallback); - Serial.println(" - Registered for unsecure characteristic notifications"); + if (pRemoteInsecureCharacteristic->canNotify()) { + pRemoteInsecureCharacteristic->registerForNotify(notifyCallback); + Serial.println(" - Registered for insecure characteristic notifications"); } if (pRemoteSecureCharacteristic->canNotify()) { @@ -173,7 +173,7 @@ void setup() { BLESecurity *pSecurity = new BLESecurity(); // Set security parameters - // Deafult parameters: + // Default parameters: // - IO capability is set to NONE // - Initiator and responder key distribution flags are set to both encryption and identity keys. // - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it. @@ -213,11 +213,11 @@ void loop() { // If we are connected to a peer BLE Server, demonstrate secure communication if (connected) { - // Write to the unsecure characteristic - String unsecureValue = "Client time: " + String(millis() / 1000); - if (pRemoteUnsecureCharacteristic->canWrite()) { - pRemoteUnsecureCharacteristic->writeValue(unsecureValue.c_str(), unsecureValue.length()); - Serial.println("Wrote to unsecure characteristic: " + unsecureValue); + // Write to the insecure characteristic + String insecureValue = "Client time: " + String(millis() / 1000); + if (pRemoteInsecureCharacteristic->canWrite()) { + pRemoteInsecureCharacteristic->writeValue(insecureValue.c_str(), insecureValue.length()); + Serial.println("Wrote to insecure characteristic: " + insecureValue); } // Write to the secure characteristic diff --git a/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino index f4fe3421849..2f1189d5cf3 100644 --- a/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino +++ b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino @@ -5,14 +5,14 @@ IO capability using a static passkey. The server will accept connections from devices that have the same passkey set. The example passkey is set to 123456. - The server will create a service and a secure and an unsecure characteristic + The server will create a service and a secure and an insecure characteristic to be used as example. This server is designed to be used with the Client_secure_static_passkey example. Note that ESP32 uses Bluedroid by default and the other SoCs use NimBLE. Bluedroid initiates security on-connect, while NimBLE initiates security on-demand. - This means that in NimBLE you can read the unsecure characteristic without entering + This means that in NimBLE you can read the insecure characteristic without entering the passkey. This is not possible in Bluedroid. Also, the SoC stores the authentication info in the NVS memory. After a successful @@ -33,7 +33,7 @@ // https://www.uuidgenerator.net/ #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define UNSECURE_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define Insecure_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" #define SECURE_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8" // This is an example passkey. You should use a different or random passkey. @@ -51,7 +51,7 @@ void setup() { BLESecurity *pSecurity = new BLESecurity(); // Set security parameters - // Deafult parameters: + // Default parameters: // - IO capability is set to NONE // - Initiator and responder key distribution flags are set to both encryption and identity keys. // - Passkey is set to BLE_SM_DEFAULT_PASSKEY (123456). It will warn if you don't change it. @@ -71,8 +71,8 @@ void setup() { BLEService *pService = pServer->createService(SERVICE_UUID); - uint32_t unsecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE; - uint32_t secure_properties = unsecure_properties; + uint32_t insecure_properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE; + uint32_t secure_properties = insecure_properties; // NimBLE uses properties to secure characteristics. // These special permission properties are not supported by Bluedroid and will be ignored. @@ -81,21 +81,21 @@ void setup() { secure_properties |= BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_WRITE_ENC; BLECharacteristic *pSecureCharacteristic = pService->createCharacteristic(SECURE_CHARACTERISTIC_UUID, secure_properties); - BLECharacteristic *pUnsecureCharacteristic = pService->createCharacteristic(UNSECURE_CHARACTERISTIC_UUID, unsecure_properties); + BLECharacteristic *pInsecureCharacteristic = pService->createCharacteristic(Insecure_CHARACTERISTIC_UUID, insecure_properties); // Bluedroid uses permissions to secure characteristics. // This is the same as using the properties above. // NimBLE does not use permissions and will ignore these calls. // This can be removed if only using NimBLE (any SoC except ESP32). pSecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - pUnsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); + pInsecureCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); // Set value for secure characteristic pSecureCharacteristic->setValue("Secure Hello World!"); - // Set value for unsecure characteristic + // Set value for insecure characteristic // When using NimBLE you will be able to read this characteristic without entering the passkey. - pUnsecureCharacteristic->setValue("Unsecure Hello World!"); + pInsecureCharacteristic->setValue("Insecure Hello World!"); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); diff --git a/libraries/BLE/src/BLESecurity.cpp b/libraries/BLE/src/BLESecurity.cpp index 3c60472e8f9..ae47aea533f 100644 --- a/libraries/BLE/src/BLESecurity.cpp +++ b/libraries/BLE/src/BLESecurity.cpp @@ -59,7 +59,7 @@ uint32_t BLESecurity::m_passkey = BLE_SM_DEFAULT_PASSKEY; #if defined(CONFIG_BLUEDROID_ENABLED) uint8_t BLESecurity::m_keySize = 16; -esp_ble_sec_act_t BLESecurity::m_securityLevel = (esp_ble_sec_act_t)0; +esp_ble_sec_act_t BLESecurity::m_securityLevel; #endif /*************************************************************************** @@ -216,7 +216,7 @@ void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) { // It should return the passkey that the peer device is showing on its output. // This might not be called if the client has a static passkey set. uint32_t BLESecurityCallbacks::onPassKeyRequest() { - Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onPassKeyRequest."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Using insecure onPassKeyRequest."); Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onPassKeyRequest with a suitable passkey in your BLESecurityCallbacks class"); Serial.printf("BLESecurityCallbacks: Default passkey: %06d\n", BLE_SM_DEFAULT_PASSKEY); return BLE_SM_DEFAULT_PASSKEY; @@ -239,7 +239,7 @@ bool BLESecurityCallbacks::onSecurityRequest() { // This callback is called by both devices when both have the DisplayYesNo capability. // It should return true if both devices display the same passkey. bool BLESecurityCallbacks::onConfirmPIN(uint32_t pin) { - Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onConfirmPIN. It will accept any passkey."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Using insecure onConfirmPIN. It will accept any passkey."); Serial.println("BLESecurityCallbacks: *ATTENTION* Please implement onConfirmPIN with a suitable confirmation logic in your BLESecurityCallbacks class"); return true; } @@ -251,7 +251,7 @@ bool BLESecurityCallbacks::onConfirmPIN(uint32_t pin) { // otherwise it is requesting to write. // It should return true if the authorization is granted, false otherwise. bool BLESecurityCallbacks::onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) { - Serial.println("BLESecurityCallbacks: *ATTENTION* Using unsecure onAuthorizationRequest. It will accept any authorization request."); + Serial.println("BLESecurityCallbacks: *ATTENTION* Using insecure onAuthorizationRequest. It will accept any authorization request."); Serial.println( "BLESecurityCallbacks: *ATTENTION* Please implement onAuthorizationRequest with a suitable authorization logic in your BLESecurityCallbacks class" ); @@ -272,17 +272,28 @@ bool BLESecurity::startSecurity(esp_bd_addr_t bd_addr, int *rcPtr) { #ifdef CONFIG_BLE_SMP_ENABLE if (m_securityStarted) { log_w("Security already started for bd_addr=%s", BLEAddress(bd_addr).toString().c_str()); + if (rcPtr) { + *rcPtr = ESP_OK; + } return true; } - int rc = esp_ble_set_encryption(bd_addr, m_securityLevel); - if (rc != ESP_OK) { - log_e("esp_ble_set_encryption: rc=%d %s", rc, GeneralUtils::errorToString(rc)); - } - if (rcPtr) { - *rcPtr = rc; + if (m_securityEnabled) { + int rc = esp_ble_set_encryption(bd_addr, m_securityLevel); + if (rc != ESP_OK) { + log_e("esp_ble_set_encryption: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + } + if (rcPtr) { + *rcPtr = rc; + } + m_securityStarted = (rc == ESP_OK); + } else { + log_e("Security is not enabled. Can't start security."); + if (rcPtr) { + *rcPtr = ESP_FAIL; + } + return false; } - m_securityStarted = (rc == ESP_OK); return m_securityStarted; #else log_e("Bluedroid SMP is not enabled. Can't start security."); @@ -324,17 +335,28 @@ void BLESecurityCallbacks::onAuthenticationComplete(esp_ble_auth_cmpl_t param) { bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) { if (m_securityStarted) { log_w("Security already started for connHandle=%d", connHandle); + if (rcPtr) { + *rcPtr = 0; + } return true; } - int rc = ble_gap_security_initiate(connHandle); - if (rc != 0) { - log_e("ble_gap_security_initiate: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); - } - if (rcPtr) { - *rcPtr = rc; + if (m_securityEnabled) { + int rc = ble_gap_security_initiate(connHandle); + if (rc != 0) { + log_e("ble_gap_security_initiate: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + } + if (rcPtr) { + *rcPtr = rc; + } + m_securityStarted = (rc == 0 || rc == BLE_HS_EALREADY); + } else { + log_e("Security is not enabled. Can't start security."); + if (rcPtr) { + *rcPtr = ESP_FAIL; + } + return false; } - m_securityStarted = (rc == 0 || rc == BLE_HS_EALREADY); return m_securityStarted; }