No description
  • Kotlin 58%
  • C++ 31.3%
  • Python 5.2%
  • Shell 5.2%
  • C 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
eddy 7f68e9ad70 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
2026-07-26 08:18:52 +02:00
android Add BLE security: Just Works pairing + HMAC-SHA256 authentication 2026-07-26 08:18:52 +02:00
src Add BLE security: Just Works pairing + HMAC-SHA256 authentication 2026-07-26 08:18:52 +02:00
tools Add BLE security: Just Works pairing + HMAC-SHA256 authentication 2026-07-26 08:18:52 +02:00
.gitignore Add BLE security: Just Works pairing + HMAC-SHA256 authentication 2026-07-26 08:18:52 +02:00
platformio.ini Initial commit: BLE relay controller for Atom VoiceS3R 2026-07-26 01:17:31 +02:00
README.md Add BLE security: Just Works pairing + HMAC-SHA256 authentication 2026-07-26 08:18:52 +02:00

Sylvia Touch Relay

BLE-gestuurde relais-switch voor de M5Stack Atom VoiceS3R (ESP32-S3) met een bijbehorende Android-app. Het relais kan worden bediend via een capacitieve touch-sensor (open-drain) óf via Bluetooth Low Energy vanaf een Android-telefoon.

De BLE-verbinding is beveiligd met twee lagen: BLE Just Works pairing (link-layer encryptie) en HMAC-SHA256 authenticatie (application-level handtekening met replay-protectie).

Hardware

Component Details
Board M5Stack Atom VoiceS3R (SKU: C126-ECHO)
SoC ESP32-S3-PICO-1-N8R8 (8MB Flash, 8MB PSRAM)
Touch sensor Open-drain sensor op G38 (3.3V, interne pullup)
Relais / LED Op G1 (3.3V GPIO output)
Status LED Op G46 (knippert 3x bij opstarten)

Pinout (Atom VoiceS3R onderste header)

G38  G39  3.3V  GND  5V  G5  G6  G7  G8
  • G38 — Touch sensor input (active LOW, interne pullup ingeschakeld)
  • G1 — Relais output (HIGH = aan, LOW = uit)
  • G46 — Status LED (3x knipperen bij opstarten, daarna uit)

Aansluiting

Sensor GND  →  GND
Sensor OUT →  G38
Sensor VCC →  3.3V

Relais +  →  G1
Relais -  →  GND

Let op: De ESP32-S3 interne pullup is ~45kΩ. Bij lange draden of elektrische ruis een externe 4.7kΩ–10kΩ pullup naar 3.3V plaatsen.

Beveiliging

De relay is beveiligd tegen ongeautoriseerde toegang via twee onafhankelijke lagen:

  • De ESP32 vereist bonding voordat de characteristic geschreven kan worden
  • IO capability: ESP_IO_CAP_NONE (Just Works — geen passcode nodig)
  • Authentication mode: ESP_LE_AUTH_REQ_SC_BOND (LE Secure Connections met bonding)
  • De characteristic heeft ESP_GATT_PERM_WRITE_ENCRYPTED — writes worden alleen geaccepteerd over een versleutelde link
  • Na de eerste keer paren onthoudt de telefoon de bonding; bij herverbinding wordt de link automatisch versleuteld

2. HMAC-SHA256 Authenticatie (application-level)

Omdat BLE Just Works geen MITM-bescherming biedt, is er een tweede laag toegevoegd:

  • Both de ESP firmware en de Android app bevatten een 32-byte shared secret (HMAC key)
  • Elk commando wordt ondertekend met HMAC-SHA256(key, command || sequence)
  • De ESP verifieert de handtekening voordat het relais schakelt
  • Een sequence counter voorkomt replay-aanvallen: elk packet moet een strikt oplopend volgnummer hebben

Packet formaat (37 bytes)

Offset  Grootte  Veld         Beschrijving
0       1 byte   command      0x01 = relais aan, 0x00 = relais uit
1       4 bytes  sequence     Big-endian uint32, strikt oplopend (start bij 1)
5       32 bytes hmac         HMAC-SHA256(key, command || sequence)

De sequence counter reset naar 0 bij elke (re)connectie op de ESP. De app start bij 1 om te zorgen dat het eerste packet altijd geaccepteerd wordt (1 > 0). Replay van een eerder packet wordt geweerd omdat seq <= lastSeq.

Key management

  • De HMAC key staat in src/hmac_key.h (niet in git, .gitignore)
  • Een CLI-tool genereert een willekeurige key: python3 tools/generate_hmac_key.py
  • De ESP print de key 5 seconden na opstarten naar serial
  • De gebruiker voert de key eenmalig in via het instellingenscherm in de Android app
  • De app slaat de key op in SharedPreferences (overleeft app-herstarts)
  • Zo kan dezelfde app gebruikt worden voor meerdere ESP modules — each met een eigen key

Beveiligingsanalyse

Aanval Hoe geweerd
Sniffen BLE link encrypted (AES-CCM via Just Works)
Replay Sequence counter — zelfde packet werkt niet 2x
Ongeautoriseerd device HMAC vereist geheime sleutel
MITM Just Works is kwetsbaar voor MITM; HMAC maakt actieve MITM wel moeilijk (aanvaller moet de key kennen)
Reverse engineering van key Sleutel staat in APK — extracteerbaar met reverse engineering. Voor hogere veiligheid: gebruiker ProGuard/R8 obfuscation.

Firmware

De firmware is geschreven in C++ met behulp van PlatformIO en de Arduino-framework. BLE wordt verzorgd door de ESP32 BLE library, HMAC door mbedtls (ingebouwd in ESP-IDF).

Logica

Het relais gaat aan als één van beide bronnen actief is:

Bron Voorwaarde Resultaat
Touch sensor G38 = LOW (aanraken) Relais aan
BLE command App stuurt HMAC-ondertekend 0x01 Relais aan
Geen van beide G38 = HIGH + geen BLE command Relais uit

De touch sensor en BLE werken onafzankelijk. Als de sensor losgelaten wordt en de BLE-knop niet ingedrukt is, gaat het relais uit.

BLE GATT

Eigenschap UUID
Service 4fafc201-1fb5-459e-8fcc-c5c9c331914b
Characteristic beb5483e-36e1-4688-b7f5-ea07361f26d8

De characteristic ondersteunt WRITE (encrypted) en NOTIFY. Writes vereisen een versleutelde link (bonding) plus HMAC-authenticatie.

HMAC Key genereren

Voordat de firmware gecompileerd kan worden, moet er een HMAC key gegenereerd worden:

python3 tools/generate_hmac_key.py

Dit maakt src/hmac_key.h aan met een willekeurige 32-byte key en print de hex-representatie. Een bestaande key overschrijven:

echo "y" | python3 tools/generate_hmac_key.py

Belangrijk: src/hmac_key.h staat in .gitignore en wordt niet gecommit. De key moet op de ESP en in de app identiek zijn.

Serial monitor

Baudrate: 115200 (USB CDC). De firmware stuurt heartbeat-statussen elke 5 seconden, logt statuswijzigingen, en print de HMAC key 5 seconden na opstarten:

=== Atom VoiceS3R BLE Relay ===
LED blink done, starting BLE...
BLE: advertising started as 'AtomS3R-Relay'
Printing HMAC key in 5 seconds...
HMAC Key: C3F17B612B93E82C56B055FA9B04E84664ACE1EC4E889F2763F271B65A5B54AF
Enter this key in the Android app > Settings.
Setup done.
[heartbeat] relay=OFF touch=no ble=disconnected
BLE: device connected
Relay ON (touch=YES, ble=connected)
BLE: relay ON (seq=1)
BLE: relay OFF (seq=2)

Uploaden

Download-mode: Houd de reset-knop ~2 seconden ingedrukt tot de groene LED aangaat, laat dan los. Het board is nu in download-mode.

# Zorg dat src/hmac_key.h bestaat (zo niet: python3 tools/generate_hmac_key.py)
pio run --target upload

Na het uploaden treedt er een harde reset op. Als het board in download-mode blijft, USB eruit/erin of kort op reset tikken om in run-mode te komen.

Android App

Features

  • Auto-connect: De app scant automatisch bij opstarten naar het BLE-apparaat "AtomS3R-Relay"
  • Auto-reconnect: Bij wegvallen van de verbinding wordt na 2 seconden opnieuw gescant
  • Hold-to-activate: Houd de knop ingedrukt voor relais aan, loslaten voor relais uit (zelfde gedrag als de sensor)
  • BLE Pairing: De app initieert bonding (createBond()) na connectie; Just Works pairing verloopt zonder gebruikersinteractie
  • MTU negotiatie: Vraagt MTU 256 aan na service discovery (voor 37-byte HMAC packets)
  • HMAC authenticatie: Elk commando wordt ondertekend met HMAC-SHA256 + sequence counter
  • Instellingenscherm: Losse Activity voor het invoeren van de HMAC key (met oog-icoon om de key te tonen/verbergen)
  • Permissies: Vraagt BLUETOOTH_SCAN, BLUETOOTH_CONNECT en ACCESS_FINE_LOCATION aan (Android 12+)

Instellingenscherm

  1. Open de app
  2. Tik op het tandwiel-icoon rechtsboven
  3. Voer de 64-teken hex key in (zelfde als de ESP op serial print)
  4. Tik op het oog-icoon om de key te tonen/verbergen
  5. Tik Opslaan

De key wordt opgeslagen in SharedPreferences en overleeft app-herstarts. Bij geen key toont de app "Geen HMAC sleutel - open Instellingen" en wordt er niet gescant.

Bouwen

Vereisten:

  • Android SDK (API 34+)
  • Java 17+ (getest met Java 17 en 21; Java 26 werkt niet met Kotlin 1.9 daemon — valt terug op non-incremental)
  • Gradle 8.2.2 (via wrapper)
cd android
./gradlew assembleDebug

De APK staat na build in android/app/build/outputs/apk/debug/app-debug.apk.

Installeren

adb install -r android/app/build/outputs/apk/debug/app-debug.apk

Of via draadloze foutopsporing:

adb connect <ip>:<poort>
adb install -r android/app/build/outputs/apk/debug/app-debug.apk

Eerste keer setup

  1. Genereer HMAC key:

    python3 tools/generate_hmac_key.py
    

    Noteer de geprinte hex key.

  2. Flash de ESP:

    pio run -t upload
    
  3. Lees de HMAC key uit serial (5 seconden na opstarten):

    # Via python:
    python3 -c "import serial,time; s=serial.Serial('/dev/ttyACM0',115200); [print(l) for l in iter(lambda: s.readline().decode(errors='replace').strip() or None, None) if 'HMAC Key:' in l] if False else exec('''
    

import serial, time s = serial.Serial("/dev/ttyACM0", 115200, timeout=2) start = time.time() while time.time() - start < 15: d = s.read(1024) if d: for line in d.decode(errors="replace").splitlines(): if line.strip(): print(line.strip()) if b"HMAC Key:" in d: break s.close() ''')"


4. **Installeer de app op de telefoon:**
```bash
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
  1. Open de app → tandwiel-icoon → voer key in → Opslaan

  2. De app scant, paart (Just Works), en verbindt. Houd de HOLD-knop ingedrukt om het relais te schakelen.

Projectstructuur

sylvia-touch-relay/
├── platformio.ini              # PlatformIO configuratie (ESP32-S3)
├── tools/
│   └── generate_hmac_key.py    # CLI tool: genereert src/hmac_key.h
├── src/
│   ├── main.cpp                # Firmware (BLE + HMAC + sensor + relais)
│   └── hmac_key.h              # Gegenereerd, niet in git (.gitignore)
├── android/
│   ├── build.gradle            # Root Gradle config
│   ├── settings.gradle
│   ├── gradle.properties
│   ├── gradlew                 # Gradle wrapper
│   ├── gradle/wrapper/
│   │   └── gradle-wrapper.properties
│   └── app/
│       ├── build.gradle        # App module config
│       └── src/main/
│           ├── AndroidManifest.xml
│           ├── java/com/eddy/atomrelay/
│           │   ├── MainActivity.kt     # BLE client + HMAC signing
│           │   └── SettingsActivity.kt  # HMAC key invoer + oog toggle
│           └── res/
│               ├── drawable/
│               │   ├── ic_eye_open.xml      # Oog open icoon
│               │   ├── ic_eye_closed.xml    # Oog dicht icoon
│               │   └── ic_settings.xml     # Tandwiel icoon
│               ├── layout/
│               │   ├── activity_main.xml      # Hoofd UI
│               │   └── activity_settings.xml  # Key invoer UI
│               └── values/strings.xml
├── .gitignore
└── README.md

Technische notities

  • De Atom VoiceS3R gebruikt de ES8311 audio codec op G45/G0/G48/G4/G3/G17/G11, IR TX op G47, en de knop op G41. Deze pinnen zijn niet beschikbaar voor eigen gebruik.
  • G1 en G2 zitten op de HY2.0-4P Grove poort. G5-G8, G38, G39 zitten op de onderste expantie-header.
  • De firmware gebruikt geen M5Unified library — alleen Arduino core + ESP32 BLE + mbedtls. Dit maakt de serial output betrouwbaarder via USB CDC.
  • ARDUINO_USB_CDC_ON_BOOT=1 is ingeschakeld voor virtuele serial via USB.
  • De HMAC key wordt via #include "hmac_key.h" in de firmware geincludeerd. Dit bestand is gegenereerd door tools/generate_hmac_key.py en staat in .gitignore.
  • De app gebruikt WRITE_TYPE_NO_RESPONSE voor minimale latency — de encryptie en HMAC verificatie gebeuren op de link layer resp. application layer en zijn onafhankelijk van het write type.
  • De sequence counter start bij 1 (niet 0) om te voorkomen dat het eerste packet na connectie als replay wordt geweerd (0 <= 0 is true in de seq <= lastSeq check).
  • Für meerdere ESP modules met verschillende keys: genereer per module een eigen key, flash de firmware, en voer de juiste key in de app in. De app ondersteunt momenteel slechtséén key (opgeslagen in SharedPreferences).