Updated on 2026-08-14
This commit is contained in:
parent
9f3ebb2e4c
commit
37c68616ba
12 changed files with 553 additions and 166 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.3.60'
|
||||
ext.kotlin_version = '1.3.61'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 issuer’s 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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ public class VerifyCardTask extends CustomReadCardTask {
|
|||
}
|
||||
mNotifications.onReadProgress(protocol, 90);
|
||||
|
||||
if( protocol.getCard().isIDCard() )
|
||||
{
|
||||
protocol.run_GetIssuerDataEx(mNotifications);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue