Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
/*
Secure client with static passkey
Secure client with static passkey and IRK retrieval
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.
After successful bonding, the example demonstrates how to retrieve the
server's Identity Resolving Key (IRK) in multiple formats:
- Comma-separated hex format: 0x1A,0x1B,0x1C,...
- Base64 encoded (for Home Assistant Private BLE Device service)
- Reverse hex order (for Home Assistant ESPresense)
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 insecure characteristic without entering
the passkey. This is not possible in Bluedroid.
IMPORTANT: MITM (Man-In-The-Middle protection) must be enabled for password prompts
to work. Without MITM, the BLE stack assumes no user interaction is needed and will use
"Just Works" pairing method (with encryption if secure connection is enabled).
IMPORTANT:
- MITM (Man-In-The-Middle protection) must be enabled for password prompts to work.
- Bonding must be enabled to store and retrieve the IRK.
- The server must distribute its Identity Key during pairing.
Based on examples from Neil Kolban and h2zero.
Created by lucasssvaz.
Expand All @@ -36,10 +43,59 @@
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLEClient *pClient = nullptr;
static BLERemoteCharacteristic *pRemoteInsecureCharacteristic;
static BLERemoteCharacteristic *pRemoteSecureCharacteristic;
static BLEAdvertisedDevice *myDevice;

// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}

static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Server) ===\n");

uint8_t irk[16];

// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}

// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);

if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}

Serial.println("=======================================\n");
}

// Callback function to handle notifications
static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
Serial.print("Notify callback for characteristic ");
Expand All @@ -62,11 +118,30 @@
}
};

// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif

#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};

bool connectToServer() {
Serial.print("Forming a secure connection to ");
Serial.println(myDevice->getAddress().toString().c_str());

BLEClient *pClient = BLEDevice::createClient();
pClient = BLEDevice::createClient();
Serial.println(" - Created client");

pClient->setClientCallbacks(new MyClientCallback());
Expand Down Expand Up @@ -192,15 +267,19 @@
pSecurity->setPassKey(true, CLIENT_PIN);

// Set authentication mode to match server requirements
// Enable secure connection and MITM (for password prompts) for this example
pSecurity->setAuthenticationMode(false, true, true);
// Enable bonding, MITM (for password prompts), and secure connection for this example
// Bonding is required to store and retrieve the IRK
pSecurity->setAuthenticationMode(true, true, true);

// Set IO capability to KeyboardOnly
// We need the proper IO capability for MITM authentication even
// if the passkey is static and won't be entered by the user
// See https://www.bluetooth.com/blog/bluetooth-pairing-part-2-key-generation-methods/
pSecurity->setCapability(ESP_IO_CAP_IN);

// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());

// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,73 @@
// This is an example passkey. You should use a different or random passkey.
#define SERVER_PIN 123456

// Print an IRK buffer as hex with leading zeros and ':' separator
static void printIrkBinary(uint8_t *irk) {
for (int i = 0; i < 16; i++) {
if (irk[i] < 0x10) {
Serial.print("0");
}
Serial.print(irk[i], HEX);
if (i < 15) {
Serial.print(":");
}
}
}

static void get_peer_irk(BLEAddress peerAddr) {
Serial.println("\n=== Retrieving peer IRK (Client) ===\n");

uint8_t irk[16];

// Get IRK in binary format
if (BLEDevice::getPeerIRK(peerAddr, irk)) {
Serial.println("Successfully retrieved peer IRK in binary format:");
printIrkBinary(irk);
Serial.println("\n");
}

// Get IRK in different string formats
String irkString = BLEDevice::getPeerIRKString(peerAddr);
String irkBase64 = BLEDevice::getPeerIRKBase64(peerAddr);
String irkReverse = BLEDevice::getPeerIRKReverse(peerAddr);

if (irkString.length() > 0) {
Serial.println("Successfully retrieved peer IRK in multiple formats:\n");
Serial.print("IRK (comma-separated hex): ");
Serial.println(irkString);
Serial.print("IRK (Base64 for Home Assistant Private BLE Device): ");
Serial.println(irkBase64);
Serial.print("IRK (reverse hex for Home Assistant ESPresense): ");
Serial.println(irkReverse);
Serial.println();
} else {
Serial.println("!!! Failed to retrieve peer IRK !!!");
Serial.println("This is expected if bonding is disabled or the peer doesn't distribute its Identity Key.");
Serial.println("To enable bonding, change setAuthenticationMode to: pSecurity->setAuthenticationMode(true, true, true);\n");
}

Serial.println("=======================================\n");
}

// Security callbacks to print IRKs once authentication completes
class MySecurityCallbacks : public BLESecurityCallbacks {
#if defined(CONFIG_BLUEDROID_ENABLED)
void onAuthenticationComplete(esp_ble_auth_cmpl_t desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc.bd_addr);
get_peer_irk(peerAddr);
}
#endif

#if defined(CONFIG_NIMBLE_ENABLED)
void onAuthenticationComplete(ble_gap_conn_desc *desc) override {
// Print the IRK received by the peer
BLEAddress peerAddr(desc->peer_id_addr.val, desc->peer_id_addr.type);
get_peer_irk(peerAddr);
}
#endif
};

void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
Expand Down Expand Up @@ -76,8 +143,11 @@
pSecurity->setCapability(ESP_IO_CAP_OUT);

// Set authentication mode
// Require secure connection and MITM (for password prompts) for this example
pSecurity->setAuthenticationMode(false, true, true);
// Enable bonding, MITM (for password prompts), and secure connection for this example
pSecurity->setAuthenticationMode(true, true, true);

// Set callbacks to handle authentication completion and print IRKs
BLEDevice::setSecurityCallbacks(new MySecurityCallbacks());

BLEServer *pServer = BLEDevice::createServer();
pServer->advertiseOnDisconnect(true);
Expand Down
Loading
Loading