1
0
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:
Daniel Dakhno 2019-12-27 18:53:21 +01:00
parent a256decfd0
commit a24067a299
6 changed files with 283 additions and 35 deletions

View File

@ -2,13 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fos
import android.os.Build; 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.devices.qhybrid.NotificationHRConfiguration;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; 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.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest; 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.SetDeviceStateRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.NotificationFilterPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest; 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.information.GetDeviceInformationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImagesPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest; 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 { 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) { public FossilHRWatchAdapter(QHybridSupport deviceSupport) {
super(deviceSupport); super(deviceSupport);
} }
@ -40,11 +30,11 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
} }
queueWrite(new VerifyPrivateKeyRequest( 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}, this.getSecretKey(),
getDeviceSupport().getQueue() this
)); ));
try { /*try {
FileInputStream fis = new FileInputStream("/sdcard/Q/images/icWhatsapp.icon"); FileInputStream fis = new FileInputStream("/sdcard/Q/images/icWhatsapp.icon");
byte[] whatsappData = new byte[fis.available()]; byte[] whatsappData = new byte[fis.available()];
fis.read(whatsappData); fis.read(whatsappData);
@ -67,15 +57,18 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
this)); this));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }*/ // icons
queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{ queueWrite(new NotificationFilterPutHRRequest(new NotificationHRConfiguration[]{
new NotificationHRConfiguration("twitter", -1),
new NotificationHRConfiguration("com.whatsapp", -1), new NotificationHRConfiguration("com.whatsapp", -1),
new NotificationHRConfiguration("generic", -1),
// new NotificationHRConfiguration("twitter", -1),
}, this)); }, 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 PlayNotificationRequest("com.whatsapp", "WhatsAp", "wHATSaPP", this));
queueWrite(new nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest("twitter", "Twitter", "huehuehue", this)); queueWrite(new PlayNotificationRequest("twitter", "Twitter", "tWITTER", this));
queueWrite(new GetDeviceInformationRequest(this));
// syncConfiguration(); // syncConfiguration();
@ -90,7 +83,31 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
public boolean playRawNotification(NotificationSpec notificationSpec) { public boolean playRawNotification(NotificationSpec notificationSpec) {
String sender = notificationSpec.sender; String sender = notificationSpec.sender;
if(sender == null) sender = notificationSpec.sourceName; 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; 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;
}
} }

View File

@ -48,9 +48,10 @@ public class PlayNotificationRequest extends FilePutRequest {
byte flags = getFlags(); byte flags = getFlags();
byte uidLength = (byte) 4; byte uidLength = (byte) 4;
byte appBundleCRCLength = (byte) 4; byte appBundleCRCLength = (byte) 4;
String nullTerminatedTitle = StringUtils.terminateNull(title);
Charset charsetUTF8 = Charset.forName("UTF-8"); Charset charsetUTF8 = Charset.forName("UTF-8");
String nullTerminatedTitle = StringUtils.terminateNull(title);
byte[] titleBytes = nullTerminatedTitle.getBytes(charsetUTF8); byte[] titleBytes = nullTerminatedTitle.getBytes(charsetUTF8);
String nullTerminatedSender = StringUtils.terminateNull(sender); String nullTerminatedSender = StringUtils.terminateNull(sender);
byte[] senderBytes = nullTerminatedSender.getBytes(charsetUTF8); byte[] senderBytes = nullTerminatedSender.getBytes(charsetUTF8);
@ -77,7 +78,7 @@ public class PlayNotificationRequest extends FilePutRequest {
lengthBuffer = ByteBuffer.allocate(mainBufferLength - lengthBufferLength); lengthBuffer = ByteBuffer.allocate(mainBufferLength - lengthBufferLength);
lengthBuffer.order(ByteOrder.LITTLE_ENDIAN); lengthBuffer.order(ByteOrder.LITTLE_ENDIAN);
lengthBuffer.putInt(0); lengthBuffer.putInt(10); // messageId
lengthBuffer.putInt(packageCrc); lengthBuffer.putInt(packageCrc);
lengthBuffer.put(titleBytes); lengthBuffer.put(titleBytes);
lengthBuffer.put(senderBytes); lengthBuffer.put(senderBytes);

View File

@ -16,18 +16,20 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; 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; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
public class VerifyPrivateKeyRequest extends FossilRequest { public class VerifyPrivateKeyRequest extends FossilRequest {
private final BtLEQueue queue; private final FossilHRWatchAdapter adapter;
private byte[] key; private byte[] key, randomPhoneNumber;
private boolean isFinished = false; private boolean isFinished = false;
public VerifyPrivateKeyRequest(byte[] key, BtLEQueue queue) { public VerifyPrivateKeyRequest(byte[] key, FossilHRWatchAdapter adapter) {
this.queue = queue; this.adapter = adapter;
this.key = key; this.key = key;
adapter.setPhoneRandomNumber(randomPhoneNumber);
} }
@Override @Override
@ -56,6 +58,11 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
System.arraycopy(result, 0, bytesToEncrypt, 8, 8); System.arraycopy(result, 0, bytesToEncrypt, 8, 8);
System.arraycopy(result, 8, bytesToEncrypt, 0, 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 = 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})); 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); result = cipher.doFinal(bytesToEncrypt);
@ -69,7 +76,7 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
new TransactionBuilder("send encrypted random numbers") new TransactionBuilder("send encrypted random numbers")
.write(characteristic, payload) .write(characteristic, payload)
.queue(this.queue); .queue(this.adapter.getDeviceSupport().getQueue());
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -93,11 +100,11 @@ public class VerifyPrivateKeyRequest extends FossilRequest {
buffer.put((byte) 0x01); buffer.put((byte) 0x01);
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(); return buffer.array();
} }

View File

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

View File

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

View File

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