diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java index 03762047b..203713234 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCharacteristic.java @@ -220,7 +220,10 @@ public class CmfCharacteristic { return; } } catch (final GeneralSecurityException e) { - LOG.error("Failed to decrypt payload for {}", cmd, e); + LOG.error("Failed to decrypt payload for {} ({}/{})", cmd, String.format("0x%04x", cmd1), String.format("0x%04x", cmd2), e); + if (cmd == CmfCommand.AUTH_FAILED) { + handler.onCommand(cmd, new byte[0]); + } if (chunkCount > 1) { chunkBuffers.remove(cmd); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java index 6f46e3a88..c1ebe0167 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfCommand.java @@ -33,6 +33,7 @@ public enum CmfCommand { AUTH_PAIR_REQUEST(0xffff, 0x8047), AUTH_PHONE_NAME(0xffff, 0x8049), AUTH_WATCH_MAC(0xffff, 0x0049), + AUTH_FAILED(0xffff, 0xa061), AUTHENTICATED_CONFIRM_REPLY(0xffff, 0x0004), AUTHENTICATED_CONFIRM_REQUEST(0xffff, 0x804d), BATTERY(0x005c, 0x0001), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java index 36b034280..21a04f3e1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import android.media.AudioManager; import android.net.Uri; import android.os.Build; +import android.widget.Toast; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -41,6 +42,7 @@ import java.util.TimeZone; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; @@ -219,6 +221,14 @@ public class CmfWatchProSupport extends AbstractBTLEDeviceSupport implements Cmf } switch (cmd) { + case AUTH_FAILED: + LOG.error("Authentication failed, disconnecting"); + GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN); + final GBDevice device = getDevice(); + if (device != null) { + GBApplication.deviceService(device).disconnect(); + } + return; case AUTH_WATCH_MAC: LOG.debug("Got auth watch mac, requesting nonce"); sendCommand("auth request nonce", CmfCommand.AUTH_NONCE_REQUEST, A5); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java index 6c3edb687..d48cf7373 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation.java @@ -38,6 +38,7 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; 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.AbstractBTLEOperation; @@ -114,30 +115,34 @@ public class InitOperation extends AbstractBTLEOperation { public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { UUID characteristicUUID = characteristic.getUuid(); - if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) { - try { - byte[] value = characteristic.getValue(); - huamiSupport.logMessageContent(value); - if (value[0] == HuamiService.AUTH_RESPONSE && - value[1] == HuamiService.AUTH_SEND_KEY && - value[2] == HuamiService.AUTH_SUCCESS) { - TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the device"); - builder.write(characteristic, requestAuthNumber()); - huamiSupport.performImmediately(builder); - } else if (value[0] == HuamiService.AUTH_RESPONSE && - (value[1] & 0x0f) == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER && - value[2] == HuamiService.AUTH_SUCCESS) { - byte[] eValue = handleAESAuth(value, getSecretKey()); - byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll( - new byte[]{(byte) (HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER | cryptFlags), authFlags}, eValue); + if (!HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) { + LOG.info("Unhandled characteristic changed: {}", characteristicUUID); + return super.onCharacteristicChanged(gatt, characteristic); + } - TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the device"); - builder.write(characteristic, responseValue); - huamiSupport.setCurrentTimeWithService(builder); - huamiSupport.performImmediately(builder); - } else if (value[0] == HuamiService.AUTH_RESPONSE && - (value[1] & 0x0f) == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER && - value[2] == HuamiService.AUTH_SUCCESS) { + try { + final byte[] value = characteristic.getValue(); + huamiSupport.logMessageContent(value); + if (value[0] != HuamiService.AUTH_RESPONSE) { + LOG.warn("Got a non-response: {}", GB.hexdump(value)); + return super.onCharacteristicChanged(gatt, characteristic); + } + + if (value[1] == HuamiService.AUTH_SEND_KEY && value[2] == HuamiService.AUTH_SUCCESS) { + TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the device"); + builder.write(characteristic, requestAuthNumber()); + huamiSupport.performImmediately(builder); + } else if ((value[1] & 0x0f) == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER && value[2] == HuamiService.AUTH_SUCCESS) { + byte[] eValue = handleAESAuth(value, getSecretKey()); + byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll( + new byte[]{(byte) (HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER | cryptFlags), authFlags}, eValue); + + TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the device"); + builder.write(characteristic, responseValue); + huamiSupport.setCurrentTimeWithService(builder); + huamiSupport.performImmediately(builder); + } else if ((value[1] & 0x0f) == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER) { + if (value[2] == HuamiService.AUTH_SUCCESS) { TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2"); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); huamiSupport.enableFurtherNotifications(builder, true); @@ -146,17 +151,23 @@ public class InitOperation extends AbstractBTLEOperation { huamiSupport.phase3Initialize(builder); huamiSupport.setInitialized(builder); huamiSupport.performImmediately(builder); + } else if (value[2] == HuamiService.AUTH_FAIL) { + LOG.error("Authentication failed, disconnecting"); + GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN); + final GBDevice device = getDevice(); + if (device != null) { + GBApplication.deviceService(device).disconnect(); + } } else { return super.onCharacteristicChanged(gatt, characteristic); } - } catch (Exception e) { - GB.toast(getContext(), "Error authenticating Huami device", Toast.LENGTH_LONG, GB.ERROR, e); + } else { + return super.onCharacteristicChanged(gatt, characteristic); } - return true; - } else { - LOG.info("Unhandled characteristic changed: " + characteristicUUID); - return super.onCharacteristicChanged(gatt, characteristic); + } catch (Exception e) { + GB.toast(getContext(), "Error authenticating Huami device", Toast.LENGTH_LONG, GB.ERROR, e); } + return true; } private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java index 533c1daea..da94d5ec9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/init/InitOperation2021.java @@ -21,6 +21,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.SU import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,8 @@ 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; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -193,10 +196,15 @@ public class InitOperation2021 extends InitOperation implements Huami2021Handler } catch (Exception e) { LOG.error("failed initializing device", e); } - return; + } else if (payload[0] == RESPONSE && payload[1] == 0x05 && payload[2] == 0x25) { + LOG.error("Authentication failed, disconnecting"); + GB.toast(getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN); + final GBDevice device = getDevice(); + if (device != null) { + GBApplication.deviceService(device).disconnect(); + } } else { - LOG.info("Unhandled auth payload: {}", GB.hexdump(payload)); - return; + LOG.warn("Unhandled auth payload: {}", GB.hexdump(payload)); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java index fbad5fbab..c1e2971aa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiAuthService.java @@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi; import android.content.SharedPreferences; import android.os.Build; +import android.widget.Toast; import androidx.annotation.Nullable; @@ -47,6 +48,7 @@ import javax.crypto.spec.SecretKeySpec; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService; @@ -116,6 +118,7 @@ public class XiaomiAuthService extends AbstractXiaomiService { final XiaomiProto.Command command = handleWatchNonce(cmd.getAuth().getWatchNonce()); if (command == null) { + GB.toast(getSupport().getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN); LOG.error("handleWatchNonce returned null, disconnecting"); final GBDevice device = getSupport().getDevice(); @@ -142,7 +145,13 @@ public class XiaomiAuthService extends AbstractXiaomiService { getSupport().onAuthSuccess(); } else { - LOG.warn("could not authenticate"); + LOG.warn("Authentication failed, subtype={}, status={}", cmd.getSubtype(), cmd.getStatus()); + GB.toast(getSupport().getContext(), R.string.authentication_failed_check_key, Toast.LENGTH_LONG, GB.WARN); + + final GBDevice device = getSupport().getDevice(); + if (device != null) { + GBApplication.deviceService(device).disconnect(); + } } break; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index a85d44f28..f4124467e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -38,6 +38,7 @@ import android.text.SpannableString; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -446,6 +447,10 @@ public class GB { toast(context, message, displayTime, severity, null); } + public static void toast(final Context context, @StringRes final int message, final int displayTime, final int severity) { + toast(context, context.getString(message), displayTime, severity, null); + } + /** * Creates and display a Toast message using the application context * Can be called from any thread. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d54d9bce..d1ccb202e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1137,6 +1137,7 @@ Minimal activity length (minutes) Authenticating Authentication required + Authentication failed, please check auth key Preferred sleep duration in hours Hardware revision: %1$s Firmware version: %1$s