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