Mi Band 8: Refactor cipher to auth service

This commit is contained in:
José Rebelo 2023-10-06 19:24:38 +01:00
parent e21b35981b
commit f0188f3499
3 changed files with 43 additions and 41 deletions

View File

@ -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();
}

View File

@ -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};
}

View File

@ -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<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
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