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 eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay 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.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import okio.ByteString.Companion.toByteString
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
@@ -40,6 +48,7 @@ class PodMonitor @Inject constructor(
private val debugSettings: DebugSettings, private val debugSettings: DebugSettings,
private val podDeviceCache: PodDeviceCache, private val podDeviceCache: PodDeviceCache,
private val permissionTool: PermissionTool, private val permissionTool: PermissionTool,
private val rpaChecker: RPAChecker,
) { ) {
private val deviceCache = mutableMapOf<PodDevice.Id, PodDevice>() private val deviceCache = mutableMapOf<PodDevice.Id, PodDevice>()
@@ -121,6 +130,7 @@ class PodMonitor @Inject constructor(
log(TAG, WARN) { "Using unfiltered scan mode" } log(TAG, WARN) { "Using unfiltered scan mode" }
setOf(ScanFilter.Builder().build()) setOf(ScanFilter.Builder().build())
} }
else -> ProximityPairing.getBleScanFilter() else -> ProximityPairing.getBleScanFilter()
} }
@@ -189,8 +199,15 @@ class PodMonitor @Inject constructor(
} }
private fun determineMainDevice(pods: List<PodDevice>): PodDevice? { 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 { val presorted = sortPodsToInterest(pods).sortedByDescending {
it.model == mainDeviceModel && it.model != PodDevice.Model.UNKNOWN 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)
}
}