Add resolvable private address check

This allows verifying the identity of a device if its Identity Resolving Key (IRK) is known.
The IRK can be optionally configured.
This commit is contained in:
darken
2025-06-06 06:03:41 +02:00
committed by Matthias Urhahn
parent fc54e540e8
commit df4ec3501b
2 changed files with 56 additions and 2 deletions
@@ -22,9 +22,17 @@ import eu.darken.capod.pods.core.PodFactory
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okio.ByteString.Companion.toByteString
import java.time.Duration
import java.time.Instant
import javax.inject.Inject
@@ -40,6 +48,7 @@ class PodMonitor @Inject constructor(
private val debugSettings: DebugSettings,
private val podDeviceCache: PodDeviceCache,
private val permissionTool: PermissionTool,
private val rpaChecker: RPAChecker,
) {
private val deviceCache = mutableMapOf<PodDevice.Id, PodDevice>()
@@ -121,6 +130,7 @@ class PodMonitor @Inject constructor(
log(TAG, WARN) { "Using unfiltered scan mode" }
setOf(ScanFilter.Builder().build())
}
else -> ProximityPairing.getBleScanFilter()
}
@@ -189,8 +199,15 @@ class PodMonitor @Inject constructor(
}
private fun determineMainDevice(pods: List<PodDevice>): PodDevice? {
val mainDeviceModel = generalSettings.mainDeviceModel.value
generalSettings.mainDeviceIdentityKey.value
.also { log(TAG) { "Identity-Resolving-Key (IRK): ${it?.toByteString()}" } }
?.let { irkKey -> pods.firstOrNull { pod -> rpaChecker.verify(pod, irkKey) } }
?.let {
log(TAG) { "Main device determined via IRK: $it" }
return it
}
val mainDeviceModel = generalSettings.mainDeviceModel.value
val presorted = sortPodsToInterest(pods).sortedByDescending {
it.model == mainDeviceModel && it.model != PodDevice.Model.UNKNOWN
}
@@ -0,0 +1,37 @@
package eu.darken.capod.monitor.core
import android.annotation.SuppressLint
import eu.darken.capod.pods.core.PodDevice
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject
class RPAChecker @Inject constructor() {
// Resolvable-Private-Address
fun verify(dev: PodDevice, irk: ByteArray): Boolean {
val rpa = dev.address.split(":").map { it.toInt(16).toByte() }.reversed().toByteArray()
val prand = rpa.copyOfRange(3, 6)
val hash = rpa.copyOfRange(0, 3)
val computedHash = ah(irk, prand)
return hash.contentEquals(computedHash)
}
// E function (Encryption function):
@SuppressLint("GetInstance")
private fun e(key: ByteArray, data: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply {
val secretKey = SecretKeySpec(key.reversedArray(), "AES")
init(Cipher.ENCRYPT_MODE, secretKey)
}
return cipher.doFinal(data.reversedArray()).reversedArray()
}
// AH function (Address Hashing function):
private fun ah(k: ByteArray, r: ByteArray): ByteArray {
val rPadded = ByteArray(16).apply {
r.copyInto(this, 0, 0, 3)
}
return e(k, rPadded).copyOfRange(0, 3)
}
}