diff --git a/app/src/main/res/raw/fw_hashes.json b/app/src/main/assets/fw_hashes.json similarity index 50% rename from app/src/main/res/raw/fw_hashes.json rename to app/src/main/assets/fw_hashes.json index 4ffa6e652e..f5a2a34424 100644 --- a/app/src/main/res/raw/fw_hashes.json +++ b/app/src/main/assets/fw_hashes.json @@ -32,5 +32,39 @@ {"block":11501,"count":1,"digest":"FF77"}, {"block":0,"count":0,"digest":"D07B"} ] + }, + { + "fw":"1.24d SDK", + "challenge":"00000000000000000000000000000001", + "sha-256":[ + {"block":0,"count":1,"digest":"6B360B47FFB16641EF9CC778746FE6B1010AB60C69281CE431A46ECC29A72304"}, + {"block":1,"count":1,"digest":"648F50D44E9A31CB4C2682EAD7B8E2E570E5F252035849CD9B08BD4F9106B432"}, + {"block":2,"count":1,"digest":"BAA31588F599E2DBC8AC06CFBF4E710B9B0E3FEA0201AE67E5A9E605DB50D1ED"}, + {"block":3,"count":1,"digest":"48CFA13758F0B8018A97831C11BB1668A39CC76B141C90038551D950F83A43BA"}, + {"block":4,"count":1,"digest":"8932B8E9D93A28EEEAC8FD3477F38A8A384AC895B06E09B8396A7806C3DCFC09"}, + {"block":5,"count":1,"digest":"A3E7F2911AB380B366E47EB9E7DCC0C3D8667EEE3CF355FCCD5B113918873EFC"}, + {"block":6,"count":1,"digest":"32CFCDC12C3AAA6C8FE4935C76B795A81206141AE7C7AB941A4B5F74EB02D844"}, + {"block":7,"count":1,"digest":"1BA7B6ED5F28A0744E177843F2A960CB757B27FF6052C17F9E57B266AEC4F564"}, + {"block":8,"count":1,"digest":"7C1A2C88529DA038C3A124CC049B9A5CD73089C2CD9C66B15DF91020E981B94B"}, + {"block":9,"count":1,"digest":"5F14E10CBE14230EEF9B8896C84998197E56738952A1ECF22202801B9AE64340"}, + {"block":10,"count":1,"digest":"8FF4F496E7C68A654E574F13517EFA4F2058FDFC9E949421DA5ACC855C876F63"}, + {"block":11491,"count":1,"digest":"E45CFEA35B1C4C217A55B78B0D78C841978F1008C3C1917D228EECB0324A0942"}, + {"block":0,"count":0,"digest":"F4199A74F2D4BAF2E282A670989A50B9186665D8EC453D1CED02F4E4019E1C0F"} + ], + "crc-16":[ + {"block":0,"count":1,"digest":"22C3"}, + {"block":1,"count":1,"digest":"ECB2"}, + {"block":2,"count":1,"digest":"4701"}, + {"block":3,"count":1,"digest":"2DD5"}, + {"block":4,"count":1,"digest":"3870"}, + {"block":5,"count":1,"digest":"66CF"}, + {"block":6,"count":1,"digest":"4405"}, + {"block":7,"count":1,"digest":"4913"}, + {"block":8,"count":1,"digest":"F51D"}, + {"block":9,"count":1,"digest":"0013"}, + {"block":10,"count":1,"digest":"5E68"}, + {"block":11491,"count":1,"digest":"4078"}, + {"block":0,"count":0,"digest":"75C3"} + ] } ] \ No newline at end of file diff --git a/app/src/main/res/raw/issuers.json b/app/src/main/assets/issuers.json similarity index 100% rename from app/src/main/res/raw/issuers.json rename to app/src/main/assets/issuers.json diff --git a/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java b/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java index 8dd6124859..0b008ac56d 100644 --- a/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java +++ b/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java @@ -44,7 +44,7 @@ public class VerificationServerProtocol { http.setRequestMethod("POST"); // PUT is another valid option http.setDoOutput(true); - String sRequestBody = getGson().toJson(this); + String sRequestBody = getGson().toJson(this.command); byte[] out = sRequestBody.getBytes(StandardCharsets.UTF_8); int length = out.length; @@ -107,7 +107,7 @@ public class VerificationServerProtocol { } } - static class ResultItem { + public static class ResultItem { public ResultItem(RequestItem request) { CID = request.CID; } @@ -117,15 +117,16 @@ public class VerificationServerProtocol { public Boolean passed; } - static class Answer extends CustomAnswer { + public static class Answer extends CustomAnswer { public ResultItem[] results; } - public Request prepare(TangemCard card) + public static Request prepare(TangemCard card) { Command c=new Command(); c.requests=new RequestItem[1]; - c.requests[0].CID= Util.bytesToHex(card.getCID()); + c.requests[0]=new RequestItem(); + c.requests[0].CID=Util.bytesToHex(card.getCID()); c.requests[0].publicKey=Util.bytesToHex(card.getCardPublicKey()); return new Request(c,Answer.class); diff --git a/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java b/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java index 894cc73af5..a120e481a6 100644 --- a/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java +++ b/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java @@ -4,10 +4,17 @@ import android.content.Context; import android.nfc.tech.IsoDep; import android.util.Log; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.tangem.domain.cardReader.CardProtocol; +import com.tangem.domain.cardReader.FW; import com.tangem.domain.cardReader.NfcManager; import com.tangem.domain.wallet.PINStorage; import com.tangem.domain.wallet.TangemCard; +import com.tangem.util.Util; + +import java.util.Arrays; /** * Created by dvol on 04.02.2018. @@ -59,23 +66,26 @@ public class VerifyCardTask extends Thread { protocol.setPIN(PIN); protocol.run_Read(); PINStorage.setLastUsedPIN(PIN); - mNotifications.OnReadProgress(protocol, 30); + mNotifications.OnReadProgress(protocol, 20); if (isCancelled) return; protocol.run_VerifyCard(); - mNotifications.OnReadProgress(protocol, 60); + mNotifications.OnReadProgress(protocol, 50); Log.i(TAG, "Manufacturer: " + protocol.getCard().getManufacturer().getOfficialName()); if (isCancelled) return; if (protocol.getCard().getStatus() == TangemCard.Status.Loaded) { protocol.run_CheckWalletWithSignatureVerify(); - mNotifications.OnReadProgress(protocol, 90); + mNotifications.OnReadProgress(protocol, 80); } - - -// if (isCancelled) return; -// if (protocol.getCard().getStatus() == TangemCard.Status.Loaded) { -// protocol.run_CheckWithSignatureVerify(); -// } - + if (isCancelled) return; + FW.VerifyCodeRecord record=FW.selectRandomVerifyCodeBlock(mCard.getFirmwareVersion()); + if (isCancelled) return; + if( record!=null ) { + byte[] returnedDigest = protocol.run_VerifyCode(record.hashAlg, record.blockIndex, record.blockCount, record.challenge); + mCard.setCodeConfirmed(Arrays.equals(returnedDigest, record.digest)); + }else{ + mCard.setCodeConfirmed(null); + } + mNotifications.OnReadProgress(protocol, 90); } catch (Exception e) { e.printStackTrace(); protocol.setError(e); diff --git a/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java b/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java index d539008856..7da2757b84 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java +++ b/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java @@ -887,7 +887,7 @@ public class CardProtocol { } } - private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { + public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { if (readResult == null) run_Read(); CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode); rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII")); diff --git a/app/src/main/java/com/tangem/domain/cardReader/FW.java b/app/src/main/java/com/tangem/domain/cardReader/FW.java new file mode 100644 index 0000000000..37ed0185c6 --- /dev/null +++ b/app/src/main/java/com/tangem/domain/cardReader/FW.java @@ -0,0 +1,62 @@ +package com.tangem.domain.cardReader; + +import android.content.Context; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.tangem.util.Util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class FW { + private static JsonArray jaFirmwares=null; + + public static boolean needInit() { + return jaFirmwares == null; + } + + public static void Init(Context context) { + try(InputStream is=context.getAssets().open("fw_hashes.json")) { + try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + JsonParser parser = new JsonParser(); + jaFirmwares = parser.parse(reader).getAsJsonArray(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static class VerifyCodeRecord + { + public String hashAlg; + public int blockIndex; + public int blockCount; + public byte[] challenge; + public byte[] digest; + } + + public static VerifyCodeRecord selectRandomVerifyCodeBlock(String firmwareVersion) throws IOException { + + for(int i=0; i instances=new ArrayList<>(); public static boolean needInit() { - return instances.size()==0; + return instances.size() == 0; } - public static void Init(Context mContext){ + public static void Init(Context context) { try { - JsonArray jaIssuers; - JsonParser jsonParser=new JsonParser(); - jaIssuers=jsonParser.parse(ReadJSONResource(mContext, R.raw.issuers)).getAsJsonArray(); - instances.clear(); - Issuer unknown=new Issuer(); - unknown.id="UNKNOWN"; - unknown.officialName="UNKNOWN"; - instances.add(unknown); +// JsonArray jaIssuers=loadIssuersFile(); +// + Issuer unknown = new Issuer(); + unknown.id = "UNKNOWN"; + unknown.officialName = "UNKNOWN"; - for(JsonElement jeIssuer: jaIssuers) - { - Issuer instance=new Gson().fromJson(jeIssuer,Issuer.class); - instances.add(instance); + try(InputStream is=context.getAssets().open("issuers.json")) { + try ( InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + Type listType = new TypeToken>() { + }.getType(); + instances = new Gson().fromJson(reader, listType); + } } + instances.add(0, unknown); +// for (JsonElement jeIssuer : jaIssuers) { +// Issuer instance = new Gson().fromJson(jeIssuer, Issuer.class); +// instances.add(instance); +// } } catch (Exception e) { e.printStackTrace(); } } - static String ReadJSONResource(Context mContext, int id) { - Resources resources = mContext.getResources(); - InputStream resourceReader = resources.openRawResource(id); - Writer writer = new StringWriter(); - try { - try(BufferedReader reader = new BufferedReader(new InputStreamReader(resourceReader, "UTF-8"))) { - String line = reader.readLine(); - while (line != null) { - writer.write(line); - line = reader.readLine(); - } - } - } catch (Exception e) { - Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e); - } finally { - try { - resourceReader.close(); - } catch (Exception e) { - Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e); - } - } - - return writer.toString(); - } +// private static JsonArray loadIssuersFile() throws IOException { +// String result; +// JsonParser jsonParser = new JsonParser(); +// try (BufferedReader reader = Files.newReader(new File("Issuers.json"), StandardCharsets.UTF_8)) { +// String str; +// StringBuilder buf = new StringBuilder(); +// while ((str = reader.readLine()) != null) { +// buf.append(str); +// buf.append("\n"); +// } +// result = buf.toString(); +// } +// return jsonParser.parse(result).getAsJsonArray(); +// } +// +// static String ReadJSONResource(Context mContext, int id) { +// Resources resources = mContext.getResources(); +// InputStream resourceReader = resources.openRawResource(id); +// Writer writer = new StringWriter(); +// try { +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceReader, "UTF-8"))) { +// String line = reader.readLine(); +// while (line != null) { +// writer.write(line); +// line = reader.readLine(); +// } +// } +// } catch (Exception e) { +// Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e); +// } finally { +// try { +// resourceReader.close(); +// } catch (Exception e) { +// Log.e("ReadJSONResource", "Unhandled exception while using JSONResourceReader", e); +// } +// } +// +// return writer.toString(); +// } public byte[] getPublicDataKey() throws Exception { - if( dataKey==null ) + if (dataKey == null) throw new Exception("Data key not specified!"); return dataKey.getPublicKey(); } public byte[] getPublicTransactionKey() throws Exception { - if( dataKey==null ) + if (dataKey == null) throw new Exception("Transaction key not specified!"); return transactionKey.getPublicKey(); } public byte[] getPrivateDataKey() throws Exception { - if( dataKey==null ) + if (dataKey == null) throw new Exception("Data key not specified!"); return dataKey.getPrivateKey(); } public byte[] getPrivateTransactionKey() throws Exception { - if( transactionKey==null ) + if (transactionKey == null) throw new Exception("Transaction key not specified!"); return transactionKey.getPrivateKey(); } public String getOfficialName() { - return officialName!=null?officialName:id; + return officialName != null ? officialName : id; } public static Issuer FindIssuer(String ID) { diff --git a/app/src/main/java/com/tangem/domain/wallet/TangemCard.java b/app/src/main/java/com/tangem/domain/wallet/TangemCard.java index 1ea3ddcd3f..7250e91f10 100644 --- a/app/src/main/java/com/tangem/domain/wallet/TangemCard.java +++ b/app/src/main/java/com/tangem/domain/wallet/TangemCard.java @@ -269,6 +269,33 @@ public class TangemCard { } } + private Boolean codeConfirmed; + public void setCodeConfirmed(Boolean codeConfirmed) { + this.codeConfirmed = codeConfirmed; + } + + public Boolean isCodeConfirmed() { + return codeConfirmed; + } + + private Boolean onlineVerified; + public void setOnlineVerified(Boolean verified) { + this.onlineVerified = verified; + } + + public Boolean isOnlineVerified() { + return onlineVerified; + } + + private Boolean onlineValidated; + public void setOnlineValidated(Boolean validated) { + this.onlineValidated = validated; + } + + public Boolean isOnlineValidated() { + return onlineValidated; + } + public int getRemainingSignatures() { return remainingSignatures; } @@ -1254,6 +1281,19 @@ public class TangemCard { B.putFloat("rate", rate); B.putFloat("rateAlter", rateAlter); B.putString("confirmTx", GetConfirmTXCount().toString(16)); + + if( codeConfirmed!=null ) + B.putBoolean("codeConfirmed", codeConfirmed); + + if( codeConfirmed!=null ) + B.putBoolean("codeConfirmed", codeConfirmed); + + if( onlineVerified!=null ) + B.putBoolean("onlineVerified", onlineVerified); + + if( onlineValidated!=null ) + B.putBoolean("onlineValidated", onlineValidated); + } public void LoadFromBundle(Bundle B) { @@ -1363,6 +1403,15 @@ public class TangemCard { rateAlter = B.getFloat("rateAlter"); if (B.containsKey("confirmTx")) countConfirmTX = new BigInteger(B.getString("confirmTx"), 16); + + if( B.containsKey("codeConfirmed") ) + codeConfirmed=B.getBoolean("codeConfirmed"); + + if( B.containsKey("onlineVerified") ) + onlineVerified=B.getBoolean("onlineVerified"); + + if( B.containsKey("onlineValidated") ) + onlineValidated=B.getBoolean("onlineValidated"); } public int getCardImageResource() { diff --git a/app/src/main/java/com/tangem/presentation/activity/MainActivity.java b/app/src/main/java/com/tangem/presentation/activity/MainActivity.java index a98b4979f8..8ca05cf583 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.java +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.java @@ -3,6 +3,7 @@ package com.tangem.presentation.activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.os.Bundle; @@ -23,6 +24,7 @@ import android.widget.TextView; import com.scottyab.rootbeer.RootBeer; import com.skyfishjy.library.RippleBackground; +import com.tangem.domain.cardReader.FW; import com.tangem.domain.wallet.DeviceNFCAntennaLocation; import com.tangem.domain.wallet.Issuer; import com.tangem.domain.wallet.LastSignStorage; @@ -217,10 +219,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI if (LastSignStorage.needInit()) { LastSignStorage.Init(context); } - if(Issuer.needInit()) - { - Issuer.Init(context); - } + if( Issuer.needInit() ) Issuer.Init(context); + if( FW.needInit() ) FW.Init(context); } public void showCleanButton() { diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.java b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.java index ab4592b573..3f2fa62945 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.java +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.java @@ -35,9 +35,11 @@ import com.google.zxing.WriterException; import com.tangem.data.network.request.ElectrumRequest; import com.tangem.data.network.request.ExchangeRequest; import com.tangem.data.network.request.InfuraRequest; +import com.tangem.data.network.request.VerificationServerProtocol; import com.tangem.data.network.task.ElectrumTask; import com.tangem.data.network.task.ExchangeTask; import com.tangem.data.network.task.InfuraTask; +import com.tangem.data.network.task.VerificationServerTask; import com.tangem.data.nfc.VerifyCardTask; import com.tangem.domain.cardReader.CardProtocol; import com.tangem.domain.cardReader.NfcManager; @@ -113,7 +115,8 @@ public class LoadedWallet extends Fragment implements SwipeRefreshLayout.OnRefre private ImageView ivPIN2orSecurityDelay; private ImageView ivDeveloperVersion; private SwipeRefreshLayout mSwipeRefreshLayout; - private List updateTasks = new ArrayList<>(); + private List updateTasks = new ArrayList<>(); + OnlineVerifyTask onlineVerifyTask; private NfcManager mNfcManager; private boolean lastReadSuccess = true; private VerifyCardTask verifyCardTask = null; @@ -561,6 +564,42 @@ public class LoadedWallet extends Fragment implements SwipeRefreshLayout.OnRefre } } + private class OnlineVerifyTask extends VerificationServerTask { + + @Override + protected void onCancelled() { + super.onCancelled(); + updateTasks.remove(this); + onlineVerifyTask=null; + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + protected void onPostExecute(List requests) { + super.onPostExecute(requests); + Log.i("OnlineVerifyTask", "onPostExecute[" + String.valueOf(updateTasks.size()) + "]"); + updateTasks.remove(this); + onlineVerifyTask=null; + + for (VerificationServerProtocol.Request request : requests) { + if (request.error == null) { + VerificationServerProtocol.Verify.Answer answer = (VerificationServerProtocol.Verify.Answer) request.answer; + if (answer.error == null) { + mCard.setOnlineVerified(answer.results[0].passed); + } else { + mCard.setOnlineVerified(null); + errorOnUpdate(request.error); + } + } else { + mCard.setOnlineVerified(null); + errorOnUpdate(request.error); + } + } + if (updateTasks.size() == 0) mSwipeRefreshLayout.setRefreshing(false); + } + } + + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -639,6 +678,12 @@ public class LoadedWallet extends Fragment implements SwipeRefreshLayout.OnRefre mSwipeRefreshLayout.postDelayed(this::onRefresh, 1000); } + if ( (mCard.isOnlineVerified()==null || !mCard.isOnlineVerified()) && onlineVerifyTask ==null) { + onlineVerifyTask = new OnlineVerifyTask(); + updateTasks.add(onlineVerifyTask); + onlineVerifyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VerificationServerProtocol.Verify.prepare(mCard)); + } + imgLookup.setOnClickListener(v15 -> { if (!mCard.hasBalanceInfo()) return; CoinEngine engineClick = CoinEngineFactory.Create(mCard.getBlockchain()); @@ -734,7 +779,7 @@ public class LoadedWallet extends Fragment implements SwipeRefreshLayout.OnRefre @Override public void onStop() { super.onStop(); - for (UpdateWalletInfoTask ut : updateTasks) { + for (AsyncTask ut : updateTasks) { ut.cancel(true); } mNfcManager.onStop(); @@ -926,6 +971,12 @@ public class LoadedWallet extends Fragment implements SwipeRefreshLayout.OnRefre updateViews(); + if ( (mCard.isOnlineVerified()==null || !mCard.isOnlineVerified()) && onlineVerifyTask ==null) { + onlineVerifyTask = new OnlineVerifyTask(); + updateTasks.add(onlineVerifyTask); + onlineVerifyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VerificationServerProtocol.Verify.prepare(mCard)); + } + CoinEngine engine = CoinEngineFactory.Create(mCard.getBlockchain()); if (mCard.getBlockchain() == Blockchain.Bitcoin || mCard.getBlockchain() == Blockchain.BitcoinTestNet) {