Updated on 2026-08-14

This commit is contained in:
Tangem 2019-09-19 16:47:38 +03:00
parent 4db63c1685
commit 4288dd4dc3
15 changed files with 177 additions and 26 deletions

View file

@ -46,7 +46,7 @@ android {
applicationIdSuffix ".debug_beta"
buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("true")'
manifestPlaceholders = [fabric_api_key: properties.hasProperty('fabric_api_key')
? project.property('fabric_api_key') : 000]
? project.property('fabric_api_key') : '000']
}
}
flavorDimensions ""
@ -91,23 +91,22 @@ dependencies {
implementation project(':tangem-card')
implementation project(':tangem-sdk')
implementation project(':server-android')
// implementation 'com.github.TangemCash:card-common:0.1.2'
// implementation 'com.github.TangemCash:card-android:0.1.3'
// implementation 'com.github.TangemCash:server-android:0.1.4'
implementation 'android.arch.navigation:navigation-fragment:1.0.0'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.0.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.0.0"
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.fragment:fragment:1.1.0-rc04'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.fragment:fragment:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.preference:preference:1.1.0-rc01"
implementation 'androidx.biometric:biometric:1.0.0-alpha04'
implementation 'com.google.android.material:material:1.1.0-alpha09'
implementation 'androidx.core:core-ktx:1.0.2'
implementation "androidx.preference:preference:1.1.0"
implementation 'androidx.biometric:biometric:1.0.0-beta01'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.1.0-alpha10'
implementation 'com.google.dagger:dagger:2.21'
kapt 'com.google.dagger:dagger-compiler:2.21'
annotationProcessor 'com.google.dagger:dagger-compiler:2.21'

View file

@ -97,4 +97,6 @@ object Constant {
const val REQUEST_CODE_SCAN_QR_SECRET = "REQUEST_CODE_SCAN_QR_SECRET"
const val REQUEST_CODE_SCAN_QR_USER_ID = "REQUEST_CODE_SCAN_QR_USER_ID"
const val TERMINAL_PRIVATE_KEY = "terminalPrivateKey"
const val TERMINAL_PUBLIC_KEY = "terminalPublicKey"
}

View file

@ -3,9 +3,9 @@ package com.tangem.data.dp
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import com.orhanobut.hawk.Hawk
import com.tangem.Constant
import com.tangem.tangem_card.reader.CardCrypto
class PrefsManager {
companion object {
@ -47,4 +47,18 @@ class PrefsManager {
}
fun getAllCids(): String = Hawk.get("CID_Key", "")
val terminalKeys: Map<String, ByteArray>
get() {
val privateKey = Hawk.get<ByteArray>(Constant.TERMINAL_PRIVATE_KEY) ?: byteArrayOf()
val publicKey = Hawk.get<ByteArray>(Constant.TERMINAL_PUBLIC_KEY) ?: byteArrayOf()
return if (privateKey.isNotEmpty() && publicKey.isNotEmpty()) {
mapOf(Constant.TERMINAL_PRIVATE_KEY to privateKey, Constant.TERMINAL_PUBLIC_KEY to publicKey)
} else {
val keys = CardCrypto.generateTerminalKeys()
Hawk.put(Constant.TERMINAL_PRIVATE_KEY, keys[Constant.TERMINAL_PRIVATE_KEY])
Hawk.put(Constant.TERMINAL_PUBLIC_KEY, keys[Constant.TERMINAL_PUBLIC_KEY])
keys
}
}
}

View file

@ -19,6 +19,7 @@ import com.tangem.tangem_card.data.TangemCard
import com.tangem.tangem_card.reader.CardProtocol
import com.tangem.tangem_card.tasks.CustomReadCardTask
import com.tangem.tangem_card.tasks.ReadCardInfoTask
import com.tangem.tangem_sdk.android.data.PINStorage
import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
import com.tangem.tangem_sdk.android.reader.NfcReader
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
@ -124,6 +125,10 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
lastTag = tag
val terminalKeys = viewModel.getTerminalKeys()
PINStorage.setTerminalPrivateKey(terminalKeys[Constant.TERMINAL_PRIVATE_KEY])
PINStorage.setTerminalPublicKey(terminalKeys[Constant.TERMINAL_PUBLIC_KEY])
task = ReadCardInfoTask(NfcReader((activity as MainActivity).nfcManager, isoDep),
App.localStorage, App.pinStorage, this)
task?.start()
@ -161,6 +166,10 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
val card = TangemCard(uid)
cardInfo.getBundle(EXTRA_TANGEM_CARD)?.let { card.loadFromBundle(it) }
val terminalKeys = viewModel.getTerminalKeys()
card.terminalPrivateKey = terminalKeys[Constant.TERMINAL_PRIVATE_KEY]
card.terminalPublicKey = terminalKeys[Constant.TERMINAL_PUBLIC_KEY]
val ctx = TangemContext(card)
when {
card.status == TangemCard.Status.Loaded -> lastTag?.let {

View file

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.tangem.Constant
import com.tangem.data.dp.PrefsManager
import com.tangem.data.network.ServerApiCommon
import com.tangem.wallet.BuildConfig
@ -35,4 +36,9 @@ class MainViewModel : ViewModel() {
}
serverApiCommon.requestLastVersion()
}
fun getTerminalKeys(): Map<String, ByteArray> {
return PrefsManager.getInstance().terminalKeys
}
}

View file

@ -354,8 +354,10 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.
if (ctx.card!!.supportBlock()!!)
features += "Blockable\n"
if (ctx.card!!.supportLinkingTerminal())
features += "Linking terminal is supported"
if (ctx.card!!.supportOnlyOneCommandAtTime()!!)
if (ctx.card!!.supportOnlyOneCommandAtTime())
features += "Atomic command mode"
if (features.endsWith("\n"))

View file

@ -26,6 +26,9 @@ public class TangemCard {
private byte[] pbCardKey = null;
private byte[] pbWalletKeyRar = null;
private Date dtPersonalization = null;
private byte[] terminalPrivateKey;
private byte[] terminalPublicKey;
private boolean terminalIsLinked = false;
public String getBlockchainID() {
return blockchainID;
@ -156,6 +159,30 @@ public class TangemCard {
this.dtPersonalization = dtPersonalization;
}
public byte[] getTerminalPrivateKey() {
return terminalPrivateKey;
}
public void setTerminalPrivateKey(byte[] terminalPrivateKey) {
this.terminalPrivateKey = terminalPrivateKey;
}
public byte[] getTerminalPublicKey() {
return terminalPublicKey;
}
public void setTerminalPublicKey(byte[] terminalPublicKey) {
this.terminalPublicKey = terminalPublicKey;
}
public boolean getTerminalIsLinked() {
return terminalIsLinked;
}
public void setTerminalIsLinked(boolean terminalIsLinked) {
this.terminalIsLinked = terminalIsLinked;
}
private int health = 0;
public int getHealth() {
@ -350,6 +377,11 @@ public class TangemCard {
return (settingsMask & SettingsMask.UseBlock) != 0;
}
public Boolean supportLinkingTerminal() {
if (settingsMask == null) return null;
return (settingsMask & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0;
}
public int getMaxSignatures() {
return maxSignatures;
}

View file

@ -27,4 +27,7 @@ public interface PINsProvider {
*/
void setLastUsedPIN(String pin);
byte[] getTerminalPublicKey();
byte[] getTerminalPrivateKey();
}

View file

@ -34,6 +34,8 @@ import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -150,7 +152,8 @@ public class CardCrypto {
int rLength = enc[3];
if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3");
if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3");
if ((enc[5 + rLength] & 0x80) != 0)
throw new Exception("unsupported length encoding 3");
int sLength = enc[5 + rLength];
@ -286,4 +289,15 @@ public class CardCrypto {
return decryptedData;
}
}
public static Map<String, byte[]> generateTerminalKeys() throws Exception {
byte[] privateKey = Util.generateRandomBytes(32);
byte[] publicKey = GeneratePublicKey(privateKey);
Map<String, byte[]> keys = new HashMap<>();
keys.put("terminalPrivateKey", privateKey);
keys.put("terminalPublicKey", publicKey);
return keys;
}
}

View file

@ -120,6 +120,11 @@ public class CardProtocol {
return (DefaultPIN2.equals(pin2));
}
private byte[] terminalPublicKey;
public void setTerminalPublicKey(byte[] terminalPubKey) {
this.terminalPublicKey = terminalPubKey;
}
private TangemCard mCard;
private Exception mError;
@ -488,12 +493,22 @@ public class CardProtocol {
CommandApdu Apdu = new CommandApdu(ins);
byte[] baPIN = Util.calculateSHA256(mPIN);
Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN);
if (ins != INS.Read) {
if (ins == INS.Read) {
addTerminalPublicKeyToApdu(Apdu);
} else {
Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID());
}
return Apdu;
}
private void addTerminalPublicKeyToApdu(CommandApdu apdu) {
if (terminalPublicKey != null) {
apdu.addTLV(TLV.Tag.TAG_Terminal_PublicKey, terminalPublicKey);
} else if (mCard.getTerminalPublicKey() != null) {
apdu.addTLV(TLV.Tag.TAG_Terminal_PublicKey, mCard.getTerminalPublicKey());
}
}
// /**
// * Run READ command and parse answer
// * {@see run_Read(boolean parseResult) }
@ -633,7 +648,8 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
public void run_CreateWallet(String PIN2) throws Exception {
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
if (readResult == null)
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -835,7 +851,8 @@ public class CardProtocol {
CommandApdu rqApdu = StartPrepareCommand(INS.Sign);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length);
rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray());
byte[] hashesConcatenated = bs.toByteArray();
rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, hashesConcatenated);
if (issuerData != null) {
if (!mCard.allowedSigningMethod.contains(TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData))
throw new TangemException("Card don't support simultaneous sign with write issuer data!");
@ -853,6 +870,8 @@ public class CardProtocol {
throw new TangemException("Card require issuer validation before sign the transaction!");
}
prepareDataForLinkingTerminal(rqApdu, hashesConcatenated);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
@ -915,6 +934,8 @@ public class CardProtocol {
throw new TangemException("Card require issuer validation before sign the transaction!");
}
prepareDataForLinkingTerminal(rqApdu, bTxOutData);
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
@ -935,6 +956,12 @@ public class CardProtocol {
}
}
private void prepareDataForLinkingTerminal(CommandApdu apdu, byte[] data) throws Exception {
byte[] transactionSignature = CardCrypto.Signature(mCard.getTerminalPrivateKey(), data);
apdu.addTLV(TLV.Tag.TAG_Terminal_TransactionSignature, transactionSignature);
addTerminalPublicKeyToApdu(apdu);
}
/**
* VERIFY_CODE command
* See [1] 8.8
@ -952,7 +979,8 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
if (readResult == null)
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode);
rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII"));
rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress);
@ -988,7 +1016,8 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
private void run_ValidateCard(String PIN2) throws Exception {
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
if (readResult == null)
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
@ -1022,7 +1051,8 @@ public class CardProtocol {
* @throws Exception - if something went wrong
*/
public void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
if (readResult == null)
throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);

View file

@ -29,6 +29,8 @@ public class SettingsMask {
public static final int DisablePrecomputedNDEF = 0x00010000;
public static final int SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000;
public static String getDescription(int iValue) {
StringBuilder sb=new StringBuilder();
sb.append("[");
@ -58,6 +60,8 @@ public class SettingsMask {
if ((iValue & SettingsMask.ForbidPurgeWallet) != 0) sb.append("ForbidPurgeWallet, ");
if ((iValue & SettingsMask.AllowSelectBlockchain) != 0) sb.append("AllowSelectBlockchain, ");
if ((iValue & SettingsMask.DisablePrecomputedNDEF) != 0) sb.append("DisablePrecomputedNDEF, ");
if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0)
sb.append("SkipSecurityDelayIfValidatedByLinkedTerminal, ");
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
sb.append("]");

View file

@ -85,8 +85,11 @@ public class TLV {
TAG_Denomination(0xC0),
TAG_ValidatedBalance(0xC1),
TAG_LastSign_Date(0xC2),
TAG_DenominationText(0xC3);
TAG_DenominationText(0xC3),
TAG_Terminal_IsLinked(0x58),
TAG_Terminal_PublicKey(0x5C),
TAG_Terminal_TransactionSignature(0x57);
Tag(int Code) {
this.Code = Code;

View file

@ -235,6 +235,9 @@ public class CustomReadCardTask extends Thread {
Log.e(TAG, "Can't get max signatures");
}
TLV terminalIsLinked = protocol.getReadResult().getTLV(TLV.Tag.TAG_Terminal_IsLinked);
mCard.setTerminalIsLinked(terminalIsLinked != null);
} catch (Exception e) {
e.printStackTrace();
throw new CardProtocol.TangemException("Can't parse card data");
@ -299,6 +302,9 @@ public class CustomReadCardTask extends Thread {
if (isCancelled) return;
protocol.setPIN(CardProtocol.DefaultPIN);
if (pinsProvider != null) {
protocol.setTerminalPublicKey(pinsProvider.getTerminalPublicKey());
}
protocol.clearReadResult();
if (lastRead_Encryption == null) {

View file

@ -20,6 +20,7 @@ import javax.crypto.Cipher;
public class PINStorage implements PINsProvider {
private static String mSavedPIN, mUserPIN, mLastUsedPIN, mEncryptedPIN, mPIN2;
private static byte[] terminalPubicKey, terminalPrivateKey;
private static SharedPreferences sharedPreferences = null;
public static void init(Context context) {
@ -194,4 +195,19 @@ public class PINStorage implements PINsProvider {
return sharedPreferences == null;
}
public byte[] getTerminalPublicKey() {
return terminalPubicKey;
}
public static void setTerminalPublicKey(byte[] terminalPubicKey) {
PINStorage.terminalPubicKey = terminalPubicKey;
}
public byte[] getTerminalPrivateKey() {
return terminalPrivateKey;
}
public static void setTerminalPrivateKey(byte[] terminalPrivateKey) {
PINStorage.terminalPrivateKey = terminalPrivateKey;
}
}

View file

@ -81,6 +81,10 @@ fun TangemCard.loadFromBundle(B: Bundle) {
if (B.containsKey("onlineValidated"))
isOnlineValidated = B.getBoolean("onlineValidated")
if (B.containsKey("terminalPrivateKey")) terminalPrivateKey = B.getByteArray("terminalPrivateKey")
if (B.containsKey("terminalPublicKey")) terminalPublicKey = B.getByteArray("terminalPublicKey")
terminalIsLinked = B.getBoolean("terminalIsLinked")
}
val TangemCard.asBundle: Bundle
@ -152,6 +156,13 @@ fun TangemCard.saveToBundle(B: Bundle) {
if (isOnlineValidated != null)
B.putBoolean("onlineValidated", isOnlineValidated)
if (terminalPrivateKey != null)
B.putByteArray("terminalPrivateKey", terminalPrivateKey)
if (terminalPublicKey != null)
B.putByteArray("terminalPublicKey", terminalPublicKey)
B.putBoolean("terminalIsLinked", terminalIsLinked)
} catch (e: Exception) {
Log.e("Can't save to bundle ", e.message)
}