fix: code review fixes — SharedPreferences docs, Log.e, dead notify code

This commit is contained in:
eddy 2026-07-29 23:51:16 +02:00
commit 0833327cea
4 changed files with 9 additions and 16 deletions

View file

@ -61,13 +61,13 @@ Offset Grootte Veld Beschrijving
5 32 bytes hmac HMAC-SHA256(key, command || sequence) 5 32 bytes hmac HMAC-SHA256(key, command || sequence)
``` ```
De sequence counter reset naar 0 bij elke (re)connectie op de module. 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`. De sequence counter wordt gepersisteerd in EncryptedSharedPreferences en overleeft app-herstarts — bij een Activity-restart met actieve BLE-verbinding blijft de counter oplopen in plaats van terug te vallen naar 1. De sequence counter reset naar 0 bij elke (re)connectie op de module. 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`. De sequence counter wordt gepersisteerd in SharedPreferences en overleeft app-herstarts — bij een Activity-restart met actieve BLE-verbinding blijft de counter oplopen in plaats van terug te vallen naar 1.
#### Key management #### Key management
- De HMAC key wordt **niet meegecompileerd** — de module start zonder key en wordt geprovisioneerd via USB - De HMAC key wordt **niet meegecompileerd** — de module start zonder key en wordt geprovisioneerd via USB
- De key wordt opgeslagen in **NVS** (Non-Volatile Storage) op de module via de Arduino `Preferences` library (namespace `hmac`, key `key`, partitie op 0x9000-0xDFFF) - De key wordt opgeslagen in **NVS** (Non-Volatile Storage) op de module via de Arduino `Preferences` library (namespace `hmac`, key `key`, partitie op 0x9000-0xDFFF)
- De app slaat de key op in **EncryptedSharedPreferences** (AES256-GCM versleuteld, overleeft app-herstarts) - De app slaat de key op in **SharedPreferences** (overleeft app-herstarts)
- **USB provisioning:** Open Instellingen → Firmware bijwerken. De app checkt eerst de firmware versie (`VERSION?`) en flasht indien nodig. Na het flashen (of als versie al actueel is) volgt automatisch de key exchange: - **USB provisioning:** Open Instellingen → Firmware bijwerken. De app checkt eerst de firmware versie (`VERSION?`) en flasht indien nodig. Na het flashen (of als versie al actueel is) volgt automatisch de key exchange:
- Als de module al een key heeft → module antwoordt `HMAC_KEY:<hex>` → app slaat op lokaal op - Als de module al een key heeft → module antwoordt `HMAC_KEY:<hex>` → app slaat op lokaal op
- Als de module nog geen key heeft → module antwoordt `NO_KEY` → app genereert willekeurige 32-byte key via `SecureRandom` → stuurt `SET_KEY:<hex>\n` en `SET_ID:<hex>\n` → module slaat op in NVS → app slaat op lokaal op - Als de module nog geen key heeft → module antwoordt `NO_KEY` → app genereert willekeurige 32-byte key via `SecureRandom` → stuurt `SET_KEY:<hex>\n` en `SET_ID:<hex>\n` → module slaat op in NVS → app slaat op lokaal op
@ -115,7 +115,7 @@ module → App: OK\n (bevestiging)
| **Commando vervalsing** | HMAC dekt command byte + sequence — wijzigen maakt handtekening ongeldig | | **Commando vervalsing** | HMAC dekt command byte + sequence — wijzigen maakt handtekening ongeldig |
| **Brute-force key** | 32-byte key = 2^256 mogelijkheden — onhaalbaar | | **Brute-force key** | 32-byte key = 2^256 mogelijkheden — onhaalbaar |
| **Sniffen** | Pakketten zijn onversleuteld; geen confidentiality. Alleen commando's (aan/uit) zijn zichtbaar, niet de key. | | **Sniffen** | Pakketten zijn onversleuteld; geen confidentiality. Alleen commando's (aan/uit) zijn zichtbaar, niet de key. |
| **Reverse engineering van key** | Sleutel staat niet in de APK — wordt pas bij USB provisioning gegenereerd en in EncryptedSharedPreferences (AES256-GCM) opgeslagen. Extractie vereist fysieke toegang tot de telefoon én root. | | **Reverse engineering van key** | Sleutel staat niet in de APK — wordt pas bij USB provisioning gegenereerd en in SharedPreferences opgeslagen. Extractie vereist fysieke toegang tot de telefoon. |
| **Key extractie via USB** | Fysieke USB-toegang tot de module geeft `KEY?\n` de huidige key terug. Bescherming: fysieke toegang tot het board beperken. | | **Key extractie via USB** | Fysieke USB-toegang tot de module geeft `KEY?\n` de huidige key terug. Bescherming: fysieke toegang tot het board beperken. |
| **NVS wiping** | `pio run -t erase` wist alle flash waaronder NVS — module is daarna leeg en kan opnieuw geprovisioneerd worden. | | **NVS wiping** | `pio run -t erase` wist alle flash waaronder NVS — module is daarna leeg en kan opnieuw geprovisioneerd worden. |
@ -149,7 +149,7 @@ De touch sensoren en BLE werken onafhankelijk. Als de sensor losgelaten wordt en
| Relay Characteristic | `beb5483e-36e1-4688-b7f5-ea07361f26d8` | | Relay Characteristic | `beb5483e-36e1-4688-b7f5-ea07361f26d8` |
| UUID Characteristic | `beb5483e-36e1-4688-b7f5-ea07361f26d9` | | UUID Characteristic | `beb5483e-36e1-4688-b7f5-ea07361f26d9` |
De relay characteristic ondersteunt `WRITE_NR` (Write Without Response) en `NOTIFY`. De UUID characteristic is read-only en bevat het unieke device-ID van de module. De relay characteristic ondersteunt `WRITE_NR` (Write Without Response). De UUID characteristic is read-only en bevat het unieke device-ID van de module.
### Key provisioning ### Key provisioning
@ -251,7 +251,7 @@ De firmware binaries worden via `tools/copy_firmware.sh` naar de app assets geko
3. **Weergave:** Kies Systeem (volgt telefoon), Licht of Donker 3. **Weergave:** Kies Systeem (volgt telefoon), Licht of Donker
4. **Relais labels:** Stel eigen namen in voor de twee relais knoppen 4. **Relais labels:** Stel eigen namen in voor de twee relais knoppen
De key wordt opgeslagen in EncryptedSharedPreferences (AES256-GCM versleuteld) en overleeft app-herstarts. Bij geen key toont de hoofdapp "Geen sleutel — open Instellingen" en wordt er niet gescant. De key wordt opgeslagen in SharedPreferences en overleeft app-herstarts. Bij geen key toont de hoofdapp "Geen sleutel — open Instellingen" en wordt er niet gescant.
### Hoofdscherm UI ### Hoofdscherm UI
@ -385,7 +385,7 @@ De app gebruikt `Theme.AppCompat.DayNight` in plaats van het eerder geforceerde
- De app gebruikt raw Android USB API (`UsbDeviceConnection.bulkTransfer`) in plaats van een externe serial library. - De app gebruikt raw Android USB API (`UsbDeviceConnection.bulkTransfer`) in plaats van een externe serial library.
- `minSdk` is 26 (Android 8.0). - `minSdk` is 26 (Android 8.0).
- ProgressDialog is vervangen door eigen `AlertDialog` layouts (`dialog_waiting.xml`, `dialog_progress.xml`). - ProgressDialog is vervangen door eigen `AlertDialog` layouts (`dialog_waiting.xml`, `dialog_progress.xml`).
- De HMAC key in de app is opgeslagen via `EncryptedSharedPreferences` (AES256-GCM). - De HMAC key in de app is opgeslagen via `SharedPreferences`.
- De app gebruikt `Theme.AppCompat.DayNight` met Systeem/Licht/Donker thema-selector. - De app gebruikt `Theme.AppCompat.DayNight` met Systeem/Licht/Donker thema-selector.
- `compileSdk`/`targetSdk` blijft op 34 (Android 14) voor maximale compatibiliteit met de doelgroep. - `compileSdk`/`targetSdk` blijft op 34 (Android 14) voor maximale compatibiliteit met de doelgroep.

View file

@ -6,7 +6,6 @@ import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanFilter
@ -34,7 +33,7 @@ class BleRelayManager(
private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b") private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b")
private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8") private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8")
private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9") private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9")
private val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
private const val SCAN_TIMEOUT_MS = 30000L private const val SCAN_TIMEOUT_MS = 30000L
private const val RECONNECT_DELAY_MS = 2000L private const val RECONNECT_DELAY_MS = 2000L
private const val MAX_SCAN_RETRIES = 3 private const val MAX_SCAN_RETRIES = 3
@ -251,12 +250,6 @@ class BleRelayManager(
val storedUuid = deviceUuid val storedUuid = deviceUuid
if (storedUuid != null && value.contentEquals(storedUuid)) { if (storedUuid != null && value.contentEquals(storedUuid)) {
Log.d(TAG, "UUID verified, setting up relay") Log.d(TAG, "UUID verified, setting up relay")
relayCharacteristic?.let { rc ->
gatt.setCharacteristicNotification(rc, true)
val descriptor = rc.getDescriptor(CCCD_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
handler.post { callbacks.onStatusChanged(BleStatus.Connected) } handler.post { callbacks.onStatusChanged(BleStatus.Connected) }
} else { } else {
Log.w(TAG, "UUID mismatch -- disconnecting") Log.w(TAG, "UUID mismatch -- disconnecting")

View file

@ -376,6 +376,7 @@ class SettingsActivity : AppCompatActivity() {
cleanupAction() cleanupAction()
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Flash inner error", e)
progressDialog?.dismiss() progressDialog?.dismiss()
progressDialog = null progressDialog = null
Toast.makeText(this@SettingsActivity, getString(R.string.flash_failed, e.message), Toast.LENGTH_LONG).show() Toast.makeText(this@SettingsActivity, getString(R.string.flash_failed, e.message), Toast.LENGTH_LONG).show()

View file

@ -192,10 +192,9 @@ void setup() {
NimBLEService *pService = pServer->createService(SERVICE_UUID); NimBLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic( pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID, CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY NIMBLE_PROPERTY::WRITE_NR
); );
pCharacteristic->setCallbacks(new MyCharacteristicCallbacks()); pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());
pCharacteristic->setValue("0");
NimBLECharacteristic *pUuidChar = pService->createCharacteristic( NimBLECharacteristic *pUuidChar = pService->createCharacteristic(
UUID_CHAR_UUID, UUID_CHAR_UUID,