mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-26 16:41:43 +01:00
Mi Band 8: Refactor cipher to auth service
This commit is contained in:
parent
e21b35981b
commit
f0188f3499
@ -53,12 +53,16 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
public class XiaomiCipher {
|
public class XiaomiAuthService extends AbstractXiaomiService {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiCipher.class);
|
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[] secretKey = new byte[16];
|
||||||
private final byte[] nonce = 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[] encryptionNonce = new byte[4];
|
||||||
private final byte[] decryptionNonce = new byte[4];
|
private final byte[] decryptionNonce = new byte[4];
|
||||||
|
|
||||||
public XiaomiCipher(final XiaomiSupport support) {
|
public XiaomiAuthService(final XiaomiSupport support) {
|
||||||
this.mSupport = support;
|
super(support);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startAuthentication(final TransactionBuilder builder) {
|
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);
|
new SecureRandom().nextBytes(nonce);
|
||||||
|
|
||||||
builder.write(
|
builder.write(
|
||||||
mSupport.getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE),
|
getSupport().getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE),
|
||||||
ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, buildNonceCommand(nonce))
|
ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, buildNonceCommand(nonce))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleAuthCommand(final XiaomiProto.Command cmd) {
|
@Override
|
||||||
if (cmd.getType() != XiaomiConstants.CMD_TYPE_AUTH) {
|
public void handleCommand(final XiaomiProto.Command cmd) {
|
||||||
|
if (cmd.getType() != COMMAND_TYPE) {
|
||||||
throw new IllegalArgumentException("Not an auth command");
|
throw new IllegalArgumentException("Not an auth command");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd.getSubtype()) {
|
switch (cmd.getSubtype()) {
|
||||||
case XiaomiConstants.CMD_AUTH_NONCE: {
|
case CMD_NONCE: {
|
||||||
LOG.debug("Got watch nonce");
|
LOG.debug("Got watch nonce");
|
||||||
|
|
||||||
// Watch nonce
|
// Watch nonce
|
||||||
final XiaomiProto.Command reply = handleWatchNonce(cmd.getAuth().getWatchNonce());
|
final XiaomiProto.Command reply = handleWatchNonce(cmd.getAuth().getWatchNonce());
|
||||||
if (reply == null) {
|
if (reply == null) {
|
||||||
mSupport.disconnect();
|
getSupport().disconnect();
|
||||||
return;
|
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?
|
// TODO maybe move these writes to support class?
|
||||||
builder.write(
|
builder.write(
|
||||||
mSupport.getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE),
|
getSupport().getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE),
|
||||||
ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, reply.toByteArray())
|
ArrayUtils.addAll(XiaomiConstants.PAYLOAD_HEADER_AUTH, reply.toByteArray())
|
||||||
);
|
);
|
||||||
builder.queue(mSupport.getQueue());
|
builder.queue(getSupport().getQueue());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case XiaomiConstants.CMD_AUTH_AUTH: {
|
case CMD_AUTH: {
|
||||||
LOG.info("Authenticated!");
|
LOG.info("Authenticated!");
|
||||||
|
|
||||||
final TransactionBuilder builder = mSupport.createTransactionBuilder("phase 2 initialize");
|
final TransactionBuilder builder = getSupport().createTransactionBuilder("phase 2 initialize");
|
||||||
builder.add(new SetDeviceStateAction(mSupport.getDevice(), GBDevice.State.INITIALIZED, mSupport.getContext()));
|
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.INITIALIZED, getSupport().getContext()));
|
||||||
mSupport.phase2Initialize(builder);
|
getSupport().phase2Initialize(builder);
|
||||||
builder.queue(mSupport.getQueue());
|
builder.queue(getSupport().getQueue());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +195,8 @@ public class XiaomiCipher {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
final XiaomiProto.Command.Builder cmd = XiaomiProto.Command.newBuilder();
|
final XiaomiProto.Command.Builder cmd = XiaomiProto.Command.newBuilder();
|
||||||
cmd.setType(XiaomiConstants.CMD_TYPE_AUTH);
|
cmd.setType(COMMAND_TYPE);
|
||||||
cmd.setSubtype(XiaomiConstants.CMD_AUTH_AUTH);
|
cmd.setSubtype(CMD_AUTH);
|
||||||
|
|
||||||
final XiaomiProto.Auth.Builder auth = XiaomiProto.Auth.newBuilder();
|
final XiaomiProto.Auth.Builder auth = XiaomiProto.Auth.newBuilder();
|
||||||
auth.setAuthStep3(authStep3);
|
auth.setAuthStep3(authStep3);
|
||||||
@ -207,8 +212,8 @@ public class XiaomiCipher {
|
|||||||
auth.setPhoneNonce(phoneNonce.build());
|
auth.setPhoneNonce(phoneNonce.build());
|
||||||
|
|
||||||
final XiaomiProto.Command.Builder command = XiaomiProto.Command.newBuilder();
|
final XiaomiProto.Command.Builder command = XiaomiProto.Command.newBuilder();
|
||||||
command.setType(XiaomiConstants.CMD_TYPE_AUTH);
|
command.setType(COMMAND_TYPE);
|
||||||
command.setSubtype(XiaomiConstants.CMD_AUTH_NONCE);
|
command.setSubtype(CMD_NONCE);
|
||||||
command.setAuth(auth.build());
|
command.setAuth(auth.build());
|
||||||
return command.build().toByteArray();
|
return command.build().toByteArray();
|
||||||
}
|
}
|
@ -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_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 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
|
// TODO not like this
|
||||||
public static final byte[] PAYLOAD_ACK = new byte[]{0, 0, 3, 0};
|
public static final byte[] PAYLOAD_CHUNKED_START = new byte[]{0, 0, 0, 1};
|
||||||
public static final byte[] PAYLOAD_HEADER_AUTH = new byte[]{0, 0, 2, 2};
|
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};
|
||||||
}
|
}
|
||||||
|
@ -69,8 +69,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
|||||||
public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class);
|
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 XiaomiMusicService musicService = new XiaomiMusicService(this);
|
||||||
private final XiaomiHealthService healthService = new XiaomiHealthService(this);
|
private final XiaomiHealthService healthService = new XiaomiHealthService(this);
|
||||||
private final XiaomiNotificationService notificationService = new XiaomiNotificationService(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 XiaomiSystemService systemService = new XiaomiSystemService(this);
|
||||||
|
|
||||||
private final Map<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
|
private final Map<Integer, AbstractXiaomiService> mServiceMap = new LinkedHashMap<Integer, AbstractXiaomiService>() {{
|
||||||
|
put(XiaomiAuthService.COMMAND_TYPE, authService);
|
||||||
put(XiaomiMusicService.COMMAND_TYPE, musicService);
|
put(XiaomiMusicService.COMMAND_TYPE, musicService);
|
||||||
put(XiaomiHealthService.COMMAND_TYPE, healthService);
|
put(XiaomiHealthService.COMMAND_TYPE, healthService);
|
||||||
put(XiaomiNotificationService.COMMAND_TYPE, notificationService);
|
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_READ), true);
|
||||||
builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), true);
|
builder.notify(getCharacteristic(UUID_CHARACTERISTIC_XIAOMI_COMMAND_WRITE), true);
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
cipher.startAuthentication(builder);
|
authService.startAuthentication(builder);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -168,6 +168,9 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
LOG.warn("Non-zero header not supported");
|
LOG.warn("Non-zero header not supported");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (type == 0) {
|
||||||
|
// Chunked
|
||||||
|
}
|
||||||
if (type != 2) {
|
if (type != 2) {
|
||||||
LOG.warn("Unsupported type {}", type);
|
LOG.warn("Unsupported type {}", type);
|
||||||
return true;
|
return true;
|
||||||
@ -175,7 +178,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
final byte[] plainValue;
|
final byte[] plainValue;
|
||||||
if (encryption == 1) {
|
if (encryption == 1) {
|
||||||
plainValue = cipher.decrypt(ArrayUtils.subarray(value, 4, value.length));
|
plainValue = authService.decrypt(ArrayUtils.subarray(value, 4, value.length));
|
||||||
} else {
|
} else {
|
||||||
plainValue = ArrayUtils.subarray(value, 4, value.length);
|
plainValue = ArrayUtils.subarray(value, 4, value.length);
|
||||||
}
|
}
|
||||||
@ -196,11 +199,6 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.getType() == CMD_TYPE_AUTH) {
|
|
||||||
cipher.handleAuthCommand(cmd);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.warn("Unexpected watch command type {}", cmd.getType());
|
LOG.warn("Unexpected watch command type {}", cmd.getType());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -441,7 +439,7 @@ public class XiaomiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
|
|
||||||
public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
|
public void sendCommand(final TransactionBuilder builder, final XiaomiProto.Command command) {
|
||||||
final byte[] commandBytes = command.toByteArray();
|
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);
|
final ByteBuffer buf = ByteBuffer.allocate(6 + encryptedCommandBytes.length).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
buf.putShort((short) 0);
|
buf.putShort((short) 0);
|
||||||
buf.put((byte) 2); // 2 for command
|
buf.put((byte) 2); // 2 for command
|
||||||
|
Loading…
Reference in New Issue
Block a user