commit
2892d385ab
4 changed files with 174 additions and 114 deletions
|
@ -0,0 +1,128 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
||||
|
||||
import com.google.crypto.tink.subtle.X25519
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import org.spongycastle.crypto.engines.AESEngine
|
||||
import org.spongycastle.crypto.macs.CMac
|
||||
import org.spongycastle.crypto.params.KeyParameter
|
||||
import java.security.SecureRandom
|
||||
|
||||
class KeyExchange(private val aapsLogger: AAPSLogger,
|
||||
var pdmPrivate: ByteArray = X25519.generatePrivateKey(),
|
||||
val pdmNonce: ByteArray = ByteArray(NONCE_SIZE)
|
||||
) {
|
||||
val pdmPublic = X25519.publicFromPrivate(pdmPrivate)
|
||||
|
||||
var podPublic = ByteArray(PUBLIC_KEY_SIZE)
|
||||
var podNonce = ByteArray(NONCE_SIZE)
|
||||
|
||||
val podConf = ByteArray(CMAC_SIZE)
|
||||
val pdmConf = ByteArray(CMAC_SIZE)
|
||||
|
||||
var ltk = ByteArray(CMAC_SIZE)
|
||||
|
||||
init {
|
||||
if (pdmNonce.all { it == 0.toByte() }) {
|
||||
// pdmNonce is in the constructor for tests
|
||||
val random = SecureRandom()
|
||||
random.nextBytes(pdmNonce)
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePodPublicData(payload: ByteArray) {
|
||||
if (payload.size != PUBLIC_KEY_SIZE + NONCE_SIZE) {
|
||||
throw MessageIOException("Invalid payload size")
|
||||
}
|
||||
podPublic = payload.copyOfRange(0, PUBLIC_KEY_SIZE)
|
||||
podNonce = payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE + NONCE_SIZE)
|
||||
generateKeys()
|
||||
}
|
||||
|
||||
fun validatePodConf(payload: ByteArray) {
|
||||
if (!podConf.contentEquals(payload)) {
|
||||
aapsLogger.warn(
|
||||
LTag.PUMPBTCOMM,
|
||||
"Received invalid podConf. Expected: ${podConf.toHex()}. Got: ${payload.toHex()}"
|
||||
)
|
||||
throw MessageIOException("Invalid podConf value received")
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateKeys() {
|
||||
val curveLTK = X25519.computeSharedSecret(pdmPrivate, podPublic)
|
||||
|
||||
val firstKey = podPublic.copyOfRange(podPublic.size - 4, podPublic.size) +
|
||||
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, "First key for LTK: ${firstKey.toHex()}")
|
||||
|
||||
val intermediateKey = ByteArray(CMAC_SIZE)
|
||||
aesCmac(firstKey, curveLTK, intermediateKey)
|
||||
|
||||
val ltkData = byteArrayOf(2.toByte()) +
|
||||
INTERMEDIARY_KEY_MAGIC_STRING +
|
||||
podNonce +
|
||||
pdmNonce +
|
||||
byteArrayOf(0.toByte(), 1.toByte())
|
||||
aesCmac(intermediateKey, ltkData, ltk)
|
||||
|
||||
val confData = byteArrayOf(1.toByte()) +
|
||||
INTERMEDIARY_KEY_MAGIC_STRING +
|
||||
podNonce +
|
||||
pdmNonce +
|
||||
byteArrayOf(0.toByte(), 1.toByte())
|
||||
val confKey = ByteArray(CMAC_SIZE)
|
||||
aesCmac(intermediateKey, confData, confKey)
|
||||
|
||||
val pdmConfData = PDM_CONF_MAGIC_PREFIX +
|
||||
pdmNonce +
|
||||
podNonce
|
||||
aesCmac(confKey, pdmConfData, pdmConf)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmConf: ${pdmConf.toHex()}")
|
||||
|
||||
val podConfData = POD_CONF_MAGIC_PREFIX +
|
||||
podNonce +
|
||||
pdmNonce
|
||||
aesCmac(confKey, podConfData, podConf)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podConf: ${podConf.toHex()}")
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPrivate: ${pdmPrivate.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPublic: ${pdmPublic.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podPublic: ${podPublic.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmNonce: ${pdmNonce.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podNonce: ${podNonce.toHex()}")
|
||||
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Intermediate key: ${intermediateKey.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "LTK: ${ltk.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Conf KEY: ${confKey.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PUBLIC_KEY_SIZE = 32
|
||||
private const val NONCE_SIZE = 16
|
||||
|
||||
const val CMAC_SIZE = 16
|
||||
|
||||
private val INTERMEDIARY_KEY_MAGIC_STRING = "TWIt".toByteArray()
|
||||
private val PDM_CONF_MAGIC_PREFIX = "KC_2_U".toByteArray()
|
||||
private val POD_CONF_MAGIC_PREFIX = "KC_2_V".toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
private fun aesCmac(key: ByteArray, data: ByteArray, result: ByteArray) {
|
||||
val aesEngine = AESEngine()
|
||||
val mac = CMac(aesEngine)
|
||||
mac.init(KeyParameter(key))
|
||||
mac.update(data, 0, data.size)
|
||||
mac.doFinal(result, 0)
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
||||
|
||||
import com.google.crypto.tink.subtle.X25519
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||
|
@ -12,27 +10,17 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
||||
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import org.spongycastle.crypto.engines.AESEngine
|
||||
import org.spongycastle.crypto.macs.CMac
|
||||
import org.spongycastle.crypto.params.KeyParameter
|
||||
import java.security.SecureRandom
|
||||
|
||||
internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgIO: MessageIO, val myId: Id, val podId: Id, val podAddress: Id) {
|
||||
internal class LTKExchanger(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
private val msgIO: MessageIO,
|
||||
val myId: Id,
|
||||
val podId: Id,
|
||||
val podAddress: Id
|
||||
) {
|
||||
|
||||
private val pdmPrivate = X25519.generatePrivateKey()
|
||||
private val pdmPublic = X25519.publicFromPrivate(pdmPrivate)
|
||||
private var podPublic = ByteArray(PUBLIC_KEY_SIZE)
|
||||
private var podNonce = ByteArray(NONCE_SIZE)
|
||||
private val pdmNonce = ByteArray(NONCE_SIZE)
|
||||
private val pdmConf = ByteArray(CMAC_SIZE)
|
||||
private val podConf = ByteArray(CMAC_SIZE)
|
||||
private val keyExchange = KeyExchange(aapsLogger)
|
||||
private var seq: Byte = 1
|
||||
private var ltk = ByteArray(CMAC_SIZE)
|
||||
|
||||
init {
|
||||
val random = SecureRandom()
|
||||
random.nextBytes(pdmNonce)
|
||||
}
|
||||
|
||||
fun negotiateLTK(): PairResult {
|
||||
// send SP1, SP2
|
||||
|
@ -48,7 +36,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
val podSps1 = msgIO.receiveMessage()
|
||||
processSps1FromPod(podSps1)
|
||||
// now we have all the data to generate: confPod, confPdm, ltk and noncePrefix
|
||||
generateKeys()
|
||||
|
||||
seq++
|
||||
// send SPS2
|
||||
val sps2 = sps2()
|
||||
|
@ -66,13 +54,13 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
// TODO: failing to read or validate p0 will lead to undefined state
|
||||
// It could be that:
|
||||
// - the pod answered with p0 and we did not receive/could not process the answer
|
||||
// - the pod answered with some sort of error
|
||||
// - the pod answered with some sort of error. This is very unlikely, because we already received(and validated) SPS2 from the pod
|
||||
// But if sps2 conf value is incorrect, then we would probablysee this when receiving the pod podSps2(to test)
|
||||
val p0 = msgIO.receiveMessage()
|
||||
validateP0(p0)
|
||||
|
||||
return PairResult(
|
||||
ltk = ltk,
|
||||
ltk = keyExchange.ltk,
|
||||
msgSeq = seq
|
||||
)
|
||||
}
|
||||
|
@ -93,7 +81,7 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
private fun sps1(): PairMessage {
|
||||
val payload = StringLengthPrefixEncoding.formatKeys(
|
||||
arrayOf("SPS1="),
|
||||
arrayOf(pdmPublic + pdmNonce)
|
||||
arrayOf(keyExchange.pdmPublic + keyExchange.pdmNonce)
|
||||
)
|
||||
return PairMessage(
|
||||
sequenceNumber = seq,
|
||||
|
@ -107,17 +95,13 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}")
|
||||
|
||||
val payload = parseKeys(arrayOf(SPS1), msg.payload)[0]
|
||||
if (payload.size != 48) {
|
||||
throw MessageIOException("Invalid payload size")
|
||||
}
|
||||
podPublic = payload.copyOfRange(0, PUBLIC_KEY_SIZE)
|
||||
podNonce = payload.copyOfRange(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE + NONCE_SIZE)
|
||||
keyExchange.updatePodPublicData(payload)
|
||||
}
|
||||
|
||||
private fun sps2(): PairMessage {
|
||||
val payload = StringLengthPrefixEncoding.formatKeys(
|
||||
arrayOf(SPS2),
|
||||
arrayOf(pdmConf)
|
||||
arrayOf(keyExchange.pdmConf)
|
||||
)
|
||||
return PairMessage(
|
||||
sequenceNumber = seq,
|
||||
|
@ -133,16 +117,10 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
val payload = parseKeys(arrayOf(SPS2), msg.payload)[0]
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "SPS2 payload from pod: ${payload.toHex()}")
|
||||
|
||||
if (payload.size != CMAC_SIZE) {
|
||||
if (payload.size != KeyExchange.CMAC_SIZE) {
|
||||
throw MessageIOException("Invalid payload size")
|
||||
}
|
||||
if (!podConf.contentEquals(payload)) {
|
||||
aapsLogger.warn(
|
||||
LTag.PUMPBTCOMM,
|
||||
"Received invalid podConf. Expected: ${podConf.toHex()}. Got: ${payload.toHex()}"
|
||||
)
|
||||
throw MessageIOException("Invalid podConf value received")
|
||||
}
|
||||
keyExchange.validatePodConf(payload)
|
||||
}
|
||||
|
||||
private fun sp2(): ByteArray {
|
||||
|
@ -171,74 +149,10 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
}
|
||||
}
|
||||
|
||||
fun generateKeys() {
|
||||
val curveLTK = X25519.computeSharedSecret(pdmPrivate, podPublic)
|
||||
|
||||
val firstKey = podPublic.copyOfRange(podPublic.size - 4, podPublic.size) +
|
||||
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, "First key for LTK: ${firstKey.toHex()}")
|
||||
|
||||
val intermediateKey = ByteArray(CMAC_SIZE)
|
||||
aesCmac(firstKey, curveLTK, intermediateKey)
|
||||
|
||||
val ltkData = byteArrayOf(2.toByte()) +
|
||||
INTERMEDIAR_KEY_MAGIC_STRING +
|
||||
podNonce +
|
||||
pdmNonce +
|
||||
byteArrayOf(0.toByte(), 1.toByte())
|
||||
aesCmac(intermediateKey, ltkData, ltk)
|
||||
|
||||
val confData = byteArrayOf(1.toByte()) +
|
||||
INTERMEDIAR_KEY_MAGIC_STRING +
|
||||
podNonce +
|
||||
pdmNonce +
|
||||
byteArrayOf(0.toByte(), 1.toByte())
|
||||
val confKey = ByteArray(CMAC_SIZE)
|
||||
aesCmac(intermediateKey, confData, confKey)
|
||||
|
||||
val pdmConfData = PDM_CONF_MAGIC_PREFIX +
|
||||
pdmNonce +
|
||||
podNonce
|
||||
aesCmac(confKey, pdmConfData, pdmConf)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmConf: ${pdmConf.toHex()}")
|
||||
|
||||
val podConfData = POD_CONF_MAGIC_PREFIX +
|
||||
podNonce +
|
||||
pdmNonce
|
||||
aesCmac(confKey, podConfData, podConf)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podConf: ${podConf.toHex()}")
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPrivate: ${pdmPrivate.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmPublic: ${pdmPublic.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podPublic: ${podPublic.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "pdmNonce: ${pdmNonce.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "podNonce: ${podNonce.toHex()}")
|
||||
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "LTK, donna key: ${curveLTK.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Intermediate key: ${intermediateKey.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "LTK: ${ltk.toHex()}")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Conf KEY: ${confKey.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PUBLIC_KEY_SIZE = 32
|
||||
private const val NONCE_SIZE = 16
|
||||
private const val CONF_SIZE = 16
|
||||
|
||||
private const val CMAC_SIZE = 16
|
||||
|
||||
private val INTERMEDIAR_KEY_MAGIC_STRING = "TWIt".toByteArray()
|
||||
private val PDM_CONF_MAGIC_PREFIX = "KC_2_U".toByteArray()
|
||||
private val POD_CONF_MAGIC_PREFIX = "KC_2_V".toByteArray()
|
||||
|
||||
private const val GET_POD_STATUS_HEX_COMMAND = "ffc32dbd08030e0100008a"
|
||||
// TODO for now we are assuming this command is build out of constant parameters,
|
||||
// use a proper command builder for that.
|
||||
private const val GET_POD_STATUS_HEX_COMMAND =
|
||||
"ffc32dbd08030e0100008a" // TODO for now we are assuming this command is build out of constant parameters, use a proper command builder for that.
|
||||
|
||||
private const val SP1 = "SP1="
|
||||
private const val SP2 = ",SP2="
|
||||
|
@ -249,11 +163,3 @@ internal class LTKExchanger(private val aapsLogger: AAPSLogger, private val msgI
|
|||
private val UNKNOWN_P0_PAYLOAD = byteArrayOf(0xa5.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
private fun aesCmac(key: ByteArray, data: ByteArray, result: ByteArray) {
|
||||
val aesEngine = AESEngine()
|
||||
val mac = CMac(aesEngine)
|
||||
mac.init(KeyParameter(key))
|
||||
mac.update(data, 0, data.size)
|
||||
mac.doFinal(result, 0)
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ import java.security.SecureRandom
|
|||
class SessionEstablisher(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
private val msgIO: MessageIO,
|
||||
private val ltk: ByteArray,
|
||||
private val eapSqn: ByteArray,
|
||||
ltk: ByteArray,
|
||||
eapSqn: ByteArray,
|
||||
private val myId: Id,
|
||||
private val podId: Id,
|
||||
private var msgSeq: Byte
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
||||
|
||||
import info.nightscout.androidaps.logging.AAPSLoggerTest
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.spongycastle.util.encoders.Hex
|
||||
|
||||
class KeyExchangeTest {
|
||||
@Test fun testLTK() {
|
||||
val aapsLogger = AAPSLoggerTest()
|
||||
val ke = KeyExchange(
|
||||
aapsLogger,
|
||||
pdmPrivate= Hex.decode("27ec94b71a201c5e92698d668806ae5ba00594c307cf5566e60c1fc53a6f6bb6"),
|
||||
pdmNonce= Hex.decode("edfdacb242c7f4e1d2bc4d93ca3c5706")
|
||||
)
|
||||
val podPublicKey = Hex.decode("2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74")
|
||||
val podNonce = Hex.decode("00000000000000000000000000000000")
|
||||
ke.updatePodPublicData(podPublicKey+podNonce)
|
||||
assertEquals(ke.pdmPublic.toHex(), "f2b6940243aba536a66e19fb9a39e37f1e76a1cd50ab59b3e05313b4fc93975e")
|
||||
assertEquals(ke.pdmConf.toHex(), "5fc3b4da865e838ceaf1e9e8bb85d1ac")
|
||||
ke.validatePodConf(Hex.decode("af4f10db5f96e5d9cd6cfc1f54f4a92f"))
|
||||
assertEquals(ke.ltk.toHex(), "341e16d13f1cbf73b19d1c2964fee02b")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue