Replace compile-time HMAC key with USB provisioning

ESP firmware:
- Store HMAC key in NVS via Preferences library instead of compile-time
  hmac_key.h
- KEY? responds with HMAC_KEY:<hex> (if provisioned) or NO_KEY
- New SET_KEY:<64-hex> command stores key in NVS and responds OK
- Boot messages updated to reflect provisioning state

Android app:
- UsbKeyReceiver detects NO_KEY response and auto-provisions:
  generates random 32-byte key via SecureRandom, sends SET_KEY,
  waits for OK, saves key to SharedPreferences
- Read loop now breaks on both HMAC_KEY: and NO_KEY responses

Cleanup:
- Removed tools/generate_hmac_key.py and src/hmac_key.h
- Removed hmac_key.h from .gitignore
- README updated for new provisioning flow
This commit is contained in:
eddy 2026-07-26 09:30:21 +02:00
commit 9a84b05b8a
5 changed files with 155 additions and 116 deletions

View file

@ -15,6 +15,7 @@ import android.os.Looper
import android.util.Log
import android.widget.Toast
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
class UsbKeyReceiver : BroadcastReceiver() {
@ -26,6 +27,7 @@ class UsbKeyReceiver : BroadcastReceiver() {
private const val MAX_RETRIES = 5
private const val RETRY_DELAY_MS = 1000L
private const val CDC_DATA_INTERFACE_CLASS = 0x0A
private const val SET_KEY_TIMEOUT_MS = 3000
}
override fun onReceive(context: Context, intent: Intent) {
@ -70,6 +72,13 @@ class UsbKeyReceiver : BroadcastReceiver() {
return usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID }
}
private fun generateRandomHexKey(): String {
val random = SecureRandom()
val bytes = ByteArray(32)
random.nextBytes(bytes)
return bytes.joinToString("") { "%02X".format(it) }
}
private fun requestPermission(context: Context, usbManager: UsbManager, device: UsbDevice) {
val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
@ -186,7 +195,7 @@ class UsbKeyReceiver : BroadcastReceiver() {
val len = connection.bulkTransfer(epIn, buf, buf.size, 500)
if (len > 0) {
sb.append(String(buf, 0, len, StandardCharsets.US_ASCII))
if (sb.contains("HMAC_KEY:") && sb.contains("\n")) {
if ((sb.contains("HMAC_KEY:") || sb.contains("NO_KEY")) && sb.contains("\n")) {
break
}
}
@ -196,6 +205,8 @@ class UsbKeyReceiver : BroadcastReceiver() {
Log.d(TAG, "Serial response: ${response.take(200)}")
val keyLine = response.lines().firstOrNull { it.startsWith("HMAC_KEY:") }
val noKey = response.lines().any { it.trim() == "NO_KEY" }
if (keyLine != null) {
val hexKey = keyLine.removePrefix("HMAC_KEY:").trim()
if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) {
@ -212,6 +223,45 @@ class UsbKeyReceiver : BroadcastReceiver() {
handler.post { Toast.makeText(context, "Ongeldig sleutel formaat", Toast.LENGTH_SHORT).show() }
return
}
} else if (noKey) {
Log.d(TAG, "ESP has no key — generating and provisioning")
val newKey = generateRandomHexKey()
Log.d(TAG, "Generated key: $newKey")
val setKeyCmd = "SET_KEY:$newKey\n".toByteArray(StandardCharsets.US_ASCII)
val written = connection.bulkTransfer(epOut, setKeyCmd, setKeyCmd.size, 1000)
Log.d(TAG, "Sent SET_KEY (${written} bytes)")
Thread.sleep(300)
val okBuf = ByteArray(256)
val okSb = StringBuilder()
val okStart = System.currentTimeMillis()
while (System.currentTimeMillis() - okStart < SET_KEY_TIMEOUT_MS) {
val len = connection.bulkTransfer(epIn, okBuf, okBuf.size, 500)
if (len > 0) {
okSb.append(String(okBuf, 0, len, StandardCharsets.US_ASCII))
if (okSb.contains("OK") && okSb.contains("\n")) break
}
}
Log.d(TAG, "SET_KEY response: ${okSb.toString().take(100)}")
if (okSb.contains("OK")) {
Log.d(TAG, "Key provisioned successfully, saving to prefs")
val prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(SettingsActivity.PREF_KEY, newKey).apply()
handler.post {
Toast.makeText(context, "Nieuwe HMAC sleutel gegenereerd en naar ESP geschreven!", Toast.LENGTH_LONG).show()
}
return
} else {
Log.w(TAG, "SET_KEY failed, no OK response")
connection.close()
connection = null
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
}
}
} else {
Log.w(TAG, "Attempt $attempt: no HMAC_KEY in response, will retry")
connection.close()