1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-10 07:07:57 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/AuthenticationHandler.java
hrglpfrmpf c1fd0b77ad Support for Withings Steel HR (#2831)
Co-authored-by: hrglpfrmpf <hrglpfrmpf@web.de>
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2831
Co-authored-by: hrglpfrmpf <hrglpfrmpf@noreply.codeberg.org>
Co-committed-by: hrglpfrmpf <hrglpfrmpf@noreply.codeberg.org>
2023-07-26 17:20:43 +00:00

129 lines
6.0 KiB
Java

/* Copyright (C) 2021 Frank Ertl
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.withingssteelhr;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Build;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.WithingsUUID;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.conversation.AbstractResponseHandler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Challenge;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ChallengeResponse;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.Probe;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeOsVersion;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.ProbeReply;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.datastructures.WithingsStructure;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.Message;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.withingssteelhr.communication.message.WithingsMessageType;
public class AuthenticationHandler extends AbstractResponseHandler {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class);
// TODO: Save this somewhere if we actually decide to use te secret for more security:
private final String secret = "2EM5zNP37QzM00hmP6BFTD92nG15XwNd";
private WithingsSteelHRDeviceSupport support;
private Challenge challengeToSend;
public AuthenticationHandler(WithingsSteelHRDeviceSupport support) {
super(support);
this.support = support;
}
@Override
public void handleResponse(Message response) {
short messageType = response.getType();
if (messageType == WithingsMessageType.PROBE) {
handleProbeReply(response);
} else if (messageType == WithingsMessageType.CHALLENGE) {
handleChallenge(response);
} else {
logger.warn("Received unkown message: " + messageType + ", will ignore this.");
}
}
private void handleChallenge(Message challengeMessage) {
try {
Challenge challenge = getTypeFromReply(Challenge.class, challengeMessage);
ChallengeResponse challengeResponse = new ChallengeResponse();
challengeResponse.setResponse(createResponse(challenge));
Message message = new WithingsMessage(WithingsMessageType.CHALLENGE);
message.addDataStructure(challengeResponse);
challengeToSend = new Challenge();
challengeToSend.setMacAddress(challenge.getMacAddress());
byte[] bArr = new byte[16];
new Random().nextBytes(bArr);
challengeToSend.setChallenge(bArr);
message.addDataStructure(challengeToSend);
support.sendToDevice(message);
} catch (Exception e) {
logger.error("Failed to create response to challenge: " + e.getMessage());
}
}
private void handleProbeReply(Message message) {
ProbeReply probeReply = getTypeFromReply(ProbeReply.class, message);
if (probeReply == null) {
throw new IllegalArgumentException("Message does not contain the required datastructure ProbeReply");
}
ChallengeResponse response = getTypeFromReply(ChallengeResponse.class, message);
if (response == null || Arrays.equals(response.getResponse(), createResponse(challengeToSend))) {
support.getDevice().setFirmwareVersion(String.valueOf(probeReply.getFirmwareVersion()));
} else {
throw new SecurityException("Response is not the one expected!");
}
support.onAuthenticationFinished();
}
private byte[] createResponse(Challenge challenge) {
try {
ByteBuffer allocate = ByteBuffer.allocate(challenge.getChallenge().length + challenge.getMacAddress().getBytes().length + secret.getBytes().length);
allocate.put(challenge.getChallenge());
allocate.put(challenge.getMacAddress().getBytes());
allocate.put(secret.getBytes());
return MessageDigest.getInstance("SHA1").digest(allocate.array());
} catch (NoSuchAlgorithmException e) {
logger.error("Failed to create response to challenge: " + e.getMessage());
}
return new byte[0];
}
private <T extends WithingsStructure> T getTypeFromReply(Class<T> type, Message message) {
for (WithingsStructure structure : message.getDataStructures()) {
if (type.isInstance(structure)) {
return (T)structure;
}
}
return null;
}
}