diff --git a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java b/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java index 64c30b3aea050802499e70eac5b49b00e373fbea..f283f844fbee0a6529dfaa5ab52e549bec048f92 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java +++ b/app/src/main/java/info/nightscout/android/medtronic/GetHmacAndKeyActivity.java @@ -340,7 +340,6 @@ public class GetHmacAndKeyActivity extends AppCompatActivity implements LoaderCa String key = MessageUtils.byteArrayToHexString((byte[]) keyResponse.readObject()); realm.beginTransaction(); - info.setHmac(hmac); info.setKey(key); realm.commitTransaction(); } diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java index f4aeb0c90953398ab78394ea7f1600430657958b..6dd908f0dd3f57b8d8c585d51690eaee4b82df5b 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlReader.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -36,6 +37,7 @@ import info.nightscout.android.medtronic.message.PumpStatusResponseMessage; import info.nightscout.android.medtronic.message.PumpTimeRequestMessage; import info.nightscout.android.medtronic.message.PumpTimeResponseMessage; import info.nightscout.android.medtronic.message.ReadInfoResponseMessage; +import info.nightscout.android.medtronic.message.RequestLinkKeyResponseMessage; import info.nightscout.android.medtronic.message.UnexpectedMessageException; import info.nightscout.android.model.medtronicNg.PumpStatusEvent; import info.nightscout.android.utils.HexDump; @@ -238,7 +240,7 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler { Log.d(TAG, "Finished enterPasshtroughMode"); } - public void openConnection() throws IOException, TimeoutException { + public void openConnection() throws IOException, TimeoutException, NoSuchAlgorithmException { Log.d(TAG, "Begin openConnection"); new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.OPEN_CONNECTION, mPumpSession, mPumpSession.getHMAC()).send(this); // FIXME - We need to care what the response message is - wrong MAC and all that @@ -264,6 +266,24 @@ public class MedtronicCnlReader implements ContourNextLinkMessageHandler { Log.d(TAG, String.format("Finished requestReadInfo. linkMAC = '%d', pumpMAC = '%d", linkMAC, pumpMAC)); } + public void requestLinkKey() throws IOException, TimeoutException, EncryptionException, ChecksumException { + Log.d(TAG, "Begin requestLinkKey"); + new ContourNextLinkBinaryMessage(ContourNextLinkBinaryMessage.CommandType.REQUEST_LINK_KEY, mPumpSession, null).send(this); + + ContourNextLinkMessage response = RequestLinkKeyResponseMessage.fromBytes(mPumpSession, readMessage()); + + // FIXME - this needs to go into RequestLinkKeyResponseMessage + ByteBuffer infoBuffer = ByteBuffer.allocate(55); + infoBuffer.order(ByteOrder.BIG_ENDIAN); + infoBuffer.put(response.encode(), 0x21, 55); + + byte[] packedLinkKey = infoBuffer.array(); + + this.getPumpSession().setPackedLinkKey(packedLinkKey); + + Log.d(TAG, String.format("Finished requestLinkKey. linkKey = '%s'", this.getPumpSession().getKey())); + } + public byte negotiateChannel(byte lastRadioChannel) throws IOException, ChecksumException, TimeoutException { ArrayList<Byte> radioChannels = new ArrayList<>(Arrays.asList(ArrayUtils.toObject(RADIO_CHANNELS))); diff --git a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java index 0f22dd79dc7950f50849f69d7397288e900a7181..215e7f3e6174163da35db8824c928d87f5d454bb 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java +++ b/app/src/main/java/info/nightscout/android/medtronic/MedtronicCnlSession.java @@ -1,12 +1,21 @@ package info.nightscout.android.medtronic; +import org.apache.commons.lang3.ArrayUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + /** * Created by lgoedhart on 26/03/2016. */ public class MedtronicCnlSession { + private static final String HMAC_PADDING = "A4BD6CED9A42602564F413123"; + private byte[] HMAC; private byte[] key; + private String stickSerial; + private long linkMAC; private long pumpMAC; @@ -14,16 +23,31 @@ public class MedtronicCnlSession { private int bayerSequenceNumber = 1; private int medtronicSequenceNumber = 1; - public byte[] getHMAC() { + /*public byte[] getHMAC() { return HMAC; + }*/ + + public byte[] getHMAC() throws NoSuchAlgorithmException { + String shortSerial = this.stickSerial.replaceAll("\\d+-", ""); + byte[] message = (shortSerial + HMAC_PADDING).getBytes(); + byte[] numArray; + + MessageDigest instance = MessageDigest.getInstance("SHA-256"); + instance.update(message); + + numArray = instance.digest(); + ArrayUtils.reverse(numArray); + + return numArray; } public byte[] getKey() { return key; } + public byte[] getIV() { byte[] iv = new byte[key.length]; - System.arraycopy(key,0,iv,0,key.length); + System.arraycopy(key, 0, iv, 0, key.length); iv[0] = radioChannel; return iv; } @@ -32,7 +56,7 @@ public class MedtronicCnlSession { return linkMAC; } - public void setLinkMAC( long linkMAC ) { + public void setLinkMAC(long linkMAC) { this.linkMAC = linkMAC; } @@ -40,7 +64,7 @@ public class MedtronicCnlSession { return pumpMAC; } - public void setPumpMAC( long pumpMAC ) { + public void setPumpMAC(long pumpMAC) { this.pumpMAC = pumpMAC; } @@ -68,11 +92,39 @@ public class MedtronicCnlSession { this.radioChannel = radioChannel; } - public void setHMAC( byte[] hmac ) { + public void setHMAC(byte[] hmac) { this.HMAC = hmac; } - public void setKey( byte[] key ) { + public void setKey(byte[] key) { this.key = key; } + + public void setPackedLinkKey(byte[] packedLinkKey) { + this.key = new byte[16]; + + int pos = this.stickSerial.charAt(this.stickSerial.length() - 1) & 7; + + for (int i = 0; i < this.key.length; i++) { + if ((packedLinkKey[pos + 1] & 1) == 1) { + this.key[i] = (byte) ~packedLinkKey[pos]; + } else { + this.key[i] = packedLinkKey[pos]; + } + + if (((packedLinkKey[pos + 1] >> 1) & 1) == 0) { + pos += 3; + } else { + pos += 2; + } + } + } + + public String getStickSerial() { + return stickSerial; + } + + public void setStickSerial(String stickSerial) { + this.stickSerial = stickSerial; + } } diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java index c5b8107a3c4c7aed1fd091f0312e8a51e76b3297..95e94baa56ef73718e3db0d2d13e272f8b48a058 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/ContourNextLinkBinaryMessage.java @@ -34,6 +34,10 @@ public class ContourNextLinkBinaryMessage extends ContourNextLinkMessage{ CommandType(int commandType) { value = (byte) commandType; } + + public int getValue() { + return value; + } } public ContourNextLinkBinaryMessage(CommandType commandType, MedtronicCnlSession pumpSession, byte[] payload) { diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java index 28dd3a5e76d16ea819f742f6487d0ff563c7fef9..c2f4a7d58cf10d70925e3d2ab15e3358a4ff3a3f 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java +++ b/app/src/main/java/info/nightscout/android/medtronic/message/MedtronicReceiveMessage.java @@ -45,7 +45,10 @@ public class MedtronicReceiveMessage extends MedtronicMessage { // TODO - Validate the message, inner CCITT, serial numbers, etc // If there's not 57 bytes, then we got back a bad message. Not sure how to process these yet. - if( bytes.length >= 57 ) { + // Also, READ_INFO and REQUEST_LINK_KEY are not encrypted + if (bytes.length >= 57 && + (bytes[18] != CommandType.READ_INFO.getValue()) && + (bytes[18] != CommandType.REQUEST_LINK_KEY_RESPONSE.getValue())) { // Replace the encrypted bytes by their decrypted equivalent (same block size) byte encryptedPayloadSize = bytes[56]; diff --git a/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..423e40fb8ab61296fc9c26995837a56932715a7e --- /dev/null +++ b/app/src/main/java/info/nightscout/android/medtronic/message/RequestLinkKeyResponseMessage.java @@ -0,0 +1,21 @@ +package info.nightscout.android.medtronic.message; + +import info.nightscout.android.medtronic.MedtronicCnlSession; + +/** + * Created by lgoedhart on 10/05/2016. + */ +public class RequestLinkKeyResponseMessage extends MedtronicReceiveMessage { + protected RequestLinkKeyResponseMessage(CommandType commandType, CommandAction commandAction, MedtronicCnlSession pumpSession, byte[] payload) { + super(commandType, commandAction, pumpSession, payload); + } + + public static ContourNextLinkMessage fromBytes(MedtronicCnlSession pumpSession, byte[] bytes) throws ChecksumException, EncryptionException { + // TODO - turn this into a factory + ContourNextLinkMessage message = MedtronicReceiveMessage.fromBytes(pumpSession, bytes); + + // TODO - Validate the MessageType + + return message; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java index 5a403aa65172fda96de03c2d04022b18acec924d..814f29de870282cf6b54da5c6619dbbadc4f6864 100644 --- a/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java +++ b/app/src/main/java/info/nightscout/android/medtronic/service/MedtronicCnlIntentService.java @@ -16,6 +16,7 @@ import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import java.io.IOException; +import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeoutException; @@ -157,6 +158,9 @@ public class MedtronicCnlIntentService extends IntentService { info = realm.copyToRealm(info); } + cnlReader.getPumpSession().setStickSerial(info.getSerialNumber()); + + /* String hmac = info.getHmac(); String key = info.getKey(); @@ -173,6 +177,7 @@ public class MedtronicCnlIntentService extends IntentService { cnlReader.getPumpSession().setHMAC(MessageUtils.hexStringToByteArray(hmac)); cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); + */ cnlReader.enterControlMode(); @@ -181,6 +186,17 @@ public class MedtronicCnlIntentService extends IntentService { cnlReader.openConnection(); cnlReader.requestReadInfo(); + String key = info.getKey(); + + if (key == null) { + cnlReader.requestLinkKey(); + + info.setKey(MessageUtils.byteArrayToHexString(cnlReader.getPumpSession().getKey())); + key = info.getKey(); + } + + cnlReader.getPumpSession().setKey(MessageUtils.hexStringToByteArray(key)); + long pumpMAC = cnlReader.getPumpSession().getPumpMAC(); Log.i(TAG, "PumpInfo MAC: " + (pumpMAC & 0xffffff)); MainActivity.setActivePumpMac(pumpMAC); @@ -250,6 +266,9 @@ public class MedtronicCnlIntentService extends IntentService { } catch (UnexpectedMessageException e) { Log.e(TAG, "Unexpected Message", e); sendStatus("Communication Error: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Could not determine CNL HMAC", e); + sendStatus("Error connecting to Contour Next Link: Hashing error."); } finally { //TODO : 05.11.2016 has the close to be here? cnlReader.closeConnection(); @@ -304,7 +323,7 @@ public class MedtronicCnlIntentService extends IntentService { final Intent receiverIntent = new Intent(this, XDripPlusUploadReceiver.class); final long timestamp = System.currentTimeMillis() + 500L; final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); - Log.d(TAG,"Scheduling xDrip+ send"); + Log.d(TAG, "Scheduling xDrip+ send"); wakeUpIntent(getApplicationContext(), timestamp, pendingIntent); } } @@ -312,7 +331,7 @@ public class MedtronicCnlIntentService extends IntentService { private void uploadToNightscout() { Intent receiverIntent = new Intent(this, NightscoutUploadReceiver.class); final long timestamp = System.currentTimeMillis() + 1000L; - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int)timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, (int) timestamp, receiverIntent, PendingIntent.FLAG_ONE_SHOT); wakeUpIntent(getApplicationContext(), timestamp, pendingIntent); } diff --git a/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java b/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java index 404d801c8250aaa33c13ca53e58b0d9f0e852a05..9b054671a3580a5c5e142405e89d8ea52147e100 100644 --- a/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java +++ b/app/src/main/java/info/nightscout/android/model/medtronicNg/ContourNextLinkInfo.java @@ -9,7 +9,6 @@ import io.realm.annotations.PrimaryKey; public class ContourNextLinkInfo extends RealmObject { @PrimaryKey private String serialNumber; - private String hmac; private String key; public String getSerialNumber() { @@ -20,14 +19,6 @@ public class ContourNextLinkInfo extends RealmObject { this.serialNumber = serialNumber; } - public String getHmac() { - return hmac; - } - - public void setHmac(String hmac) { - this.hmac = hmac; - } - public String getKey() { return key; }