implement EAP-AKA resynchronization
This commit is contained in:
parent
c4291113a6
commit
a29874dc6d
16 changed files with 227 additions and 53 deletions
|
@ -17,6 +17,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -142,8 +143,19 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
val podId = uniqueId?.let { Id.fromLong(uniqueId) }
|
val podId = uniqueId?.let { Id.fromLong(uniqueId) }
|
||||||
?: myId.increment() // pod not activated
|
?: myId.increment() // pod not activated
|
||||||
|
|
||||||
val eapSqn = podState.increaseEapAkaSequenceNumber()
|
var eapSqn = podState.increaseEapAkaSequenceNumber()
|
||||||
conn.establishSession(ltk, msgSeq, myId, podId, eapSqn)
|
|
||||||
|
var newSqn = conn.establishSession(ltk, msgSeq, myId, podId, eapSqn)
|
||||||
|
|
||||||
|
if (newSqn != null) {
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "Updating EAP SQN to: $newSqn")
|
||||||
|
podState.eapAkaSequenceNumber = newSqn.toLong()
|
||||||
|
var newSqn = conn.establishSession(ltk, msgSeq, myId, podId, podState.increaseEapAkaSequenceNumber())
|
||||||
|
if (newSqn != null) {
|
||||||
|
throw SessionEstablishmentException("Received resynchronization SQN for the second time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
podState.commitEapAkaSequenceNumber()
|
podState.commitEapAkaSequenceNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class BleCommCallbacks(
|
||||||
private val incomingPackets: IncomingPackets,
|
private val incomingPackets: IncomingPackets,
|
||||||
) : BluetoothGattCallback() {
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
|
private var serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
|
||||||
private var connected: CountDownLatch = CountDownLatch(1)
|
private var connected: CountDownLatch = CountDownLatch(1)
|
||||||
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue(1)
|
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue(1)
|
||||||
|
|
||||||
|
@ -152,7 +152,9 @@ class BleCommCallbacks(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetConnection() {
|
fun resetConnection() {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Reset connection")
|
||||||
connected = CountDownLatch(1)
|
connected = CountDownLatch(1)
|
||||||
|
serviceDiscoveryComplete = CountDownLatch(1)
|
||||||
flushConfirmationQueue()
|
flushConfirmationQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,15 +54,15 @@ open class BleIO(
|
||||||
* @param payload the data to send
|
* @param payload the data to send
|
||||||
*/
|
*/
|
||||||
fun sendAndConfirmPacket(payload: ByteArray): BleSendResult {
|
fun sendAndConfirmPacket(payload: ByteArray): BleSendResult {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending on ${type.name}: ${payload.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending on $type: ${payload.toHex()}")
|
||||||
val set = characteristic.setValue(payload)
|
val set = characteristic.setValue(payload)
|
||||||
if (!set) {
|
if (!set) {
|
||||||
return BleSendErrorSending("Could set setValue on ${type.name}")
|
return BleSendErrorSending("Could set setValue on $type")
|
||||||
}
|
}
|
||||||
bleCommCallbacks.flushConfirmationQueue()
|
bleCommCallbacks.flushConfirmationQueue()
|
||||||
val sent = gatt.writeCharacteristic(characteristic)
|
val sent = gatt.writeCharacteristic(characteristic)
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
return BleSendErrorSending("Could not writeCharacteristic on {$type.name}")
|
return BleSendErrorSending("Could not writeCharacteristic on $type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (
|
return when (
|
||||||
|
@ -111,6 +111,7 @@ open class BleIO(
|
||||||
if (!wrote) {
|
if (!wrote) {
|
||||||
throw ConnectException("Could not enable indications on descriptor")
|
throw ConnectException("Could not enable indications on descriptor")
|
||||||
}
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Enabling indications for $type")
|
||||||
val confirmation = bleCommCallbacks.confirmWrite(
|
val confirmation = bleCommCallbacks.confirmWrite(
|
||||||
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE,
|
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE,
|
||||||
descriptor.uuid.toString(),
|
descriptor.uuid.toString(),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ServiceD
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleSendSuccess
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleSendSuccess
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO
|
||||||
|
@ -87,13 +88,14 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
|
||||||
if (waitForConnection() is NotConnected) {
|
if (waitForConnection() is NotConnected) {
|
||||||
throw FailedToConnectException(podDevice.address)
|
throw FailedToConnectException(podDevice.address)
|
||||||
}
|
}
|
||||||
|
discoverer.discoverServices()
|
||||||
cmdBleIO.hello()
|
cmdBleIO.hello()
|
||||||
cmdBleIO.readyToRead()
|
cmdBleIO.readyToRead()
|
||||||
dataBleIO.readyToRead()
|
dataBleIO.readyToRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting")
|
||||||
bleCommCallbacks.resetConnection()
|
bleCommCallbacks.resetConnection()
|
||||||
gattConnection.disconnect()
|
gattConnection.disconnect()
|
||||||
session = null
|
session = null
|
||||||
|
@ -118,9 +120,13 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
|
||||||
return Connected
|
return Connected
|
||||||
}
|
}
|
||||||
|
|
||||||
fun establishSession(ltk: ByteArray, msgSeq: Byte, myId: Id, podID: Id, eapSqn: ByteArray) {
|
fun establishSession(ltk: ByteArray, msgSeq: Byte, myId: Id, podID: Id, eapSqn: ByteArray): EapSqn? {
|
||||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq)
|
var eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq)
|
||||||
val keys = eapAkaExchanger.negotiateSessionKeys()
|
var keys = eapAkaExchanger.negotiateSessionKeys()
|
||||||
|
return when (keys) {
|
||||||
|
is SessionNegotiationResynchronization ->
|
||||||
|
keys.syncronizedEapSqn
|
||||||
|
is SessionKeys -> {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}")
|
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}")
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}")
|
aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}")
|
||||||
|
@ -132,6 +138,9 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
|
||||||
keys.ck
|
keys.ck
|
||||||
)
|
)
|
||||||
session = Session(aapsLogger, msgIO, myId, podID, sessionKeys = keys, enDecrypt = enDecrypt)
|
session = Session(aapsLogger, msgIO, myId, podID, sessionKeys = keys, enDecrypt = enDecrypt)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -9,6 +9,7 @@ enum class EapAkaAttributeType(val type: Byte) {
|
||||||
AT_RAND(1),
|
AT_RAND(1),
|
||||||
AT_AUTN(2),
|
AT_AUTN(2),
|
||||||
AT_RES(3),
|
AT_RES(3),
|
||||||
|
AT_AUTS(4),
|
||||||
AT_CLIENT_ERROR_CODE(22),
|
AT_CLIENT_ERROR_CODE(22),
|
||||||
AT_CUSTOM_IV(126);
|
AT_CUSTOM_IV(126);
|
||||||
|
|
||||||
|
@ -47,6 +48,8 @@ sealed class EapAkaAttribute {
|
||||||
ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, EapAkaAttributeCustomIV.SIZE)))
|
ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, EapAkaAttributeCustomIV.SIZE)))
|
||||||
EapAkaAttributeType.AT_AUTN ->
|
EapAkaAttributeType.AT_AUTN ->
|
||||||
ret.add(EapAkaAttributeAutn.parse(tail.copyOfRange(2, EapAkaAttributeAutn.SIZE)))
|
ret.add(EapAkaAttributeAutn.parse(tail.copyOfRange(2, EapAkaAttributeAutn.SIZE)))
|
||||||
|
EapAkaAttributeType.AT_AUTS ->
|
||||||
|
ret.add(EapAkaAttributeAuts.parse(tail.copyOfRange(2, EapAkaAttributeAuts.SIZE)))
|
||||||
EapAkaAttributeType.AT_RAND ->
|
EapAkaAttributeType.AT_RAND ->
|
||||||
ret.add(EapAkaAttributeRand.parse(tail.copyOfRange(2, EapAkaAttributeRand.SIZE)))
|
ret.add(EapAkaAttributeRand.parse(tail.copyOfRange(2, EapAkaAttributeRand.SIZE)))
|
||||||
EapAkaAttributeType.AT_CLIENT_ERROR_CODE ->
|
EapAkaAttributeType.AT_CLIENT_ERROR_CODE ->
|
||||||
|
@ -112,6 +115,29 @@ data class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class EapAkaAttributeAuts(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(payload.size == 14) { "AT_AUTS payload size has to be 14 bytes. Payload: ${payload.toHex()}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toByteArray(): ByteArray {
|
||||||
|
return byteArrayOf(EapAkaAttributeType.AT_AUTS.type, (SIZE / SIZE_MULTIPLIER).toByte(), 0, 0) + payload
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun parse(payload: ByteArray): EapAkaAttribute {
|
||||||
|
if (payload.size < SIZE-2) {
|
||||||
|
throw MessageIOException("Could not parse AUTS attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeAuts(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
const val SIZE = 16 // type, size, 2 reserved bytes, payload=16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute() {
|
data class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -23,6 +23,7 @@ enum class EapCode(val code: Byte) {
|
||||||
data class EapMessage(
|
data class EapMessage(
|
||||||
val code: EapCode,
|
val code: EapCode,
|
||||||
val identifier: Byte,
|
val identifier: Byte,
|
||||||
|
val subType: Byte = 0,
|
||||||
val attributes: Array<EapAkaAttribute>
|
val attributes: Array<EapAkaAttribute>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -56,6 +57,8 @@ data class EapMessage(
|
||||||
|
|
||||||
private const val HEADER_SIZE = 8
|
private const val HEADER_SIZE = 8
|
||||||
private const val SUBTYPE_AKA_CHALLENGE = 1.toByte()
|
private const val SUBTYPE_AKA_CHALLENGE = 1.toByte()
|
||||||
|
const val SUBTYPE_SYNCRONIZATION_FAILURE = 4.toByte()
|
||||||
|
|
||||||
private const val AKA_PACKET_TYPE = 0x17.toByte()
|
private const val AKA_PACKET_TYPE = 0x17.toByte()
|
||||||
|
|
||||||
fun parse(aapsLogger: AAPSLogger, payload: ByteArray): EapMessage {
|
fun parse(aapsLogger: AAPSLogger, payload: ByteArray): EapMessage {
|
||||||
|
@ -81,7 +84,8 @@ data class EapMessage(
|
||||||
return EapMessage(
|
return EapMessage(
|
||||||
code = EapCode.byValue(payload[0]),
|
code = EapCode.byValue(payload[0]),
|
||||||
identifier = payload[1],
|
identifier = payload[1],
|
||||||
attributes = EapAkaAttribute.parseAttributes(aapsLogger, attributesPayload).toTypedArray()
|
attributes = EapAkaAttribute.parseAttributes(aapsLogger, attributesPayload).toTypedArray(),
|
||||||
|
subType = payload[5],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class EapSqn(val value: ByteArray) {
|
||||||
|
constructor(v: Long): this(fromLong(v))
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(value.size == SIZE) {"Eap SQN is $SIZE bytes long"}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun increment(): EapSqn {
|
||||||
|
return EapSqn(toLong() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toLong(): Long {
|
||||||
|
return ByteBuffer.wrap(
|
||||||
|
byteArrayOf(0x00, 0x00) +
|
||||||
|
value
|
||||||
|
).long
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SIZE = 6
|
||||||
|
private fun fromLong(v: Long): ByteArray {
|
||||||
|
return ByteBuffer.allocate(8).putLong(v).array().copyOfRange(2, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,12 +12,17 @@ class Milenage(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
private val k: ByteArray,
|
private val k: ByteArray,
|
||||||
val sqn: ByteArray,
|
val sqn: ByteArray,
|
||||||
private val randParam: ByteArray? = null
|
randParam: ByteArray? = null,
|
||||||
|
val auts: ByteArray = ByteArray(AUTS_SIZE),
|
||||||
|
val amf: ByteArray = MILENAGE_AMF,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(k.size == KEY_SIZE) { "Milenage key has to be $KEY_SIZE bytes long. Received: ${k.toHex()}" }
|
require(k.size == KEY_SIZE) { "Milenage key has to be $KEY_SIZE bytes long. Received: ${k.toHex()}" }
|
||||||
require(sqn.size == SQN) { "Milenage SQN has to be $SQN long. Received: ${sqn.toHex()}" }
|
require(sqn.size == SQN) { "Milenage SQN has to be $SQN long. Received: ${sqn.toHex()}" }
|
||||||
|
require(auts.size == AUTS_SIZE) { "Milenage AUTS has to be $AUTS_SIZE long. Received: ${auts.toHex()}"}
|
||||||
|
require(amf.size == MILENAGE_AMF.size) { "Milenage AMF has to be ${MILENAGE_AMF.size} long." +
|
||||||
|
"Received: ${amf.toHex()}"}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val secretKeySpec = SecretKeySpec(k, "AES")
|
private val secretKeySpec = SecretKeySpec(k, "AES")
|
||||||
|
@ -61,7 +66,7 @@ class Milenage(
|
||||||
|
|
||||||
val ck = cipher.doFinal(ckInput) xor opc
|
val ck = cipher.doFinal(ckInput) xor opc
|
||||||
|
|
||||||
private val sqnAmf = sqn + MILENAGE_AMF + sqn + MILENAGE_AMF
|
private val sqnAmf = sqn + amf + sqn + amf
|
||||||
private val sqnAmfXorOpc = sqnAmf xor opc
|
private val sqnAmfXorOpc = sqnAmf xor opc
|
||||||
private val macAInput = ByteArray(KEY_SIZE)
|
private val macAInput = ByteArray(KEY_SIZE)
|
||||||
|
|
||||||
|
@ -73,7 +78,24 @@ class Milenage(
|
||||||
|
|
||||||
private val macAFull = cipher.doFinal(macAInput xor randOpcEncrypted) xor opc
|
private val macAFull = cipher.doFinal(macAInput xor randOpcEncrypted) xor opc
|
||||||
private val macA = macAFull.copyOfRange(0, 8)
|
private val macA = macAFull.copyOfRange(0, 8)
|
||||||
val autn = (ak xor sqn) + MILENAGE_AMF + macA
|
val macS = macAFull.copyOfRange(8, 16)
|
||||||
|
|
||||||
|
val autn = (ak xor sqn) + amf + macA
|
||||||
|
|
||||||
|
// Used for re-synchronisation AUTS = SQN^AK || MAC-S
|
||||||
|
private val akStarInput = ByteArray(KEY_SIZE)
|
||||||
|
init {
|
||||||
|
for (i in 0..15) {
|
||||||
|
akStarInput[(i + 4) % 16] = randOpcEncryptedXorOpc[i]
|
||||||
|
}
|
||||||
|
akStarInput[15] = (akStarInput[15].toInt() xor 8).toByte()
|
||||||
|
}
|
||||||
|
private val akStarFull = cipher.doFinal(akStarInput) xor opc
|
||||||
|
private val akStar = akStarFull.copyOfRange(0, 6)
|
||||||
|
|
||||||
|
private val seqXorAkStar = auts.copyOfRange(0, 6)
|
||||||
|
val synchronizationSqn = akStar xor seqXorAkStar
|
||||||
|
val receivedMacS = auts.copyOfRange(6, 14)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage K: ${k.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage K: ${k.toHex()}")
|
||||||
|
@ -83,15 +105,23 @@ class Milenage(
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AUTN: ${autn.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AUTN: ${autn.toHex()}")
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage RES: ${res.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage RES: ${res.toHex()}")
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AK: ${ak.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AK: ${ak.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AK STAR: ${akStar.toHex()}")
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage OPC: ${opc.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage OPC: ${opc.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage FullMAC: ${macAFull.toHex()}")
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage MacA: ${macA.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage MacA: ${macA.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage MacS: ${macS.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage AUTS: ${auts.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage synchronizationSqn: ${synchronizationSqn.toHex()}")
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Milenage receivedMacS: ${receivedMacS.toHex()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
val RESYNC_AMF = Hex.decode("0000")
|
||||||
private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318")
|
private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318")
|
||||||
private val MILENAGE_AMF = Hex.decode("b9b9")
|
private val MILENAGE_AMF = Hex.decode("b9b9")
|
||||||
private const val KEY_SIZE = 16
|
const val KEY_SIZE = 16
|
||||||
|
const val AUTS_SIZE = 14
|
||||||
private const val SQN = 6
|
private const val SQN = 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import java.util.*
|
||||||
class SessionEstablisher(
|
class SessionEstablisher(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
private val msgIO: MessageIO,
|
private val msgIO: MessageIO,
|
||||||
ltk: ByteArray,
|
private val ltk: ByteArray,
|
||||||
eapSqn: ByteArray,
|
private val eapSqn: ByteArray,
|
||||||
private val myId: Id,
|
private val myId: Id,
|
||||||
private val podId: Id,
|
private val podId: Id,
|
||||||
private var msgSeq: Byte
|
private var msgSeq: Byte
|
||||||
|
@ -37,7 +37,7 @@ class SessionEstablisher(
|
||||||
random.nextBytes(controllerIV)
|
random.nextBytes(controllerIV)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun negotiateSessionKeys(): SessionKeys {
|
fun negotiateSessionKeys(): SessionNegotiationResponse {
|
||||||
msgSeq++
|
msgSeq++
|
||||||
var challenge = eapAkaChallenge()
|
var challenge = eapAkaChallenge()
|
||||||
val sendResult = msgIO.sendMessage(challenge)
|
val sendResult = msgIO.sendMessage(challenge)
|
||||||
|
@ -47,7 +47,13 @@ class SessionEstablisher(
|
||||||
val challengeResponse = msgIO.receiveMessage()
|
val challengeResponse = msgIO.receiveMessage()
|
||||||
?: throw SessionEstablishmentException("Could not establish session")
|
?: throw SessionEstablishmentException("Could not establish session")
|
||||||
|
|
||||||
processChallengeResponse(challengeResponse)
|
val newSqn = processChallengeResponse(challengeResponse)
|
||||||
|
if (newSqn != null) {
|
||||||
|
return SessionNegotiationResynchronization(
|
||||||
|
syncronizedEapSqn = newSqn,
|
||||||
|
msgSequenceNumber = msgSeq
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
msgSeq++
|
msgSeq++
|
||||||
var success = eapSuccess()
|
var success = eapSuccess()
|
||||||
|
@ -84,7 +90,7 @@ class SessionEstablisher(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
private fun processChallengeResponse(challengeResponse: MessagePacket): EapSqn? {
|
||||||
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
||||||
if (eapMsg.identifier != identifier) {
|
if (eapMsg.identifier != identifier) {
|
||||||
aapsLogger.debug(
|
aapsLogger.debug(
|
||||||
|
@ -93,6 +99,39 @@ class SessionEstablisher(
|
||||||
)
|
)
|
||||||
throw SessionEstablishmentException("Received incorrect EAP identifier: ${eapMsg.identifier}")
|
throw SessionEstablishmentException("Received incorrect EAP identifier: ${eapMsg.identifier}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eapMsg.subType == EapMessage.SUBTYPE_SYNCRONIZATION_FAILURE &&
|
||||||
|
eapMsg.attributes.size == 1 &&
|
||||||
|
eapMsg.attributes[0] is EapAkaAttributeAuts
|
||||||
|
) {
|
||||||
|
val auts = eapMsg.attributes[0] as EapAkaAttributeAuts
|
||||||
|
val autsMilenage = Milenage(
|
||||||
|
aapsLogger = aapsLogger,
|
||||||
|
k = ltk,
|
||||||
|
sqn = eapSqn,
|
||||||
|
randParam = milenage.rand,
|
||||||
|
auts = auts.payload
|
||||||
|
)
|
||||||
|
|
||||||
|
val newSqnMilenage = Milenage(
|
||||||
|
aapsLogger = aapsLogger,
|
||||||
|
k = ltk,
|
||||||
|
sqn = autsMilenage.synchronizationSqn,
|
||||||
|
randParam = milenage.rand,
|
||||||
|
auts = auts.payload,
|
||||||
|
amf = Milenage.RESYNC_AMF,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!newSqnMilenage.macS.contentEquals(newSqnMilenage.receivedMacS)) {
|
||||||
|
throw SessionEstablishmentException(
|
||||||
|
"MacS mismatch. " +
|
||||||
|
"Expected: ${newSqnMilenage.macS.toHex()}. " +
|
||||||
|
"Received: ${newSqnMilenage.receivedMacS.toHex()}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return EapSqn(autsMilenage.synchronizationSqn)
|
||||||
|
}
|
||||||
|
|
||||||
if (eapMsg.attributes.size != 2) {
|
if (eapMsg.attributes.size != 2) {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
||||||
if (eapMsg.attributes.size == 1 && eapMsg.attributes[0] is EapAkaAttributeClientErrorCode) {
|
if (eapMsg.attributes.size == 1 && eapMsg.attributes[0] is EapAkaAttributeClientErrorCode) {
|
||||||
|
@ -104,11 +143,12 @@ class SessionEstablisher(
|
||||||
}
|
}
|
||||||
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
||||||
}
|
}
|
||||||
|
|
||||||
for (attr in eapMsg.attributes) {
|
for (attr in eapMsg.attributes) {
|
||||||
when (attr) {
|
when (attr) {
|
||||||
is EapAkaAttributeRes ->
|
is EapAkaAttributeRes ->
|
||||||
if (!milenage.res.contentEquals(attr.payload)) {
|
if (!milenage.res.contentEquals(attr.payload)) {
|
||||||
throw SessionEstablishmentException("RES missmatch. Expected: ${milenage.res.toHex()} Actual: ${attr.payload.toHex()} ")
|
throw SessionEstablishmentException("RES mismatch. Expected: ${milenage.res.toHex()} Actual: ${attr.payload.toHex()} ")
|
||||||
}
|
}
|
||||||
is EapAkaAttributeCustomIV ->
|
is EapAkaAttributeCustomIV ->
|
||||||
nodeIV = attr.payload.copyOfRange(0, IV_SIZE)
|
nodeIV = attr.payload.copyOfRange(0, IV_SIZE)
|
||||||
|
@ -116,6 +156,7 @@ class SessionEstablisher(
|
||||||
throw SessionEstablishmentException("Unknown attribute received: $attr")
|
throw SessionEstablishmentException("Unknown attribute received: $attr")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun eapSuccess(): MessagePacket {
|
private fun eapSuccess(): MessagePacket {
|
||||||
|
|
|
@ -2,8 +2,13 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce
|
||||||
|
|
||||||
data class SessionKeys(val ck: ByteArray, val nonce: Nonce, var msgSequenceNumber: Byte) {
|
sealed class SessionNegotiationResponse
|
||||||
|
|
||||||
|
data class SessionKeys(val ck: ByteArray, val nonce: Nonce, var msgSequenceNumber: Byte):SessionNegotiationResponse() {
|
||||||
init {
|
init {
|
||||||
require(ck.size == 16) { "CK has to be 16 bytes long" }
|
require(ck.size == 16) { "CK has to be 16 bytes long" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SessionNegotiationResynchronization(val syncronizedEapSqn: EapSqn?, val msgSequenceNumber: Byte)
|
||||||
|
:SessionNegotiationResponse()
|
|
@ -8,6 +8,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.EventOmnipodDashPump
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
||||||
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.pair.PairResult
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.EapSqn
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
|
||||||
|
@ -173,10 +174,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
||||||
|
|
||||||
override fun increaseEapAkaSequenceNumber(): ByteArray {
|
override fun increaseEapAkaSequenceNumber(): ByteArray {
|
||||||
podState.eapAkaSequenceNumber++
|
podState.eapAkaSequenceNumber++
|
||||||
return ByteBuffer.allocate(8)
|
return EapSqn(podState.eapAkaSequenceNumber).value
|
||||||
.putLong(podState.eapAkaSequenceNumber)
|
|
||||||
.array()
|
|
||||||
.copyOfRange(2, 8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun commitEapAkaSequenceNumber() {
|
override fun commitEapAkaSequenceNumber() {
|
||||||
|
|
|
@ -117,8 +117,7 @@ class DashPodManagementActivity : NoSplashAppCompatActivity() {
|
||||||
// Only show the discard button to reset a cached unique ID before the unique ID has actually been set
|
// Only show the discard button to reset a cached unique ID before the unique ID has actually been set
|
||||||
// Otherwise, users should use the Deactivate Pod Wizard. In case proper deactivation fails,
|
// Otherwise, users should use the Deactivate Pod Wizard. In case proper deactivation fails,
|
||||||
// they will get an option to discard the Pod there
|
// they will get an option to discard the Pod there
|
||||||
val discardButtonEnabled =
|
val discardButtonEnabled = true
|
||||||
podStateManager.uniqueId != null && podStateManager.activationProgress.isBefore(ActivationProgress.SET_UNIQUE_ID)
|
|
||||||
binding.buttonDiscardPod.visibility = discardButtonEnabled.toVisibility()
|
binding.buttonDiscardPod.visibility = discardButtonEnabled.toVisibility()
|
||||||
|
|
||||||
binding.buttonActivatePod.isEnabled = podStateManager.activationProgress.isBefore(ActivationProgress.COMPLETED)
|
binding.buttonActivatePod.isEnabled = podStateManager.activationProgress.isBefore(ActivationProgress.COMPLETED)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
||||||
|
|
||||||
import com.google.crypto.tink.subtle.Hex
|
import com.google.crypto.tink.subtle.Hex
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.PayloadJoiner
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.PayloadJoiner
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.PayloadJoiner
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.PayloadJoiner
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.PayloadSplitter
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
||||||
|
|
||||||
import com.google.crypto.tink.subtle.Hex
|
import com.google.crypto.tink.subtle.Hex
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.PayloadSplitter
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
|
|
|
@ -11,10 +11,10 @@ class MilenageTest {
|
||||||
@Test fun testMilenage() {
|
@Test fun testMilenage() {
|
||||||
val aapsLogger = AAPSLoggerTest()
|
val aapsLogger = AAPSLoggerTest()
|
||||||
val m = Milenage(
|
val m = Milenage(
|
||||||
aapsLogger,
|
aapsLogger = aapsLogger,
|
||||||
Hex.decode("c0772899720972a314f557de66d571dd"),
|
k = Hex.decode("c0772899720972a314f557de66d571dd"),
|
||||||
byteArrayOf(0, 0, 0, 0, 0, 2),
|
sqn = byteArrayOf(0, 0, 0, 0, 0, 2),
|
||||||
Hex.decode("c2cd1248451103bd77a6c7ef88c441ba")
|
randParam = Hex.decode("c2cd1248451103bd77a6c7ef88c441ba")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "a40bc6d13861447e")
|
Assert.assertEquals(m.res.toHex(), "a40bc6d13861447e")
|
||||||
Assert.assertEquals(m.ck.toHex(), "55799fd26664cbf6e476525e2dee52c6")
|
Assert.assertEquals(m.ck.toHex(), "55799fd26664cbf6e476525e2dee52c6")
|
||||||
|
@ -24,10 +24,10 @@ class MilenageTest {
|
||||||
@Test fun testMilenage2() {
|
@Test fun testMilenage2() {
|
||||||
val aapsLogger = AAPSLoggerTest()
|
val aapsLogger = AAPSLoggerTest()
|
||||||
val m = Milenage(
|
val m = Milenage(
|
||||||
aapsLogger,
|
aapsLogger = aapsLogger,
|
||||||
Hex.decode("78411ccad0fd0fb6f381a47fb3335ecb"),
|
k = Hex.decode("78411ccad0fd0fb6f381a47fb3335ecb"),
|
||||||
byteArrayOf(0, 0, 0, 0, 0, 2), // 1 + 1
|
sqn = byteArrayOf(0, 0, 0, 0, 0, 2), // 1 + 1
|
||||||
Hex.decode("4fc01ac1a94376ae3e052339c07d9e1f")
|
randParam = Hex.decode("4fc01ac1a94376ae3e052339c07d9e1f")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "ec549e00fa668a19")
|
Assert.assertEquals(m.res.toHex(), "ec549e00fa668a19")
|
||||||
Assert.assertEquals(m.ck.toHex(), "ee3dac761fe358a9f476cc5ee81aa3e9")
|
Assert.assertEquals(m.ck.toHex(), "ee3dac761fe358a9f476cc5ee81aa3e9")
|
||||||
|
@ -37,14 +37,28 @@ class MilenageTest {
|
||||||
@Test fun testMilenageIncrementedSQN() {
|
@Test fun testMilenageIncrementedSQN() {
|
||||||
val aapsLogger = AAPSLoggerTest()
|
val aapsLogger = AAPSLoggerTest()
|
||||||
val m = Milenage(
|
val m = Milenage(
|
||||||
aapsLogger,
|
aapsLogger = aapsLogger,
|
||||||
Hex.decode("c0772899720972a314f557de66d571dd"),
|
k = Hex.decode("c0772899720972a314f557de66d571dd"),
|
||||||
// byteArrayOf(0,0,0,0,0x01,0x5d), this is in logs. SQN has to be incremented.
|
// byteArrayOf(0,0,0,0,0x01,0x5d), this is in logs. SQN has to be incremented.
|
||||||
byteArrayOf(0, 0, 0, 0, 0x01, 0x5e),
|
sqn = byteArrayOf(0, 0, 0, 0, 0x01, 0x5e),
|
||||||
Hex.decode("d71cc44820e5419f42c62ae97c035988")
|
randParam = Hex.decode("d71cc44820e5419f42c62ae97c035988")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "5f807a379a5c5d30")
|
Assert.assertEquals(m.res.toHex(), "5f807a379a5c5d30")
|
||||||
Assert.assertEquals(m.ck.toHex(), "8dd4b3ceb849a01766e37f9d86045c39")
|
Assert.assertEquals(m.ck.toHex(), "8dd4b3ceb849a01766e37f9d86045c39")
|
||||||
Assert.assertEquals(m.autn.toHex(), "0e0264d056fcb9b9752227365a090955")
|
Assert.assertEquals(m.autn.toHex(), "0e0264d056fcb9b9752227365a090955")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun testMileageSynchronization() {
|
||||||
|
val aapsLogger = AAPSLoggerTest()
|
||||||
|
val m = Milenage(
|
||||||
|
aapsLogger = aapsLogger,
|
||||||
|
k = Hex.decode("689b860fde3331dd7e1671ad39985e3b"),
|
||||||
|
sqn = byteArrayOf(0, 0, 0, 0, 0, 8), // 1 + 1
|
||||||
|
auts = Hex.decode("84ff173947a67567985de71e4890"),
|
||||||
|
randParam = Hex.decode("396707041ca3a5931fc0e52d2d7b9ecf"),
|
||||||
|
amf = byteArrayOf(0, 0),
|
||||||
|
)
|
||||||
|
Assert.assertEquals(m.receivedMacS.toHex(), m.macS.toHex())
|
||||||
|
Assert.assertEquals(m.sqn.toHex(), m.synchronizationSqn.toHex())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue