WIP: add Milenage

AUTN is not correct yet
This commit is contained in:
Andrei Vereha 2021-03-02 10:44:36 +01:00
parent e82826bf5a
commit b6692a5ac6
7 changed files with 161 additions and 14 deletions

View file

@ -133,9 +133,9 @@ class OmnipodDashBleManagerImpl @Inject constructor(
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${ltk.ltk.toHex()}") 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() val sessionKeys = EapAkaExchanger.negotiateSessionKeys()
aapsLogger.info(LTag.PUMPCOMM, "Got session Key: $sessionKeys") aapsLogger.info(LTag.PUMPCOMM, "Got session Key: $sessionKeys")

View file

@ -182,7 +182,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size) + pdmPublic.copyOfRange(pdmPublic.size - 4, pdmPublic.size) +
podNonce.copyOfRange(podNonce.size - 4, podNonce.size) + podNonce.copyOfRange(podNonce.size - 4, podNonce.size) +
pdmNonce.copyOfRange(pdmNonce.size - 4, pdmNonce.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) val intermediateKey = ByteArray(CMAC_SIZE)
aesCmac(firstKey, curveLTK, intermediateKey) aesCmac(firstKey, curveLTK, intermediateKey)

View file

@ -42,7 +42,7 @@ class PodScanner(private val logger: AAPSLogger, private val bluetoothAdapter: B
companion object { companion object {
const val SCAN_FOR_SERVICE_UUID = "00004024-0000-1000-8000-00805F9B34FB" 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 private const val SCAN_DURATION_MS = 5000
} }
} }

View file

@ -1,16 +1,34 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session 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.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl 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.MessageIO
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket 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 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 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 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 { fun negotiateSessionKeys(): SessionKeys {
// send EAP-AKA challenge // send EAP-AKA challenge
@ -33,10 +51,18 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult)
private fun eapAkaChallenge(): EapAkaMessage { private fun eapAkaChallenge(): EapAkaMessage {
val payload = ByteArray(0) val payload = ByteArray(0)
val attributes =
arrayOf(
// TODO: verify the order or attributes
EapAkaAttributeRand(milenage.rand),
EapAkaAttributeAutn(milenage.autn),
EapAkaAttributeCustomIV(controllerIV),
)
return EapAkaMessage( return EapAkaMessage(
code = EapCode.REQUEST, code = EapCode.REQUEST,
identifier = 42, // TODO: find what value we need here identifier = 42, // TODO: find what value we need here
attributes = null, attributes = attributes,
sequenceNumber = seq, sequenceNumber = seq,
source = controllerId, source = controllerId,
destination = ltk.podId, destination = ltk.podId,
@ -52,7 +78,7 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult)
val payload = ByteArray(0) val payload = ByteArray(0)
return EapAkaMessage( return EapAkaMessage(
code = EapCode.SUCCESS, code = EapCode.SUCCESS,
attributes = null, attributes = null,
identifier = 42, // TODO: find what value we need here identifier = 42, // TODO: find what value we need here
sequenceNumber = seq, sequenceNumber = seq,
source = controllerId, source = controllerId,
@ -61,10 +87,13 @@ class EapAkaExchanger(private val msgIO: MessageIO, private val ltk: PairResult)
) )
} }
companion object { companion object {
private val MILENAGE_OP = "cdc202d5123e20f62b6d676ac72cb318".decodeHex() private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318")
private val MILENAGE_AMF = "b9b9".decodeHex() private val MILENAGE_AMF = Hex.decode("b9b9")
private const val KEY_SIZE = 16 // 128 bits
private const val IV_SIZE = 4
} }
} }

View file

@ -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
}

View file

@ -1,4 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
class SessionKeys { class SessionKeys
}

View file

@ -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")
}
}