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

@ -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) {