Zepp OS: Extract authentication to standalone service

Replicate the logic from the InitOperation2021, but this makes it
reusable for btrfcomm devices, without refactoring BLE devices previous
to Zepp OS.
This commit is contained in:
José Rebelo
2025-04-04 22:59:12 +01:00
parent bbd9868eaf
commit 732cbd9b16
4 changed files with 257 additions and 59 deletions
@@ -320,8 +320,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
protected BluetoothGattCharacteristic characteristicHRControlPoint;
private BluetoothGattCharacteristic characteristicChunked;
private BluetoothGattCharacteristic characteristicChunked2021Write;
private BluetoothGattCharacteristic characteristicChunked2021Read;
protected BluetoothGattCharacteristic characteristicChunked2021Write;
protected BluetoothGattCharacteristic characteristicChunked2021Read;
private boolean needsAuth;
private volatile boolean telephoneRinging;
@@ -335,7 +335,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
protected boolean isMusicAppStarted = false;
protected MediaManager mediaManager;
private boolean heartRateNotifyEnabled;
private static final int MIN_MTU = 23;
protected static final int MIN_MTU = 23;
private int mMTU = MIN_MTU;
// Keep track of the previous MTU before reconnection, so that we can request it after reconnection
private int previousMtu = -1;
@@ -373,7 +373,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
super.setContext(gbDevice, btAdapter, context);
this.mediaManager = new MediaManager(context);
this.sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
}
@Override
@@ -408,9 +408,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
} else {
new InitOperation(authenticate, authFlags, cryptFlags, this, builder).perform();
}
if (sleepAsAndroidSender == null) {
sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
}
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
} catch (IOException e) {
@@ -4103,11 +4100,11 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
public void writeToChunked2021(final String taskName, short type, byte[] data, boolean encrypt) {
try {
final TransactionBuilder builder = performInitialized(taskName);
final TransactionBuilder builder = createTransactionBuilder(taskName);
writeToChunked2021(builder, type, data, encrypt);
builder.queue(getQueue());
} catch (final Exception e) {
LOG.error("Failed to " + taskName, e);
LOG.error("Failed to {}", taskName, e);
}
}
@@ -26,10 +26,10 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
@@ -46,12 +46,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.ECDH_B163;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class InitOperation2021 extends InitOperation implements Huami2021Handler {
private byte[] privateEC = new byte[24];
private final byte[] privateEC = new byte[24];
private byte[] publicEC;
private byte[] remotePublicEC = new byte[48];
private final byte[] remoteRandom = new byte[16];
private byte[] sharedEC;
private final byte[] finalSharedSessionAES = new byte[16];
private final Huami2021ChunkedEncoder huami2021ChunkedEncoder;
private final Huami2021ChunkedDecoder huami2021ChunkedDecoder;
@@ -71,60 +67,36 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
this.huami2021ChunkedDecoder.setHuami2021Handler(this);
}
private void testAuth() {
byte[] secretKey = getSecretKey();
privateEC = new byte[]{0x0b, 0x42, (byte) 0xb9, (byte) 0xe6, 0x1c, 0x23, 0x34, 0x0e, 0x35, (byte) 0xc1, 0x6e, 0x2e, 0x7d, (byte) 0xe4, 0x33, (byte) 0xf4, (byte) 0xb5, (byte) 0x85, (byte) 0x9a, 0x72, (byte) 0xec, 0x11, 0x40, 0x27};
remotePublicEC = new byte[]{(byte) 0xe6, 0x01, 0x6a, (byte) 0xba, 0x1d, (byte) 0xe7, (byte) 0xac, 0x0f, 0x0c, 0x7f, 0x0f, (byte) 0xf7, (byte) 0xe2, 0x24, 0x3e, 0x66, 0x62, (byte) 0xb5, (byte) 0xe0, 0x3b, 0x01, 0x00, 0x00, 0x00, (byte) 0xad, (byte) 0x8a, 0x4b, (byte) 0xed, (byte) 0xc7, 0x6a, 0x1e, (byte) 0xfd, (byte) 0xe7, 0x72, 0x5c, (byte) 0xc6, 0x62, (byte) 0xb5, 0x48, 0x35, 0x51, 0x3e, 0x3d, 0x57, 0x05, 0x00, 0x00, 0x00};
publicEC = ECDH_B163.ecdh_generate_public(privateEC);
sharedEC = ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC);
LOG.warn("publicEC: " + GB.hexdump(publicEC));
LOG.warn("privateEC: " + GB.hexdump(privateEC));
LOG.warn("remotepubEC: " + GB.hexdump(remotePublicEC));
LOG.warn("sharedEC: " + GB.hexdump(sharedEC));
for (int i = 0; i < 16; i++) {
finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]);
}
LOG.warn("finalSharedAES: " + GB.hexdump(finalSharedSessionAES));
}
@Override
protected void doPerform() {
huamiSupport.enableNotifications(builder, true);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
// get random auth number
generateKeyPair();
byte[] sendPubkeyCommand = new byte[48 + 4];
sendPubkeyCommand[0] = 0x04;
sendPubkeyCommand[1] = 0x02;
sendPubkeyCommand[2] = 0x00;
sendPubkeyCommand[3] = 0x02;
System.arraycopy(publicEC, 0, sendPubkeyCommand, 4, 48);
//testAuth();
huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, sendPubkeyCommand, true, false);
final byte[] sendPubKeyCommand = new byte[48 + 4];
sendPubKeyCommand[0] = 0x04;
sendPubKeyCommand[1] = 0x02;
sendPubKeyCommand[2] = 0x00;
sendPubKeyCommand[3] = 0x02;
System.arraycopy(publicEC, 0, sendPubKeyCommand, 4, 48);
huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, sendPubKeyCommand, true, false);
}
private void generateKeyPair() {
Random r = new Random();
r.nextBytes(privateEC);
new Random().nextBytes(privateEC);
publicEC = ECDH_B163.ecdh_generate_public(privateEC);
}
@Override
public TransactionBuilder performInitialized(String taskName) {
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
final UUID characteristicUUID = characteristic.getUuid();
if (!HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
LOG.warn("Unhandled characteristic changed: {}", characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
byte[] value = characteristic.getValue();
final byte[] value = characteristic.getValue();
if (value.length <= 1 || value[0] != 0x03) {
// Not chunked
return super.onCharacteristicChanged(gatt, characteristic);
@@ -149,12 +121,16 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
LOG.debug("Got remote random + public key");
// Received remote random (16 bytes) + public key (48 bytes)
final byte[] remotePublicEC = new byte[48];
final byte[] remoteRandom = new byte[16];
final byte[] finalSharedSessionAES = new byte[16];
System.arraycopy(payload, 3, remoteRandom, 0, 16);
System.arraycopy(payload, 19, remotePublicEC, 0, 48);
sharedEC = ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC);
int encryptedSequenceNumber = (sharedEC[0] & 0xff) | ((sharedEC[1] & 0xff) << 8) | ((sharedEC[2] & 0xff) << 16) | ((sharedEC[3] & 0xff) << 24);
final byte[] sharedEC = Objects.requireNonNull(ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC));
final int encryptedSequenceNumber = (sharedEC[0] & 0xff) | ((sharedEC[1] & 0xff) << 8) | ((sharedEC[2] & 0xff) << 16) | ((sharedEC[3] & 0xff) << 24);
byte[] secretKey = getSecretKey();
final byte[] secretKey = getSecretKey();
for (int i = 0; i < 16; i++) {
finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]);
}
@@ -164,10 +140,10 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
huami2021ChunkedDecoder.setEncryptionParameters(finalSharedSessionAES);
try {
byte[] encryptedRandom1 = CryptoUtils.encryptAES(remoteRandom, secretKey);
byte[] encryptedRandom2 = CryptoUtils.encryptAES(remoteRandom, finalSharedSessionAES);
final byte[] encryptedRandom1 = CryptoUtils.encryptAES(remoteRandom, secretKey);
final byte[] encryptedRandom2 = CryptoUtils.encryptAES(remoteRandom, finalSharedSessionAES);
if (encryptedRandom1.length == 16 && encryptedRandom2.length == 16) {
byte[] command = new byte[33];
final byte[] command = new byte[33];
command[0] = 0x05;
System.arraycopy(encryptedRandom1, 0, command, 1, 16);
System.arraycopy(encryptedRandom2, 0, command, 17, 16);
@@ -175,14 +151,14 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
huami2021ChunkedEncoder.write(builder, Huami2021Service.CHUNKED2021_ENDPOINT_AUTH, command, true, false);
huamiSupport.performImmediately(builder);
}
} catch (Exception e) {
} catch (final Exception e) {
LOG.error("AES encryption failed", e);
}
} else if (payload[0] == RESPONSE && payload[1] == 0x05 && payload[2] == SUCCESS) {
LOG.debug("Auth Success");
try {
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
final TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setCallback(null); // remove init operation as the callback
huamiSupport.enableFurtherNotifications(builder, true);
@@ -192,7 +168,7 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler
huamiSupport.phase3Initialize(builder);
huamiSupport.setInitialized(builder);
huamiSupport.performImmediately(builder);
} catch (Exception e) {
} catch (final Exception e) {
LOG.error("failed initializing device", e);
}
} else if (payload[0] == RESPONSE && payload[1] == 0x05 && payload[2] == 0x25) {
@@ -85,6 +85,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsMapsInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsMusicInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
@@ -115,6 +116,9 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedDecoder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedEncoder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiPhoneGpsStatus;
@@ -129,6 +133,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAlarmsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAssistantService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAppsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAuthenticationService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCalendarService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsDisplayItemsService;
@@ -171,6 +176,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
// Services
private final ZeppOsServicesService servicesService = new ZeppOsServicesService(this);
private final ZeppOsAuthenticationService authenticationService = new ZeppOsAuthenticationService(this);
private final ZeppOsFileTransferService fileTransferService = new ZeppOsFileTransferService(this);
private final ZeppOsConfigService configService = new ZeppOsConfigService(this);
private final ZeppOsAgpsService agpsService = new ZeppOsAgpsService(this);
@@ -205,6 +211,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
private final Set<Short> mIsEncrypted = new HashSet<>();
private final Map<Short, AbstractZeppOsService> mServiceMap = new LinkedHashMap<Short, AbstractZeppOsService>() {{
put(servicesService.getEndpoint(), servicesService);
put(authenticationService.getEndpoint(), authenticationService);
put(fileTransferService.getEndpoint(), fileTransferService);
put(configService.getEndpoint(), configService);
put(agpsService.getEndpoint(), agpsService);
@@ -274,6 +281,31 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
super.dispose();
}
@Override
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
characteristicChunked2021Read = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ);
if (characteristicChunked2021Read != null && huami2021ChunkedDecoder == null) {
huami2021ChunkedDecoder = new Huami2021ChunkedDecoder(this, force2021Protocol());
}
characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(characteristicChunked2021Write, force2021Protocol(), getMTU());
}
if (characteristicChunked2021Write == null || characteristicChunked2021Read == null) {
LOG.warn("Chunked 2021 characteristics are null, will attempt to reconnect");
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.WAITING_FOR_RECONNECT, getContext()));
return builder;
}
builder.notify(characteristicChunked2021Read, true);
authenticationService.startAuthentication(builder);
return builder;
}
@Override
public void onSendConfiguration(final String config) {
final Prefs prefs = getDevicePrefs();
@@ -1157,6 +1189,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
@Override
public void phase2Initialize(final TransactionBuilder builder) {
LOG.info("2021 phase2Initialize...");
setCurrentTimeWithService(builder);
requestMTU(builder);
requestBatteryInfo(builder);
@@ -1384,6 +1417,11 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
}
public void setEncryptionParameters(final int encryptedSequenceNr, final byte[] sharedSessionKey) {
huami2021ChunkedEncoder.setEncryptionParameters(encryptedSequenceNr, sharedSessionKey);
huami2021ChunkedDecoder.setEncryptionParameters(sharedSessionKey);
}
protected void handle2021Workout(final byte[] payload) {
switch (payload[0]) {
case WORKOUT_CMD_APP_OPEN:
@@ -0,0 +1,187 @@
/* Copyright (C) 2025 José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.Random;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
import nodomain.freeyourgadget.gadgetbridge.util.ECDH_B163;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
/**
* This is a reimplementation of nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.init.InitOperation2021
* to avoid a big refactor on older devices, while making it compatible with btrfcomm devices.
*/
public class ZeppOsAuthenticationService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAuthenticationService.class);
public static final short ENDPOINT = 0x0082;
public static final byte CMD_PUB_KEY = 0x04;
public static final byte CMD_SESSION_KEY = 0x05;
private final byte[] privateEC = new byte[24];
private final byte[] publicEC = new byte[48];
private final byte[] finalSharedSessionAES = new byte[16];
public ZeppOsAuthenticationService(final ZeppOsSupport support) {
super(support, false);
}
@Override
public short getEndpoint() {
return ENDPOINT;
}
@Override
public void handlePayload(final byte[] payload) {
if (payload[0] != HuamiService.RESPONSE) {
LOG.warn("Got non-response auth byte {}", String.format("0x%02x", payload[0]));
return;
}
switch (payload[1]) {
case CMD_PUB_KEY:
if (payload[2] != HuamiService.SUCCESS) {
LOG.error("Got pub key command failure, status={}", String.format("0x%02x", payload[0]));
return;
}
LOG.debug("Got remote random + public key");
final byte[] remoteRandom = new byte[16];
final byte[] remotePublicEC = new byte[48];
// Received remote random (16 bytes) + public key (48 bytes)
System.arraycopy(payload, 3, remoteRandom, 0, 16);
System.arraycopy(payload, 19, remotePublicEC, 0, 48);
final byte[] sharedEC = Objects.requireNonNull(ECDH_B163.ecdh_generate_shared(privateEC, remotePublicEC));
final int encryptedSequenceNumber = BLETypeConversions.toUint32(sharedEC, 0);
final byte[] secretKey = getSecretKey();
for (int i = 0; i < 16; i++) {
finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]);
}
LOG.debug("Shared Session Key: {}", GB.hexdump(finalSharedSessionAES));
getSupport().setEncryptionParameters(encryptedSequenceNumber, finalSharedSessionAES);
try {
final byte[] encryptedRandom1 = CryptoUtils.encryptAES(remoteRandom, secretKey);
final byte[] encryptedRandom2 = CryptoUtils.encryptAES(remoteRandom, finalSharedSessionAES);
if (encryptedRandom1.length == 16 && encryptedRandom2.length == 16) {
byte[] command = new byte[33];
command[0] = 0x05;
System.arraycopy(encryptedRandom1, 0, command, 1, 16);
System.arraycopy(encryptedRandom2, 0, command, 17, 16);
write("send double encrypted random to device", command);
}
} catch (final Exception e) {
LOG.error("AES encryption failed", e);
}
return;
case CMD_SESSION_KEY:
if (payload[2] == 0x25) {
// wrong auth key
onAuthFailed();
return;
} else if (payload[2] != HuamiService.SUCCESS) {
LOG.error("Got session key failure, status={}", String.format("0x%02x", payload[0]));
return;
}
LOG.debug("Auth Success");
try {
// Authenticated, now initialize phase 2
final TransactionBuilder builder = getSupport().createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setCallback(null); // remove init operation as the callback
getSupport().phase2Initialize(builder);
getSupport().phase3Initialize(builder);
getSupport().setInitialized(builder);
getSupport().performImmediately(builder);
} catch (Exception e) {
LOG.error("failed initializing device", e);
}
return;
}
LOG.warn("Got unknown auth byte {}", String.format("0x%02x", payload[0]));
}
public void startAuthentication(final TransactionBuilder builder) {
new Random().nextBytes(privateEC);
final byte[] pub = ECDH_B163.ecdh_generate_public(privateEC);
assert pub != null;
System.arraycopy(pub, 0, publicEC, 0, 48);
final byte[] sendPubKeyCommand = new byte[48 + 4];
sendPubKeyCommand[0] = 0x04;
sendPubKeyCommand[1] = 0x02;
sendPubKeyCommand[2] = 0x00;
sendPubKeyCommand[3] = 0x02;
System.arraycopy(publicEC, 0, sendPubKeyCommand, 4, 48);
write(builder, sendPubKeyCommand);
}
private void onAuthFailed() {
LOG.error("Authentication failed, disconnecting");
GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN);
final GBDevice device = getSupport().getDevice();
if (device != null) {
GBApplication.deviceService(device).disconnect();
}
}
private byte[] getSecretKey() {
final byte[] authKeyBytes = new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45};
final Prefs devicePrefs = getDevicePrefs();
final String authKey = devicePrefs.getString("authkey", null);
if (authKey != null && !authKey.isEmpty()) {
byte[] srcBytes = authKey.trim().getBytes();
if (authKey.length() == 34 && authKey.startsWith("0x")) {
srcBytes = GB.hexStringToByteArray(authKey.substring(2));
} else if (authKey.length() == 32) {
// All Zepp OS devices require a hex key
srcBytes = GB.hexStringToByteArray(authKey);
}
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
}
return authKeyBytes;
}
}