Replace HMAC-SHA256 with AES-256-GCM + ECDH key exchange for BLE relay commands (issue #2)
- New: src/ecdh_helper.h — ECDH keypair gen, shared secret, pubkey HMAC sign/verify - Server (src/main.cpp): ECDH handshake callback, AES-GCM decrypt, PSK from NVS - Client (src/main.cpp→client.cpp): ECDH after connect, AES-GCM encrypt, PSK from NVS - Android: ECDH (KeyAgreement) + AES-GCM (Cipher) in BleRelayManager.kt - PSK provisioned via USB CDC SET_KEY (SettingsActivity.kt) - PrefsHelper: hmac_key → psk - Bumped versions: server=18, client=4 - NVS namespace: hmac→syl, key:hmac_key→psk - BLE ECDH characteristic: beb5483e-...-f26dc (WRITE_NR|NOTIFY)
This commit is contained in:
parent
8b2dfcbfd0
commit
d6f1062a44
11 changed files with 788 additions and 173 deletions
|
|
@ -21,8 +21,15 @@ import android.util.Log
|
|||
import androidx.core.app.ActivityCompat
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.interfaces.ECPublicKey
|
||||
import java.util.UUID
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyAgreement
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.text.Charsets
|
||||
|
||||
|
|
@ -38,30 +45,50 @@ class BleRelayManager(
|
|||
private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9")
|
||||
private val VERSION_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26db")
|
||||
private val OTA_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26da")
|
||||
private val ECDH_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26dc")
|
||||
private val OTA_CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
private const val SCAN_TIMEOUT_MS = 30000L
|
||||
private const val RECONNECT_DELAY_MS = 2000L
|
||||
private const val MAX_SCAN_RETRIES = 3
|
||||
private const val HMAC_PKT_SIZE = 37
|
||||
private const val HMAC_LEN = 32
|
||||
private const val AES_PKT_SIZE = 29
|
||||
private const val GCM_TAG_LEN = 16
|
||||
private const val GCM_NONCE_LEN = 12
|
||||
private const val AES_KEY_LEN = 32
|
||||
private const val PSK_LEN = 32
|
||||
private const val ECDH_PUBKEY_LEN = 65
|
||||
private const val ECDH_PKT_SIZE = 97
|
||||
private const val BLE_MTU = 512
|
||||
private const val ECDH_TIMEOUT_MS = 5000L
|
||||
}
|
||||
|
||||
private var bluetoothGatt: BluetoothGatt? = null
|
||||
private var relayCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var ecdhCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var isScanning = false
|
||||
private var scanTimedOut = false
|
||||
private var scanTimeoutRetries = 0
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var seq = 1
|
||||
private var hmacKey: ByteArray? = null
|
||||
private var psk: ByteArray? = null
|
||||
@Volatile private var aesKey: ByteArray? = null
|
||||
private var deviceUuid: ByteArray? = null
|
||||
private var triedAddresses = mutableSetOf<String>()
|
||||
private var otaCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var espVersion: String? = null
|
||||
private var appVersion: String = ""
|
||||
private var negotiatedMtu: Int = 23
|
||||
private var ecdhDone = false
|
||||
@Volatile private var ecdhDeferred: CompletableDeferred<Boolean>? = null
|
||||
private var ecdhNotifyBuf: ByteArray = ByteArray(0)
|
||||
|
||||
private class CompletableDeferred<T> {
|
||||
private var result: T? = null
|
||||
private var completed = false
|
||||
@Synchronized fun complete(value: T) { result = value; completed = true }
|
||||
@Synchronized fun isDone(): Boolean = completed
|
||||
@Synchronized fun get(): T? = result
|
||||
}
|
||||
|
||||
interface OtaNotifyCallback {
|
||||
fun onOtaNotify(status: Int, seq: Int)
|
||||
|
|
@ -107,8 +134,8 @@ class BleRelayManager(
|
|||
return
|
||||
}
|
||||
loadState()
|
||||
if (hmacKey == null || deviceUuid == null) {
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||
if (psk == null || deviceUuid == null) {
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||
return
|
||||
}
|
||||
isScanning = true
|
||||
|
|
@ -175,7 +202,7 @@ class BleRelayManager(
|
|||
fun sendRelayCommand(cmd: Byte) {
|
||||
val char = relayCharacteristic ?: return
|
||||
val gatt = bluetoothGatt ?: return
|
||||
val key = hmacKey ?: return
|
||||
val key = aesKey ?: return
|
||||
|
||||
val packet = buildPacket(cmd, key)
|
||||
char.value = packet
|
||||
|
|
@ -256,9 +283,9 @@ class BleRelayManager(
|
|||
@SuppressLint("MissingPermission")
|
||||
override fun onConnectionStateChange(gatt: android.bluetooth.BluetoothGatt, status: Int, newState: Int) {
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
if (hmacKey == null) {
|
||||
Log.w(TAG, "GATT connected but no key -- disconnecting")
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||
if (psk == null) {
|
||||
Log.w(TAG, "GATT connected but no PSK -- disconnecting")
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
|
|
@ -271,6 +298,9 @@ class BleRelayManager(
|
|||
gatt.close()
|
||||
bluetoothGatt = null
|
||||
relayCharacteristic = null
|
||||
ecdhCharacteristic = null
|
||||
aesKey = null
|
||||
ecdhDone = false
|
||||
seq = 1
|
||||
triedAddresses.clear()
|
||||
handler.postDelayed(scanRunnable, RECONNECT_DELAY_MS)
|
||||
|
|
@ -296,6 +326,7 @@ class BleRelayManager(
|
|||
if (service != null) {
|
||||
relayCharacteristic = service.getCharacteristic(CHARACTERISTIC_UUID)
|
||||
otaCharacteristic = service.getCharacteristic(OTA_UUID)
|
||||
ecdhCharacteristic = service.getCharacteristic(ECDH_UUID)
|
||||
val uuidChar = service.getCharacteristic(UUID_CHAR_UUID)
|
||||
val versionChar = service.getCharacteristic(VERSION_UUID)
|
||||
if (relayCharacteristic != null && uuidChar != null) {
|
||||
|
|
@ -316,21 +347,8 @@ class BleRelayManager(
|
|||
if (characteristic.uuid == UUID_CHAR_UUID && status == BluetoothGatt.GATT_SUCCESS) {
|
||||
val storedUuid = deviceUuid
|
||||
if (storedUuid != null && value.contentEquals(storedUuid)) {
|
||||
Log.d(TAG, "UUID verified, reading version...")
|
||||
val service = gatt.getService(SERVICE_UUID)
|
||||
val versionChar = service?.getCharacteristic(VERSION_UUID)
|
||||
if (versionChar != null) {
|
||||
val ok = gatt.readCharacteristic(versionChar)
|
||||
if (!ok) {
|
||||
Log.w(TAG, "readCharacteristic(VERSION) returned false, treating as no version")
|
||||
handler.post { callbacks.onFirmwareVersion("", appVersion) }
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.Connected) }
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "VERSION characteristic not found on ESP (old firmware)")
|
||||
handler.post { callbacks.onFirmwareVersion("", appVersion) }
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.Connected) }
|
||||
}
|
||||
Log.d(TAG, "UUID verified, starting ECDH handshake...")
|
||||
startEcdhHandshake(gatt)
|
||||
} else {
|
||||
Log.w(TAG, "UUID mismatch -- disconnecting")
|
||||
triedAddresses.add(gatt.device.address)
|
||||
|
|
@ -366,35 +384,185 @@ class BleRelayManager(
|
|||
value: ByteArray
|
||||
) {
|
||||
if (characteristic.uuid == OTA_UUID && value.size >= 1) {
|
||||
val status = value[0].toInt() and 0xFF
|
||||
val seq = if (value.size >= 5) {
|
||||
val statusVal = value[0].toInt() and 0xFF
|
||||
val seqVal = if (value.size >= 5) {
|
||||
((value[1].toInt() and 0xFF) shl 24) or
|
||||
((value[2].toInt() and 0xFF) shl 16) or
|
||||
((value[3].toInt() and 0xFF) shl 8) or
|
||||
(value[4].toInt() and 0xFF)
|
||||
} else 0
|
||||
val statusName = when (status) { 0x00 -> "ACK"; 0x01 -> "NAK"; 0x10 -> "COMPLETE"; 0x11 -> "ERROR"; else -> "UNKNOWN(0x${status.toString(16)})" }
|
||||
Log.d(TAG, "OTA notify: status=$statusName seq=$seq raw=${value.joinToString("") { "%02X".format(it) }}")
|
||||
handler.post { otaNotifyCallback?.onOtaNotify(status, seq) }
|
||||
val statusName = when (statusVal) { 0x00 -> "ACK"; 0x01 -> "NAK"; 0x10 -> "COMPLETE"; 0x11 -> "ERROR"; else -> "UNKNOWN(0x${statusVal.toString(16)})" }
|
||||
Log.d(TAG, "OTA notify: status=$statusName seq=$seqVal raw=${value.joinToString("") { "%02X".format(it) }}")
|
||||
handler.post { otaNotifyCallback?.onOtaNotify(statusVal, seqVal) }
|
||||
} else if (characteristic.uuid == ECDH_UUID) {
|
||||
ecdhNotifyBuf = value.copyOf()
|
||||
ecdhDeferred?.complete(true)
|
||||
Log.d(TAG, "ECDH notify received (${value.size} bytes)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadState() {
|
||||
val prefs = PrefsHelper.getPrefs(context)
|
||||
val hexKey = prefs.getString(PrefsHelper.PREF_KEY, null)
|
||||
hmacKey = hexKey?.let { hexStringToBytes(it) }
|
||||
val hexUuid = PrefsHelper.getServerUuid(context)
|
||||
?: prefs.getString(PrefsHelper.PREF_UUID, null)
|
||||
deviceUuid = hexUuid?.let { hexStringToBytes(it) }
|
||||
seq = prefs.getInt(PrefsHelper.PREF_SEQ, 1).coerceAtMost(PrefsHelper.SEQ_MAX)
|
||||
Log.d(TAG, "HMAC key loaded: ${hmacKey?.size ?: "none"} bytes, UUID: ${deviceUuid?.size ?: "none"} bytes, seq=$seq")
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun startEcdhHandshake(gatt: BluetoothGatt) {
|
||||
val char = ecdhCharacteristic
|
||||
if (char == null) {
|
||||
Log.w(TAG, "ECDH characteristic not found")
|
||||
handler.post { callbacks.onStatusChanged(BleStatus.ServiceNotFound) }
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
if (!gatt.setCharacteristicNotification(char, true)) {
|
||||
Log.w(TAG, "ECDH: setCharacteristicNotification failed")
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
val cccd = char.getDescriptor(OTA_CCCD_UUID)
|
||||
if (cccd != null) {
|
||||
cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
gatt.writeDescriptor(cccd)
|
||||
}
|
||||
|
||||
val myPsk = psk ?: run {
|
||||
Log.w(TAG, "ECDH: no PSK")
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val kpg = KeyPairGenerator.getInstance("EC")
|
||||
kpg.initialize(ECGenParameterSpec("secp256r1"))
|
||||
val keyPair = kpg.generateKeyPair()
|
||||
val pubKey = keyPair.public as ECPublicKey
|
||||
|
||||
val pubBytes = encodeEcPubKey(pubKey)
|
||||
if (pubBytes.size != ECDH_PUBKEY_LEN) {
|
||||
Log.e(TAG, "ECDH: pubkey wrong size ${pubBytes.size}")
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
val handshake = ByteArray(ECDH_PKT_SIZE)
|
||||
System.arraycopy(pubBytes, 0, handshake, 0, ECDH_PUBKEY_LEN)
|
||||
val sig = hmacSha256(pubBytes, myPsk)
|
||||
System.arraycopy(sig, 0, handshake, ECDH_PUBKEY_LEN, 32)
|
||||
|
||||
ecdhDeferred = CompletableDeferred()
|
||||
char.value = handshake
|
||||
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
if (!gatt.writeCharacteristic(char)) {
|
||||
Log.w(TAG, "ECDH: write pubkey failed")
|
||||
gatt.disconnect()
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "ECDH: pubkey written, waiting for server...")
|
||||
|
||||
Thread {
|
||||
val start = System.currentTimeMillis()
|
||||
while (!ecdhDeferred!!.isDone() && System.currentTimeMillis() - start < ECDH_TIMEOUT_MS) {
|
||||
Thread.sleep(50)
|
||||
}
|
||||
if (!ecdhDeferred!!.isDone()) {
|
||||
Log.w(TAG, "ECDH: notify timeout")
|
||||
handler.post {
|
||||
callbacks.onStatusChanged(BleStatus.CommError)
|
||||
gatt.disconnect()
|
||||
}
|
||||
return@Thread
|
||||
}
|
||||
|
||||
val resp = ecdhNotifyBuf
|
||||
if (resp.size != ECDH_PKT_SIZE) {
|
||||
Log.w(TAG, "ECDH: bad response size ${resp.size}")
|
||||
handler.post {
|
||||
callbacks.onStatusChanged(BleStatus.CommError)
|
||||
gatt.disconnect()
|
||||
}
|
||||
return@Thread
|
||||
}
|
||||
|
||||
val serverPub = resp.copyOfRange(0, ECDH_PUBKEY_LEN)
|
||||
val serverSig = resp.copyOfRange(ECDH_PUBKEY_LEN, ECDH_PKT_SIZE)
|
||||
val expectedSig = hmacSha256(serverPub, myPsk)
|
||||
if (!serverSig.contentEquals(expectedSig)) {
|
||||
Log.w(TAG, "ECDH: server pubkey HMAC mismatch")
|
||||
handler.post {
|
||||
callbacks.onStatusChanged(BleStatus.CommError)
|
||||
gatt.disconnect()
|
||||
}
|
||||
return@Thread
|
||||
}
|
||||
|
||||
val ka = KeyAgreement.getInstance("ECDH")
|
||||
ka.init(keyPair.private)
|
||||
val spec = java.security.spec.ECPublicKeySpec(
|
||||
java.security.spec.ECPoint(
|
||||
java.math.BigInteger(1, serverPub.copyOfRange(1, 33)),
|
||||
java.math.BigInteger(1, serverPub.copyOfRange(33, 65))
|
||||
),
|
||||
java.security.spec.ECParameterSpec(
|
||||
java.security.spec.EllipticCurve(
|
||||
java.security.spec.ECFieldFp(
|
||||
java.math.BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)
|
||||
),
|
||||
java.math.BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16),
|
||||
java.math.BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16)
|
||||
),
|
||||
java.security.spec.ECPoint(
|
||||
java.math.BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16),
|
||||
java.math.BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)
|
||||
),
|
||||
java.math.BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16),
|
||||
1
|
||||
)
|
||||
)
|
||||
val serverKey = java.security.KeyFactory.getInstance("EC").generatePublic(spec)
|
||||
ka.doPhase(serverKey, true)
|
||||
val shared = ka.generateSecret()
|
||||
|
||||
val md = java.security.MessageDigest.getInstance("SHA-256")
|
||||
aesKey = md.digest(shared)
|
||||
|
||||
ecdhDone = true
|
||||
seq = 1
|
||||
|
||||
handler.post {
|
||||
Log.d(TAG, "ECDH: handshake complete, reading version...")
|
||||
val service = gatt.getService(SERVICE_UUID)
|
||||
val versionChar = service?.getCharacteristic(VERSION_UUID)
|
||||
if (versionChar != null) {
|
||||
val ok = gatt.readCharacteristic(versionChar)
|
||||
if (!ok) {
|
||||
Log.w(TAG, "readCharacteristic(VERSION) returned false")
|
||||
callbacks.onFirmwareVersion("", appVersion)
|
||||
callbacks.onStatusChanged(BleStatus.Connected)
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "VERSION characteristic not found on ESP")
|
||||
callbacks.onFirmwareVersion("", appVersion)
|
||||
callbacks.onStatusChanged(BleStatus.Connected)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "ECDH: exception", e)
|
||||
gatt.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hexStringToBytes(hex: String): ByteArray? {
|
||||
val cleaned = hex.uppercase().filter { it in "0123456789ABCDEF" }
|
||||
if (cleaned.isEmpty() || cleaned.length % 2 != 0) return null
|
||||
return cleaned.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
private fun encodeEcPubKey(pubKey: ECPublicKey): ByteArray {
|
||||
val x = pubKey.w.affineX.toByteArray()
|
||||
val y = pubKey.w.affineY.toByteArray()
|
||||
val xPadded = ByteArray(32)
|
||||
val yPadded = ByteArray(32)
|
||||
System.arraycopy(x, maxOf(0, x.size - 32), xPadded, maxOf(0, 32 - x.size), minOf(x.size, 32))
|
||||
System.arraycopy(y, maxOf(0, y.size - 32), yPadded, maxOf(0, 32 - y.size), minOf(y.size, 32))
|
||||
val out = ByteArray(65)
|
||||
out[0] = 0x04
|
||||
System.arraycopy(xPadded, 0, out, 1, 32)
|
||||
System.arraycopy(yPadded, 0, out, 33, 32)
|
||||
return out
|
||||
}
|
||||
|
||||
private fun hmacSha256(data: ByteArray, key: ByteArray): ByteArray {
|
||||
|
|
@ -403,16 +571,43 @@ class BleRelayManager(
|
|||
return mac.doFinal(data)
|
||||
}
|
||||
|
||||
private fun loadState() {
|
||||
val prefs = PrefsHelper.getPrefs(context)
|
||||
val hexKey = prefs.getString(PrefsHelper.PREF_KEY, null)
|
||||
psk = hexKey?.let { hexStringToBytes(it) }
|
||||
val hexUuid = PrefsHelper.getServerUuid(context)
|
||||
?: prefs.getString(PrefsHelper.PREF_UUID, null)
|
||||
deviceUuid = hexUuid?.let { hexStringToBytes(it) }
|
||||
seq = prefs.getInt(PrefsHelper.PREF_SEQ, 1).coerceAtMost(PrefsHelper.SEQ_MAX)
|
||||
Log.d(TAG, "PSK loaded: ${psk?.size ?: "none"} bytes, UUID: ${deviceUuid?.size ?: "none"} bytes, seq=$seq")
|
||||
}
|
||||
|
||||
private fun hexStringToBytes(hex: String): ByteArray? {
|
||||
val cleaned = hex.uppercase().filter { it in "0123456789ABCDEF" }
|
||||
if (cleaned.isEmpty() || cleaned.length % 2 != 0) return null
|
||||
return cleaned.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
private fun buildPacket(cmd: Byte, key: ByteArray): ByteArray {
|
||||
val seqBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(seq).array()
|
||||
val hmacData = ByteArray(5)
|
||||
hmacData[0] = cmd
|
||||
System.arraycopy(seqBytes, 0, hmacData, 1, 4)
|
||||
val hmac = hmacSha256(hmacData, key)
|
||||
val packet = ByteArray(HMAC_PKT_SIZE)
|
||||
packet[0] = cmd
|
||||
System.arraycopy(seqBytes, 0, packet, 1, 4)
|
||||
System.arraycopy(hmac, 0, packet, 5, HMAC_LEN)
|
||||
val nonceRandom = ByteArray(8)
|
||||
SecureRandom().nextBytes(nonceRandom)
|
||||
|
||||
val nonce = ByteArray(GCM_NONCE_LEN)
|
||||
System.arraycopy(seqBytes, 0, nonce, 0, 4)
|
||||
System.arraycopy(nonceRandom, 0, nonce, 4, 8)
|
||||
|
||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE,
|
||||
SecretKeySpec(key, "AES"),
|
||||
GCMParameterSpec(GCM_TAG_LEN * 8, nonce))
|
||||
|
||||
val cipherOutput = cipher.doFinal(byteArrayOf(cmd))
|
||||
|
||||
val packet = ByteArray(AES_PKT_SIZE)
|
||||
System.arraycopy(seqBytes, 0, packet, 0, 4)
|
||||
System.arraycopy(nonceRandom, 0, packet, 4, 8)
|
||||
System.arraycopy(cipherOutput, 0, packet, 12, cipherOutput.size)
|
||||
return packet
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ object PrefsHelper {
|
|||
|
||||
private const val TAG = "PrefsHelper"
|
||||
const val PREFS_NAME = "relay_prefs"
|
||||
const val PREF_KEY = "hmac_key"
|
||||
const val PREF_KEY = "psk"
|
||||
const val PREF_UUID = "device_uuid"
|
||||
const val PREF_SEQ = "seq_counter"
|
||||
const val PREF_THEME = "theme"
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ class SettingsActivity : AppCompatActivity() {
|
|||
private const val STYLE_SPINNER = 0
|
||||
private const val STYLE_HORIZONTAL = 1
|
||||
private const val USB_READ_BUF_SIZE = 512
|
||||
private const val HMAC_HEX_LEN = 64
|
||||
private const val PSK_HEX_LEN = 64
|
||||
private const val UUID_HEX_LEN = 32
|
||||
private const val UUID_BYTE_COUNT = 16
|
||||
private const val HMAC_BYTE_COUNT = 32
|
||||
private const val PSK_BYTE_COUNT = 32
|
||||
private const val VERSION_TIMEOUT_MS = 3000L
|
||||
private const val REBOOT_WAIT_MS = 5000L
|
||||
private const val REBOOT_POLL_MS = 500L
|
||||
|
|
@ -642,16 +642,16 @@ class SettingsActivity : AppCompatActivity() {
|
|||
Thread.sleep(RESPONSE_WAIT_MS)
|
||||
|
||||
val response = readResponse(connection, epIn, KEY_TIMEOUT_MS)
|
||||
Log.d(TAG, "Attempt $attempt response: ${response.take(300)}")
|
||||
Log.d(TAG, "Attempt $attempt: got ${response.lines().size} line(s)")
|
||||
|
||||
val keyLine = response.lines().firstOrNull { it.startsWith("HMAC_KEY:") }
|
||||
val keyLine = response.lines().firstOrNull { it.startsWith("PSK:") }
|
||||
val uuidLine = response.lines().firstOrNull { it.startsWith("DEVICE_ID:") }
|
||||
val noKey = response.lines().any { it.trim() == "NO_KEY" }
|
||||
|
||||
if (keyLine != null && uuidLine != null) {
|
||||
val hexKey = keyLine.removePrefix("HMAC_KEY:").trim()
|
||||
val hexKey = keyLine.removePrefix("PSK:").trim()
|
||||
val hexUuid = uuidLine.removePrefix("DEVICE_ID:").trim()
|
||||
if (hexKey.length == HMAC_HEX_LEN && hexKey.all { it in "0123456789ABCDEFabcdef" } &&
|
||||
if (hexKey.length == PSK_HEX_LEN && hexKey.all { it in "0123456789ABCDEFabcdef" } &&
|
||||
hexUuid.length == UUID_HEX_LEN && hexUuid.all { it in "0123456789ABCDEFabcdef" }) {
|
||||
val espKeyUpper = hexKey.uppercase()
|
||||
val espUuidUpper = hexUuid.uppercase()
|
||||
|
|
@ -666,7 +666,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
val newKey: String
|
||||
val newUuid: String
|
||||
if (appKey != null) {
|
||||
Log.d(TAG, "ESP has no keys, using existing HMAC key")
|
||||
Log.d(TAG, "ESP has no keys, using existing PSK")
|
||||
newKey = appKey
|
||||
newUuid = if (PrefsHelper.getDeviceMode(this) == "server") {
|
||||
generateRandomHex(UUID_BYTE_COUNT)
|
||||
|
|
@ -757,7 +757,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
val len = connection.bulkTransfer(epIn, buf, buf.size, READ_CHUNK_TIMEOUT_MS)
|
||||
if (len > 0) {
|
||||
sb.append(String(buf, 0, len, StandardCharsets.US_ASCII))
|
||||
if ((sb.contains("HMAC_KEY:") || sb.contains("NO_KEY") || sb.contains("OK") || sb.contains("ERROR")) && sb.contains("\n")) {
|
||||
if ((sb.contains("PSK:") || sb.contains("NO_KEY") || sb.contains("OK") || sb.contains("ERROR")) && sb.contains("\n")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -802,7 +802,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
|
||||
private fun generateRandomHexKey(): String {
|
||||
val random = SecureRandom()
|
||||
val bytes = ByteArray(HMAC_BYTE_COUNT)
|
||||
val bytes = ByteArray(PSK_BYTE_COUNT)
|
||||
random.nextBytes(bytes)
|
||||
return bytes.joinToString("") { "%02X".format(it) }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue