From b6692a5ac69e291cc31afd9a5f5594daeeab3803 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Tue, 2 Mar 2021 10:44:36 +0100 Subject: [PATCH] WIP: add Milenage AUTN is not correct yet --- .../driver/comm/OmnipodDashBleManagerImpl.kt | 4 +- .../dash/driver/comm/pair/LTKExchanger.kt | 2 +- .../dash/driver/comm/scan/PodScanner.kt | 2 +- .../driver/comm/session/EapAkaExchanger.kt | 45 +++++++-- .../dash/driver/comm/session/Milenage.kt | 94 +++++++++++++++++++ .../dash/driver/comm/session/SessionKeys.kt | 3 +- .../dash/driver/comm/session/MilenageTest.kt | 25 +++++ 7 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Milenage.kt create mode 100644 omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 1c201a8d16..5859f5558b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -133,9 +133,9 @@ class OmnipodDashBleManagerImpl @Inject constructor( aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}") - emitter.onNext(PodEvent.EstablishingSession) + // emitter.onNext(PodEvent.EstablishingSession) - val EapAkaExchanger = EapAkaExchanger(msgIO, ltk) + val EapAkaExchanger = EapAkaExchanger(aapsLogger, msgIO, ltk) val sessionKeys = EapAkaExchanger.negotiateSessionKeys() aapsLogger.info(LTag.PUMPCOMM, "Got session Key: $sessionKeys") diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt index 417a9338f0..018b932308 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt @@ -182,7 +182,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size) + podNonce.copyOfRange(podNonce.size - 4, podNonce.size) + pdmNonce.copyOfRange(pdmNonce.size - 4, pdmNonce.size) - aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, first key: ${firstKey.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "First key for LTK: ${firstKey.toHex()}") val intermediateKey = ByteArray(CMAC_SIZE) aesCmac(firstKey, curveLTK, intermediateKey) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt index 4a9932a4de..b4a32b8827 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt @@ -42,7 +42,7 @@ class PodScanner(private val logger: AAPSLogger, private val bluetoothAdapter: B companion object { const val SCAN_FOR_SERVICE_UUID = "00004024-0000-1000-8000-00805F9B34FB" - const val POD_ID_NOT_ACTIVATED = 0xFFFFFFFFL + const val POD_ID_NOT_ACTIVATED = 0xFFFFFFFEL private const val SCAN_DURATION_MS = 5000 } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaExchanger.kt index 203729e5f5..5515a88398 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapAkaExchanger.kt @@ -1,16 +1,34 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult -import okio.ByteString.Companion.decodeHex +import info.nightscout.androidaps.utils.extensions.toHex +import org.spongycastle.util.encoders.Hex +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec -class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult) { +class EapAkaExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, private val ltk: PairResult) { var seq = ltk.seq + + private val controllerIV = ByteArray(IV_SIZE) + private var nodeIV = ByteArray(IV_SIZE) + private val controllerId = Id.fromInt(OmnipodDashBleManagerImpl.CONTROLLER_ID) + private val sqn = byteArrayOf(0, 0, 0, 0, 0, 1) + private val milenage = Milenage(aapsLogger, ltk.ltk, sqn) + + init { + aapsLogger.debug(LTag.PUMPBTCOMM, "Starting EAP-AKA") + val random = SecureRandom() + random.nextBytes(controllerIV) + } fun negotiateSessionKeys(): SessionKeys { // send EAP-AKA challenge @@ -33,10 +51,18 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult) private fun eapAkaChallenge(): EapAkaMessage { val payload = ByteArray(0) + val attributes = + arrayOf( + // TODO: verify the order or attributes + EapAkaAttributeRand(milenage.rand), + EapAkaAttributeAutn(milenage.autn), + EapAkaAttributeCustomIV(controllerIV), + ) + return EapAkaMessage( code = EapCode.REQUEST, identifier = 42, // TODO: find what value we need here - attributes = null, + attributes = attributes, sequenceNumber = seq, source = controllerId, destination = ltk.podId, @@ -52,7 +78,7 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult) val payload = ByteArray(0) return EapAkaMessage( code = EapCode.SUCCESS, - attributes = null, + attributes = null, identifier = 42, // TODO: find what value we need here sequenceNumber = seq, source = controllerId, @@ -61,10 +87,13 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult) ) } + companion object { - private val MILENAGE_OP = "cdc202d5123e20f62b6d676ac72cb318".decodeHex() - private val MILENAGE_AMF = "b9b9".decodeHex() - + private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318") + private val MILENAGE_AMF = Hex.decode("b9b9") + private const val KEY_SIZE = 16 // 128 bits + private const val IV_SIZE = 4 } -} \ No newline at end of file +} + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Milenage.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Milenage.kt new file mode 100644 index 0000000000..6dac2a4399 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Milenage.kt @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session + +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.utils.extensions.toHex +import org.spongycastle.util.encoders.Hex +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +class Milenage(private val aapsLogger: AAPSLogger, private val k: ByteArray, val sqn: ByteArray, val _rand: ByteArray? = null) { + init { + require(k.size == KEY_SIZE) { "Milenage key has to be $KEY_SIZE bytes long. Received: ${k.toHex()}" } + require(sqn.size == SEQ_SIZE) { "Milenage SEQ has to be $SEQ_SIZE long. Received: ${sqn.toHex()}" } + } + + private val secretKeySpec = SecretKeySpec(k, "AES") + private val cipher: Cipher = Cipher.getInstance("AES/ECB/NoPadding") + + init { + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec) + } + + val rand = _rand ?: ByteArray(KEY_SIZE) + + init { + if (_rand == null ){ + val random = SecureRandom() + random.nextBytes(rand) + } + } + + private val opc = cipher.doFinal(MILENAGE_OP) xor MILENAGE_OP + private val rand_opc_encrypted = cipher.doFinal(rand xor opc) + private val rand_opc_encrypted_opc = rand_opc_encrypted xor opc + + init { + rand_opc_encrypted_opc[15] = (rand_opc_encrypted_opc[15].toInt() xor 1).toByte() + } + + private val res_ak = cipher.doFinal(rand_opc_encrypted_opc) xor opc + + val res = res_ak.copyOfRange(8, 16) + val ak = res_ak.copyOfRange(0, 6) + + private val ck_input = ByteArray(KEY_SIZE) + + init { + for (i in 0..15) { + ck_input[(i + 12) % 16] = (rand_opc_encrypted[i].toInt() xor opc[i].toInt()).toByte() + } + ck_input[15] = (ck_input[15].toInt() xor 2).toByte() + } + + val ck = cipher.doFinal(ck_input) xor opc + + private val macAInput = ByteArray(KEY_SIZE) + + init { + for (i in 0..15) { + macAInput[(i + 8) % 16] = (rand_opc_encrypted[i].toInt() xor opc[i].toInt()).toByte() + } + } + + private val macAFull = cipher.doFinal(macAInput xor opc) + private val macA = macAFull.copyOfRange(0, 8) + val autn = (ak xor sqn) + MILENAGE_AMF + macA + + init { + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage K: ${k.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage RAND: ${rand.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage SEQ: ${sqn.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage CK: ${ck.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AUTN: ${autn.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage RES: ${res.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AK: ${ak.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage OPC: ${opc.toHex()}") + aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage MacA: ${macA.toHex()}") + } + + companion object { + + private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318") + private val MILENAGE_AMF = Hex.decode("b9b9") + private const val KEY_SIZE = 16 + private const val SEQ_SIZE = 6 + } +} + +private infix fun ByteArray.xor(other: ByteArray): ByteArray { + val out = ByteArray(size) + for (i in indices) out[i] = (this[i].toInt() xor other[i].toInt()).toByte() + return out +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt index f6c80965d4..9f7d225e8f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt @@ -1,4 +1,3 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session -class SessionKeys { -} \ No newline at end of file +class SessionKeys diff --git a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt new file mode 100644 index 0000000000..384456b30a --- /dev/null +++ b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/MilenageTest.kt @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session + +import info.nightscout.androidaps.logging.AAPSLoggerTest +import info.nightscout.androidaps.utils.extensions.toHex +import okio.ByteString.Companion.decodeHex +import org.junit.Assert +import org.junit.Assert.* +import org.junit.Test +import org.spongycastle.util.encoders.Hex + +class MilenageTest { + + @Test fun testMilenage() { + val aapsLogger = AAPSLoggerTest() + val m = Milenage( + aapsLogger, + Hex.decode("c0772899720972a314f557de66d571dd"), + byteArrayOf(0,0,0,0,0,1), + Hex.decode("c2cd1248451103bd77a6c7ef88c441ba") + ) + Assert.assertEquals(m.ck.toHex(), "55799fd26664cbf6e476525e2dee52c6") + Assert.assertEquals(m.res.toHex(), "a40bc6d13861447e") + Assert.assertEquals(m.autn.toHex(), "00c55c78e8d3b9b9e935860a7259f6c0") + } +} \ No newline at end of file