dash eap-aka: complete the session key exchange
Fake pod logs ``` INFO[0021] got CK: daff384a4098571975d136a480a71b36 INFO[0021] got Nonce: 33a0c6dd0a0a0a0a ``` The application logs: ``` [OmnipodDashBleManagerImpl$connect$1.subscribe():139]: CK: daff384a4098571975d136a480a71b36 [OmnipodDashBleManagerImpl$connect$1.subscribe():140]: noncePrefix: 33a0c6dd0a0a0a0a ```
This commit is contained in:
parent
9c42e5749f
commit
e90bda0234
16 changed files with 247 additions and 144 deletions
|
@ -26,7 +26,7 @@ data class Id(val address: ByteArray) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toLong(): Long {
|
fun toLong(): Long {
|
||||||
return ByteBuffer.wrap(address).long
|
return ByteBuffer.wrap(address).int.toLong() and 0xffffffffL
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -76,7 +76,6 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
val discoverer = ServiceDiscoverer(aapsLogger, gatt, bleCommCallbacks)
|
val discoverer = ServiceDiscoverer(aapsLogger, gatt, bleCommCallbacks)
|
||||||
val chars = discoverer.discoverServices()
|
val chars = discoverer.discoverServices()
|
||||||
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks)
|
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gatt, bleCommCallbacks)
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Saying hello to the pod")
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
|
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
|
||||||
bleIO.readyToRead()
|
bleIO.readyToRead()
|
||||||
return bleIO
|
return bleIO
|
||||||
|
@ -137,7 +136,9 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
|
|
||||||
val EapAkaExchanger = EapAkaExchanger(aapsLogger, 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, "CK: ${sessionKeys.ck.toHex()}")
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "noncePrefix: ${sessionKeys.noncePrefix.toHex()}")
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "SQN: ${sessionKeys.sqn.toHex()}")
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) // TODO supply actual pod id
|
emitter.onNext(PodEvent.Connected(ltk.podId.toLong())) // TODO supply actual pod id
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class SessionEstablishmentException(val msg: String) : Exception(msg)
|
|
@ -15,12 +15,12 @@ data class MessagePacket(
|
||||||
val sequenceNumber: Byte,
|
val sequenceNumber: Byte,
|
||||||
val ack: Boolean = false,
|
val ack: Boolean = false,
|
||||||
val ackNumber: Byte = 0.toByte(),
|
val ackNumber: Byte = 0.toByte(),
|
||||||
val eqos: Short = 0.toShort(), // TODO: understand
|
val eqos: Short = 0.toShort(), // TODO: understand. Seems to be set to 1 for commands
|
||||||
val priority: Boolean = false,
|
val priority: Boolean = false,
|
||||||
val lastMessage: Boolean = false,
|
val lastMessage: Boolean = false,
|
||||||
val gateway: Boolean = false,
|
val gateway: Boolean = false,
|
||||||
val sas: Boolean = false, // TODO: understand
|
val sas: Boolean = true, // TODO: understand, seems to always be true
|
||||||
val tfs: Boolean = false, // TODO: understand
|
val tfs: Boolean = false, // TODO: understand, seems to be false
|
||||||
val version: Short = 0.toShort()
|
val version: Short = 0.toShort()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class BleDiscoveredDevice(val scanResult: ScanResult, private val scanRecord: Sc
|
||||||
val podId = hexPodId.toLong(16)
|
val podId = hexPodId.toLong(16)
|
||||||
if (this.podId != podId) {
|
if (this.podId != podId) {
|
||||||
throw DiscoveredInvalidPodException(
|
throw DiscoveredInvalidPodException(
|
||||||
"This is not the POD we are looking for: ${this.podId} . Found: $podId/$hexPodId" ,
|
"This is not the POD we are looking for: ${this.podId} . Found: $podId/$hexPodId",
|
||||||
serviceUUIDs
|
serviceUUIDs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
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.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
enum class EapAkaAttributeType(val type: Byte) {
|
enum class EapAkaAttributeType(val type: Byte) {
|
||||||
AT_RAND(1),
|
AT_RAND(1),
|
||||||
|
@ -16,26 +19,49 @@ enum class EapAkaAttributeType(val type: Byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class EapAkaAttribute(val type: EapAkaAttributeType) {
|
sealed class EapAkaAttribute {
|
||||||
|
|
||||||
abstract fun toByteArray(): ByteArray
|
abstract fun toByteArray(): ByteArray
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val SIZE_MULTIPLIER = 4 // The length for EAP-AKA attributes is a multiple of 4
|
const val SIZE_MULTIPLIER = 4 // The length for EAP-AKA attributes is a multiple of 4
|
||||||
|
|
||||||
|
fun parseAttributes(aapsLogger: AAPSLogger, payload: ByteArray): List<EapAkaAttribute> {
|
||||||
|
var tail = payload
|
||||||
|
val ret = LinkedList<EapAkaAttribute>()
|
||||||
|
while (tail.size > 0) {
|
||||||
|
if (tail.size < 2) {
|
||||||
|
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
val size = SIZE_MULTIPLIER * tail[1].toInt()
|
||||||
|
if (tail.size < size) {
|
||||||
|
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
val type = EapAkaAttributeType.byValue(tail[0])
|
||||||
|
when (type) {
|
||||||
|
EapAkaAttributeType.AT_RES ->
|
||||||
|
ret.add(EapAkaAttributeRes.parse(tail.copyOfRange(2, size)))
|
||||||
|
EapAkaAttributeType.AT_CUSTOM_IV ->
|
||||||
|
ret.add(EapAkaAttributeCustomIV.parse(tail.copyOfRange(2, size)))
|
||||||
|
else ->
|
||||||
|
throw MessageIOException("Could not parse EAP attributes: ${payload.toHex()}. Expecting only AT_RES or CUSTOM_IV attribute types from the POD")
|
||||||
|
}
|
||||||
|
tail = tail.copyOfRange(size, tail.size)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EapAkaAttributeRand(val payload: ByteArray) : EapAkaAttribute(
|
data class EapAkaAttributeRand(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
type = EapAkaAttributeType.AT_RAND
|
|
||||||
) {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(payload.size == 16) { "AT_RAND payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
require(payload.size == 16) { "AT_RAND payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_RAND.type, SIZE, 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -44,16 +70,14 @@ class EapAkaAttributeRand(val payload: ByteArray) : EapAkaAttribute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute(
|
data class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
type = EapAkaAttributeType.AT_AUTN
|
|
||||||
) {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(payload.size == 16) { "AT_AUTN payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
require(payload.size == 16) { "AT_AUTN payload size has to be 16 bytes. Payload: ${payload.toHex()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_AUTN.type, SIZE, 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -62,39 +86,50 @@ class EapAkaAttributeAutn(val payload: ByteArray) : EapAkaAttribute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute(
|
data class EapAkaAttributeRes(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
type = EapAkaAttributeType.AT_AUTN
|
|
||||||
) {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(payload.size == 8) { "AT_RES payload size has to be 8 bytes. Payload: ${payload.toHex()}" }
|
require(payload.size == 8) { "AT_RES payload size has to be 8 bytes. Payload: ${payload.toHex()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(type.type, SIZE, 0, PAYLOAD_SIZE_BITS) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_RES.type, SIZE, 0, PAYLOAD_SIZE_BITS) + payload
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
fun parse(payload: ByteArray): EapAkaAttributeRes {
|
||||||
|
if (payload.size < 2 + 8) {
|
||||||
|
throw MessageIOException("Could not parse RES attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeRes(payload.copyOfRange(2, 2 + 8))
|
||||||
|
}
|
||||||
|
|
||||||
private const val SIZE = (12 / SIZE_MULTIPLIER).toByte() // type, size, len in bits=2, payload=8
|
private const val SIZE = (12 / SIZE_MULTIPLIER).toByte() // type, size, len in bits=2, payload=8
|
||||||
private const val PAYLOAD_SIZE_BITS = 64.toByte() // type, size, 2 reserved bytes, payload
|
private const val PAYLOAD_SIZE_BITS = 64.toByte() // type, size, 2 reserved bytes, payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EapAkaAttributeCustomIV(val payload: ByteArray) : EapAkaAttribute(
|
data class EapAkaAttributeCustomIV(val payload: ByteArray) : EapAkaAttribute() {
|
||||||
type = EapAkaAttributeType.AT_CUSTOM_IV
|
|
||||||
) {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(payload.size == 4) { "CUSTOM_IV payload size has to be 4 bytes. Payload: ${payload.toHex()}" }
|
require(payload.size == 4) { "CUSTOM_IV payload size has to be 4 bytes. Payload: ${payload.toHex()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toByteArray(): ByteArray {
|
override fun toByteArray(): ByteArray {
|
||||||
return byteArrayOf(type.type, SIZE, 0, 0) + payload
|
return byteArrayOf(EapAkaAttributeType.AT_CUSTOM_IV.type, SIZE, 0, 0) + payload
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
fun parse(payload: ByteArray): EapAkaAttributeCustomIV {
|
||||||
|
if (payload.size < 2 + 4) {
|
||||||
|
throw MessageIOException("Could not parse CUSTOM_IV attribute: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
return EapAkaAttributeCustomIV(payload.copyOfRange(2, 2 + 4))
|
||||||
|
}
|
||||||
|
|
||||||
private const val SIZE = (8 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=4
|
private const val SIZE = (8 / SIZE_MULTIPLIER).toByte() // type, size, 2 reserved bytes, payload=4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.logging.LTag
|
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.exceptions.SessionEstablishmentException
|
||||||
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.message.MessageType
|
||||||
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.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import org.spongycastle.util.encoders.Hex
|
import org.spongycastle.util.encoders.Hex
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
class EapAkaExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, private val ltk: PairResult) {
|
class EapAkaExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, private val ltk: PairResult) {
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class EapAkaExchanger(private val aapsLogger: AAPSLogger, private val msgIO: Mes
|
||||||
private var nodeIV = 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 sqn = byteArrayOf(0, 0, 0, 0, 0, 2)
|
||||||
private val milenage = Milenage(aapsLogger, ltk.ltk, sqn)
|
private val milenage = Milenage(aapsLogger, ltk.ltk, sqn)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -32,62 +32,85 @@ class EapAkaExchanger(private val aapsLogger: AAPSLogger, private val msgIO: Mes
|
||||||
|
|
||||||
fun negotiateSessionKeys(): SessionKeys {
|
fun negotiateSessionKeys(): SessionKeys {
|
||||||
// send EAP-AKA challenge
|
// send EAP-AKA challenge
|
||||||
seq++
|
seq++ // TODO: get from pod state. This only works for activating a new pod
|
||||||
var challenge = eapAkaChallenge()
|
var challenge = eapAkaChallenge()
|
||||||
msgIO.sendMesssage(challenge.messagePacket)
|
msgIO.sendMesssage(challenge)
|
||||||
|
|
||||||
// read SPS1
|
|
||||||
val challengeResponse = msgIO.receiveMessage()
|
val challengeResponse = msgIO.receiveMessage()
|
||||||
processChallengeResponse(challengeResponse)
|
processChallengeResponse(challengeResponse)
|
||||||
// now we have all the data to generate: confPod, confPdm, ltk and noncePrefix
|
|
||||||
// TODO: what do we have to answer if challenge response does not validate?
|
// TODO: what do we have to answer if challenge response does not validate?
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
var success = eapSuccess()
|
var success = eapSuccess()
|
||||||
msgIO.sendMesssage(success.messagePacket)
|
msgIO.sendMesssage(success)
|
||||||
|
|
||||||
return SessionKeys()
|
return SessionKeys(
|
||||||
|
milenage.ck,
|
||||||
|
controllerIV + nodeIV,
|
||||||
|
sqn,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun eapAkaChallenge(): EapAkaMessage {
|
private fun eapAkaChallenge(): MessagePacket {
|
||||||
val payload = ByteArray(0)
|
val attributes = arrayOf(
|
||||||
val attributes =
|
|
||||||
arrayOf(
|
|
||||||
// TODO: verify the order or attributes
|
|
||||||
EapAkaAttributeRand(milenage.rand),
|
|
||||||
EapAkaAttributeAutn(milenage.autn),
|
EapAkaAttributeAutn(milenage.autn),
|
||||||
|
EapAkaAttributeRand(milenage.rand),
|
||||||
EapAkaAttributeCustomIV(controllerIV),
|
EapAkaAttributeCustomIV(controllerIV),
|
||||||
)
|
)
|
||||||
|
|
||||||
return EapAkaMessage(
|
val eapMsg = EapMessage(
|
||||||
code = EapCode.REQUEST,
|
code = EapCode.REQUEST,
|
||||||
identifier = 42, // TODO: find what value we need here
|
identifier = 42, // TODO: find what value we need here, it's probably random
|
||||||
attributes = attributes,
|
attributes = attributes
|
||||||
|
)
|
||||||
|
return MessagePacket(
|
||||||
|
type = MessageType.SESSION_ESTABLISHMENT,
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = controllerId,
|
||||||
destination = ltk.podId,
|
destination = ltk.podId,
|
||||||
payload = payload
|
payload = eapMsg.toByteArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
||||||
|
//TODO verify that identifier matches identifer from the Challenge
|
||||||
|
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
||||||
|
if (eapMsg.attributes.size != 2) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got RES message: $eapMsg")
|
||||||
|
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
||||||
|
|
||||||
|
}
|
||||||
|
for (attr in eapMsg.attributes) {
|
||||||
|
when (attr) {
|
||||||
|
is EapAkaAttributeRes ->
|
||||||
|
if (!milenage.res.contentEquals(attr.payload)) {
|
||||||
|
throw SessionEstablishmentException("RES missmatch. Expected: ${milenage.res.toHex()} Actual: ${attr.payload.toHex()} ")
|
||||||
|
}
|
||||||
|
is EapAkaAttributeCustomIV ->
|
||||||
|
nodeIV = attr.payload.copyOfRange(0, IV_SIZE)
|
||||||
|
else ->
|
||||||
|
throw SessionEstablishmentException("Unknown attribute received: ${attr}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun eapSuccess(): EapAkaMessage {
|
private fun eapSuccess(): MessagePacket {
|
||||||
val payload = ByteArray(0)
|
val eapMsg = EapMessage(
|
||||||
return EapAkaMessage(
|
|
||||||
code = EapCode.SUCCESS,
|
code = EapCode.SUCCESS,
|
||||||
attributes = null,
|
attributes = arrayOf(),
|
||||||
identifier = 42, // TODO: find what value we need here
|
identifier = 44, // TODO: find what value we need here
|
||||||
|
)
|
||||||
|
|
||||||
|
return MessagePacket(
|
||||||
|
type = MessageType.SESSION_ESTABLISHMENT,
|
||||||
sequenceNumber = seq,
|
sequenceNumber = seq,
|
||||||
source = controllerId,
|
source = controllerId,
|
||||||
destination = ltk.podId,
|
destination = ltk.podId,
|
||||||
payload = payload
|
payload = eapMsg.toByteArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318")
|
private val MILENAGE_OP = Hex.decode("cdc202d5123e20f62b6d676ac72cb318")
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageType
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
enum class EapCode(val code: Byte) {
|
|
||||||
REQUEST(1),
|
|
||||||
RESPONSE(2),
|
|
||||||
SUCCESS(3),
|
|
||||||
FAILURE(4);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun byValue(value: Byte): EapCode =
|
|
||||||
EapCode.values().firstOrNull { it.code == value }
|
|
||||||
?: throw IllegalArgumentException("Unknown EAP-AKA attribute type: $value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EapAkaMessage(
|
|
||||||
val code: EapCode,
|
|
||||||
val identifier: Byte,
|
|
||||||
val sequenceNumber: Byte,
|
|
||||||
val source: Id,
|
|
||||||
val destination: Id,
|
|
||||||
val payload: ByteArray,
|
|
||||||
val attributes: Array<EapAkaAttribute>?,
|
|
||||||
val messagePacket: MessagePacket = MessagePacket(
|
|
||||||
type = MessageType.SESSION_ESTABLISHMENT,
|
|
||||||
source = source,
|
|
||||||
destination = destination,
|
|
||||||
payload = payload,
|
|
||||||
sequenceNumber = sequenceNumber,
|
|
||||||
sas = true // TODO: understand why this is true for PairMessages
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun toByteArray(): ByteArray {
|
|
||||||
|
|
||||||
val serializedAttributes = attributes?.flatMap { it.toByteArray().asIterable() }
|
|
||||||
val joinedAttributes = serializedAttributes?.toTypedArray()?.toByteArray()
|
|
||||||
|
|
||||||
val attrSize = joinedAttributes?.size ?: 0
|
|
||||||
val totalSize = HEADER_SIZE + attrSize
|
|
||||||
|
|
||||||
var bb = ByteBuffer
|
|
||||||
.allocate(totalSize)
|
|
||||||
.put(code.code)
|
|
||||||
.put(identifier)
|
|
||||||
.put(((totalSize ushr 1) and 0XFF).toByte())
|
|
||||||
.put((totalSize and 0XFF).toByte())
|
|
||||||
.put(AKA_PACKET_TYPE)
|
|
||||||
.put(SUBTYPE_AKA_CHALLENGE)
|
|
||||||
.put(byteArrayOf(0, 0))
|
|
||||||
.put(joinedAttributes ?: ByteArray(0))
|
|
||||||
|
|
||||||
val ret = bb.array()
|
|
||||||
return ret.copyOfRange(0, ret.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val HEADER_SIZE = 8
|
|
||||||
private const val SUBTYPE_AKA_CHALLENGE = 1.toByte()
|
|
||||||
private const val AKA_PACKET_TYPE = 0x17.toByte()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
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.exceptions.MessageIOException
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import okio.ByteString.Companion.toByteString
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
enum class EapCode(val code: Byte) {
|
||||||
|
REQUEST(1),
|
||||||
|
RESPONSE(2),
|
||||||
|
SUCCESS(3),
|
||||||
|
FAILURE(4);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun byValue(value: Byte): EapCode =
|
||||||
|
EapCode.values().firstOrNull { it.code == value }
|
||||||
|
?: throw IllegalArgumentException("Unknown EAP-AKA attribute type: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EapMessage(
|
||||||
|
val code: EapCode,
|
||||||
|
val identifier: Byte,
|
||||||
|
val attributes: Array<EapAkaAttribute>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun toByteArray(): ByteArray {
|
||||||
|
|
||||||
|
val serializedAttributes = attributes.flatMap { it.toByteArray().asIterable() }
|
||||||
|
val joinedAttributes = serializedAttributes.toTypedArray().toByteArray()
|
||||||
|
|
||||||
|
val attrSize = joinedAttributes.size
|
||||||
|
if (attrSize == 0) {
|
||||||
|
return byteArrayOf(code.code, identifier, 0, 4)
|
||||||
|
}
|
||||||
|
val totalSize = HEADER_SIZE + attrSize
|
||||||
|
|
||||||
|
var bb = ByteBuffer
|
||||||
|
.allocate(totalSize)
|
||||||
|
.put(code.code)
|
||||||
|
.put(identifier)
|
||||||
|
.put(((totalSize ushr 1) and 0XFF).toByte())
|
||||||
|
.put((totalSize and 0XFF).toByte())
|
||||||
|
.put(AKA_PACKET_TYPE)
|
||||||
|
.put(SUBTYPE_AKA_CHALLENGE)
|
||||||
|
.put(byteArrayOf(0, 0))
|
||||||
|
.put(joinedAttributes)
|
||||||
|
|
||||||
|
val ret = bb.array()
|
||||||
|
return ret.copyOfRange(0, ret.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val HEADER_SIZE = 8
|
||||||
|
private const val SUBTYPE_AKA_CHALLENGE = 1.toByte()
|
||||||
|
private const val AKA_PACKET_TYPE = 0x17.toByte()
|
||||||
|
|
||||||
|
fun parse(aapsLogger: AAPSLogger, payload: ByteArray): EapMessage {
|
||||||
|
if (payload.size < 4) {
|
||||||
|
throw MessageIOException("Invalid eap payload: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
val totalSize = (payload[2].toInt() shl 1) or payload[3].toInt()
|
||||||
|
if (totalSize > payload.size) {
|
||||||
|
throw MessageIOException("Invalid eap payload. Too short: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
if (payload.size == 4) { // SUCCESS/FAILURE
|
||||||
|
return EapMessage(
|
||||||
|
code = EapCode.byValue(payload[0]),
|
||||||
|
identifier = payload[1],
|
||||||
|
attributes = arrayOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (totalSize > 0 && payload[4] != AKA_PACKET_TYPE) {
|
||||||
|
throw MessageIOException("Invalid eap payload. Expected AKA packet type: ${payload.toHex()}")
|
||||||
|
}
|
||||||
|
val attributesPayload = payload.copyOfRange(8, totalSize)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP attributes: ${attributesPayload.toByteString()}")
|
||||||
|
return EapMessage(
|
||||||
|
code = EapCode.byValue(payload[0]),
|
||||||
|
identifier = payload[1],
|
||||||
|
attributes = EapAkaAttribute.parseAttributes(aapsLogger, attributesPayload).toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,13 @@ import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
class Milenage(private val aapsLogger: AAPSLogger, private val k: ByteArray, val sqn: ByteArray, val _rand: ByteArray? = null) {
|
class Milenage(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val k: ByteArray,
|
||||||
|
val sqn: ByteArray,
|
||||||
|
val _rand: ByteArray? = null
|
||||||
|
) {
|
||||||
|
|
||||||
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()}" }
|
||||||
|
@ -24,7 +30,7 @@ class Milenage(private val aapsLogger: AAPSLogger, private val k: ByteArray, val
|
||||||
val rand = _rand ?: ByteArray(KEY_SIZE)
|
val rand = _rand ?: ByteArray(KEY_SIZE)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (_rand == null ){
|
if (_rand == null) {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
random.nextBytes(rand)
|
random.nextBytes(rand)
|
||||||
}
|
}
|
||||||
|
@ -58,6 +64,7 @@ class Milenage(private val aapsLogger: AAPSLogger, private val k: ByteArray, val
|
||||||
private val sqnAmf = sqn + MILENAGE_AMF + sqn + MILENAGE_AMF
|
private val sqnAmf = sqn + MILENAGE_AMF + sqn + MILENAGE_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)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
for (i in 0..15) {
|
for (i in 0..15) {
|
||||||
macAInput[(i + 8) % 16] = sqnAmfXorOpc[i]
|
macAInput[(i + 8) % 16] = sqnAmfXorOpc[i]
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
class SessionKeys
|
data class SessionKeys(val ck: ByteArray, val noncePrefix: ByteArray, val sqn: ByteArray) {
|
||||||
|
init {
|
||||||
|
require(ck.size == 16) { "CK has to be 16 bytes long" }
|
||||||
|
require(noncePrefix.size == 8) { "noncePrefix has to be 8 bytes long" }
|
||||||
|
require(sqn.size == 6) { "SQN has to be 6 bytes long" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -477,9 +477,12 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateResumeDeliveryButton() {
|
private fun updateResumeDeliveryButton() {
|
||||||
if (podStateManager.isPodRunning && (podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(
|
if (podStateManager.isPodRunning && (
|
||||||
|
podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(
|
||||||
CommandResumeDelivery::class.java
|
CommandResumeDelivery::class.java
|
||||||
))) {
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
buttonBinding.buttonResumeDelivery.visibility = View.VISIBLE
|
buttonBinding.buttonResumeDelivery.visibility = View.VISIBLE
|
||||||
buttonBinding.buttonResumeDelivery.isEnabled = isQueueEmpty()
|
buttonBinding.buttonResumeDelivery.isEnabled = isQueueEmpty()
|
||||||
} else {
|
} else {
|
||||||
|
@ -488,9 +491,12 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSilenceAlertsButton() {
|
private fun updateSilenceAlertsButton() {
|
||||||
if (isAutomaticallySilenceAlertsEnabled() && podStateManager.isPodRunning && (podStateManager.activeAlerts!!.size > 0 || commandQueue.isCustomCommandInQueue(
|
if (isAutomaticallySilenceAlertsEnabled() && podStateManager.isPodRunning && (
|
||||||
|
podStateManager.activeAlerts!!.size > 0 || commandQueue.isCustomCommandInQueue(
|
||||||
CommandAcknowledgeAlerts::class.java
|
CommandAcknowledgeAlerts::class.java
|
||||||
))) {
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
buttonBinding.buttonSilenceAlerts.visibility = View.VISIBLE
|
buttonBinding.buttonSilenceAlerts.visibility = View.VISIBLE
|
||||||
buttonBinding.buttonSilenceAlerts.isEnabled = isQueueEmpty()
|
buttonBinding.buttonSilenceAlerts.isEnabled = isQueueEmpty()
|
||||||
} else {
|
} else {
|
||||||
|
@ -500,9 +506,12 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
||||||
|
|
||||||
private fun updateSuspendDeliveryButton() {
|
private fun updateSuspendDeliveryButton() {
|
||||||
// If the Pod is currently suspended, we show the Resume delivery button instead.
|
// If the Pod is currently suspended, we show the Resume delivery button instead.
|
||||||
if (isSuspendDeliveryButtonEnabled() && podStateManager.isPodRunning && (!podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(
|
if (isSuspendDeliveryButtonEnabled() && podStateManager.isPodRunning && (
|
||||||
|
!podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(
|
||||||
CommandSuspendDelivery::class.java
|
CommandSuspendDelivery::class.java
|
||||||
))) {
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
buttonBinding.buttonSuspendDelivery.visibility = View.VISIBLE
|
buttonBinding.buttonSuspendDelivery.visibility = View.VISIBLE
|
||||||
buttonBinding.buttonSuspendDelivery.isEnabled =
|
buttonBinding.buttonSuspendDelivery.isEnabled =
|
||||||
podStateManager.isPodRunning && !podStateManager.isSuspended && isQueueEmpty()
|
podStateManager.isPodRunning && !podStateManager.isSuspended && isQueueEmpty()
|
||||||
|
|
|
@ -2,9 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import okio.ByteString.Companion.decodeHex
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.spongycastle.util.encoders.Hex
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ class MilenageTest {
|
||||||
val m = Milenage(
|
val m = Milenage(
|
||||||
aapsLogger,
|
aapsLogger,
|
||||||
Hex.decode("c0772899720972a314f557de66d571dd"),
|
Hex.decode("c0772899720972a314f557de66d571dd"),
|
||||||
byteArrayOf(0,0,0,0,0,2),
|
byteArrayOf(0, 0, 0, 0, 0, 2),
|
||||||
Hex.decode("c2cd1248451103bd77a6c7ef88c441ba")
|
Hex.decode("c2cd1248451103bd77a6c7ef88c441ba")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "a40bc6d13861447e")
|
Assert.assertEquals(m.res.toHex(), "a40bc6d13861447e")
|
||||||
|
@ -28,7 +26,7 @@ class MilenageTest {
|
||||||
val m = Milenage(
|
val m = Milenage(
|
||||||
aapsLogger,
|
aapsLogger,
|
||||||
Hex.decode("78411ccad0fd0fb6f381a47fb3335ecb"),
|
Hex.decode("78411ccad0fd0fb6f381a47fb3335ecb"),
|
||||||
byteArrayOf(0,0,0,0,0,2), // 1 + 1
|
byteArrayOf(0, 0, 0, 0, 0, 2), // 1 + 1
|
||||||
Hex.decode("4fc01ac1a94376ae3e052339c07d9e1f")
|
Hex.decode("4fc01ac1a94376ae3e052339c07d9e1f")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "ec549e00fa668a19")
|
Assert.assertEquals(m.res.toHex(), "ec549e00fa668a19")
|
||||||
|
@ -42,7 +40,7 @@ class MilenageTest {
|
||||||
aapsLogger,
|
aapsLogger,
|
||||||
Hex.decode("c0772899720972a314f557de66d571dd"),
|
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),
|
byteArrayOf(0, 0, 0, 0, 0x01, 0x5e),
|
||||||
Hex.decode("d71cc44820e5419f42c62ae97c035988")
|
Hex.decode("d71cc44820e5419f42c62ae97c035988")
|
||||||
)
|
)
|
||||||
Assert.assertEquals(m.res.toHex(), "5f807a379a5c5d30")
|
Assert.assertEquals(m.res.toHex(), "5f807a379a5c5d30")
|
||||||
|
|
Loading…
Reference in a new issue