From 28f58e805989753a71f3f0a10f0b86016a346724 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Jul 2018 09:24:14 +0300 Subject: [PATCH 1/4] Updated on 2026-08-14 --- .idea/caches/build_file_checksums.ser | Bin 536 -> 536 bytes app/build.gradle | 1 + .../request/VerificationServerProtocol.java | 192 ++++++++++ .../network/task/VerificationServerTask.java | 42 +++ .../com/tangem/domain/BitcoinNodeTestNet.kt | 5 +- .../tangem/domain/cardReader/CardCrypto.java | 22 +- .../domain/cardReader/CardProtocol.java | 4 +- .../domain/cardReader/ResponseApdu.java | 2 +- .../java/com/tangem/domain/wallet/Issuer.java | 333 +++++++++++++----- .../com/tangem/domain/wallet/TangemCard.java | 6 +- .../presentation/activity/MainActivity.java | 5 + app/src/main/res/raw/fw_hashes.json | 36 ++ app/src/main/res/raw/issuers.json | 39 ++ 13 files changed, 584 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java create mode 100644 app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java create mode 100644 app/src/main/res/raw/fw_hashes.json create mode 100644 app/src/main/res/raw/issuers.json diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index de2ab77eefa4bdedc46096f6365940f590a640f5..a879c3a9a527147d0b76512a179a22b783a50f91 100644 GIT binary patch delta 167 zcmbQiGJ|Ep43->q2d0U0`~w6M3kqVAN;7j(^wNtGQ*u&Eix`-qoz{F7|G~x)@_>nf zp`w6+fk9x8LSb*y?8Tfvu6||gySI2(2?Kv|YDr0EUV1T1Vt_R1U!8J delta 175 zcmbQiGJ|Ep43^ZzzjsZX<8RJioLW+nnU`LymtK^Zl9O6m#K08owC1z;4>pdF2TTkM z6$K0o3G(9yhRUc}m{^U1|(vzDRg(kmd)X^(p5J)U2&`&DO%t?V+TF6kxzy`9%(dS_D SW9}*&#>o>zyB2*7sQ>_)zdz>y diff --git a/app/build.gradle b/app/build.gradle index e8e6a9abfb..41058ef901 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,6 +61,7 @@ dependencies { implementation 'org.bitcoinj:bitcoinj-core:0.14.4' implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'info.hoang8f:android-segmented:1.0.6' + implementation 'com.google.code.gson:gson:2.8.5' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 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 new file mode 100644 index 0000000000..8dd6124859 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java @@ -0,0 +1,192 @@ +package com.tangem.data.network.request; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.tangem.domain.wallet.TangemCard; +import com.tangem.util.Util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +public class VerificationServerProtocol { + + public static class Request + { + public Request(CustomCommand command, Class answerClass) + { + this.command=command; + this.answerClass=answerClass; + } + public String error; + public CustomCommand command; + public CustomAnswer answer; + public Type answerClass; + + public void doPost(String hostURL) throws IOException { + URL url = new URL(hostURL+"/"+command.getURL()); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + try { + http.setConnectTimeout(30000); + http.setReadTimeout(30000); + http.setRequestMethod("POST"); // PUT is another valid option + http.setDoOutput(true); + + String sRequestBody = getGson().toJson(this); + + byte[] out = sRequestBody.getBytes(StandardCharsets.UTF_8); + int length = out.length; + + http.setFixedLengthStreamingMode(length); + http.setRequestProperty("Content-Type", "application/json"); + http.connect(); + try (OutputStream os = http.getOutputStream()) { + os.write(out); + } + try (InputStream is = http.getInputStream()) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + answer = getGson().fromJson(br, answerClass); + } + } + } + finally { + http.disconnect(); + } + } + } + + static abstract class CustomCommand { + public abstract String getURL(); + + public byte[] toBytes() + { + return getGson().toJson(this).getBytes(StandardCharsets.UTF_8); + } + + @Override + public String toString() { + return getGson().toJson(this); + } + + } + + static class CustomAnswer { + public String error; + + @Override + public String toString() { + return getGson().toJson(this); + } + } + + public static class Verify { + + static class RequestItem { + public String CID; + public String publicKey; + } + + static class Command extends CustomCommand { + public RequestItem[] requests; + + @Override + public String getURL() { + return "verify"; + } + } + + static class ResultItem { + public ResultItem(RequestItem request) { + CID = request.CID; + } + + public String error; + public String CID; + public Boolean passed; + } + + static class Answer extends CustomAnswer { + public ResultItem[] results; + } + + public Request prepare(TangemCard card) + { + Command c=new Command(); + c.requests=new RequestItem[1]; + c.requests[0].CID= Util.bytesToHex(card.getCID()); + c.requests[0].publicKey=Util.bytesToHex(card.getCardPublicKey()); + + return new Request(c,Answer.class); + } + } + + public static class Validate { + + static class RequestItem { + public String CID; + public int counter; + public String signature; + } + + static class Command extends CustomCommand { + public RequestItem[] requests; + + @Override + public String getURL() { + return "validate"; + } + } + + static class ResultItem { + public ResultItem(RequestItem request) { + CID = request.CID; + } + public String error; + public String CID; + public Integer previousCounter; + public Boolean passed; + } + + static class Answer extends CustomAnswer { + public ResultItem[] results; + } + + public Request prepare(TangemCard card, int ValidationCounter, byte[] ValidationSignature) + { + Command c=new Command(); + c.requests=new RequestItem[1]; + c.requests[0].CID= Util.bytesToHex(card.getCID()); + c.requests[0].counter=ValidationCounter; + c.requests[0].signature=Util.bytesToHex(ValidationSignature); + return new Request(new Command(),Answer.class); + } + } + + + public static Date strToDate(String date) throws ParseException { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + return new Date(formatter.parse(date).getTime()); + } + + public static String dateToStr(Date date) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + return formatter.format(date); + } + + public static Gson getGson() { + return new GsonBuilder().create(); + } + +} diff --git a/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java b/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java new file mode 100644 index 0000000000..6e83e322ac --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java @@ -0,0 +1,42 @@ +package com.tangem.data.network.task; + +import android.os.AsyncTask; + +import com.tangem.data.network.request.VerificationServerProtocol; + +import java.util.ArrayList; +import java.util.List; + +public class VerificationServerTask extends AsyncTask> { + public static final String hostURL ="https://tangem-webapp.appspot.com"; + + public VerificationServerTask() { + + } + + protected List doInBackground(VerificationServerProtocol.Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (VerificationServerProtocol.Request request : result) { + try { + request.doPost(hostURL); + } catch (Exception e) { + request.error = e.getMessage(); + if( request.error==null || request.error.isEmpty() ) + { + request.error = e.getClass().getName(); + } + } + } + + return result; + } + + public String getValidationNodeDescription() { + return hostURL; + } + + } diff --git a/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt b/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt index 19eb277f5a..2fda931758 100644 --- a/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt +++ b/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt @@ -1,9 +1,8 @@ package com.tangem.domain enum class BitcoinNodeTestNet(val host: String, val port: Int) { - arihanc_com("testnetnode.arihanc.com", 51001), - hsmiths_com("testnet.hsmiths.com", 53011), qtornado_com("testnet.qtornado.com", 51001), + hsmiths_com("testnet.hsmiths.com", 53011), bauerj_eu("testnet1.bauerj.eu", 50001), - + arihanc_com("testnetnode.arihanc.com", 51001), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/cardReader/CardCrypto.java b/app/src/main/java/com/tangem/domain/cardReader/CardCrypto.java index fc44b64f60..7b65ce619b 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/CardCrypto.java +++ b/app/src/main/java/com/tangem/domain/cardReader/CardCrypto.java @@ -179,12 +179,12 @@ public class CardCrypto { Log.e("cardCrypto","enc:" +Util.bytesToHex(enc)); throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)); } - + if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res)) { throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res)); } - + return res; } @@ -206,7 +206,7 @@ public class CardCrypto { * @return the PBDKF2 hash of the password */ public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations) - throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + throws InvalidKeyException { return PBKDF2.deriveKey(password, salt, iterations); } @@ -219,22 +219,20 @@ public class CardCrypto { return mEncryptedData; } - public static byte[] Decrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - try { + public static byte[] Decrypt(byte[] key, byte[] data, boolean UsePKCS7) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { + if (UsePKCS7) { SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); - byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length)); return decryptedData; - } - catch (Exception e) - { + } else { SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING"); Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16])); - byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length )); - Log.e("decrypt",Util.bytesToHex(decryptedData)); - throw e; + byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length)); + return decryptedData; } } } 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 dae5d50b8e..d539008856 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java +++ b/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java @@ -539,7 +539,7 @@ public class CardProtocol { } catch (Exception ee) { ee.printStackTrace(); Log.e(logTag, "Cannot get issuer"); - mCard.setIssuer(Issuer.Unknown); + mCard.setIssuer(Issuer.Unknown()); } } @@ -830,7 +830,7 @@ public class CardProtocol { rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature); } if (issuerData != null) { - if (issuer == null || issuer == Issuer.Unknown) + if (issuer == null || issuer == Issuer.Unknown()) throw new Exception("Need known Issuer to write issuer Data"); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData); diff --git a/app/src/main/java/com/tangem/domain/cardReader/ResponseApdu.java b/app/src/main/java/com/tangem/domain/cardReader/ResponseApdu.java index f6160245ef..c4bddff265 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/ResponseApdu.java +++ b/app/src/main/java/com/tangem/domain/cardReader/ResponseApdu.java @@ -59,7 +59,7 @@ public class ResponseApdu { responseApdu.mSw2 = ((int) data[1] & 0xFF); return responseApdu; }else if( data.length>=18 ){ - byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2)); + byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2),true); ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedData); byte[] baLength = new byte[2]; diff --git a/app/src/main/java/com/tangem/domain/wallet/Issuer.java b/app/src/main/java/com/tangem/domain/wallet/Issuer.java index cf37b68369..e5b395dab3 100644 --- a/app/src/main/java/com/tangem/domain/wallet/Issuer.java +++ b/app/src/main/java/com/tangem/domain/wallet/Issuer.java @@ -1,120 +1,289 @@ package com.tangem.domain.wallet; -import com.tangem.domain.cardReader.CardCrypto; +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.tangem.domain.cardReader.CardCrypto; +import com.tangem.util.Util; +import com.tangem.wallet.R; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Created by dvol on 14.11.2017. */ -public enum Issuer { - Unknown("Unknown", "Unknown", null, null, null, null), - SMART_CASH_AG("SMART CASH AG", "SMART CASH AG", - IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), - IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), - TANGEM_SDK("TANGEM SDK", "TANGEM SDK", - IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), - IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), - TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey) - ; +//public enum Issuer { +// Unknown("Unknown", "Unknown", null, null, null, null), +// SMART_CASH_AG("SMART CASH AG", "SMART CASH AG", +// IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), +// IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), +// TANGEM_SDK("TANGEM SDK", "TANGEM SDK", +// IssuerKeyStorage.sdkPrivateDataKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateDataKey), +// IssuerKeyStorage.sdkPrivateTransactionKey, IssuerKeyStorage.GeneratePublicKey(IssuerKeyStorage.sdkPrivateTransactionKey)), +// TANGEM("TANGEM", "TANGEM", null, IssuerKeyStorage.tangemPublicDataKey, null, IssuerKeyStorage.tangemPublicTransactionKey) +// ; +// +// +// static class IssuerKeyStorage { +// private static final byte[] sdkPrivateDataKey = new byte[]{ +// (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, +// (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, +// (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, +// (byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92}; +// +// private static final byte[] sdkPrivateTransactionKey = new byte[]{ +// (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, +// (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, +// (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, +// (byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12}; +// +// private static byte[] tangemPublicDataKey = { +// (byte) 0x04 , +// (byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A, +// (byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A, +// (byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67, +// (byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F, +// (byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35, +// (byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E, +// (byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5, +// (byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2}; +// +// private static byte[] tangemPublicTransactionKey = { +// (byte) 0x04 , +// (byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8, +// (byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29, +// (byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54, +// (byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84, +// (byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93, +// (byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B, +// (byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74, +// (byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80}; +// +// public static byte[] GeneratePublicKey(byte[] privateKey) { +// try { +// return CardCrypto.GeneratePublicKey(privateKey); +// } +// catch (Exception e) +// { +// e.printStackTrace(); +// return null; +// } +// } +// } +// +// private String ID; +// private String officialName; +// private byte[] privateDataKeyArray; +// private byte[] publicDataKeyArray; +// private byte[] privateTransactionKeyArray; +// private byte[] publicTransactionKeyArray; +// +// Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) { +// this.ID = id; +// this.officialName = officialName; +// this.privateDataKeyArray = privateDataKey; +// this.privateTransactionKeyArray = privateTransactionKey; +// this.publicDataKeyArray = publicDataKey; +// this.publicTransactionKeyArray = publicTransactionKey; +// } +// +// public byte[] getPublicDataKey() { +// return publicDataKeyArray; +// } +// +// public byte[] getPublicTransactionKey() { +// return publicTransactionKeyArray; +// } +// +// public byte[] getPrivateDataKey() { +// return privateDataKeyArray; +// } +// +// public byte[] getPrivateTransactionKey() { +// return privateTransactionKeyArray; +// } +// +// public byte[] getID() { +// return ID.getBytes(); +// } +// +// public String getOfficialName() { +// return officialName; +// } +// +// public static Issuer FindIssuer(String ID, byte[] publicDataKey) { +// Issuer[] issuers = Issuer.values(); +// for (int i = 1; i < issuers.length; i++) { +// if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) { +// return issuers[i]; +// } +// } +// return Issuer.Unknown; +// } +// +//} +public class Issuer { - static class IssuerKeyStorage { - private static final byte[] sdkPrivateDataKey = new byte[]{ - (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, - (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, - (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, - (byte) 0x39, (byte) 0x27, (byte) 0x37, (byte) 0x2D, (byte) 0x40, (byte) 0xDA, (byte) 0x9E, (byte) 0x92}; + static class KeyPair + { + String privateKey; + String publicKey; - private static final byte[] sdkPrivateTransactionKey = new byte[]{ - (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, - (byte) 0x47, (byte) 0x71, (byte) 0xED, (byte) 0x81, (byte) 0xF2, (byte) 0xBA, (byte) 0xCF, (byte) 0x57, - (byte) 0x47, (byte) 0x9E, (byte) 0x47, (byte) 0x35, (byte) 0xEB, (byte) 0x14, (byte) 0x05, (byte) 0x08, - (byte) 0x19, (byte) 0x18, (byte) 0x17, (byte) 0x16, (byte) 0x15, (byte) 0x14, (byte) 0x13, (byte) 0x12}; - - private static byte[] tangemPublicDataKey = { - (byte) 0x04 , - (byte) 0x81 ,(byte) 0x96 ,(byte) 0xAA ,(byte) 0x4B ,(byte) 0x41 ,(byte) 0x0A ,(byte) 0xC4 ,(byte) 0x4A, - (byte) 0x3B ,(byte) 0x9C ,(byte) 0xCE ,(byte) 0x18 ,(byte) 0xE7 ,(byte) 0xBE ,(byte) 0x22 ,(byte) 0x6A, - (byte) 0xEA ,(byte) 0x07 ,(byte) 0x0A ,(byte) 0xCC ,(byte) 0x83 ,(byte) 0xA9 ,(byte) 0xCF ,(byte) 0x67, - (byte) 0x54 ,(byte) 0x0F ,(byte) 0xAC ,(byte) 0x49 ,(byte) 0xAF ,(byte) 0x25 ,(byte) 0x12 ,(byte) 0x9F, - (byte) 0x6A ,(byte) 0x53 ,(byte) 0x8A ,(byte) 0x28 ,(byte) 0xAD ,(byte) 0x63 ,(byte) 0x41 ,(byte) 0x35, - (byte) 0x8E ,(byte) 0x3C ,(byte) 0x4F ,(byte) 0x99 ,(byte) 0x63 ,(byte) 0x06 ,(byte) 0x4F ,(byte) 0x7E, - (byte) 0x36 ,(byte) 0x53 ,(byte) 0x72 ,(byte) 0xA6 ,(byte) 0x51 ,(byte) 0xD3 ,(byte) 0x74 ,(byte) 0xE5, - (byte) 0xC2 ,(byte) 0x3C ,(byte) 0xDD ,(byte) 0x37 ,(byte) 0xFD ,(byte) 0x09 ,(byte) 0x9B ,(byte) 0xF2}; - - private static byte[] tangemPublicTransactionKey = { - (byte) 0x04 , - (byte) 0x34 ,(byte) 0x3D ,(byte) 0x40 ,(byte) 0x49 ,(byte) 0x6C ,(byte) 0xBE ,(byte) 0x1F ,(byte) 0xE8, - (byte) 0xA8 ,(byte) 0xC0 ,(byte) 0x26 ,(byte) 0x57 ,(byte) 0x5C ,(byte) 0x43 ,(byte) 0x5A ,(byte) 0x29, - (byte) 0x14 ,(byte) 0x1E ,(byte) 0xA3 ,(byte) 0xBC ,(byte) 0x33 ,(byte) 0x5D ,(byte) 0xA5 ,(byte) 0x54, - (byte) 0x9A ,(byte) 0xB6 ,(byte) 0xC6 ,(byte) 0x46 ,(byte) 0x85 ,(byte) 0xA6 ,(byte) 0x46 ,(byte) 0x84, - (byte) 0x80 ,(byte) 0x36 ,(byte) 0xD4 ,(byte) 0x81 ,(byte) 0xCF ,(byte) 0x9A ,(byte) 0x98 ,(byte) 0x93, - (byte) 0x90 ,(byte) 0xA8 ,(byte) 0xB0 ,(byte) 0x34 ,(byte) 0xB2 ,(byte) 0x29 ,(byte) 0xD9 ,(byte) 0x9B, - (byte) 0xD4 ,(byte) 0x9E ,(byte) 0x6F ,(byte) 0x07 ,(byte) 0xD2 ,(byte) 0xFF ,(byte) 0x02 ,(byte) 0x74, - (byte) 0x6E ,(byte) 0xA2 ,(byte) 0x65 ,(byte) 0xEF ,(byte) 0x99 ,(byte) 0x38 ,(byte) 0x0A ,(byte) 0x80}; - - public static byte[] GeneratePublicKey(byte[] privateKey) { - try { - return CardCrypto.GeneratePublicKey(privateKey); + byte[] getPrivateKey() throws Exception { + if (privateKey != null) { + return CardCrypto.GeneratePublicKey(Util.hexToBytes(privateKey)); + } else { + throw new Exception("No private key!"); } - catch (Exception e) + } + + byte[] getPublicKey() throws Exception { + if (publicKey == null) { + if (privateKey != null) { + return CardCrypto.GeneratePublicKey(Util.hexToBytes(privateKey)); + } else { + throw new Exception("Invalid key format: no public and no private"); + } + } else { + return Util.hexToBytes(publicKey); + } + } + + } + String id; + String officialName; + KeyPair dataKey; + KeyPair transactionKey; + + public String getID() { + return id; + } + + private static List instances=new ArrayList<>(); + + public static boolean needInit() { + return instances.size()==0; + } + + public static void Init(Context mContext){ + 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); + + for(JsonElement jeIssuer: jaIssuers) { - e.printStackTrace(); - return null; + Issuer instance=new Gson().fromJson(jeIssuer,Issuer.class); + instances.add(instance); } + + } catch (Exception e) { + e.printStackTrace(); } } - private String ID; - private String officialName; - private byte[] privateDataKeyArray; - private byte[] publicDataKeyArray; - private byte[] privateTransactionKeyArray; - private byte[] publicTransactionKeyArray; + 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); + } + } - Issuer(String id, String officialName, byte[] privateDataKey, byte[] publicDataKey, byte[] privateTransactionKey, byte[] publicTransactionKey) { - this.ID = id; - this.officialName = officialName; - this.privateDataKeyArray = privateDataKey; - this.privateTransactionKeyArray = privateTransactionKey; - this.publicDataKeyArray = publicDataKey; - this.publicTransactionKeyArray = publicTransactionKey; + return writer.toString(); } - public byte[] getPublicDataKey() { - return publicDataKeyArray; + public byte[] getPublicDataKey() throws Exception { + if( dataKey==null ) + throw new Exception("Data key not specified!"); + return dataKey.getPublicKey(); } - public byte[] getPublicTransactionKey() { - return publicTransactionKeyArray; + public byte[] getPublicTransactionKey() throws Exception { + if( dataKey==null ) + throw new Exception("Transaction key not specified!"); + return transactionKey.getPublicKey(); } - public byte[] getPrivateDataKey() { - return privateDataKeyArray; + public byte[] getPrivateDataKey() throws Exception { + if( dataKey==null ) + throw new Exception("Data key not specified!"); + return dataKey.getPrivateKey(); } - public byte[] getPrivateTransactionKey() { - return privateTransactionKeyArray; - } - - public byte[] getID() { - return ID.getBytes(); + public byte[] getPrivateTransactionKey() throws Exception { + if( transactionKey==null ) + throw new Exception("Transaction key not specified!"); + return transactionKey.getPrivateKey(); } public String getOfficialName() { - return officialName; + return officialName!=null?officialName:id; + } + + public static Issuer FindIssuer(String ID) { + for (int i = 0; i < instances.size(); i++) { + try { + if (instances.get(i).id.equals(ID)) { + return instances.get(i); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return Unknown(); } public static Issuer FindIssuer(String ID, byte[] publicDataKey) { - Issuer[] issuers = Issuer.values(); - for (int i = 1; i < issuers.length; i++) { - if (issuers[i].ID.equals(ID) && Arrays.equals(issuers[i].getPublicDataKey(), publicDataKey)) { - return issuers[i]; + for (int i = 0; i < instances.size(); i++) { + try { + if (instances.get(i).id.equals(ID) && Arrays.equals(instances.get(i).getPublicDataKey(), publicDataKey)) { + return instances.get(i); + } + } catch (Exception e) { + e.printStackTrace(); } } - return Issuer.Unknown; + return Unknown(); } + public static Issuer Unknown() { + return instances.get(0); + } } 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 06bcefe213..1ea3ddcd3f 100644 --- a/app/src/main/java/com/tangem/domain/wallet/TangemCard.java +++ b/app/src/main/java/com/tangem/domain/wallet/TangemCard.java @@ -578,7 +578,7 @@ public class TangemCard { return health == 0; } - private Issuer issuer = Issuer.Unknown; + private Issuer issuer = Issuer.Unknown(); public void setIssuer(Issuer issuer) { this.issuer = issuer; @@ -1202,7 +1202,7 @@ public class TangemCard { if (signingMethod != null) B.putString("signingMethod", signingMethod.name()); if (manufacturer != null) B.putString("Manufacturer", manufacturer.name()); if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name()); - if (issuer != null) B.putString("Issuer", issuer.name()); + if (issuer != null) B.putString("Issuer", issuer.getID()); if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion); B.putString("BalanceDecimal", balanceDecimal); B.putString("BalanceDecimalAlter", balanceDecimalAlter); @@ -1291,7 +1291,7 @@ public class TangemCard { else encryptionMode = null; - if (B.containsKey("Issuer")) issuer = Issuer.valueOf(B.getString("Issuer")); + if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer")); if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion"); cardPublicKeyValid = B.getBoolean("CardPublicKeyValid"); 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 731f03981a..a98b4979f8 100644 --- a/app/src/main/java/com/tangem/presentation/activity/MainActivity.java +++ b/app/src/main/java/com/tangem/presentation/activity/MainActivity.java @@ -24,6 +24,7 @@ import android.widget.TextView; import com.scottyab.rootbeer.RootBeer; import com.skyfishjy.library.RippleBackground; import com.tangem.domain.wallet.DeviceNFCAntennaLocation; +import com.tangem.domain.wallet.Issuer; import com.tangem.domain.wallet.LastSignStorage; import com.tangem.domain.wallet.Logger; import com.tangem.domain.wallet.PINStorage; @@ -216,6 +217,10 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI if (LastSignStorage.needInit()) { LastSignStorage.Init(context); } + if(Issuer.needInit()) + { + Issuer.Init(context); + } } public void showCleanButton() { diff --git a/app/src/main/res/raw/fw_hashes.json b/app/src/main/res/raw/fw_hashes.json new file mode 100644 index 0000000000..4ffa6e652e --- /dev/null +++ b/app/src/main/res/raw/fw_hashes.json @@ -0,0 +1,36 @@ +[ + { + "fw":"1.24r", + "challenge":"00000000000000000000000000000001", + "sha-256":[ + {"block":0,"count":1,"digest":"9B091DF443FE951D27AA8843517096DC421433F5603EBC566B11891F867E3D18"}, + {"block":1,"count":1,"digest":"B4F723EE79075C8C00FA17D037BF10528BB0C7CF421539EE15F4A397204C87AF"}, + {"block":2,"count":1,"digest":"1258585B438B6BC8F41FC22AFF383C86690E984E84E96AA752C9A54954DF731A"}, + {"block":3,"count":1,"digest":"14082C75D6AA533A06E9B764642F97B5783FED91BD1173EFADC3CF57D8769AAE"}, + {"block":4,"count":1,"digest":"A8F6CB02D56933361127072BB0D89999020CE196AE9718E9CF019DCCC9437E85"}, + {"block":5,"count":1,"digest":"491604BA2A2F6EE03A2B4812958F0356D745678FB0ABABF0109A9E8F59099D9D"}, + {"block":6,"count":1,"digest":"B2F057FF8B83343BC40058CA459F3C63B9670ABCD059730BE70223AEBDC88D77"}, + {"block":7,"count":1,"digest":"73F4391010345E27745A1AFEC93C02B2CA460D9EAB437605F82A18281DF17DCE"}, + {"block":8,"count":1,"digest":"74588EBB6704DD3DB9ECDFE6B85EC2872BD6A1F7C73E080E57697D3FF8DDD470"}, + {"block":9,"count":1,"digest":"3B382C35ADBBF206B0A6B216EE95AF2321EE34148E93BBFECCF275AB13ED08A2"}, + {"block":11500,"count":1,"digest":"21DDEC9788E3EA04663297AD0208F2714F139434E3540EBB7C96150EE1D320C9"}, + {"block":11501,"count":1,"digest":"C545C5D092D1EBC4BA266C139CE0F2A399E4D23E74EB4EBD0E53019AB58CB486"}, + {"block":0,"count":0,"digest":"C689C50352E9F6E41BF2E6EC852D8466814A51018F40BE32BAADEE9C2A9E6B6D"} + ], + "crc-16":[ + {"block":0,"count":1,"digest":"1BFC"}, + {"block":1,"count":1,"digest":"D58D"}, + {"block":2,"count":1,"digest":"7E3E"}, + {"block":3,"count":1,"digest":"14EA"}, + {"block":4,"count":1,"digest":"014F"}, + {"block":5,"count":1,"digest":"5FF0"}, + {"block":6,"count":1,"digest":"7D3A"}, + {"block":7,"count":1,"digest":"702C"}, + {"block":8,"count":1,"digest":"CC22"}, + {"block":9,"count":1,"digest":"392C"}, + {"block":11500,"count":1,"digest":"5FCB"}, + {"block":11501,"count":1,"digest":"FF77"}, + {"block":0,"count":0,"digest":"D07B"} + ] + } +] \ No newline at end of file diff --git a/app/src/main/res/raw/issuers.json b/app/src/main/res/raw/issuers.json new file mode 100644 index 0000000000..337db6eb5f --- /dev/null +++ b/app/src/main/res/raw/issuers.json @@ -0,0 +1,39 @@ +[ + { + "id": "TANGEM SDK", + "dataKey": { + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKey": { + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + { + "id": "TANGEM SDK OLD", + "dataKey": { + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKey": { + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + { + "id": "TANGEM", + "dataKey": { + "publicKey": "048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540FAC49AF25129F6A538A28AD6341358E3C4F9963064F7E365372A651D374E5C23CDD37FD099BF2" + }, + "transactionKey": { + "publicKey": "04343D40496CBE1FE8A8C026575C435A29141EA3BC335DA5549AB6C64685A646848036D481CF9A989390A8B034B229D99BD49E6F07D2FF02746EA265EF99380A80" + } + }, + { + "id": "SUPERBLOOM", + "dataKey": { + "publicKey": "04EAB7F2444CFFA2E9CB4AA01CF1CA3694888811BDF0F8E0CDBCCFB5F497F14A5B86655FEF0C7E8F86841B0C14660E09F28DDFB9690F7302DA56F661A6A064564E" + }, + "transactionKey": { + "publicKey": "04D292BFC802FFA70ABB1C094A19F6C6766DD353114C25415871BE4586129648CFD77B22FBC1AF3C612497732A04CF845B41D760AFDD820051AB250798503DB340" + } + } + +] From cb6678380fc1e898f8c9dca119a372c02ceb15b9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 7 Jul 2018 00:58:19 +0300 Subject: [PATCH 2/4] Updated on 2026-08-14 --- .../main/{res/raw => assets}/fw_hashes.json | 34 +++ app/src/main/{res/raw => assets}/issuers.json | 0 .../request/VerificationServerProtocol.java | 11 +- .../com/tangem/data/nfc/VerifyCardTask.java | 30 ++- .../domain/cardReader/CardProtocol.java | 2 +- .../java/com/tangem/domain/cardReader/FW.java | 62 +++++ .../java/com/tangem/domain/wallet/Issuer.java | 243 ++++++------------ .../com/tangem/domain/wallet/TangemCard.java | 49 ++++ .../presentation/activity/MainActivity.java | 8 +- .../presentation/fragment/LoadedWallet.java | 55 +++- 10 files changed, 302 insertions(+), 192 deletions(-) rename app/src/main/{res/raw => assets}/fw_hashes.json (50%) rename app/src/main/{res/raw => assets}/issuers.json (100%) create mode 100644 app/src/main/java/com/tangem/domain/cardReader/FW.java 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) { From f7bd06e040f90f445d9081c2b0ec263757738d0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 13 Jul 2018 12:03:02 +0300 Subject: [PATCH 3/4] Updated on 2026-08-14 --- app/build.gradle | 4 +- .../request/VerificationServerProtocol.java | 193 ++++++++++++++++++ .../network/task/VerificationServerTask.java | 42 ++++ .../com/tangem/data/nfc/ReadCardInfoTask.java | 22 +- .../tangem/domain/cardReader/NfcManager.java | 22 ++ .../tangem/presentation/fragment/Main.java | 37 ++-- 6 files changed, 297 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java create mode 100644 app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java diff --git a/app/build.gradle b/app/build.gradle index d7ffe39557..18afdc7471 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,6 @@ android { versionName "0.85.5." + generateVersionName() testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } - buildTypes { release { debuggable false @@ -34,11 +33,11 @@ android { signingConfig signingConfigs.debug } } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + buildToolsVersion '27.0.3' } dependencies { @@ -61,6 +60,7 @@ dependencies { implementation 'org.bitcoinj:bitcoinj-core:0.14.4' implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'info.hoang8f:android-segmented:1.0.6' + implementation 'com.google.code.gson:gson:2.8.5' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 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 new file mode 100644 index 0000000000..0b008ac56d --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/request/VerificationServerProtocol.java @@ -0,0 +1,193 @@ +package com.tangem.data.network.request; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.tangem.domain.wallet.TangemCard; +import com.tangem.util.Util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +public class VerificationServerProtocol { + + public static class Request + { + public Request(CustomCommand command, Class answerClass) + { + this.command=command; + this.answerClass=answerClass; + } + public String error; + public CustomCommand command; + public CustomAnswer answer; + public Type answerClass; + + public void doPost(String hostURL) throws IOException { + URL url = new URL(hostURL+"/"+command.getURL()); + URLConnection con = url.openConnection(); + HttpURLConnection http = (HttpURLConnection) con; + try { + http.setConnectTimeout(30000); + http.setReadTimeout(30000); + http.setRequestMethod("POST"); // PUT is another valid option + http.setDoOutput(true); + + String sRequestBody = getGson().toJson(this.command); + + byte[] out = sRequestBody.getBytes(StandardCharsets.UTF_8); + int length = out.length; + + http.setFixedLengthStreamingMode(length); + http.setRequestProperty("Content-Type", "application/json"); + http.connect(); + try (OutputStream os = http.getOutputStream()) { + os.write(out); + } + try (InputStream is = http.getInputStream()) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + answer = getGson().fromJson(br, answerClass); + } + } + } + finally { + http.disconnect(); + } + } + } + + static abstract class CustomCommand { + public abstract String getURL(); + + public byte[] toBytes() + { + return getGson().toJson(this).getBytes(StandardCharsets.UTF_8); + } + + @Override + public String toString() { + return getGson().toJson(this); + } + + } + + static class CustomAnswer { + public String error; + + @Override + public String toString() { + return getGson().toJson(this); + } + } + + public static class Verify { + + static class RequestItem { + public String CID; + public String publicKey; + } + + static class Command extends CustomCommand { + public RequestItem[] requests; + + @Override + public String getURL() { + return "verify"; + } + } + + public static class ResultItem { + public ResultItem(RequestItem request) { + CID = request.CID; + } + + public String error; + public String CID; + public Boolean passed; + } + + public static class Answer extends CustomAnswer { + public ResultItem[] results; + } + + public static Request prepare(TangemCard card) + { + Command c=new Command(); + c.requests=new RequestItem[1]; + 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); + } + } + + public static class Validate { + + static class RequestItem { + public String CID; + public int counter; + public String signature; + } + + static class Command extends CustomCommand { + public RequestItem[] requests; + + @Override + public String getURL() { + return "validate"; + } + } + + static class ResultItem { + public ResultItem(RequestItem request) { + CID = request.CID; + } + public String error; + public String CID; + public Integer previousCounter; + public Boolean passed; + } + + static class Answer extends CustomAnswer { + public ResultItem[] results; + } + + public Request prepare(TangemCard card, int ValidationCounter, byte[] ValidationSignature) + { + Command c=new Command(); + c.requests=new RequestItem[1]; + c.requests[0].CID= Util.bytesToHex(card.getCID()); + c.requests[0].counter=ValidationCounter; + c.requests[0].signature=Util.bytesToHex(ValidationSignature); + return new Request(new Command(),Answer.class); + } + } + + + public static Date strToDate(String date) throws ParseException { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + return new Date(formatter.parse(date).getTime()); + } + + public static String dateToStr(Date date) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + return formatter.format(date); + } + + public static Gson getGson() { + return new GsonBuilder().create(); + } + +} diff --git a/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java b/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java new file mode 100644 index 0000000000..6e83e322ac --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/task/VerificationServerTask.java @@ -0,0 +1,42 @@ +package com.tangem.data.network.task; + +import android.os.AsyncTask; + +import com.tangem.data.network.request.VerificationServerProtocol; + +import java.util.ArrayList; +import java.util.List; + +public class VerificationServerTask extends AsyncTask> { + public static final String hostURL ="https://tangem-webapp.appspot.com"; + + public VerificationServerTask() { + + } + + protected List doInBackground(VerificationServerProtocol.Request... requests) { + List result = new ArrayList<>(); + for (int i = 0; i < requests.length; i++) { + result.add(requests[i]); + } + + for (VerificationServerProtocol.Request request : result) { + try { + request.doPost(hostURL); + } catch (Exception e) { + request.error = e.getMessage(); + if( request.error==null || request.error.isEmpty() ) + { + request.error = e.getClass().getName(); + } + } + } + + return result; + } + + public String getValidationNodeDescription() { + return hostURL; + } + + } diff --git a/app/src/main/java/com/tangem/data/nfc/ReadCardInfoTask.java b/app/src/main/java/com/tangem/data/nfc/ReadCardInfoTask.java index ce7e62dbac..f58b7d99d3 100644 --- a/app/src/main/java/com/tangem/data/nfc/ReadCardInfoTask.java +++ b/app/src/main/java/com/tangem/data/nfc/ReadCardInfoTask.java @@ -21,16 +21,23 @@ public class ReadCardInfoTask extends Thread { private Context mContext; private NfcManager mNfcManager; - private ArrayList lastRead_UnsuccessfullPINs = new ArrayList<>(); - private TangemCard.EncryptionMode lastRead_Encryption = null; - private String lastRead_UID; + // this fields are static to optimize process when need enter pin and scan card again + private static ArrayList lastRead_UnsuccessfullPINs = new ArrayList<>(); + private static TangemCard.EncryptionMode lastRead_Encryption = null; + private static String lastRead_UID; - public ReadCardInfoTask(Context context, NfcManager nfcManager, String lastRead_UID, IsoDep isoDep, CardProtocol.Notifications notifications) { + public static void resetLastReadInfo() { + lastRead_UID=""; + lastRead_Encryption=null; + lastRead_UnsuccessfullPINs.clear(); + } + + public ReadCardInfoTask(Context context, NfcManager nfcManager, IsoDep isoDep, CardProtocol.Notifications notifications) { mContext = context; mIsoDep = isoDep; mNotifications = notifications; mNfcManager = nfcManager; - this.lastRead_UID = lastRead_UID; +// ReadCardInfoTask.lastRead_UID = lastRead_UID; } @Override @@ -56,9 +63,7 @@ public class ReadCardInfoTask extends Thread { byte[] UID = mIsoDep.getTag().getId(); String sUID = Util.byteArrayToHexString(UID); if (!lastRead_UID.equals(sUID)) { - lastRead_UID = sUID; - lastRead_UnsuccessfullPINs.clear(); - lastRead_Encryption = null; + resetLastReadInfo(); } Log.i(TAG, "[-- Start read card info --]"); @@ -146,6 +151,7 @@ public class ReadCardInfoTask extends Thread { } } catch (Exception e) { e.printStackTrace(); + mNfcManager.notifyReadResult(false); } } diff --git a/app/src/main/java/com/tangem/domain/cardReader/NfcManager.java b/app/src/main/java/com/tangem/domain/cardReader/NfcManager.java index 9d796d76dc..941b1cff92 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/NfcManager.java +++ b/app/src/main/java/com/tangem/domain/cardReader/NfcManager.java @@ -15,6 +15,7 @@ import android.os.Build; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.util.Log; +import android.widget.Toast; import com.tangem.presentation.dialog.NFCEnableDialog; @@ -76,6 +77,27 @@ public class NfcManager { // } } + int errorCount=0; + public void notifyReadResult(boolean success) + { + if(success) + { + errorCount=0; + }else{ + errorCount++; + } + if( errorCount>=3 ) + { + disableReaderMode(); + mNfcAdapter=null; + Toast.makeText(mActivity,"NFC restarted!",Toast.LENGTH_SHORT).show(); + mActivity.runOnUiThread(()->{ + mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity); + enableReaderMode(); + }); + } + } + private void showNFCEnableDialog() { mEnableNfcDialog = new NFCEnableDialog(); mEnableNfcDialog.show(mActivity.getFragmentManager(), NFCEnableDialog.TAG); diff --git a/app/src/main/java/com/tangem/presentation/fragment/Main.java b/app/src/main/java/com/tangem/presentation/fragment/Main.java index 8b0b879adf..f8d8939c7f 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/Main.java +++ b/app/src/main/java/com/tangem/presentation/fragment/Main.java @@ -76,7 +76,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis private int unsuccessReadCount = 0; private Tag lastTag = null; - private String lastRead_UID = ""; +// private String lastRead_UID = ""; public Main() { } @@ -119,9 +119,9 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis int cardIndex = viewHolder.getAdapterPosition(); if (cardIndex < 0 || cardIndex >= mCardListAdapter.getItemCount()) return; slCardUIDs.remove(mCardListAdapter.getCard(cardIndex).getUID()); - if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) { - lastRead_UID = ""; - } +// if (mCardListAdapter.getCard(cardIndex).getUID() == lastRead_UID) { +// lastRead_UID = ""; +// } mCardListAdapter.removeCard(cardIndex); if (mCardListAdapter.getItemCount() == 0 && getActivity().getClass() == MainActivity.class) { ((MainActivity) getActivity()).hideCleanButton(); @@ -142,6 +142,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis @Override public void onResume() { super.onResume(); + ReadCardInfoTask.resetLastReadInfo(); mNfcManager.onResume(); } @@ -201,8 +202,13 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis } } - } else if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) { - if (lastTag != null) onTagDiscovered(lastTag); + } else if (requestCode == REQUEST_CODE_ENTER_PIN_ACTIVITY) { + if( resultCode == Activity.RESULT_OK && lastTag != null) + { + onTagDiscovered(lastTag); + }else{ + ReadCardInfoTask.resetLastReadInfo(); + } } } @@ -233,13 +239,14 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis } lastTag = tag; - readCardInfoTask = new ReadCardInfoTask(getActivity(), mNfcManager, lastRead_UID, isoDep, this); + readCardInfoTask = new ReadCardInfoTask(getActivity(), mNfcManager, isoDep, this); readCardInfoTask.start(); Log.i(TAG, "onTagDiscovered " + Arrays.toString(tag.getId())); } catch (Exception e) { e.printStackTrace(); + mNfcManager.notifyReadResult(false); } } @@ -290,6 +297,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis readCardInfoTask = null; if (cardProtocol != null) { if (cardProtocol.getError() == null) { + mNfcManager.notifyReadResult(true); progressBar.post(() -> { rlProgressBar.setVisibility(View.GONE); progressBar.setProgress(100); @@ -334,7 +342,6 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis for (RequestWalletInfoTask rt : requestTasks) { rt.cancel(true); } - lastRead_UID = ""; }); @@ -349,12 +356,15 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis slCardUIDs.remove(cardProtocol.getCard().getUID()); if (cardProtocol.getError() instanceof CardProtocol.TangemException_InvalidPIN) { doEnterPIN(); - } else if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { - if (!NoExtendedLengthSupportDialog.allreadyShowed) { - new NoExtendedLengthSupportDialog().show(Objects.requireNonNull(getActivity()).getFragmentManager(), NoExtendedLengthSupportDialog.TAG); - } } else { + if (cardProtocol.getError() instanceof CardProtocol.TangemException_ExtendedLengthNotSupported) { + if (!NoExtendedLengthSupportDialog.allreadyShowed) { + new NoExtendedLengthSupportDialog().show(Objects.requireNonNull(getActivity()).getFragmentManager(), NoExtendedLengthSupportDialog.TAG); + } + } lastTag = null; + ReadCardInfoTask.resetLastReadInfo(); + mNfcManager.notifyReadResult(false); } } }); @@ -376,6 +386,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis public void OnReadCancel() { readCardInfoTask = null; + ReadCardInfoTask.resetLastReadInfo(); progressBar.postDelayed(() -> { try { rlProgressBar.setVisibility(View.GONE); @@ -410,7 +421,7 @@ public class Main extends Fragment implements NfcAdapter.ReaderCallback, CardLis for (RequestWalletInfoTask rt : requestTasks) { rt.cancel(true); } - lastRead_UID = ""; + ReadCardInfoTask.resetLastReadInfo(); } public void refreshCard(TangemCard card) { From 0f30672177400eca992f0da3f71d8736fe637840 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Jul 2018 09:31:13 +0300 Subject: [PATCH 4/4] Updated on 2026-08-14 --- .idea/caches/build_file_checksums.ser | Bin 536 -> 536 bytes .../com/tangem/data/nfc/VerifyCardTask.java | 4 - .../com/tangem/domain/BitcoinNodeTestNet.kt | 8 +- .../domain/cardReader/CardProtocol.java | 14 +++- .../com/tangem/domain/cardReader/TLV.java | 3 +- .../com/tangem/domain/wallet/TangemCard.java | 75 +++++++++++++----- 6 files changed, 76 insertions(+), 28 deletions(-) diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index a879c3a9a527147d0b76512a179a22b783a50f91..bbe4e971ef4c27fe279554d56f8f31274365a57d 100644 GIT binary patch delta 56 zcmV-80LTBB1egSnm;|A+`*M+-cMyIq)frGvUp6LnwuH~A*6Wia0f`U=nigj!_snG< O);BfEo5u;0ya8OmCm68+ delta 56 zcmV-80LTBB1egSnm;`JmKmw7RcM!ZFagBnTqY3!c^#P9Fqq>tL0f`X#BebPe&oo<; OAl?9La?XL1ya8OP%^3aw 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 a94dd336d6..08fe1fd037 100644 --- a/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java +++ b/app/src/main/java/com/tangem/data/nfc/VerifyCardTask.java @@ -4,15 +4,11 @@ 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; diff --git a/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt b/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt index 2fda931758..8d64d95276 100644 --- a/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt +++ b/app/src/main/java/com/tangem/domain/BitcoinNodeTestNet.kt @@ -1,8 +1,8 @@ package com.tangem.domain enum class BitcoinNodeTestNet(val host: String, val port: Int) { - qtornado_com("testnet.qtornado.com", 51001), - hsmiths_com("testnet.hsmiths.com", 53011), - bauerj_eu("testnet1.bauerj.eu", 50001), - arihanc_com("testnetnode.arihanc.com", 51001), + n1("testnetnode.arihanc.com", 51001), + n2("testnet.hsmiths.com", 53011), + n3("testnet.qtornado.com", 51001), + n4("testnet1.bauerj.eu", 50001), } \ No newline at end of file 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 b651d1c699..1fc57bcf48 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java +++ b/app/src/main/java/com/tangem/domain/cardReader/CardProtocol.java @@ -470,7 +470,11 @@ public class CardProtocol { // try read denomination if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null) { - mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value); + if(tlvIssuerData.getTLV(TLV.Tag.TAG_DenominationText) != null) { + mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value, tlvIssuerData.getTLV(TLV.Tag.TAG_DenominationText).getAsString()); + }else{ + mCard.setDenomination(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value); + } } else { mCard.clearDenomination(); } @@ -504,6 +508,8 @@ public class CardProtocol { TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value); + mCard.setBatch(tlvCardData.getTLV(TLV.Tag.TAG_Batch).getAsHexString()); + TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol); TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address); TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal); @@ -974,6 +980,9 @@ public class CardProtocol { Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data); TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature); + TLV issuerDataCounter = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Counter); + + boolean protectIssuerDataAgainstReplay=(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask)&SettingsMask.ProtectIssuerDataAgainstReplay)!=0; if (issuerData == null || issuerDataSignature == null) throw new TangemException("Invalid answer format (GetIssuerData)"); @@ -981,6 +990,9 @@ public class CardProtocol { ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); bsDataToVerify.write(mCard.getCID()); bsDataToVerify.write(issuerData.Value); + if( protectIssuerDataAgainstReplay ) { + bsDataToVerify.write(issuerDataCounter.Value); + } try { if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) { mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value); diff --git a/app/src/main/java/com/tangem/domain/cardReader/TLV.java b/app/src/main/java/com/tangem/domain/cardReader/TLV.java index 57a1cc68ca..7c2d7b6f11 100644 --- a/app/src/main/java/com/tangem/domain/cardReader/TLV.java +++ b/app/src/main/java/com/tangem/domain/cardReader/TLV.java @@ -90,7 +90,8 @@ public class TLV { TAG_Token_Decimal(0xA2), TAG_Denomination(0xC0), TAG_ValidatedBalance(0xC1), - TAG_LastSign_Date(0xC2); + TAG_LastSign_Date(0xC2), + TAG_DenominationText(0xC3); Tag(int Code) { 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 33b1add1c3..bba733e7d7 100644 --- a/app/src/main/java/com/tangem/domain/wallet/TangemCard.java +++ b/app/src/main/java/com/tangem/domain/wallet/TangemCard.java @@ -271,6 +271,7 @@ public class TangemCard { } private Boolean codeConfirmed; + public void setCodeConfirmed(Boolean codeConfirmed) { this.codeConfirmed = codeConfirmed; } @@ -280,6 +281,7 @@ public class TangemCard { } private Boolean onlineVerified; + public void setOnlineVerified(Boolean verified) { this.onlineVerified = verified; } @@ -289,6 +291,7 @@ public class TangemCard { } private Boolean onlineValidated; + public void setOnlineValidated(Boolean validated) { this.onlineValidated = validated; } @@ -654,8 +657,8 @@ public class TangemCard { return issuer.getOfficialName(); } - byte[] issuerData; - byte[] issuerDataSignature; + private byte[] issuerData; + private byte[] issuerDataSignature; public byte[] getIssuerData() { return issuerData; @@ -667,9 +670,10 @@ public class TangemCard { public void setIssuerData(byte[] value, byte[] signature) { issuerData = value; + issuerDataSignature = signature; } - boolean needWriteIssuerData = false; + private boolean needWriteIssuerData = false; public boolean getNeedWriteIssuerData() { return needWriteIssuerData; @@ -828,6 +832,16 @@ public class TangemCard { return numbers1[0] > numbers2[0] || (numbers1[0] == numbers2[0] && numbers1[1] > numbers2[1]); } + private String batch; + + public void setBatch(String batch) { + this.batch = batch; + } + + public String getBatch() { + return batch; + } + private String validationNodeDescription = ""; public String getValidationNodeDescription() { @@ -1153,17 +1167,39 @@ public class TangemCard { } private byte[] Denomination; + private String DenominationText; - public void setDenomination(byte[] denomination) { + public void setDenomination(byte[] denomination, String denominationText) { this.Denomination = denomination; + this.DenominationText=denominationText; + } + + public void setDenomination(byte[] denomination) + { + this.Denomination=denomination; + try { + this.DenominationText=CoinEngineFactory.Create(getBlockchain()).ConvertByteArrayToAmount(this, denomination); + } catch (Exception e) { + e.printStackTrace(); + this.DenominationText="N/A"; + } } public byte[] getDenomination() { return Denomination; } + public String getDenominationText() { + return DenominationText; + } + + public void setDenominationText(String denominationText) { + DenominationText = denominationText; + } + public void clearDenomination() { Denomination = null; + DenominationText=null; } public void setError(String error) { @@ -1232,6 +1268,7 @@ public class TangemCard { if (encryptionMode != null) B.putString("EncryptionMode", encryptionMode.name()); if (issuer != null) B.putString("Issuer", issuer.getID()); if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion); + if (batch != null) B.putString("Batch", batch); B.putString("BalanceDecimal", balanceDecimal); B.putString("BalanceDecimalAlter", balanceDecimalAlter); B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed); @@ -1283,16 +1320,16 @@ public class TangemCard { B.putFloat("rateAlter", rateAlter); B.putString("confirmTx", GetConfirmTXCount().toString(16)); - if( codeConfirmed!=null ) + if (codeConfirmed != null) B.putBoolean("codeConfirmed", codeConfirmed); - if( codeConfirmed!=null ) + if (codeConfirmed != null) B.putBoolean("codeConfirmed", codeConfirmed); - if( onlineVerified!=null ) + if (onlineVerified != null) B.putBoolean("onlineVerified", onlineVerified); - if( onlineValidated!=null ) + if (onlineValidated != null) B.putBoolean("onlineValidated", onlineValidated); } @@ -1334,6 +1371,7 @@ public class TangemCard { if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer")); if (B.containsKey("FirmwareVersion")) firmwareVersion = B.getString("FirmwareVersion"); + if (B.containsKey("Batch")) batch = B.getString("Batch"); cardPublicKeyValid = B.getBoolean("CardPublicKeyValid"); if (B.containsKey("CardPublicKey")) setCardPublicKey(B.getByteArray("CardPublicKey")); @@ -1350,7 +1388,7 @@ public class TangemCard { if (B.containsKey("Denomination")) setDenomination(B.getByteArray("Denomination")); else clearDenomination(); - if (B.containsKey("IssuerData")) + if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature")) setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature")); else setIssuerData(null, null); @@ -1405,21 +1443,22 @@ public class TangemCard { if (B.containsKey("confirmTx")) countConfirmTX = new BigInteger(B.getString("confirmTx"), 16); - if( B.containsKey("codeConfirmed") ) - codeConfirmed=B.getBoolean("codeConfirmed"); + if (B.containsKey("codeConfirmed")) + codeConfirmed = B.getBoolean("codeConfirmed"); - if( B.containsKey("onlineVerified") ) - onlineVerified=B.getBoolean("onlineVerified"); + if (B.containsKey("onlineVerified")) + onlineVerified = B.getBoolean("onlineVerified"); - if( B.containsKey("onlineValidated") ) - onlineValidated=B.getBoolean("onlineValidated"); + if (B.containsKey("onlineValidated")) + onlineValidated = B.getBoolean("onlineValidated"); } private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes) { if (bytes == null) return ""; char[] hexChars = new char[bytes.length * 2]; - for ( int j = 0; j < bytes.length; j++ ) { + for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; @@ -1430,9 +1469,9 @@ public class TangemCard { public int getCardImageResource() { switch (getBlockchainID()) { case "BTC": - if ( bytesToHex(getDenomination()).equals("40420F0000000000") ) + if (bytesToHex(getDenomination()).equals("40420F0000000000")) return R.drawable.card_btc001; - else if (bytesToHex(getDenomination()).equals("404B4C0000000000") ) + else if (bytesToHex(getDenomination()).equals("404B4C0000000000")) return R.drawable.card_btc005; else return R.drawable.card_default;