/* Copyright (C) 2021-2023 Gaignon Damien Copyright (C) 2022-2023 MartinJM 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 . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.Random; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig.HiChain; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; public class GetHiChainRequest extends Request { private static final Logger LOG = LoggerFactory.getLogger(GetHiChainRequest.class); // Attributs used along all operation private HiChain.Request req = null; private byte operationCode = 0x02; private byte step; private byte[] authIdSelf = null; private byte[] authIdPeer = null; private byte[] randSelf = null; private byte[] randPeer = null; private long requestId = 0x00; private JSONObject json = null; private byte[] sessionKey = null; // Attributs used once private byte[] seed = null; private byte[] challenge = null; private byte[] psk = null; public GetHiChainRequest(HuaweiSupportProvider support, boolean firstConnection) { super(support); this.serviceId = DeviceConfig.id; this.commandId = HiChain.id; if (firstConnection) { operationCode = 0x01; } this.step = 0x01; } public GetHiChainRequest(Request prevReq) { super(prevReq.supportProvider); this.serviceId = DeviceConfig.id; this.commandId = HiChain.id; GetHiChainRequest hcReq = (GetHiChainRequest)prevReq; this.req = hcReq.req; this.requestId = (Long)hcReq.requestId; this.operationCode = (byte)hcReq.operationCode; this.step = (byte)hcReq.step; this.authIdSelf = hcReq.authIdSelf; this.authIdPeer = hcReq.authIdPeer; this.randSelf = hcReq.randSelf; this.randPeer = hcReq.randPeer; this.psk = hcReq.psk; this.json = hcReq.json; this.sessionKey = hcReq.sessionKey; } @Override protected List createRequest() throws RequestCreationException { if (requestId == 0x00) { requestId = System.currentTimeMillis(); } LOG.debug("Request operationCode: " + operationCode + " - step: " + step); if (req == null) req = new HiChain.Request( operationCode, requestId, supportProvider.getAndroidId(), HuaweiConstants.GROUP_ID ); HuaweiPacket packet = null; int messageId = step; try { if (step == 0x01) { seed = new byte[32]; new Random().nextBytes(seed); randSelf = new byte[16]; new Random().nextBytes(randSelf); HiChain.Request.StepOne stepOne = req.new StepOne(paramsProvider, messageId, randSelf, seed ); packet = stepOne; } else if (step == 0x02) { byte[] message = ByteBuffer .allocate(randPeer.length + randSelf.length + authIdSelf.length + authIdPeer.length) .put(randSelf) .put(randPeer) .put(authIdPeer) .put(authIdSelf) .array(); byte[] selfToken = CryptoUtils.calcHmacSha256(psk, message); HiChain.Request.StepTwo stepTwo = req.new StepTwo(paramsProvider, messageId, selfToken); packet = stepTwo; } else if (step == 0x03) { byte[] salt = ByteBuffer .allocate( randSelf.length + randPeer.length) .put(randSelf) .put(randPeer) .array(); byte[] info = "hichain_iso_session_key".getBytes(StandardCharsets.UTF_8); sessionKey = CryptoUtils.hkdfSha256(psk, salt, info, 32); LOG.debug("sessionKey: " + GB.hexdump(sessionKey)); if (operationCode == 0x01) { byte[] nonce = new byte[12]; new Random().nextBytes(nonce); challenge = new byte[16]; new Random().nextBytes(challenge); byte[] aad = "hichain_iso_exchange".getBytes(StandardCharsets.UTF_8); byte[] encData = CryptoUtils.encryptAES_GCM_NoPad(challenge, sessionKey, nonce, aad); //aesGCMNoPadding encrypt(sessionKey as key, challenge to encrypt, nonce as iv) HiChain.Request.StepThree stepThree = req.new StepThree(paramsProvider, messageId, nonce, encData); packet = stepThree; } else { step += 0x01; } } if (step == 0x04) { LOG.debug("Step " + step); byte[] nonce = new byte[12]; new Random().nextBytes(nonce); byte[] input = new byte[]{0x00, 0x00, 0x00, 0x00}; byte[] aad = "hichain_iso_result".getBytes(StandardCharsets.UTF_8); byte[] encResult = CryptoUtils.encryptAES_GCM_NoPad(input, sessionKey, nonce, aad); HiChain.Request.StepFour stepFour = req.new StepFour(paramsProvider, messageId, nonce, encResult); packet = stepFour; } LOG.debug("JSONObject on creation:" + (new JSONObject(packet.getTlv().getString(1))).getJSONObject("payload").toString()); return packet.serialize(); } catch (Exception e) { // TODO: Make exception explicit throw new RequestCreationException("HiChain exception", e); } //return null; } @Override protected void processResponse() throws ResponseParseException { if (!(receivedPacket instanceof HiChain.Response)) throw new ResponseTypeMismatchException(receivedPacket, HiChain.Response.class); // TODO: handle failure codes HiChain.Response response = (HiChain.Response)receivedPacket; step = response.step; LOG.debug("Response operationCode: " + operationCode + " - step: " + step); try { if (step == 0x04) { if (operationCode == 0x01) { LOG.debug("Finished auth operation, go to bind"); GetHiChainRequest nextRequest = new GetHiChainRequest(supportProvider, false); nextRequest.setFinalizeReq(this.finalizeReq); this.nextRequest(nextRequest); } else { LOG.debug("Finished bind operation"); byte[] salt = ByteBuffer .allocate( randSelf.length + randPeer.length) .put(randSelf) .put(randPeer) .array(); byte[] info = "hichain_return_key".getBytes(StandardCharsets.UTF_8); byte[] key = CryptoUtils.hkdfSha256(sessionKey, salt, info, 32); LOG.debug("Final sessionKey:" + GB.hexdump(key)); paramsProvider.setSecretKey(key); } } else { if (step == 0x01) { byte[] key = null; authIdSelf = supportProvider.getAndroidId(); authIdPeer = response.step1Data.peerAuthId; randPeer = response.step1Data.isoSalt; byte[] peerToken = response.step1Data.token; // GeneratePsk if (operationCode == 0x01) { String pinCodeHexStr = StringUtils.bytesToHex(paramsProvider.getPinCode()); byte[] pinCode = pinCodeHexStr.getBytes(StandardCharsets.UTF_8); key = CryptoUtils.digest(pinCode); } else { key = supportProvider.getSecretKey(); } psk = CryptoUtils.calcHmacSha256(key, seed); byte[] message = ByteBuffer .allocate(randPeer.length + randSelf.length + authIdSelf.length + authIdPeer.length) .put(randPeer) .put(randSelf) .put(authIdSelf) .put(authIdPeer) .array(); byte[] tokenCheck = CryptoUtils.calcHmacSha256(psk, message); if (!Arrays.equals(peerToken, tokenCheck)) { LOG.debug("tokenCheck: " + GB.hexdump(tokenCheck) + " is different than " + GB.hexdump(peerToken)); throw new RequestCreationException("tokenCheck: " + GB.hexdump(tokenCheck) + " is different than " + GB.hexdump(peerToken)); } else { LOG.debug("Token check passes"); } } else if (step == 0x02) { byte[] returnCodeMac = response.step2Data.returnCodeMac; byte[] returnCodeMacCheck = CryptoUtils.calcHmacSha256(psk, new byte[]{0x00, 0x00, 0x00, 0x00}); if (!Arrays.equals(returnCodeMacCheck, returnCodeMac)) { LOG.debug("returnCodeMacCheck: " + GB.hexdump(returnCodeMacCheck) + " is different than " + GB.hexdump(returnCodeMac)); throw new RequestCreationException("returnCodeMacCheck: " + GB.hexdump(returnCodeMacCheck) + " is different than " + GB.hexdump(returnCodeMac)); } else { LOG.debug("returnCodeMac check passes"); } } else if (step == 0x03) { if (operationCode == 0x01) { byte[] nonce = response.step3Data.nonce; byte[] encAuthToken = response.step3Data.encAuthToken; byte[] authToken = CryptoUtils.decryptAES_GCM_NoPad(encAuthToken, sessionKey, nonce, challenge); supportProvider.setSecretKey(authToken); LOG.debug("Set secret key"); } } this.step += 0x01; GetHiChainRequest nextRequest = new GetHiChainRequest(this); nextRequest.setFinalizeReq(this.finalizeReq); this.nextRequest(nextRequest); } } catch (Exception e) { // TODO: Specify exceptions throw new ResponseParseException(e); } } }