tangem-app-android-audited/card-common/src/main/java/com/tangem/tangemcommon/tasks/CustomReadCardTask.java
2019-02-27 21:45:00 +03:00

449 lines
19 KiB
Java

package com.tangem.tangemcommon.tasks;
import com.tangem.tangemcommon.data.external.CardDataSubstitutionProvider;
import com.tangem.tangemcommon.data.Manufacturer;
import com.tangem.tangemcommon.data.external.PINsProvider;
import com.tangem.tangemcommon.data.TangemCard;
import com.tangem.tangemcommon.reader.CardCrypto;
import com.tangem.tangemcommon.reader.CardProtocol;
import com.tangem.tangemcommon.reader.NfcReader;
import com.tangem.tangemcommon.reader.TLV;
import com.tangem.tangemcommon.reader.TLVException;
import com.tangem.tangemcommon.reader.TLVList;
import com.tangem.tangemcommon.util.Log;
import com.tangem.tangemcommon.util.Util;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.math.ec.ECPoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
/**
* Base class for card communication task
*/
public class CustomReadCardTask extends Thread {
public static final String TAG = CustomReadCardTask.class.getSimpleName();
protected NfcReader mIsoDep;
protected CardProtocol.Notifications mNotifications;
protected boolean isCancelled = false;
protected TangemCard mCard;
CardDataSubstitutionProvider localStorage;
PINsProvider pinsProvider;
CardProtocol protocol;
// this fields are static to optimize process when need enter pin and scan card again
private static ArrayList<String> lastRead_UnsuccessfullPINs = new ArrayList<>();
private static TangemCard.EncryptionMode lastRead_Encryption = null;
private static String lastRead_UID;
public static void resetLastReadInfo() {
lastRead_UID = "";
lastRead_Encryption = null;
lastRead_UnsuccessfullPINs.clear();
}
public CustomReadCardTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, CardProtocol.Notifications notifications) {
mIsoDep = reader;
mNotifications = notifications;
localStorage = cardDataSubstitutionProvider;
this.pinsProvider = pinsProvider;
mCard = card;
}
/**
* Executing GET_ISSUER_DATA or WRITE_ISSUER_DATA depending on state in TangemCard object {@see TangemCard.getNeedWriteIssuerData()}
* See [1] 8.7
* Usually this function must be called if run_Task() of descendants
*
* @throws Exception if something went wrong
*/
protected void run_ReadOrWriteIssuerData() throws Exception {
TLVList tlvIssuerData;
if (mCard.getNeedWriteIssuerData()) {
protocol.run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature());
try {
tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData());
} catch (TLVException e) {
e.printStackTrace();
tlvIssuerData = null;
}
} else {
try {
tlvIssuerData = protocol.run_GetIssuerData();
} catch (Exception e) {
e.printStackTrace();
tlvIssuerData = null;
}
}
// try read offline balance data
if (mCard.getStatus() == TangemCard.Status.Loaded) {
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) {
mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_ValidatedBalance).Value);
} else {
mCard.clearOfflineBalance();
}
} else {
mCard.clearOfflineBalance();
}
// try read denomination
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null) {
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, null);
}
} else {
mCard.clearDenomination();
}
}
/**
* Parse response of the last READ command into TangemCard object
* See [1] 8.2, 5.3, 3.3
*
* @throws CardProtocol.TangemException - if something went wrong
*/
public void parseReadResult() throws CardProtocol.TangemException {
if (mCard == null) mCard = protocol.getCard();
// These tags always present in the parsed response: TAG_Status, TAG_CID, TAG_Manufacture_ID, TAG_Health, TAG_Firmware
TLV tlvStatus = protocol.getReadResult().getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(protocol.getReadResult().getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
// Support of legacy Firmware
if (protocol.getReadResult().getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(protocol.getReadResult().getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
mCard.setHealth(protocol.getReadResult().getTLV(TLV.Tag.TAG_Health).getAsInt());
// If the card was previously personalized then parse personalized card data - card public key, blockchain data and other settings
if (mCard.getStatus() != TangemCard.Status.NotPersonalized) {
try {
TLV tlvCardPubkicKey = protocol.getReadResult().getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
throw new CardProtocol.TangemException("Invalid answer format");
mCard.setCardPublicKey(tlvCardPubkicKey.Value);
TLVList tlvCardData = TLVList.fromBytes(protocol.getReadResult().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);
if (tokenSymbol != null)
mCard.setTokenSymbol(tokenSymbol.getAsString());
if (contractAddress != null)
mCard.setContractAddress(contractAddress.getAsString());
if (tokens_decimal != null)
mCard.setTokensDecimal(tokens_decimal.getAsInt());
byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value;
int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF);
int month = tlvPersonalizationDT[2] - 1;
int day = tlvPersonalizationDT[3];
Calendar cd = Calendar.getInstance();
cd.set(year, month, day, 0, 0, 0);
mCard.setPersonalizationDateTime(cd.getTime());
try {
// Support of legacy Firmware
if (mCard.getFirmwareVersion() == null) {
mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Cannot get firmware version");
mCard.setFirmwareVersion("0.00");
}
try {
if (mCard.getFirmwareVersion().compareTo("1.05") < 0) {
// In FW ver 1.05, the issuer has one key pair used as both DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
//mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} else {
// In newer FW versions, the issuer has two different key pairs - DataKey and TransactionKey, see [1] 3.3.1 and [1] 3.3.2
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Cannot get issuer, try a version for older cards");
try {
// for very very old cards Issuer key can be stored in different TLV tag
mCard.setIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), protocol.getReadResult().getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} catch (Exception ee) {
ee.printStackTrace();
Log.e(TAG, "Cannot get issuer");
mCard.setIssuer("Unknown", null);
}
}
// Overriding of missing card data, e.g. for cards with unknown ERC20 contract data
// This method must be called after the issuer data is defined because newly written data is verified by issuer data key
try {
// Hardcoded for some known batches, or, for other batches, received from Tangem server and stored in local storage
if (mCard.getBatch().equals("0017")) {
mCard.setContractAddress("0x9Eef75bA8e81340da9D8d1fd06B2f313DB88839c");
} else if (mCard.getBatch().equals("0019")) {
mCard.setContractAddress("0x0c056b0cda0763cc14b8b2d6c02465c91e33ec72");
} else {
//CardDataSubstitutionProvider localStorage = new CardDataSubstitutionProvider(mContext);
if (localStorage != null) localStorage.applySubstitution(mCard);
}
} catch (Exception e) {
Log.e(TAG, "Can't apply card data substitution");
e.printStackTrace();
}
mCard.setBlockchainID(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString());
try {
mCard.setSettingsMask(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SettingsMask));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get settings mask");
}
if (protocol.getReadResult().getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) {
mCard.setPauseBeforePIN2(10 * protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2));
}
try {
mCard.setSigningMethod(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SigningMethod));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get signing method");
mCard.setSigningMethod(0);
}
try {
mCard.setMaxSignatures(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_MaxSignatures));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Can't get max signatures");
}
} catch (Exception e) {
e.printStackTrace();
throw new CardProtocol.TangemException("Can't parse card data");
}
}
// Parse additional parameters for Loaded cards: wallet public key, remaining signatures, etc
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = protocol.getReadResult().getTLV(TLV.Tag.TAG_Wallet_PublicKey);
String curveID = protocol.getReadResult().getTLV(TLV.Tag.TAG_CurveID).getAsString();
CardCrypto.Curve curve = CardCrypto.Curve.valueOf(curveID);
switch (curve) {
case secp256k1:
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
break;
case ed25519:
mCard.setWalletPublicKey(tlvPublicKey.Value);
mCard.setWalletPublicKeyRar(tlvPublicKey.Value);
break;
}
mCard.setRemainingSignatures(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_RemainingSignatures));
if (protocol.getReadResult() != null && protocol.getReadResult().getTLV(TLV.Tag.TAG_SignedHashes) != null) {
mCard.setSignedHashes(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_SignedHashes));
}
} else {
// mCard.setWallet("N/A");
}
}
/**
* On first time card reading run READ command, parse answer and Executing GET_ISSUER_DATA or WRITE_ISSUER_DATA depending on state in TangemCard object {@see TangemCard.getNeedWriteIssuerData()}
* See [1] 8.7
*
* @throws Exception if something went wrong
*/
public void run_FirstTimeRead() throws Exception {
byte[] UID = mIsoDep.getId();
String sUID = Util.byteArrayToHexString(UID);
if (!lastRead_UID.equals(sUID)) {
resetLastReadInfo();
}
Log.i(TAG, "[-- Start read card info --]");
if (isCancelled) return;
protocol.setPIN(CardProtocol.DefaultPIN);
protocol.clearReadResult();
if (lastRead_Encryption == null) {
Log.i(TAG, "Try get supported encryption mode");
protocol.run_GetSupportedEncryption();
} else {
Log.i(TAG, "Use already defined encryption mode: " + lastRead_Encryption.name());
protocol.getCard().encryptionMode = lastRead_Encryption;
}
if (protocol.haveReadResult()) {
//already have read result (obtained while get supported encryption), only read issuer data and define offline balance
parseReadResult();
pinsProvider.setLastUsedPIN(protocol.getCard().getPIN());
} else {
//don't have read result - may be don't get supported encryption on this try, need encryption or need another PIN
if (lastRead_Encryption == null) {
// we try get supported encryption on this time
lastRead_Encryption = protocol.getCard().encryptionMode;
if (protocol.getCard().encryptionMode == TangemCard.EncryptionMode.None) {
// default pin not accepted
lastRead_UnsuccessfullPINs.add(CardProtocol.DefaultPIN);
}
}
boolean pinFound = false;
for (String PIN : pinsProvider.getPINs()) {
Log.e(TAG, "PIN: " + PIN);
boolean skipPin = false;
for (int i = 0; i < lastRead_UnsuccessfullPINs.size(); i++) {
if (lastRead_UnsuccessfullPINs.get(i).equals(PIN)) {
skipPin = true;
break;
}
}
if (skipPin) {
Log.e(TAG, "Skip PIN - already checked before");
continue;
}
try {
protocol.setPIN(PIN);
if (protocol.getCard().encryptionMode != TangemCard.EncryptionMode.None) {
protocol.CreateProtocolKey();
}
protocol.run_Read();
// After first successful read, data will be parsed into TangemCard object
parseReadResult();
pinsProvider.setLastUsedPIN(PIN);
pinFound = true;
protocol.getCard().setPIN(PIN);
break;
} catch (CardProtocol.TangemException_InvalidPIN e) {
Log.e(TAG, e.getMessage());
lastRead_UnsuccessfullPINs.add(PIN);
}
}
if (!pinFound) {
throw new CardProtocol.TangemException_InvalidPIN("No valid PIN found!");
}
}
}
/**
* Run read and check that the card is the same
*
* @throws Exception if something went wrong
*/
public void run_SecondTimeRead() throws Exception {
String PIN = mCard.getPIN();
protocol.setPIN(PIN);
protocol.run_Read();
pinsProvider.setLastUsedPIN(PIN);
if (!Arrays.equals(mCard.getCID(), protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID).Value)) {
throw new CardProtocol.TangemException("Card must be the same. Reading attempt on different card!");
}
}
/**
* This function must be override in descendants to arrive the task goal - sign, verify and etc
* @throws Exception if something went wrong
*/
public void run_Task() throws Exception {
}
@Override
public void run() {
if (mIsoDep == null) {
return;
}
try {
// for Samsung's bugs -
// Workaround for the Samsung Galaxy S5 (since the
// first connection always hangs on transceive).
mIsoDep.connect();
try {
protocol = new CardProtocol(mIsoDep, mCard, mNotifications);
mNotifications.onReadStart(protocol);
try {
Log.i(TAG, String.format("[-- Start task -- %s --]", getClass().getSimpleName()));
mNotifications.onReadProgress(protocol, 5);
if (mCard == null) {
// first time reading
run_FirstTimeRead();
} else {
run_SecondTimeRead();
}
run_Task();
mNotifications.onReadProgress(protocol, 100);
} catch (Exception e) {
e.printStackTrace();
protocol.setError(e);
} finally {
Log.i(TAG, String.format("[-- Finish task -- %s --]", getClass().getSimpleName()));
mNotifications.onReadFinish(protocol);
}
} finally {
mIsoDep.ignoreTag();
}
} catch (Exception e) {
e.printStackTrace();
mIsoDep.notifyReadResult(false);
}
}
public void cancel(Boolean AllowInterrupt) {
try {
if (isAlive()) {
isCancelled = true;
join(500);
}
if (isAlive() && AllowInterrupt) {
interrupt();
mNotifications.onReadCancel();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}