From 8af12a5ee18a5816dc69aa7a868001cb2d437ad3 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/2] fix(ble): Fix security for Bluedroid --- libraries/BLE/src/BLECharacteristic.cpp | 55 ++++ libraries/BLE/src/BLECharacteristic.h | 12 +- libraries/BLE/src/BLEClient.cpp | 92 ++++--- libraries/BLE/src/BLEClient.h | 3 - libraries/BLE/src/BLEDescriptor.h | 2 + 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 | 2 +- 15 files changed, 648 insertions(+), 144 deletions(-) diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index 0234cd11cca..7333462522b 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -582,6 +582,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; @@ -637,6 +665,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 27df5a30c3e..7ec11b4665f 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -140,17 +140,25 @@ 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 /*************************************************************************** * NimBLE public properties * ***************************************************************************/ + + #if defined(CONFIG_NIMBLE_ENABLED) static const uint32_t PROPERTY_READ = BLE_GATT_CHR_F_READ; static const uint32_t PROPERTY_READ_ENC = BLE_GATT_CHR_F_READ_ENC; @@ -161,8 +169,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 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.h b/libraries/BLE/src/BLEDescriptor.h index d0d34b24388..61e22d2311a 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -42,6 +42,8 @@ #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 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..ba55f07cdf6 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -1568,7 +1568,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; From 3f549d3a8b4129b8f7d825752d8e555f687dcdfe Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:45:01 -0300 Subject: [PATCH 2/2] Test IDF call --- .../Client_secure_static_passkey.ino | 235 ++++++++++++++++++ .../Client_secure_static_passkey/ci.json | 6 + .../Server_authorization_test.ino | 134 ++++++++++ .../Server_authorization_test/ci.json | 9 + .../Server_secure_static_passkey.ino | 115 +++++++++ .../Server_secure_static_passkey/ci.json | 6 + libraries/BLE/src/BLERemoteCharacteristic.cpp | 7 + 7 files changed, 512 insertions(+) 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_authorization_test/Server_authorization_test.ino create mode 100644 libraries/BLE/examples/Server_authorization_test/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..371c11ed646 --- /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_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 bonding, with MITM and secure connection + pSecurity->setAuthenticationMode(true, true, 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_authorization_test/Server_authorization_test.ino b/libraries/BLE/examples/Server_authorization_test/Server_authorization_test.ino new file mode 100644 index 00000000000..672d0783378 --- /dev/null +++ b/libraries/BLE/examples/Server_authorization_test/Server_authorization_test.ino @@ -0,0 +1,134 @@ +/* + BLE Server Authorization Test + + This example demonstrates how to use authorization in both NimBLE and Bluedroid. + The server creates a service with two characteristics: + - One characteristic that requires read/write authorization + - One characteristic that does not require authorization + + When authorization is required, the server will call the onAuthorizationRequest callback. + This example shows a simple authorization logic that accepts requests from connection handle 0 + and rejects requests from all other connection handles. + + In a real application, you would implement more sophisticated authorization logic + based on your security requirements. + + This example works with both NimBLE and Bluedroid implementations. + + Created by lucasssvaz. +*/ + +#include +#include +#include +#include +#include + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define OPEN_CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define AUTHORIZED_CHARACTERISTIC_UUID "ff1d2614-e2d6-4c87-9154-6625d39ca7f8" + +class MySecurityCallbacks : public BLESecurityCallbacks { +public: + bool onAuthorizationRequest(uint16_t connHandle, uint16_t attrHandle, bool isRead) override { + Serial.printf("Authorization request: conn_handle=%d, attr_handle=%d, is_read=%s\n", + connHandle, attrHandle, isRead ? "true" : "false"); + + // Simple authorization logic: allow only connection handle 0 + // In a real application, you would implement more sophisticated logic + bool authorized = (connHandle == 0); + + Serial.printf("Authorization %s for connection handle %d\n", + authorized ? "granted" : "denied", connHandle); + + return authorized; + } +}; + +class MyCharacteristicCallbacks : public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) override { + String value = pCharacteristic->getValue(); + Serial.printf("Characteristic written: UUID=%s, value=%s\n", + pCharacteristic->getUUID().toString().c_str(), value.c_str()); + } + + void onRead(BLECharacteristic* pCharacteristic) override { + Serial.printf("Characteristic read: UUID=%s\n", + pCharacteristic->getUUID().toString().c_str()); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE Authorization Test Server!"); + + Serial.printf("Using BLE stack: %s\n", BLEDevice::getBLEStackString()); + + BLEDevice::init("Authorization Test Server"); + + // Set security callbacks + BLEDevice::setSecurityCallbacks(new MySecurityCallbacks()); + + // Create the BLE Server + BLEServer* pServer = BLEDevice::createServer(); + + // Create the BLE Service + BLEService* pService = pServer->createService(SERVICE_UUID); + + // Create open characteristic (no authorization required) + BLECharacteristic* pOpenCharacteristic = pService->createCharacteristic( + OPEN_CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + pOpenCharacteristic->setCallbacks(new MyCharacteristicCallbacks()); + pOpenCharacteristic->setValue("Open characteristic - no authorization required"); + + // Create authorized characteristic (authorization required for read/write) + BLECharacteristic* pAuthorizedCharacteristic = pService->createCharacteristic( + AUTHORIZED_CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + pAuthorizedCharacteristic->setCallbacks(new MyCharacteristicCallbacks()); + pAuthorizedCharacteristic->setValue("Authorized characteristic - authorization required"); + + // Set authorization permissions for the authorized characteristic +#if defined(CONFIG_BLUEDROID_ENABLED) + pAuthorizedCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_AUTHORIZATION | ESP_GATT_PERM_WRITE_AUTHORIZATION); + Serial.println("Bluedroid: Set authorization permissions using setAccessPermissions()"); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + // In NimBLE, authorization is controlled by the characteristic properties + // The PROPERTY_READ_AUTHOR and PROPERTY_WRITE_AUTHOR flags trigger authorization + pAuthorizedCharacteristic = pService->createCharacteristic( + AUTHORIZED_CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_READ_AUTHOR | BLECharacteristic::PROPERTY_WRITE_AUTHOR + ); + pAuthorizedCharacteristic->setCallbacks(new MyCharacteristicCallbacks()); + pAuthorizedCharacteristic->setValue("Authorized characteristic - authorization required"); + Serial.println("NimBLE: Set authorization permissions using PROPERTY_READ_AUTHOR and PROPERTY_WRITE_AUTHOR"); +#endif + + // Start the service + pService->start(); + + // Start advertising + BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(false); + pAdvertising->setMinPreferred(0x0); // Set value to 0x00 to not advertise this parameter + + BLEDevice::startAdvertising(); + Serial.println("Waiting for client connection to test authorization..."); + Serial.println(); + Serial.println("Connect with a BLE client and try to:"); + Serial.println("1. Read/write the open characteristic (should work without authorization)"); + Serial.println("2. Read/write the authorized characteristic (will trigger authorization callback)"); + Serial.println(); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/libraries/BLE/examples/Server_authorization_test/ci.json b/libraries/BLE/examples/Server_authorization_test/ci.json new file mode 100644 index 00000000000..5569a377b09 --- /dev/null +++ b/libraries/BLE/examples/Server_authorization_test/ci.json @@ -0,0 +1,9 @@ +{ + "targets": [ + "esp32", + "esp32s3", + "esp32c3", + "esp32c6", + "esp32h2" + ] +} 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..58ef1b2a17e --- /dev/null +++ b/libraries/BLE/examples/Server_secure_static_passkey/Server_secure_static_passkey.ino @@ -0,0 +1,115 @@ +/* + 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 + // Enable bonding, with MITM and secure connection so keys are stored and reused on reconnect + pSecurity->setAuthenticationMode(true, true, 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; + secure_properties |= BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_WRITE_AUTHEN; + + 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_ENC_MITM | ESP_GATT_PERM_WRITE_ENC_MITM); + 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/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index dd3aa313e83..364ec590131 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -566,8 +566,11 @@ String BLERemoteCharacteristic::readValue() { return String(); } + log_d("readValue: taking semaphore"); m_semaphoreReadCharEvt.take("readValue"); + log_d("readValue: calling esp_ble_gattc_read_char"); + log_d("readValue: gattcIf: %d, connId: %d, handle: %d, auth: %d", m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), getHandle(), m_auth); // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. // This is an asynchronous request which means that we must block waiting for the response // to become available. @@ -578,6 +581,8 @@ String BLERemoteCharacteristic::readValue() { (esp_gatt_auth_req_t)m_auth ); // Security + log_d("readValue: esp_ble_gattc_read_char returned: %d", errRc); + if (errRc != ESP_OK) { log_e("esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return ""; @@ -585,7 +590,9 @@ String BLERemoteCharacteristic::readValue() { // Block waiting for the event that indicates that the read has completed. When it has, the String found // in m_value will contain our data. + log_d("readValue: waiting for semaphore to be released with ESP_GATTC_READ_CHAR_EVT..."); m_semaphoreReadCharEvt.wait("readValue"); + log_d("readValue: semaphore released"); log_v("<< readValue(): length: %d", m_value.length()); return m_value;