WIP: add Milenage
AUTN is not correct yet
This commit is contained in:
parent
e82826bf5a
commit
b6692a5ac6
7 changed files with 161 additions and 14 deletions
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue