From 33ca43eaf137972680ee3742a822296568823a10 Mon Sep 17 00:00:00 2001 From: darken Date: Tue, 3 Jun 2025 18:15:05 +0200 Subject: [PATCH] Use decrypted proximity data if available This change introduces the capability to decrypt the last 16 bytes of the proximity pairing message if an encryption key is available. The decrypted payload provides more precise battery information (0-100% instead of 0-10%) and charging status for each pod and the case. This information is now used by `DualApplePods` to enhance battery reporting accuracy. The `RPAChecker` has also been updated with improved error logging. --- .../darken/capod/monitor/core/PodMonitor.kt | 1 + .../darken/capod/monitor/core/RPAChecker.kt | 16 ++++- .../capod/pods/core/apple/AppleFactory.kt | 8 +++ .../darken/capod/pods/core/apple/ApplePods.kt | 3 + .../capod/pods/core/apple/ApplePodsFactory.kt | 1 + .../capod/pods/core/apple/DualApplePods.kt | 67 ++++++++++++++----- .../pods/core/apple/airpods/AirPodsGen1.kt | 6 +- .../pods/core/apple/airpods/AirPodsGen2.kt | 6 +- .../pods/core/apple/airpods/AirPodsGen3.kt | 6 +- .../pods/core/apple/airpods/AirPodsGen4.kt | 6 +- .../pods/core/apple/airpods/AirPodsGen4Anc.kt | 6 +- .../pods/core/apple/airpods/AirPodsMax.kt | 6 +- .../pods/core/apple/airpods/AirPodsMaxUsbc.kt | 6 +- .../pods/core/apple/airpods/AirPodsPro.kt | 14 +++- .../pods/core/apple/airpods/AirPodsPro2.kt | 6 +- .../core/apple/airpods/AirPodsPro2Usbc.kt | 6 +- .../pods/core/apple/beats/BeatsFitPro.kt | 6 +- .../capod/pods/core/apple/beats/BeatsFlex.kt | 6 +- .../capod/pods/core/apple/beats/BeatsSolo3.kt | 6 +- .../pods/core/apple/beats/BeatsStudio3.kt | 6 +- .../capod/pods/core/apple/beats/BeatsX.kt | 6 +- .../pods/core/apple/beats/PowerBeats3.kt | 6 +- .../pods/core/apple/beats/PowerBeats4.kt | 6 +- .../pods/core/apple/beats/PowerBeatsPro.kt | 6 +- .../pods/core/apple/misc/FakeAirPodsGen1.kt | 6 +- .../pods/core/apple/misc/FakeAirPodsGen2.kt | 6 +- .../pods/core/apple/misc/FakeAirPodsGen3.kt | 6 +- .../pods/core/apple/misc/FakeAirPodsPro.kt | 6 +- .../pods/core/apple/misc/FakeAirPodsPro2.kt | 6 +- .../core/apple/misc/UnknownAppleDevice.kt | 1 + .../core/apple/protocol/MessageDecrypter.kt | 44 ++++++++++++ 31 files changed, 245 insertions(+), 42 deletions(-) create mode 100644 app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt index 9b8dae91..c3792428 100644 --- a/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/PodMonitor.kt @@ -19,6 +19,7 @@ import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.PermissionTool import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodFactory +import eu.darken.capod.monitor.core.RPAChecker import eu.darken.capod.pods.core.apple.protocol.ProximityPairing import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay diff --git a/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt b/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt index 22c8de2a..4db04bc4 100644 --- a/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt +++ b/app-common/src/main/java/eu/darken/capod/monitor/core/RPAChecker.kt @@ -1,7 +1,12 @@ package eu.darken.capod.monitor.core import android.annotation.SuppressLint +import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR +import eu.darken.capod.common.debug.logging.asLog +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag import eu.darken.capod.pods.core.PodDevice +import okio.ByteString.Companion.toByteString import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec import javax.inject.Inject @@ -9,12 +14,15 @@ import javax.inject.Inject class RPAChecker @Inject constructor() { // Resolvable-Private-Address - fun verify(dev: PodDevice, irk: ByteArray): Boolean { + fun verify(dev: PodDevice, irk: ByteArray): Boolean = try { 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) + hash.contentEquals(computedHash) + } catch (e: Exception) { + log(TAG, ERROR) { "Failed to verify RPA\nPod=${dev}\nIRK=${irk.toByteString()}\n${e.asLog()}" } + false } // E function (Encryption function): @@ -34,4 +42,8 @@ class RPAChecker @Inject constructor() { } return e(k, rPadded).copyOfRange(0, 3) } + + companion object { + private val TAG = logTag("Monitor", "PodMonitor", "RPAChecker") + } } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt index dc854507..3751644c 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/AppleFactory.kt @@ -5,9 +5,11 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.WARN import eu.darken.capod.common.debug.logging.asLog import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.logTag +import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol +import eu.darken.capod.pods.core.apple.protocol.MessageDecrypter import eu.darken.capod.pods.core.apple.protocol.ProximityPairing import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -20,6 +22,8 @@ class AppleFactory @Inject constructor( private val proximityPairingDecoder: ProximityPairing.Decoder, private val podFactories: @JvmSuppressWildcards Set>, private val unknownAppleFactory: UnknownAppleDevice.Factory, + private val generalSettings: GeneralSettings, + private val messageDecrypter: MessageDecrypter, ) { private val lock = Mutex() @@ -54,9 +58,13 @@ class AppleFactory @Inject constructor( val factory = podFactories.firstOrNull { it.isResponsible(pm) } + val decryptedData = generalSettings.mainDeviceEncryptionKey.value + ?.let { messageDecrypter.decrypt(pm, it) } + return@withLock (factory ?: unknownAppleFactory).create( scanResult = scanResult, message = pm, + decrypted = decryptedData, ) } diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt index 476b10a6..84d2185e 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePods.kt @@ -10,6 +10,9 @@ interface ApplePods : PodDevice { val proximityMessage: ProximityPairing.Message + val decryptedPayload: UByteArray? + get() = null + // We start counting at the airpods prefix byte val rawPrefix: UByte get() = proximityMessage.data[0] diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt index 5633f809..dde34f60 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt @@ -202,5 +202,6 @@ abstract class ApplePodsFactory(private val tag: String) { abstract fun create( scanResult: BleScanResult, message: ProximityPairing.Message, + decrypted: UByteArray?, ): ApplePods } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt index 3cc17948..c27b38da 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/DualApplePods.kt @@ -4,8 +4,12 @@ import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.isBitSet import eu.darken.capod.common.lowerNibble import eu.darken.capod.common.upperNibble -import eu.darken.capod.pods.core.* +import eu.darken.capod.pods.core.DualPodDevice import eu.darken.capod.pods.core.DualPodDevice.Pod +import eu.darken.capod.pods.core.HasCase +import eu.darken.capod.pods.core.HasChargeDetectionDual +import eu.darken.capod.pods.core.HasDualMicrophone +import eu.darken.capod.pods.core.HasEarDetectionDual interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase, HasDualMicrophone, HasAppleColor { @@ -25,6 +29,10 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE override val batteryLeftPodPercent: Float? get() { + decryptedPayload?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> + val level = (raw and 0x7Fu).toInt() / 100f + if (level <= 1.0f) return level + } val value = when (areValuesFlipped) { true -> rawPodsBattery.upperNibble.toInt() false -> rawPodsBattery.lowerNibble.toInt() @@ -42,6 +50,10 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE override val batteryRightPodPercent: Float? get() { + decryptedPayload?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> + val level = (raw and 0x7Fu).toInt() / 100f + if (level <= 1.0f) return level + } val value = when (areValuesFlipped) { true -> rawPodsBattery.lowerNibble.toInt() false -> rawPodsBattery.upperNibble.toInt() @@ -93,30 +105,55 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE get() = !rawStatus.isBitSet(5) xor isThisPodInThecase override val isLeftPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(0) - true -> rawFlags.isBitSet(1) + get() { + decryptedPayload?.get(if (areValuesFlipped) 2 else 1)?.let { raw -> + val isCharging = (raw and 0x80u).toInt() != 0 + return isCharging + } + return when (areValuesFlipped) { + false -> rawFlags.isBitSet(0) + true -> rawFlags.isBitSet(1) + } } override val isRightPodCharging: Boolean - get() = when (areValuesFlipped) { - false -> rawFlags.isBitSet(1) - true -> rawFlags.isBitSet(0) + get() { + decryptedPayload?.get(if (areValuesFlipped) 1 else 2)?.let { raw -> + val isCharging = (raw and 0x80u).toInt() != 0 + return isCharging + } + return when (areValuesFlipped) { + false -> rawFlags.isBitSet(1) + true -> rawFlags.isBitSet(0) + } } override val batteryCasePercent: Float? - get() = when (val value = rawCaseBattery.toInt()) { - 15 -> null - else -> if (value > 10) { - log { "Case: Above 100% battery: $value" } - 1.0f - } else { - value / 10f + get() { + decryptedPayload?.get(3)?.let { raw -> + val level = (raw and 0x7Fu).toInt() / 100f + if (level <= 1.0f) return level + } + return when (val value = rawCaseBattery.toInt()) { + 15 -> null + else -> if (value > 10) { + log { "Case: Above 100% battery: $value" } + 1.0f + } else { + value / 10f + } } } override val isCaseCharging: Boolean - get() = rawFlags.isBitSet(2) + get() { + decryptedPayload?.get(3)?.let { raw -> + val isCharging = (raw and 0x80u).toInt() != 0 + if (batteryCasePercent != null) return isCharging + } + + return rawFlags.isBitSet(2) + } val caseLidState: LidState get() { diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt index 24deeb8b..4502acb9 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt @@ -41,7 +41,11 @@ data class AirPodsGen1 constructor( } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt index 7fa0c320..1c1f012a 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt @@ -40,7 +40,11 @@ data class AirPodsGen2 constructor( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt index f5701726..eb340d07 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt @@ -40,7 +40,11 @@ data class AirPodsGen3 constructor( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt index f793c673..569653a8 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4.kt @@ -40,7 +40,11 @@ data class AirPodsGen4( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsGen4(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt index 36d25478..aacc1e54 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen4Anc.kt @@ -40,7 +40,11 @@ data class AirPodsGen4Anc( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsGen4Anc(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt index 33c043e0..051957fe 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt @@ -42,7 +42,11 @@ data class AirPodsMax( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsMax(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt index 4b886a2f..77348bc4 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMaxUsbc.kt @@ -42,7 +42,11 @@ data class AirPodsMaxUsbc( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsMaxUsbc(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt index 2f2d98c3..648c2e8f 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt @@ -20,6 +20,7 @@ data class AirPodsPro( override val seenCounter: Int = 1, override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, + override val decryptedPayload: UByteArray?, override val reliability: Float = PodDevice.BASE_CONFIDENCE, private val rssiAverage: Int? = null, private val cachedBatteryPercentage: Float? = null, @@ -59,8 +60,16 @@ data class AirPodsPro( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { - var basic = AirPodsPro(scanResult = scanResult, proximityMessage = message) + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { + var basic = AirPodsPro( + scanResult = scanResult, + proximityMessage = message, + decryptedPayload = decrypted + ) val result = searchHistory(basic) if (result != null) basic = basic.copy(identifier = result.id) @@ -79,7 +88,6 @@ data class AirPodsPro( cachedCaseState = result.getLatestCaseLidState(basic) ) } - } companion object { diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt index 2353b09e..de40e69c 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2.kt @@ -59,7 +59,11 @@ data class AirPodsPro2( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsPro2(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt index 5ee90850..386c8826 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro2Usbc.kt @@ -59,7 +59,11 @@ data class AirPodsPro2Usbc( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = AirPodsPro2Usbc(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt index ba821e35..ce5deb2f 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFitPro.kt @@ -40,7 +40,11 @@ data class BeatsFitPro( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = BeatsFitPro(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt index 02dcc5e2..52a92239 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt @@ -32,7 +32,11 @@ data class BeatsFlex( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = BeatsFlex(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt index ab965acd..2e026583 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt @@ -31,7 +31,11 @@ data class BeatsSolo3( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt index 43d9199a..b0c2f649 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt @@ -29,7 +29,11 @@ data class BeatsStudio3( getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt index 879f1ebb..74c63a00 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt @@ -32,7 +32,11 @@ data class BeatsX( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = BeatsX(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt index 2ab40e8e..6b4c0d4e 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt @@ -32,7 +32,11 @@ data class PowerBeats3( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = PowerBeats3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt index 02b30b13..5f159fb6 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats4.kt @@ -33,7 +33,11 @@ data class PowerBeats4( getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = PowerBeats4(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index aaabfe56..5a8b94a3 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -40,7 +40,11 @@ data class PowerBeatsPro( getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = PowerBeatsPro(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt index a4edb0c0..c07e93e1 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen1.kt @@ -124,7 +124,11 @@ data class FakeAirPodsGen1 constructor( getModelInfo().full == DEVICE_CODE && length == 19 } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = FakeAirPodsGen1(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt index 53d06708..95a396cd 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen2.kt @@ -122,7 +122,11 @@ data class FakeAirPodsGen2 constructor( getModelInfo().full == DEVICE_CODE && length == 19 } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = FakeAirPodsGen2(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt index c6765186..2db60339 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsGen3.kt @@ -124,7 +124,11 @@ data class FakeAirPodsGen3 constructor( getModelInfo().full == DEVICE_CODE && length == 19 } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = FakeAirPodsGen3(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt index 03dee942..06332a45 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro.kt @@ -142,7 +142,11 @@ data class FakeAirPodsPro constructor( getModelInfo().full == DEVICE_CODE && length == 19 } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = FakeAirPodsPro(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt index dc3a5a00..601110ab 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/FakeAirPodsPro2.kt @@ -139,7 +139,11 @@ data class FakeAirPodsPro2 constructor( getModelInfo().full == DEVICE_CODE && length == 19 } - override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods { + override fun create( + scanResult: BleScanResult, + message: ProximityPairing.Message, + decrypted: UByteArray? + ): ApplePods { var basic = FakeAirPodsPro2(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt index 2a6fd530..b58a2d49 100644 --- a/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/misc/UnknownAppleDevice.kt @@ -35,6 +35,7 @@ data class UnknownAppleDevice( override fun create( scanResult: BleScanResult, message: ProximityPairing.Message, + decrypted: UByteArray?, ): ApplePods { var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = message) val result = searchHistory(basic) diff --git a/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt new file mode 100644 index 00000000..88adb82d --- /dev/null +++ b/app-common/src/main/java/eu/darken/capod/pods/core/apple/protocol/MessageDecrypter.kt @@ -0,0 +1,44 @@ +package eu.darken.capod.pods.core.apple.protocol + +import android.annotation.SuppressLint +import eu.darken.capod.common.debug.logging.Logging +import eu.darken.capod.common.debug.logging.asLog +import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag +import okio.ByteString.Companion.toByteString +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec +import javax.inject.Inject + +class MessageDecrypter @Inject constructor() { + + @SuppressLint("GetInstance") + fun decrypt(message: ProximityPairing.Message, key: ByteArray): UByteArray? { + val data = message.data.toByteArray() + if (data.size < 16) return null + + val decryptedData = try { + val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply { + val secretKey = SecretKeySpec(key, "AES") + init(Cipher.DECRYPT_MODE, secretKey) + } + cipher.doFinal(data.copyOfRange(data.size - 16, data.size)) + } catch (e: Exception) { + log(TAG, Logging.Priority.ERROR) { "Failed to decrypt $message with ${key.toByteString()}\n${e.asLog()}" } + null + } + + log( + TAG, + Logging.Priority.VERBOSE + ) { "Decrypted $message with ${key.toByteString()} to ${decryptedData?.toByteString()}" } + + if (decryptedData == null || decryptedData.size != 16) return null + + return decryptedData.toUByteArray() + } + + companion object { + private val TAG = logTag("Pod", "Factory", "Decrypter") + } +} \ No newline at end of file