Updated on 2026-08-14

This commit is contained in:
Tangem 2020-01-19 14:37:37 +03:00
parent 9f3ebb2e4c
commit 37c68616ba
12 changed files with 553 additions and 166 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.60'
ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()

View file

@ -1,9 +1,14 @@
package com.tangem.tangem_card.data;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.reader.ProductMask;
import com.tangem.tangem_card.reader.SettingsMask;
import com.tangem.tangem_card.reader.TLV;
import com.tangem.tangem_card.reader.TLVList;
import com.tangem.tangem_card.util.Util;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
@ -288,6 +293,28 @@ public class TangemCard {
return "";
}
private byte[] issuerDataEx;
private byte[] issuerDataExSignature;
private int issuerDataExCounter;
public byte[] getIssuerDataEx() {
return issuerDataEx;
}
public byte[] getIssuerDataExSignature() {
return issuerDataExSignature;
}
public int getIssuerDataExCounter() {
return issuerDataExCounter;
}
public void setIssuerDataEx(byte[] value, byte[] signature, int counter) {
issuerDataEx = value;
issuerDataExSignature = signature;
issuerDataExCounter = counter;
}
private int pauseBeforePIN2 = 0;
public void setPauseBeforePIN2(int value) {
@ -579,6 +606,79 @@ public class TangemCard {
this.UID = UID;
}
public int productMask = ProductMask.Note;
public void setProductMask(int productMask) {
this.productMask = productMask;
}
public boolean isIDCard() {
try {
return (productMask & ProductMask.IdCard) != 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
TLVList tlvIDCardData = null;
public boolean hasIDCardData() {
if (tlvIDCardData == null) {
if (issuerDataEx == null) return false;
try {
tlvIDCardData = TLVList.fromBytes(issuerDataEx);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
return tlvIDCardData.hasTag(TLV.Tag.TAG_FullName) && tlvIDCardData.hasTag(TLV.Tag.TAG_Birthday) && tlvIDCardData.hasTag(TLV.Tag.TAG_Gender) && tlvIDCardData.hasTag(TLV.Tag.TAG_Photo)
&& tlvIDCardData.hasTag(TLV.Tag.TAG_IssueDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_ExpireDate) && tlvIDCardData.hasTag(TLV.Tag.TAG_TrustedAddress);
}
public static class IDCardData
{
public String fullName;
public String birthday;
public String gender;
public byte[] photo;
public String issueDate;
public String expireDate;
public String trustedAddress;
public TLVList toTLVList() {
TLVList tlvIDCardData=new TLVList();
tlvIDCardData.add(new TLV(TLV.Tag.TAG_FullName,fullName.getBytes(StandardCharsets.UTF_8)));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_Birthday,birthday.getBytes(StandardCharsets.UTF_8)));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_Gender, gender.getBytes(StandardCharsets.UTF_8)));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_Photo, photo));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_IssueDate,issueDate.getBytes(StandardCharsets.UTF_8)));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_ExpireDate,expireDate.getBytes(StandardCharsets.UTF_8)));
tlvIDCardData.add(new TLV(TLV.Tag.TAG_TrustedAddress,trustedAddress.getBytes(StandardCharsets.UTF_8)));
return tlvIDCardData;
}
public static IDCardData fromTLVList(TLVList tlvIDCardData)
{
IDCardData result=new IDCardData();
result.fullName=tlvIDCardData.getTLV(TLV.Tag.TAG_FullName).getAsString();
result.birthday=tlvIDCardData.getTLV(TLV.Tag.TAG_Birthday).getAsString();
result.gender=tlvIDCardData.getTLV(TLV.Tag.TAG_Gender).getAsString();
result.photo=tlvIDCardData.getTLV(TLV.Tag.TAG_Photo).Value;
result.issueDate=tlvIDCardData.getTLV(TLV.Tag.TAG_IssueDate).getAsString();
result.expireDate=tlvIDCardData.getTLV(TLV.Tag.TAG_ExpireDate).getAsString();
result.trustedAddress=tlvIDCardData.getTLV(TLV.Tag.TAG_TrustedAddress).getAsString();
return result;
}
}
public IDCardData getIDCardData()
{
if( !hasIDCardData() ) return null;
return IDCardData.fromTLVList(tlvIDCardData);
}
private byte[] offlineBalance;
@ -638,164 +738,6 @@ public class TangemCard {
this.tagSignature = tagSignature;
}
// public Bundle getAsBundle() {
// Bundle B = new Bundle();
// saveToBundle(B);
// return B;
// }
//
// public void saveToBundle(Bundle B) {
// try {
// B.putString("UID", UID);
// B.putByteArray("CID", CID);
// B.putString("PIN", PIN);
// B.putString("PIN2", PIN2.name());
// B.putString("Status", status.name());
// B.putString("Blockchain", blockchainID);
// B.putString("BlockchainName", blockchainName);
// B.putInt("TokensDecimal", tokensDecimal);
// B.putString("TokenSymbol", tokenSymbol);
// B.putString("ContractAddress", contractAddress);
// if (dtPersonalization != null) B.putLong("dtPersonalization", dtPersonalization.getTime());
// B.putInt("RemainingSignatures", remainingSignatures);
// B.putInt("MaxSignatures", maxSignatures);
// B.putInt("Health", health);
// if (settingsMask != null) B.putInt("settingsMask", settingsMask);
// B.putInt("pauseBeforePIN2", pauseBeforePIN2);
// 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.getID());
// if (issuerPublicDataKey != null) B.putByteArray("IssuerPublicDataKey", issuerPublicDataKey);
// if (firmwareVersion != null) B.putString("FirmwareVersion", firmwareVersion);
// if (batch != null) B.putString("Batch", batch);
// B.putBoolean("ManufacturerConfirmed", manufacturerConfirmed);
// B.putBoolean("CardPublicKeyValid", isCardPublicKeyValid());
// B.putByteArray("CardPublicKey", getCardPublicKey());
//
// B.putInt("SignedHashes", getSignedHashes());
// B.putBoolean("WalletPublicKeyValid", isWalletPublicKeyValid());
// if (pbWalletKey != null)
// B.putByteArray("PublicKey", pbWalletKey);
// if (pbWalletKeyRar != null)
// B.putByteArray("PublicKeyRar", pbWalletKeyRar);
//
// if (getOfflineBalance() != null) B.putByteArray("OfflineBalance", getOfflineBalance());
//
// if (getDenomination() != null) B.putByteArray("Denomination", getDenomination());
// if (getDenominationText() != null) B.putString("DenominationText", getDenominationText());
//
// if (getIssuerData() != null && getIssuerDataSignature() != null) {
// B.putByteArray("IssuerData", getIssuerData());
// B.putByteArray("IssuerDataSignature", getIssuerDataSignature());
// B.putBoolean("NeedWriteIssuerData", getNeedWriteIssuerData());
// }
//
// 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);
//
// 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);
// } catch (Exception e) {
// Log.e("Can't save to bundle ", e.getMessage());
// }
//
// }
//
// public void loadFromBundle(Bundle B) {
// UID = B.getString("UID");
// CID = B.getByteArray("CID");
// PIN = B.getString("PIN");
// PIN2 = PIN2_Mode.valueOf(B.getString("PIN2"));
// status = Status.valueOf(B.getString("Status"));
// blockchainID = B.getString("Blockchain");
// tokensDecimal = B.getInt("TokensDecimal", 18);
// tokenSymbol = B.getString("TokenSymbol", "");
// contractAddress = B.getString("ContractAddress", "");
// if (B.containsKey("BlockchainName"))
// blockchainName = B.getString("BlockchainName", "");
// if (B.containsKey("dtPersonalization")) {
// dtPersonalization = new Date(B.getLong("dtPersonalization"));
// }
// remainingSignatures = B.getInt("RemainingSignatures");
// maxSignatures = B.getInt("MaxSignatures");
// health = B.getInt("health");
// if (B.containsKey("settingsMask")) settingsMask = B.getInt("settingsMask");
// pauseBeforePIN2 = B.getInt("pauseBeforePIN2");
// if (B.containsKey("signingMethod"))
// signingMethod = SigningMethod.valueOf(B.getString("signingMethod"));
// if (B.containsKey("Manufacturer"))
// manufacturer = Manufacturer.valueOf(B.getString("Manufacturer"));
// manufacturerConfirmed = B.getBoolean("ManufacturerConfirmed");
// if (B.containsKey("EncryptionMode"))
// encryptionMode = EncryptionMode.valueOf(B.getString("EncryptionMode"));
// else
// encryptionMode = null;
//
// if (B.containsKey("SignedHashes")) setSignedHashes(B.getInt("SignedHashes"));
//
// if (B.containsKey("Issuer")) issuer = Issuer.FindIssuer(B.getString("Issuer"));
// if (B.containsKey("IssuerPublicDataKey"))
// issuerPublicDataKey = B.getByteArray("IssuerPublicDataKey");
//
// 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"));
//
// if (B.containsKey("OfflineBalance")) setOfflineBalance(B.getByteArray("OfflineBalance"));
// else clearOfflineBalance();
//
// if (B.containsKey("Denomination") && B.containsKey("DenominationText")) {
// setDenomination(B.getByteArray("Denomination"), B.getString("DenominationText"));
// } else if (B.containsKey("Denomination")) {
// setDenomination(B.getByteArray("Denomination"), "N/A");
// } else clearDenomination();
//
// if (B.containsKey("IssuerData") && B.containsKey("IssuerDataSignature"))
// setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"));
// else setIssuerData(null, null);
//
// if (B.containsKey("NeedWriteIssuerData"))
// setNeedWriteIssuerData(B.getBoolean("NeedWriteIssuerData"));
//
// walletPublicKeyValid = B.getBoolean("WalletPublicKeyValid");
// if (B.containsKey("PublicKey")) {
// pbWalletKey = B.getByteArray("PublicKey");
// }
// if (B.containsKey("PublicKeyRar")) {
// pbWalletKeyRar = B.getByteArray("PublicKeyRar");
// }
//
// 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 enum EncryptionMode {
None((byte) 0x0), Fast((byte) 0x1), Strong((byte) 0x2);

View file

@ -1,5 +1,6 @@
package com.tangem.tangem_card.reader;
import com.tangem.tangem_card.data.Issuer;
import com.tangem.tangem_card.data.Manufacturer;
import com.tangem.tangem_card.data.TangemCard;
import com.tangem.tangem_card.util.Log;
@ -17,6 +18,7 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import javax.crypto.KeyAgreement;
@ -99,6 +101,11 @@ public class CardProtocol {
private NfcReader mIsoDep;
public NfcReader getReader()
{
return mIsoDep;
}
public static final String DefaultPIN = "000000";
public static final String DefaultPIN2 = "000";
@ -1095,6 +1102,12 @@ public class CardProtocol {
if (issuerData == null || issuerDataSignature == null)
throw new TangemException("Invalid answer format (GetIssuerData)");
if( issuerData.Value.length==0 && issuerDataSignature.Value.length==0 )
{
mCard.setIssuerData(null, null);
return new TLVList();
}
ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
bsDataToVerify.write(mCard.getCID());
bsDataToVerify.write(issuerData.Value);
@ -1117,6 +1130,185 @@ public class CardProtocol {
}
}
/**
* Series of GET_ISSUER_DATA command to read extra data and verify issuer signature of returned extra data
* See [1] 3.3, 8.7
* This command returns Issuer_Extra_Data data block and its issuers signature.
*
* @return TLVList with issuerData (if success read and verify)
* @throws Exception - if something went wrong
*/
public TLVList run_GetIssuerDataEx(Notifications notifications) throws Exception {
boolean needReadIssuerDataCounter = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
TLV issuerDataSignature = null;
TLV issuerDataCounter = null;
TLV issuerDataSize = null;
ByteArrayOutputStream bsIssuerDataEx = new ByteArrayOutputStream();
do {
CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData);
rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01);
rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, bsIssuerDataEx.size());
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data);
if (issuerData != null && issuerData.Value != null)
bsIssuerDataEx.write(issuerData.Value);
issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature);
issuerDataCounter = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Counter);
if (issuerDataSize == null)
issuerDataSize = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Size);
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
if (issuerDataSize != null && issuerDataSize.getAsInt() != 0) {
if (notifications != null)
notifications.onReadProgress(this, (int) Math.round(bsIssuerDataEx.size() * 100.0 / issuerDataSize.getAsInt()));
Log.i(logTag, String.format("Read %d/%d bytes", bsIssuerDataEx.size(), issuerDataSize.getAsInt()));
}
} while ((issuerDataSignature == null) || (needReadIssuerDataCounter && (issuerDataCounter == null)));
boolean protectIssuerDataAgainstReplay = (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
if (notifications != null) notifications.onReadProgress(this, 100);
if (bsIssuerDataEx.size() > 0 && issuerDataSignature != null && issuerDataSignature.Value != null) {
ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
bsDataToVerify.write(mCard.getCID());
bsDataToVerify.write(bsIssuerDataEx.toByteArray());
if (protectIssuerDataAgainstReplay) {
bsDataToVerify.write(issuerDataCounter.Value);
}
try {
if (CardCrypto.VerifySignature(mCard.getIssuerPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) {
mCard.setIssuerDataEx(bsIssuerDataEx.toByteArray(), issuerDataSignature.Value, issuerDataCounter.getAsInt());
return TLVList.fromBytes(bsIssuerDataEx.toByteArray());
} else {
throw new TangemException("Invalid issuer data read (signature verification failed)");
}
} catch (Exception e) {
e.printStackTrace();
throw new TangemException("Invalid issuer data read");
}
} else {
mCard.setIssuerDataEx(null, null, issuerDataCounter.getAsInt());
return null;
}
}
/**
* Series of WRITE_ISSUER_DATA command to write extra data:
* 1. start write procedure
* 2. series of writing data blocks
* 3. finalize write procedure
* This command make signatures with provided issuer privateDataKey
* - to start write procedure (sign CID|Counter|Size)
* - to finalize write procedure (sign CID|dataEx|Counter)
* See [1] 3.3, 8.8
*
* @throws Exception - if something went wrong
*/
public void run_WriteIssuerDataEx(Notifications notifications, byte[] dataEx, byte[] issuerPrivateDataKey) throws Exception {
boolean needCounter = readResult.getTLV(TLV.Tag.TAG_SettingsMask) != null && (readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask) & SettingsMask.ProtectIssuerDataAgainstReplay) != 0;
Log.i(logTag, "WriteIssuerDataEx - Start");
int issuerDataCounter = 0;
ByteArrayOutputStream osDataToSign = new ByteArrayOutputStream();
osDataToSign.write(mCard.getCID());
if (needCounter) {
issuerDataCounter = mCard.getIssuerDataExCounter() + 1;
osDataToSign.write(Util.intToByteArray4(issuerDataCounter));
}
osDataToSign.write(Util.intToByteArray2(dataEx.length));
byte[] issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray());
CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x01);
if (needCounter) {
rqApdu.addTLV_U32(TLV.Tag.TAG_Issuer_Data_Counter, issuerDataCounter);
}
rqApdu.addTLV_U16(TLV.Tag.TAG_Size, dataEx.length);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, true);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
if (notifications != null)
notifications.onReadProgress(this, 10);
Log.i(logTag, "WriteIssuerDataEx - Send data");
int offset = 0;
int partSize = 1524;
while (offset < dataEx.length) {
if (dataEx.length - offset < partSize) {
partSize = dataEx.length - offset;
}
byte[] part = Arrays.copyOfRange(dataEx, offset, offset + partSize);
rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x02);
rqApdu.addTLV_U16(TLV.Tag.TAG_Offset, offset);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, part);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
rspApdu = SendAndReceive(rqApdu, true);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
offset += partSize;
if (notifications != null)
notifications.onReadProgress(this, 10+(int) Math.round(offset * 80.0 / dataEx.length));
}
Log.i(logTag, "WriteIssuerDataEx - Finalize");
osDataToSign = new ByteArrayOutputStream();
osDataToSign.write(mCard.getCID());
osDataToSign.write(dataEx);
if (needCounter) {
osDataToSign.write(Util.intToByteArray4(issuerDataCounter));
}
issuerDataSignature = CardCrypto.Signature(issuerPrivateDataKey, osDataToSign.toByteArray());
rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV_U8(TLV.Tag.TAG_Mode, 0x03);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
rspApdu = SendAndReceive(rqApdu, true);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
if (notifications != null)
notifications.onReadProgress(this, 100);
}
/**
* Execute consecutive READ commands with increasing encryption level from EncryptionMode.None to EncryptionMode.Strong, see {@link TangemCard.EncryptionMode}
* If card requires stricter encryption level it returns SW.NEED_ENCRYPTION status word

View file

@ -0,0 +1,25 @@
package com.tangem.tangem_card.reader;
/**
* Created by dvol on 15.07.2019.
*/
public class ProductMask {
public static final int Note = 0x0001;
public static final int Tag = 0x0002;
public static final int IdCard = 0x0004;
public static String getDescription(int iValue) {
StringBuilder sb = new StringBuilder();
sb.append("[");
if ((iValue & ProductMask.Note) != 0) sb.append("Note, ");
if ((iValue & ProductMask.Tag) != 0)
sb.append("Tag, ");
if ((iValue & ProductMask.IdCard) != 0)
sb.append("IdCard, ");
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
sb.append("]");
return sb.toString();
}
}

View file

@ -29,8 +29,16 @@ public class SettingsMask {
public static final int DisablePrecomputedNDEF = 0x00010000;
public static final int SkipSecurityDelayIfValidatedByIssuer = 0x00020000;
public static final int SkipCheckPIN2andCVCIfValidatedByIssuer = 0x00040000;
public static final int SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000;
public static final int RestrictOverwriteIssuerDataEx = 0x00100000;
public static final int RequireTermTxSignature = 0x01000000;
public static final int RequireTermCertSignature = 0x02000000;
public static final int CheckPIN3onCard = 0x04000000;
public static String getDescription(int iValue) {
StringBuilder sb=new StringBuilder();
sb.append("[");
@ -63,6 +71,24 @@ public class SettingsMask {
if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByLinkedTerminal) != 0)
sb.append("SkipSecurityDelayIfValidatedByLinkedTerminal, ");
if ((iValue & SettingsMask.SkipSecurityDelayIfValidatedByIssuer) != 0)
sb.append("SkipSecurityDelayIfValidatedByIssuer, ");
if ((iValue & SettingsMask.SkipCheckPIN2andCVCIfValidatedByIssuer) != 0)
sb.append("SkipCheckPIN2andCVCIfValidatedByIssuer, ");
if ((iValue & SettingsMask.RestrictOverwriteIssuerDataEx) != 0)
sb.append("RestrictOverwriteIssuerDataEx, ");
if ((iValue & SettingsMask.RequireTermTxSignature) != 0)
sb.append("RequireTermTxSignature, ");
if ((iValue & SettingsMask.RequireTermCertSignature) != 0)
sb.append("RequireTermCertSignature, ");
if ((iValue & SettingsMask.CheckPIN3onCard) != 0)
sb.append("CheckPIN3onCard, ");
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
sb.append("]");
return sb.toString();

View file

@ -44,15 +44,27 @@ public class TLV {
TAG_Session_Key_B(0x1B),
TAG_Pause(0x1C),
TAG_PIN3(0x1D),
TAG_NewPIN3(0x1E),
TAG_CrEx_Key(0x1F),
TAG_Manufacture_ID(0x20),
TAG_Manufacturer_Signature(0x21),
TAG_Mode(0x23),
TAG_Offset(0x24),
TAG_Size(0x25),
TAG_User_Data(0x2A),
TAG_User_ProtectedData(0x2B),
TAG_Issuer_Data_PublicKey(0x30),
TAG_Issuer_Transaction_PublicKey(0x31),
TAG_Issuer_Data(0x32),
TAG_Issuer_Data_Signature(0x33),
TAG_Issuer_Transaction_Signature(0x34),
TAG_Issuer_Data_Counter(0x35),
TAG_Acquirer_PublicKey(0x37),
TAG_IsActivated(0x3A),
TAG_ActivationSeed(0x3B),
@ -65,12 +77,38 @@ public class TLV {
TAG_TrOut_Hash(0x50),
TAG_TrOut_HashSize(0x51),
TAG_TrOut_Raw(0x52),
TAG_TrOut_Amount(0x53),
TAG_Terminal_PaymentFlowVersion(0x54),
TAG_Terminal_Certificate(0x55),
// hash of extra data to transaction (for SIGN_HASH_EXTERNAL)
TAG_TrOut_Extra_Hash(0x56),
TAG_Terminal_Transaction_Signature(0x57),
TAG_Terminal_Certificate_Id(0x5A),
TAG_Terminal_Certificate_Param(0x5B),
TAG_Terminal_Certificate_PublicKey(0x5C),
TAG_Terminal_Certificate_Signature(0x5D),
TAG_Wallet_PublicKey(0x60),
TAG_Signature(0x61),
TAG_RemainingSignatures(0x62),
TAG_SignedHashes(0x63),
TAG_CheckWalletCounter(0x64),
TAG_CrEx_Salt(0x65),
TAG_CrEx_Message(0x66),
TAG_CrEx_CryptedMessage(0x67),
TAG_CrEx_HMAC(0x68),
TAG_CrEx_RequirePIN3(0x69),
TAG_TrOut_Extra_Signature(0x6A),
TAG_SignCommand_ChecksPassed(0x6B),
TAG_Wallet_PrivateKey(0x70),
TAG_Card_PrivateKey(0x71),
TAG_Block_Reason(0x72),
TAG_Firmware(0x80),
TAG_Batch(0x81),
TAG_ManufactureDateTime(0x82),
@ -79,6 +117,10 @@ public class TLV {
TAG_Manufacturer_PublicKey(0x85),
TAG_CardID_Manufacturer_Signature(0x86),
TAG_ProductMask(0x8A),
TAG_PinLessFloorLimit(0x90),
TAG_Token_Symbol(0xA0),
TAG_Token_Contract_Address(0xA1),
TAG_Token_Decimal(0xA2),
@ -87,6 +129,14 @@ public class TLV {
TAG_LastSign_Date(0xC2),
TAG_DenominationText(0xC3),
TAG_FullName (0xD0),
TAG_Birthday (0xD1),
TAG_Photo (0xD2),
TAG_Gender(0xD3),
TAG_IssueDate (0xD4),
TAG_ExpireDate(0xD5),
TAG_TrustedAddress (0xD6),
TAG_Terminal_IsLinked(0x58),
TAG_Terminal_PublicKey(0x5C),
TAG_Terminal_TransactionSignature(0x57);
@ -172,7 +222,7 @@ public class TLV {
}
public String getAsString() {
if( Value.length==0 ) return "";
if (Value.length == 0) return "";
if (Value[Value.length - 1] == 0) {
String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8"));
@ -214,18 +264,29 @@ public class TLV {
return String.format("%s[]: [[NULL]]", tag.name());
}
case TAG_SettingsMask: {
StringBuilder sb=new StringBuilder();
if( Value!=null ) {
StringBuilder sb = new StringBuilder();
if (Value != null) {
try {
int iValue = Util.byteArrayToInt(Value);
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), SettingsMask.getDescription(iValue));
}
catch (Exception e)
{
} catch (Exception e) {
e.printStackTrace();
return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value));
}
}else{
} else {
return String.format("%s[]: [[NULL]]", tag.name());
}
}
case TAG_ProductMask: {
if (Value != null) {
try {
int iValue = Util.byteArrayToInt(Value);
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), ProductMask.getDescription(iValue));
} catch (Exception e) {
e.printStackTrace();
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value));
}
} else {
return String.format("%s[]: [[NULL]]", tag.name());
}
}

View file

@ -85,4 +85,8 @@ public class TLVList extends ArrayList<TLV> {
while (tlv != null);
return tlvList;
}
public boolean hasTag(TLV.Tag tag) {
return getTLV(tag)!=null;
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.tangem_card.data.external.PINsProvider;
import com.tangem.tangem_card.reader.CardCrypto;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.reader.NfcReader;
import com.tangem.tangem_card.reader.ProductMask;
import com.tangem.tangem_card.reader.TLV;
import com.tangem.tangem_card.reader.TLVException;
import com.tangem.tangem_card.reader.TLVList;
@ -137,6 +138,12 @@ public class CustomReadCardTask extends Thread {
mCard.setBatch(tlvCardData.getTLV(TLV.Tag.TAG_Batch).getAsHexString());
if( tlvCardData.hasTag(TLV.Tag.TAG_ProductMask)) {
mCard.setProductMask(tlvCardData.getTagAsInt(TLV.Tag.TAG_ProductMask));
}else{
mCard.setProductMask(ProductMask.Note);
}
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);

View file

@ -46,6 +46,10 @@ public class VerifyCardTask extends CustomReadCardTask {
}
mNotifications.onReadProgress(protocol, 90);
if( protocol.getCard().isIDCard() )
{
protocol.run_GetIssuerDataEx(mNotifications);
}
}
}

View file

@ -0,0 +1,36 @@
package com.tangem.tangem_card.tasks;
import com.tangem.tangem_card.data.TangemCard;
import com.tangem.tangem_card.data.external.CardDataSubstitutionProvider;
import com.tangem.tangem_card.data.external.PINsProvider;
import com.tangem.tangem_card.reader.CardProtocol;
import com.tangem.tangem_card.reader.NfcReader;
/**
* Created by dvol on 19.01.2020.
*/
public class WriteIDCardTask extends CustomReadCardTask {
public static final String TAG = WriteIDCardTask.class.getSimpleName();
private TangemCard.IDCardData idCardData;
private byte[] issuerPrivateDataKey;
public WriteIDCardTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, TangemCard.IDCardData idCardData, byte[] issuerPrivateDataKey, CardProtocol.Notifications notifications) {
super(card, reader, cardDataSubstitutionProvider, pinsProvider, notifications);
this.idCardData=idCardData;
this.issuerPrivateDataKey=issuerPrivateDataKey;
}
@Override
public void run_Task() throws Exception {
mNotifications.onReadProgress(protocol, 0);
if (isCancelled) return;
if( !protocol.getCard().isIDCard() )
throw new Exception("It's not an IDCard!");
protocol.run_WriteIssuerDataEx(mNotifications,idCardData.toTLVList().toBytes(),issuerPrivateDataKey);
mNotifications.onReadProgress(protocol, 100);
}
}

View file

@ -61,6 +61,13 @@ fun TangemCard.loadFromBundle(B: Bundle) {
setIssuerData(B.getByteArray("IssuerData"), B.getByteArray("IssuerDataSignature"))
else setIssuerData(null, null)
if (B.containsKey("IssuerDataEx") && B.containsKey("IssuerDataExSignature") && B.containsKey("IssuerDataExCounter"))
setIssuerDataEx(B.getByteArray("IssuerDataEx"), B.getByteArray("IssuerDataExSignature"), B.getInt("IssuerDataExCounter"))
else setIssuerDataEx(null, null, 0)
if( B.containsKey("ProductMask") )
setProductMask(B.getInt("ProductMask"))
if (B.containsKey("NeedWriteIssuerData"))
needWriteIssuerData = B.getBoolean("NeedWriteIssuerData")
@ -149,6 +156,14 @@ fun TangemCard.saveToBundle(B: Bundle) {
B.putBoolean("NeedWriteIssuerData", needWriteIssuerData)
}
if (issuerDataEx != null && issuerDataExSignature != null) {
B.putByteArray("IssuerDataEx", issuerDataEx)
B.putByteArray("IssuerDataExSignature", issuerDataExSignature)
B.putInt("IssuerDataExCounter", issuerDataExCounter)
}
B.putInt("ProductMask",productMask)
if (isCodeConfirmed != null)
B.putBoolean("codeConfirmed", isCodeConfirmed)