From f0188f3499a5ca766815609c0e910b88c9e45990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Fri, 6 Oct 2023 19:24:38 +0100 Subject: [PATCH] Mi Band 8: Refactor cipher to auth service --- ...aomiCipher.java => XiaomiAuthService.java} | 53 ++++++++++--------- .../devices/xiaomi/XiaomiConstants.java | 13 +++-- .../service/devices/xiaomi/XiaomiSupport.java | 18 +++---- 3 files changed, 43 insertions(+), 41 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/{XiaomiCipher.java => XiaomiAuthService.java} (87%) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCipher.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java similarity index 87% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCipher.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java index cd418bcd0..34798c038 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiCipher.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java @@ -53,12 +53,16 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService; import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class XiaomiCipher { - private static final Logger LOG = LoggerFactory.getLogger(XiaomiCipher.class); +public class XiaomiAuthService extends AbstractXiaomiService { + private static final Logger LOG = LoggerFactory.getLogger(XiaomiAuthService.class); - private final XiaomiSupport mSupport; + public static final int COMMAND_TYPE = 1; + + public static final int CMD_NONCE = 26; + public static final int CMD_AUTH = 27; private final byte[] secretKey = new byte[16]; private final byte[] nonce = new byte[16]; @@ -67,55 +71,56 @@ public class XiaomiCipher { private final byte[] encryptionNonce = new byte[4]; private final byte[] decryptionNonce = new byte[4]; - public XiaomiCipher(final XiaomiSupport support) { - this.mSupport = support; + public XiaomiAuthService(final XiaomiSupport support) { + super(support); } protected void startAuthentication(final TransactionBuilder builder) { - builder.add(new SetDeviceStateAction(mSupport.getDevice(), GBDevice.State.AUTHENTICATING, mSupport.getContext())); + builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.AUTHENTICATING, getSupport().getContext())); - System.arraycopy(getSecretKey(mSupport.getDevice()), 0, secretKey, 0, 16); + System.arraycopy(getSecretKey(getSupport().getDevice()), 0, secretKey, 0, 16); new SecureRandom().nextBytes(nonce); builder.write( - mSupport.getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), + getSupport().getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, buildNonceCommand(nonce)) ); } - protected void handleAuthCommand(final XiaomiProto.Command cmd) { - if (cmd.getType() != XiaomiConstants.CMD_TYPE_AUTH) { + @Override + public void handleCommand(final XiaomiProto.Command cmd) { + if (cmd.getType() != COMMAND_TYPE) { throw new IllegalArgumentException("Not an auth command"); } switch (cmd.getSubtype()) { - case XiaomiConstants.CMD_AUTH_NONCE: { + case CMD_NONCE: { LOG.debug("Got watch nonce"); // Watch nonce final XiaomiProto.Command reply = handleWatchNonce(cmd.getAuth().getWatchNonce()); if (reply == null) { - mSupport.disconnect(); + getSupport().disconnect(); return; } - final TransactionBuilder builder = mSupport.createTransactionBuilder("auth step 2"); + final TransactionBuilder builder = getSupport().createTransactionBuilder("auth step 2"); // TODO maybe move these writes to support class? builder.write( - mSupport.getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), + getSupport().getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, reply.toByteArray()) ); - builder.queue(mSupport.getQueue()); + builder.queue(getSupport().getQueue()); break; } - case XiaomiConstants.CMD_AUTH_AUTH: { + case CMD_AUTH: { LOG.info("Authenticated!"); - final TransactionBuilder builder = mSupport.createTransactionBuilder("phase 2 initialize"); - builder.add(new SetDeviceStateAction(mSupport.getDevice(), GBDevice.State.INITIALIZED, mSupport.getContext())); - mSupport.phase2Initialize(builder); - builder.queue(mSupport.getQueue()); + final TransactionBuilder builder = getSupport().createTransactionBuilder("phase 2 initialize"); + builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.INITIALIZED, getSupport().getContext())); + getSupport().phase2Initialize(builder); + builder.queue(getSupport().getQueue()); break; } @@ -190,8 +195,8 @@ public class XiaomiCipher { .build(); final XiaomiProto.Command.Builder cmd = XiaomiProto.Command.newBuilder(); - cmd.setType(XiaomiConstants.CMD_TYPE_AUTH); - cmd.setSubtype(XiaomiConstants.CMD_AUTH_AUTH); + cmd.setType(COMMAND_TYPE); + cmd.setSubtype(CMD_AUTH); final XiaomiProto.Auth.Builder auth = XiaomiProto.Auth.newBuilder(); auth.setAuthStep3(authStep3); @@ -207,8 +212,8 @@ public class XiaomiCipher { auth.setPhoneNonce(phoneNonce.build()); final XiaomiProto.Command.Builder command = XiaomiProto.Command.newBuilder(); - command.setType(XiaomiConstants.CMD_TYPE_AUTH); - command.setSubtype(XiaomiConstants.CMD_AUTH_NONCE); + command.setType(COMMAND_TYPE); + command.setSubtype(CMD_NONCE); command.setAuth(auth.build()); return command.build().toByteArray(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConstants.java index b002988aa..146bdcebc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiConstants.java @@ -39,12 +39,11 @@ public class XiaomiConstants { public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0002 = UUID.fromString((String.format(BASE_UUID, "0002"))); public static final UUID UUID_CHARACTERISTIC_XIAOMI_UNKNOWN_0003 = UUID.fromString((String.format(BASE_UUID, "0003"))); - public static final int CMD_TYPE_AUTH = 1; - - public static final int CMD_AUTH_NONCE = 26; - public static final int CMD_AUTH_AUTH = 27; - // TODO not like this - public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0}; - public static final byte[] PAYLOAD_HEADER_AUTH = new byte[]{0, 0, 2, 2}; + public static final byte[] PAYLOAD_CHUNKED_START = new byte[]{0, 0, 0, 1}; + public static final byte[] PAYLOAD_CHUNKED_START_ACK = new byte[]{0, 0, 1, 1}; + public static final byte[] PAYLOAD_CHUNKED_END_ACK = new byte[]{0, 0, 1, 0}; + public static final byte[] PAYLOAD_HEADER_AUTH = new byte[]{0, 0, 2, 2}; + public static final byte[] PAYLOAD_HEADER_CMD = new byte[]{0, 0, 2, 1}; + public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0}; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java index 5fe46e649..3fdc8facd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java @@ -69,8 +69,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class XiaomiSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class); - private final XiaomiCipher cipher = new XiaomiCipher(this); - + private final XiaomiAuthService authService = new XiaomiAuthService(this); private final XiaomiMusicService musicService = new XiaomiMusicService(this); private final XiaomiHealthService healthService = new XiaomiHealthService(this); private final XiaomiNotificationService notificationService = new XiaomiNotificationService(this); @@ -79,6 +78,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { private final XiaomiSystemService systemService = new XiaomiSystemService(this); private final Map mServiceMap = new LinkedHashMap() {{ + put(XiaomiAuthService.COMMAND_TYPE, authService); put(XiaomiMusicService.COMMAND_TYPE, musicService); put(XiaomiHealthService.COMMAND_TYPE, healthService); put(XiaomiNotificationService.COMMAND_TYPE, notificationService); @@ -133,7 +133,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_READ), true); builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), true); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); - cipher.startAuthentication(builder); + authService.startAuthentication(builder); return builder; } @@ -168,6 +168,9 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { LOG.warn("Non-zero header not supported"); return true; } + if (type == 0) { + // Chunked + } if (type != 2) { LOG.warn("Unsupported type {}", type); return true; @@ -175,7 +178,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { final byte[] plainValue; if (encryption == 1) { - plainValue = cipher.decrypt(ArrayUtils.subarray(value, 4, value.length)); + plainValue = authService.decrypt(ArrayUtils.subarray(value, 4, value.length)); } else { plainValue = ArrayUtils.subarray(value, 4, value.length); } @@ -196,11 +199,6 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { return true; } - if (cmd.getType() == CMD_TYPE_AUTH) { - cipher.handleAuthCommand(cmd); - return true; - } - LOG.warn("Unexpected watch command type {}", cmd.getType()); return true; } @@ -441,7 +439,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport { public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) { final byte[] commandBytes = command.toByteArray(); - final byte[] encryptedCommandBytes = cipher.encrypt(commandBytes, encryptedIndex); + final byte[] encryptedCommandBytes = authService.encrypt(commandBytes, encryptedIndex); final ByteBuffer buf = ByteBuffer.allocate(6 + encryptedCommandBytes.length).order(ByteOrder.LITTLE_ENDIAN); buf.putShort((short) 0); buf.put((byte) 2); // 2 for command