mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user