Add BLE security: Just Works pairing + HMAC-SHA256 authentication
Two-layer security for the BLE relay: 1. BLE Just Works pairing (link-layer encryption) - ESP32 requires bonding before accepting writes - Characteristic permissions set to ESP_GATT_PERM_WRITE_ENCRYPTED - IO capability NO_INPUT_NO_OUTPUT (no passkey needed) - LE Secure Connections with bonding (ESP_LE_AUTH_REQ_SC_BOND) 2. HMAC-SHA256 authentication (application-level) - 32-byte shared secret in src/hmac_key.h (generated, gitignored) - Each command signed: HMAC-SHA256(key, command || sequence) - 37-byte packet format: [cmd:1][seq:4][hmac:32] - Sequence counter prevents replay (strictly increasing, starts at 1) - mbedtls on ESP, javax.crypto.Mac on Android Key management: - tools/generate_hmac_key.py generates random key + writes src/hmac_key.h - ESP prints hex key to serial 5 seconds after boot - Android app: SettingsActivity with eye-toggle password field - Key stored in SharedPreferences, supports multiple ESP modules Other changes: - Android app: bonding flow (createBond -> BOND_BONDED -> discover) - Android app: MTU negotiation (request 256) - Android app: settings button (gear icon) in main screen - Fix: sequence counter starts at 1 to prevent first-packet rejection - Updated README with full security documentation
This commit is contained in:
parent
7c9dde172a
commit
7f68e9ad70
13 changed files with 621 additions and 46 deletions
89
src/main.cpp
89
src/main.cpp
|
|
@ -3,6 +3,9 @@
|
|||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <BLE2902.h>
|
||||
#include <BLESecurity.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include "hmac_key.h"
|
||||
|
||||
#define SENSOR_PIN 38
|
||||
#define RELAY_PIN 1
|
||||
|
|
@ -11,36 +14,85 @@
|
|||
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
|
||||
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d8"
|
||||
|
||||
#define HMAC_PKT_SIZE 37 // 1 cmd + 4 seq + 32 hmac
|
||||
|
||||
BLECharacteristic *pCharacteristic;
|
||||
bool deviceConnected = false;
|
||||
bool bleCommand = false;
|
||||
bool relayState = false;
|
||||
uint32_t lastSeq = 0;
|
||||
|
||||
class MyServerCallbacks : public BLEServerCallbacks {
|
||||
void onConnect(BLEServer *pServer) override {
|
||||
deviceConnected = true;
|
||||
lastSeq = 0;
|
||||
Serial.println("BLE: device connected");
|
||||
}
|
||||
void onDisconnect(BLEServer *pServer) override {
|
||||
deviceConnected = false;
|
||||
bleCommand = false;
|
||||
lastSeq = 0;
|
||||
Serial.println("BLE: device disconnected");
|
||||
BLEDevice::startAdvertising();
|
||||
}
|
||||
};
|
||||
|
||||
static bool verifyHmac(const uint8_t *packet) {
|
||||
uint8_t cmd = packet[0];
|
||||
uint32_t seq = ((uint32_t)packet[1] << 24) |
|
||||
((uint32_t)packet[2] << 16) |
|
||||
((uint32_t)packet[3] << 8) |
|
||||
(uint32_t)packet[4];
|
||||
|
||||
if (seq <= lastSeq) {
|
||||
Serial.printf("BLE: replay rejected (seq=%lu, last=%lu)\n", seq, lastSeq);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hmacData[5];
|
||||
hmacData[0] = cmd;
|
||||
hmacData[1] = (seq >> 24) & 0xFF;
|
||||
hmacData[2] = (seq >> 16) & 0xFF;
|
||||
hmacData[3] = (seq >> 8) & 0xFF;
|
||||
hmacData[4] = seq & 0xFF;
|
||||
|
||||
uint8_t computed[32];
|
||||
const mbedtls_md_info_t *mdInfo = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
mbedtls_md_hmac(mdInfo, HMAC_KEY, 32, hmacData, 5, computed);
|
||||
|
||||
if (memcmp(computed, packet + 5, 32) != 0) {
|
||||
Serial.println("BLE: HMAC mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
lastSeq = seq;
|
||||
return true;
|
||||
}
|
||||
|
||||
class MyCharacteristicCallbacks : public BLECharacteristicCallbacks {
|
||||
void onWrite(BLECharacteristic *pChar) override {
|
||||
std::string value = pChar->getValue();
|
||||
if (value.length() > 0) {
|
||||
uint8_t cmd = (uint8_t)value[0];
|
||||
if (cmd == 0x01) {
|
||||
bleCommand = true;
|
||||
Serial.println("BLE: relay ON");
|
||||
} else {
|
||||
bleCommand = false;
|
||||
Serial.println("BLE: relay OFF");
|
||||
}
|
||||
if (value.length() != HMAC_PKT_SIZE) {
|
||||
Serial.printf("BLE: bad packet length %d (expected %d)\n",
|
||||
(int)value.length(), HMAC_PKT_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *pkt = (const uint8_t *)value.data();
|
||||
|
||||
if (!verifyHmac(pkt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t cmd = pkt[0];
|
||||
uint32_t seq = lastSeq;
|
||||
|
||||
if (cmd == 0x01) {
|
||||
bleCommand = true;
|
||||
Serial.printf("BLE: relay ON (seq=%lu)\n", seq);
|
||||
} else {
|
||||
bleCommand = false;
|
||||
Serial.printf("BLE: relay OFF (seq=%lu)\n", seq);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -55,7 +107,6 @@ void setup() {
|
|||
pinMode(RELAY_PIN, OUTPUT);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
|
||||
// Visual confirmation: blink LED 3x before BLE init
|
||||
for (int i = 0; i < 3; i++) {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(200);
|
||||
|
|
@ -65,7 +116,12 @@ void setup() {
|
|||
Serial.println("LED blink done, starting BLE...");
|
||||
|
||||
BLEDevice::init("AtomS3R-Relay");
|
||||
BLEDevice::setMTU(23);
|
||||
|
||||
BLESecurity *pSecurity = new BLESecurity();
|
||||
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
|
||||
pSecurity->setCapability(ESP_IO_CAP_NONE);
|
||||
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
|
||||
|
||||
BLEServer *pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new MyServerCallbacks());
|
||||
|
||||
|
|
@ -74,6 +130,7 @@ void setup() {
|
|||
CHARACTERISTIC_UUID,
|
||||
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
|
||||
);
|
||||
pCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENCRYPTED);
|
||||
pCharacteristic->addDescriptor(new BLE2902());
|
||||
pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
|
||||
pCharacteristic->setValue("0");
|
||||
|
|
@ -87,6 +144,16 @@ void setup() {
|
|||
BLEDevice::startAdvertising();
|
||||
|
||||
Serial.println("BLE: advertising started as 'AtomS3R-Relay'");
|
||||
|
||||
Serial.println("Printing HMAC key in 5 seconds...");
|
||||
delay(5000);
|
||||
Serial.print("HMAC Key: ");
|
||||
for (int i = 0; i < 32; i++) {
|
||||
Serial.printf("%02X", HMAC_KEY[i]);
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println("Enter this key in the Android app > Settings.");
|
||||
|
||||
Serial.println("Setup done.");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue