mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-02-04 22:17:31 +01:00
WIP: added encrypted setting request
This commit is contained in:
parent
a256decfd0
commit
a24067a299
@ -2,13 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fos
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -16,19 +9,16 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.Image;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.information.GetDeviceInformationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.PlayNotificationHRRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.SetCurrentStepCountRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.utils.StringUtils;
|
||||
|
||||
public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
private byte[] secretKey = new byte[]{(byte) 0x60, (byte) 0x26, (byte) 0xB7, (byte) 0xFD, (byte) 0xB2, (byte) 0x6D, (byte) 0x05, (byte) 0x5E, (byte) 0xDA, (byte) 0xF7, (byte) 0x4B, (byte) 0x49, (byte) 0x98, (byte) 0x78, (byte) 0x02, (byte) 0x38};
|
||||
private byte[] phoneRandomNumber;
|
||||
private byte[] watchRandomNumber;
|
||||
|
||||
public FossilHRWatchAdapter(QHybridSupport deviceSupport) {
|
||||
super(deviceSupport);
|
||||
}
|
||||
@ -40,11 +30,11 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
}
|
||||
|
||||
queueWrite(new VerifyPrivateKeyRequest(
|
||||
new byte[]{(byte) 0x60, (byte) 0x26, (byte) 0xB7, (byte) 0xFD, (byte) 0xB2, (byte) 0x6D, (byte) 0x05, (byte) 0x5E, (byte) 0xDA, (byte) 0xF7, (byte) 0x4B, (byte) 0x49, (byte) 0x98, (byte) 0x78, (byte) 0x02, (byte) 0x38},
|
||||
getDeviceSupport().getQueue()
|
||||
this.getSecretKey(),
|
||||
this
|
||||
));
|
||||
|
||||
try {
|
||||
/*try {
|
||||
FileInputStream fis = new FileInputStream("/sdcard/Q/images/icWhatsapp.icon");
|
||||
byte[] whatsappData = new byte[fis.available()];
|
||||
fis.read(whatsappData);
|
||||
@ -67,15 +57,18 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
this));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}*/ // icons
|
||||
|
||||
queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
|
||||
new NotificationHRConfiguration("twitter", -1),
|
||||
new NotificationHRConfiguration("com.whatsapp", -1),
|
||||
new NotificationHRConfiguration("generic", -1),
|
||||
// new NotificationHRConfiguration("twitter", -1),
|
||||
}, this));
|
||||
|
||||
queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest("com.whatsapp", "Test App", "this is a generic message", this));
|
||||
queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest("twitter", "Twitter", "huehuehue", this));
|
||||
queueWrite(new PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
|
||||
queueWrite(new PlayNotificationRequest("twitter", "Twitter", "tWITTER", this));
|
||||
|
||||
queueWrite(new GetDeviceInformationRequest(this));
|
||||
|
||||
// syncConfiguration();
|
||||
|
||||
@ -90,7 +83,31 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
public boolean playRawNotification(NotificationSpec notificationSpec) {
|
||||
String sender = notificationSpec.sender;
|
||||
if(sender == null) sender = notificationSpec.sourceName;
|
||||
queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest("generic", notificationSpec.sourceName, notificationSpec.body, this));
|
||||
queueWrite(new PlayNotificationRequest("generic", notificationSpec.sourceName, notificationSpec.body, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(byte[] secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public void setPhoneRandomNumber(byte[] phoneRandomNumber) {
|
||||
this.phoneRandomNumber = phoneRandomNumber;
|
||||
}
|
||||
|
||||
public byte[] getPhoneRandomNumber() {
|
||||
return phoneRandomNumber;
|
||||
}
|
||||
|
||||
public void setWatchRandomNumber(byte[] watchRandomNumber) {
|
||||
this.watchRandomNumber = watchRandomNumber;
|
||||
}
|
||||
|
||||
public byte[] getWatchRandomNumber() {
|
||||
return watchRandomNumber;
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ public class PlayNotificationRequest extends FilePutRequest {
|
||||
byte flags = getFlags();
|
||||
byte uidLength = (byte) 4;
|
||||
byte appBundleCRCLength = (byte) 4;
|
||||
String nullTerminatedTitle = StringUtils.terminateNull(title);
|
||||
|
||||
Charset charsetUTF8 = Charset.forName("UTF-8");
|
||||
|
||||
String nullTerminatedTitle = StringUtils.terminateNull(title);
|
||||
byte[] titleBytes = nullTerminatedTitle.getBytes(charsetUTF8);
|
||||
String nullTerminatedSender = StringUtils.terminateNull(sender);
|
||||
byte[] senderBytes = nullTerminatedSender.getBytes(charsetUTF8);
|
||||
@ -77,7 +78,7 @@ public class PlayNotificationRequest extends FilePutRequest {
|
||||
|
||||
lengthBuffer = ByteBuffer.allocate(mainBufferLength - lengthBufferLength);
|
||||
lengthBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
lengthBuffer.putInt(0);
|
||||
lengthBuffer.putInt(10); // messageId
|
||||
lengthBuffer.putInt(packageCrc);
|
||||
lengthBuffer.put(titleBytes);
|
||||
lengthBuffer.put(senderBytes);
|
||||
|
@ -16,18 +16,20 @@ import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
|
||||
public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
private final BtLEQueue queue;
|
||||
private byte[] key;
|
||||
private final FossilHRWatchAdapter adapter;
|
||||
private byte[] key, randomPhoneNumber;
|
||||
private boolean isFinished = false;
|
||||
|
||||
public VerifyPrivateKeyRequest(byte[] key, BtLEQueue queue) {
|
||||
this.queue = queue;
|
||||
public VerifyPrivateKeyRequest(byte[] key, FossilHRWatchAdapter adapter) {
|
||||
this.adapter = adapter;
|
||||
this.key = key;
|
||||
|
||||
adapter.setPhoneRandomNumber(randomPhoneNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -56,6 +58,11 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
System.arraycopy(result, 0, bytesToEncrypt, 8, 8);
|
||||
System.arraycopy(result, 8, bytesToEncrypt, 0, 8);
|
||||
|
||||
byte[] watchRandomNumber = new byte[8];
|
||||
System.arraycopy(result, 0, watchRandomNumber, 0, 8);
|
||||
|
||||
adapter.setWatchRandomNumber(watchRandomNumber);
|
||||
|
||||
cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
|
||||
result = cipher.doFinal(bytesToEncrypt);
|
||||
@ -69,7 +76,7 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
|
||||
new TransactionBuilder("send encrypted random numbers")
|
||||
.write(characteristic, payload)
|
||||
.queue(this.queue);
|
||||
.queue(this.adapter.getDeviceSupport().getQueue());
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -93,11 +100,11 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
|
||||
buffer.put((byte) 0x01);
|
||||
buffer.put((byte) 0x01);
|
||||
|
||||
byte[] random = new byte[8];
|
||||
this.randomPhoneNumber = new byte[8];
|
||||
|
||||
new Random().nextBytes(random);
|
||||
new Random().nextBytes(randomPhoneNumber);
|
||||
|
||||
buffer.put(random);
|
||||
buffer.put(randomPhoneNumber);
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
@ -0,0 +1,165 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
|
||||
|
||||
public abstract class FileEncryptedGetRequest extends FossilRequest {
|
||||
private short handle;
|
||||
private FossilHRWatchAdapter adapter;
|
||||
|
||||
private ByteBuffer fileBuffer;
|
||||
|
||||
private byte[] fileData;
|
||||
|
||||
private boolean finished = false;
|
||||
|
||||
public FileEncryptedGetRequest(short handle, FossilHRWatchAdapter adapter) {
|
||||
this.handle = handle;
|
||||
this.adapter = adapter;
|
||||
|
||||
this.data =
|
||||
createBuffer()
|
||||
.putShort(handle)
|
||||
.putInt(0)
|
||||
.putInt(0xFFFFFFFF)
|
||||
.array();
|
||||
}
|
||||
|
||||
public FossilWatchAdapter getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished(){
|
||||
return finished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResponse(BluetoothGattCharacteristic characteristic) {
|
||||
byte[] value = characteristic.getValue();
|
||||
byte first = value[0];
|
||||
if(characteristic.getUuid().toString().equals("3dda0003-957f-7d4a-34a6-74696673696d")){
|
||||
if((first & 0x0F) == 1){
|
||||
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
short handle = buffer.getShort(1);
|
||||
int size = buffer.getInt(4);
|
||||
|
||||
byte status = buffer.get(3);
|
||||
|
||||
if(status != 0){
|
||||
throw new RuntimeException("FileGet error: " + status);
|
||||
}
|
||||
|
||||
if(this.handle != handle){
|
||||
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||
}
|
||||
log("file size: " + size);
|
||||
fileBuffer = ByteBuffer.allocate(size);
|
||||
}else if((first & 0x0F) == 8){
|
||||
this.finished = true;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(value);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
short handle = buffer.getShort(1);
|
||||
if(this.handle != handle){
|
||||
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||
}
|
||||
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(this.fileData);
|
||||
|
||||
int crcExpected = buffer.getInt(8);
|
||||
|
||||
if((int) crc.getValue() != crcExpected){
|
||||
throw new RuntimeException("handle: " + handle + " expected: " + this.handle);
|
||||
}
|
||||
|
||||
this.handleFileData(this.fileData);
|
||||
}
|
||||
}else if(characteristic.getUuid().toString().equals("3dda0004-957f-7d4a-34a6-74696673696d")){
|
||||
SecretKeySpec keySpec = new SecretKeySpec(this.adapter.getSecretKey(), "AES");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
|
||||
byte[] fileIV = new byte[16];
|
||||
|
||||
|
||||
byte[] phoneRandomNumber = adapter.getPhoneRandomNumber();
|
||||
byte[] watchRandomNumber = adapter.getWatchRandomNumber();
|
||||
|
||||
System.arraycopy(phoneRandomNumber, 0, fileIV, 2, 6);
|
||||
System.arraycopy(watchRandomNumber, 0, fileIV, 9, 7);
|
||||
|
||||
fileIV[7]++;
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(fileIV));
|
||||
|
||||
byte[] result = cipher.doFinal(value);
|
||||
|
||||
fileBuffer.put(result, 1, result.length - 1);
|
||||
if((result[0] & 0x80) == 0x80){
|
||||
this.fileData = fileBuffer.array();
|
||||
}
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getRequestUUID() {
|
||||
return UUID.fromString("3dda0003-957f-7d4a-34a6-74696673696d");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStartSequence() {
|
||||
return new byte[]{1};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPayloadLength() {
|
||||
return 11;
|
||||
}
|
||||
|
||||
abstract public void handleFileData(byte[] fileData);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Copyright (C) 2019 Daniel Dakhno
|
||||
|
||||
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 <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest;
|
||||
|
||||
public abstract class FileEncryptedLookupAndGetRequest extends FileLookupRequest {
|
||||
public FileEncryptedLookupAndGetRequest(byte fileType, FossilHRWatchAdapter adapter) {
|
||||
super(fileType, adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFileLookup(short fileHandle){
|
||||
getAdapter().queueWrite(new FileEncryptedGetRequest(getHandle(), (FossilHRWatchAdapter) getAdapter()) {
|
||||
@Override
|
||||
public void handleFileData(byte[] fileData) {
|
||||
FileEncryptedLookupAndGetRequest.this.handleFileData(fileData);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
abstract public void handleFileData(byte[] fileData);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.information;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedGetRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedLookupAndGetRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.utils.StringUtils;
|
||||
|
||||
public class GetDeviceInformationRequest extends FileEncryptedLookupAndGetRequest {
|
||||
public GetDeviceInformationRequest(FossilHRWatchAdapter adapter) {
|
||||
super((byte) 0x08, adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFileData(byte[] fileData) {
|
||||
log("device info: " + StringUtils.bytesToHex(fileData));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user