Updated on 2026-08-14

This commit is contained in:
Tangem 2018-11-30 13:14:34 +03:00
commit ebc3abcfe1
9 changed files with 396 additions and 71 deletions

View file

@ -61,7 +61,7 @@ public class SwapPINTask extends Thread {
}
// try {
protocol.run_SwapPIN(PINStorage.getPIN2(), newPIN, newPIN2, false);
protocol.run_SetPIN(PINStorage.getPIN2(), newPIN, newPIN2, false);
protocol.setPIN(newPIN);
mCard.setPIN(newPIN);
// } finally {

View file

@ -5,7 +5,7 @@ import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.domain.cardReader.CardProtocol;
import com.tangem.domain.cardReader.FW;
import com.tangem.domain.cardReader.Firmwares;
import com.tangem.domain.cardReader.NfcManager;
import com.tangem.data.db.PINStorage;
import com.tangem.domain.wallet.TangemCard;
@ -73,7 +73,7 @@ public class VerifyCardTask extends Thread {
mNotifications.onReadProgress(protocol, 80);
}
if (isCancelled) return;
FW.VerifyCodeRecord record=FW.selectRandomVerifyCodeBlock(mCard.getFirmwareVersion());
Firmwares.VerifyCodeRecord record=Firmwares.selectRandomVerifyCodeBlock(mCard.getFirmwareVersion());
if (isCancelled) return;
if( record!=null ) {
byte[] returnedDigest = protocol.run_VerifyCode(record.hashAlg, record.blockIndex, record.blockCount, record.challenge);

View file

@ -21,6 +21,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
@ -37,6 +38,10 @@ import javax.crypto.spec.SecretKeySpec;
*/
public class CardCrypto {
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception {
if( publicKeyArray==null ) throw new Exception("Public key not specified!");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");

View file

@ -26,63 +26,102 @@ import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Calendar;
import javax.crypto.KeyAgreement;
/**
* Created by dvol on 14.07.2017.
* Implementation of the Tangem Card NFC Protocol
* See "Tangem card and NFC protocol Technical manual" [1]
*
* @author dvol
* 14.07.2017
*/
public class CardProtocol {
private static final String logTag = "CardProtocol";
/**
* UI notifications
*/
public interface Notifications {
/**
* call on start reading card
*
* @param cardProtocol - instance of protocol class
*/
void onReadStart(CardProtocol cardProtocol);
/**
* called during reading thread progress - to show progress bar, etc.
*
* @param cardProtocol - instance of protocol class
* @param progress - progress in percents
*/
void onReadProgress(CardProtocol cardProtocol, int progress);
/**
* called after the reading is over
*
* @param cardProtocol - instance of protocol class
*/
void onReadFinish(CardProtocol cardProtocol);
/**
* called if reading thread was canceled (e.g. due to paused activity)
*/
void onReadCancel();
/**
* called approx. every second while card security delay is pending
* called with msec=0 after the delay is over and command has been executed
*
* @param msec - estimated time in ms before the delay is finished
*/
void onReadWait(int msec);
/**
* called before sending command to a card
*
* @param timeout - maximum waiting time before the answer is received or timeout occurs
*/
void onReadBeforeRequest(int timeout);
/**
* called when the command answer is received or error/timeout occurred
*/
void onReadAfterRequest();
}
public void setError(Exception error) {
mError = error;
}
private Notifications mNotifications;
/**
* Return object containing data received from a card
*
* @return TangemCard
* @see TangemCard
*/
public TangemCard getCard() {
return mCard;
}
public Exception getError() {
return mError;
}
private static final int SW_PIN_ERROR = SW.INVALID_PARAMS;
private static final String logTag = "CardProtocol";
public static final int SW_PIN_ERROR = SW.INVALID_PARAMS;
public static final String DefaultPIN = "000000";
public static final String DefaultPIN2 = "000";
protected IsoDep mIsoDep;
private IsoDep mIsoDep;
/**
* @return Android NFC tag object
*/
public Tag getTag() {
return mIsoDep.getTag();
}
protected String mPIN;
protected Notifications mNotifications;
public static final String DefaultPIN = "000000";
public static final String DefaultPIN2 = "000";
private String mPIN;
public void setPIN(String PIN) {
mPIN = PIN;
@ -91,14 +130,35 @@ public class CardProtocol {
}
}
protected TangemCard mCard;
protected Exception mError;
protected Context mContext;
private TangemCard mCard;
private Exception mError;
private Context mContext;
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
/**
* Set occurred error
*
* @param error Exception object
*/
public void setError(Exception error) {
mError = error;
}
/**
* Get occurred error
*
* @return Exception occurred during reading a card
*/
public Exception getError() {
return mError;
}
/**
* Constructor for reading a card for the first time
*
* @param context - Android context
* @param isoDep - Android NFC tag
* @param notifications - UI notification callbacks
*/
public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
@ -107,6 +167,14 @@ public class CardProtocol {
mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
}
/**
* Constructor for subsequent reading of the card
*
* @param context - Android context
* @param isoDep - Android NFC tag
* @param card - TangemCard object, stored data from previous reading
* @param notifications - UI notification callbacks
*/
public CardProtocol(Context context, IsoDep isoDep, TangemCard card, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
@ -115,24 +183,38 @@ public class CardProtocol {
mCard = card;
}
/**
* Base exception class for exceptions on card reading
*/
public static class TangemException extends Exception {
public TangemException(String message) {
super(message);
}
}
/**
* Thrown on failed attempt of reading with possible wrong PIN
*/
public static class TangemException_InvalidPIN extends TangemException {
public TangemException_InvalidPIN(String message) {
super(message);
}
}
/**
* Thrown when card enforces security delay
*/
public static class TangemException_NeedPause extends TangemException {
public TangemException_NeedPause(String message) {
super(message);
}
}
/**
* Thrown if APDU commands with extended length is not supported by an Android NFC device. This issue can occur on some legacy devices.
* This exception means that this particular action can't be executed on this device because it's impossible to transfer all needed data to/from a card.
*/
public static class TangemException_ExtendedLengthNotSupported extends TangemException {
public TangemException_ExtendedLengthNotSupported(String message) {
super(message);
@ -145,40 +227,85 @@ public class CardProtocol {
}
}
/**
* Return ISO 14443-3 tag UID - unique card identifier
* The tag identifier is a low level number used for anti-collision
* and identification. It has nothing to do with CID.
* <p>
* Used internally for protocol encryption
* <p>
* Can be used on subsequent reading to first fast check that
* you read the same card
*
* @return UID byte array
*/
public byte[] GetUID() {
if (mIsoDep == null || mIsoDep.getTag() == null) return null;
return mIsoDep.getTag().getId();
}
/**
* Return ISO 14443-3 tag reading timeout
*/
public int getTimeout() {
if (mIsoDep == null || mIsoDep.getTag() == null) return 60000;
return mIsoDep.getTimeout();
}
protected byte[] protocolKey;
/**
* protocolKey
* a base value used to construct the communication encryption key derived from PIN and UID
* See [1] 4.3, 4.4, 4.5
* {@see run_OpenSession}
*/
private byte[] protocolKey;
public void resetProtocolKey() {
protocolKey = null;
}
public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), mIsoDep.getTag().getId(), 50);
/**
* Calculate protocolKey
* See [1] 4.3, 4.4, 4.5
*
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeyException {
protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), GetUID(), 50);
//Log.e("Reader", String.format("PIN: %s, Protocol key: %s", mPIN, Util.bytesToHex(protocolKey)));
if (sessionKey != null) {
sessionKey = null;
}
}
byte[] sessionKey = null;
/**
* current session communication encryption key
* See [1] 4.1, 4.3, 4.4, 4.5
*/
private byte[] sessionKey = null;
public void run_OpenSession(TangemCard.EncryptionMode encryptionMode) throws Exception {
/**
* Execute open session command and calculate session key {@see CardProtocol.sessionKey}
* During this command the card and the device exchange with random challenges (for Fast encryption mode) or
* public keys (for Strong encryption mode) and calculate sessionKey based protocolKey as:
* - sha256(challengeDevice|challengeCard|protocolKey) for Fast encryption
* - sha256(ECDH shared secret|protocolKey) for Strong encryption
* See [1] 4.1, 4.3, 4.4, 4.5
*
* @param encryptionMode - mode of encryption {@link TangemCard.EncryptionMode}
* @throws Exception if something went wrong
*/
private void run_OpenSession(TangemCard.EncryptionMode encryptionMode) throws Exception {
sessionKey = null;
try {
CommandApdu cmdApdu = new CommandApdu(CommandApdu.ISO_CLA, INS.OpenSession.Code, 0, encryptionMode.getP());
switch (encryptionMode) {
case Fast: {
// See [1] 4.3
byte[] baMyChallenge = Util.generateRandomBytes(16);
cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyChallenge);
@ -223,6 +350,7 @@ public class CardProtocol {
}
break;
case Strong: {
// See [1] 4.4
KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "SC");
kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom());
KeyPair KP = kpgen.generateKeyPair();
@ -285,6 +413,17 @@ public class CardProtocol {
}
}
/**
* Send the specified APDU command to a card and receive an answer
* Should have a prior opened encryption session if encryption is used
* See [1] 4
*
* @param cmdApdu - APDU command to send
* @param breakOnNeedPause - Specifies what to do when the card requests a security delay (interrupt transfer or wait till the end of the delay )
* @return - response APDU
* @throws Exception - if something went wrong
*/
private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception {
if (mCard.encryptionMode != TangemCard.EncryptionMode.None) {
if (sessionKey == null) {
@ -346,6 +485,13 @@ public class CardProtocol {
return rspApdu;
}
/**
* Helper for preparing APDU command - init CommandApdu object and add common TLV tags ([CID,] PIN)
*
* @param ins - instruction code {@link INS}
* @return CommandAPDU
* @throws NoSuchAlgorithmException - if no sha256 library found
*/
private CommandApdu StartPrepareCommand(INS ins) throws NoSuchAlgorithmException {
CommandApdu Apdu = new CommandApdu(ins);
byte[] baPIN = Util.calculateSHA256(mPIN);
@ -356,10 +502,30 @@ public class CardProtocol {
return Apdu;
}
/**
* Run READ command and parse answer
* {@see run_Read(boolean parseResult) }
*
* @throws Exception if something went wrong
*/
public void run_Read() throws Exception {
run_Read(true);
}
/**
* Run READ command and parse answer (if specified)
* <p>
* This command returns all card and wallet data, including unique card number (CID) that has to be submitted when further calling all other commands. Therefore,
* READ_CARD should always be used in the beginning of communication session between NFC device and Tangem card
* <p>
* In order to obtain cards data, the app should call READ_CARD command with correct PIN1 value as a parameter. The card will not respond if wrong PIN1
* has been submitted
* This command requires only PIN1 parameter while other commands also need CID
* See [1] 8, 8.2
*
* @param parseResult - parse answer into TangemCard object or not
* @throws Exception if something went wrong
*/
public void run_Read(boolean parseResult) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.Read);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -368,17 +534,15 @@ public class CardProtocol {
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
readResult = rspApdu.getTLVs();
if (parseResult) {
// After first successful read, data will be parsed into TangemCard object
parseReadResult();
//TODO: move issuer data out from here
run_ReadWriteIssuerData();
} else {
//TODO: проверить что карта та же
//TODO: check that card is the same
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
@ -388,6 +552,13 @@ public class CardProtocol {
}
}
/**
* 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_ReadWriteIssuerData() throws Exception {
TLVList tlvIssuerData;
if (mCard.getNeedWriteIssuerData()) {
@ -400,7 +571,7 @@ public class CardProtocol {
}
} else {
try {
tlvIssuerData = run_ReadIssuerData();
tlvIssuerData = run_GetIssuerData();
} catch (Exception e) {
e.printStackTrace();
tlvIssuerData = null;
@ -430,6 +601,9 @@ public class CardProtocol {
}
}
/**
* A TLV list containing response of the last READ command
*/
private TLVList readResult = null;
public void clearReadResult() {
@ -441,19 +615,33 @@ public class CardProtocol {
return readResult != null;
}
public void parseReadResult() throws TangemException, NoSuchProviderException, NoSuchAlgorithmException {
/**
* Parse response of the last READ command into TangemCard object
* See [1] 8.2, 5.3, 3.3
*
* @throws TangemException - if something went wrong
*/
public void parseReadResult() throws TangemException {
// These tags always present in the parsed response: TAG_Status, TAG_CID, TAG_Manufacture_ID, TAG_Health, TAG_Firmware
TLV tlvStatus = readResult.getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = readResult.getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
// Support of legacy Firmware
if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
mCard.setHealth(readResult.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 = readResult.getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
throw new TangemException("Invalid answer format");
mCard.setCardPublicKey(tlvCardPubkicKey.Value);
TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value);
@ -467,13 +655,8 @@ public class CardProtocol {
if (tokenSymbol != null)
mCard.setTokenSymbol(tokenSymbol.getAsString());
// Hardcoded smart contracts for the cards manufactured before registration of smart-contract in ETH
if (contractAddress != null)
{
mCard.setContractAddress(contractAddress.getAsString());
}
if (tokens_decimal != null)
mCard.setTokensDecimal(tokens_decimal.getAsInt());
@ -486,9 +669,8 @@ public class CardProtocol {
cd.set(year, month, day, 0, 0, 0);
mCard.setPersonalizationDateTime(cd.getTime());
try {
if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString());
} else {
// Support of legacy Firmware
if (mCard.getFirmwareVersion() == null) {
mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
} catch (Exception e) {
@ -499,14 +681,17 @@ public class CardProtocol {
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(), readResult.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(), readResult.getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value);
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "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(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value);
} catch (Exception ee) {
ee.printStackTrace();
@ -515,28 +700,23 @@ public class CardProtocol {
}
}
// substitutions for card, that was produced with wrong blockchain data (for example token contract was unknown)
// this method must be called after set issuer because substitution is verified by issuer data key
// 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 {
if( mCard.getBatch().equals("0017") )
{
// 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") )
{
} else if (mCard.getBatch().equals("0019")) {
mCard.setContractAddress("0x0c056b0cda0763cc14b8b2d6c02465c91e33ec72");
} else {
LocalStorage localStorage = new LocalStorage(mContext);
localStorage.applySubstitution(mCard);
}
}catch(Exception e)
{
} catch (Exception e) {
Log.e(logTag, "Can't apply card data substitution");
e.printStackTrace();
}
// this method has reflection TokenSymbol
// you mast call setBlockchainIDFromCard after calling setTokensXXX and make substitutions
mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString());
try {
@ -572,6 +752,7 @@ public class CardProtocol {
}
// Parse additional parameters for Loaded cards: wallet public key, remaining signatures, etc
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey);
@ -587,8 +768,9 @@ public class CardProtocol {
TangemContext ctx = new TangemContext(mCard);
try {
// TODO - store in mCard only public key, not wallet address, move wallet address to coinData
CoinEngine engineCoin = CoinEngineFactory.INSTANCE.create(ctx);
if( engineCoin==null ) throw new Exception("Can't create CoinEngine!");
if (engineCoin == null) throw new Exception("Can't create CoinEngine!");
String wallet = engineCoin.calculateAddress(pkUncompressed);
mCard.setWallet(wallet);
} catch (Exception e) {
@ -608,6 +790,15 @@ public class CardProtocol {
}
}
/**
* VERIFY_CARD command and verify card's signature
* By using standard challenge-response scheme, the card proves
* possession of CARD_PRIVATE_KEY that corresponds to CARD_PUBLIC_KEY returned by READ_CARD command
* See [1] 3.2, 8.9
*
* @return TLVList with response in case of success
* @throws Exception - if something went wrong
*/
public TLVList run_VerifyCard() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
@ -635,7 +826,6 @@ public class CardProtocol {
TLV tlvSalt = verifyResult.getTLV(TLV.Tag.TAG_Salt);
TLV tlvCardSignature = verifyResult.getTLV(TLV.Tag.TAG_CardSignature);
// TLV tlvManufacturerSignature = verifyResult.getTLV(TLV.Tag.TAG_Manufacturer_Signature);
if (tlvSalt == null || tlvCardSignature == null) {
throw new TangemException("Not all data read, can't verify card!");
@ -654,7 +844,6 @@ public class CardProtocol {
getCard().setCardPublicKeyValid(false);
}
//getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString(), bChallenge, tlvSalt.Value, tlvManufacturerSignature.Value), true);
getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
} catch (Exception e) {
e.printStackTrace();
@ -670,6 +859,15 @@ public class CardProtocol {
}
/**
* CREATE_WALLET command
* This command will create a new wallet on the card having Empty state. A key pair WALLET_PUBLIC_KEY / WALLET_PRIVATE_KEY is generated and securely stored in
* the card.
* See [1] 3.4, 8.3
*
* @param PIN2 - PIN2 code to confirm operation
* @throws Exception - if something went wrong
*/
public void run_CreateWallet(String PIN2) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet);
@ -693,7 +891,15 @@ public class CardProtocol {
}
}
public TLVList run_CheckWallet() throws Exception {
/**
* CHECK_WALLET command without signature verification
* Card will sign a challenge to prove that it possesses WALLET_PRIVATE_KEY corresponding to WALLET_PUBLIC_KEY. Standard challenge/response scheme is used.
* See [1] 3.4, 8.4
*
* @return TLVList from answer in case of success
* @throws Exception - if something went wrong
*/
private TLVList run_CheckWallet() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.CheckWallet);
byte[] bChallenge = Util.generateRandomBytes(16);
rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge);
@ -713,6 +919,15 @@ public class CardProtocol {
}
}
/**
* CHECK_WALLET command and verify wallet's signature
* Card will sign a challenge to prove that it possesses WALLET_PRIVATE_KEY corresponding to WALLET_PUBLIC_KEY. Standard challenge/response scheme is used.
* It will first run READ command if no reads where made before. Then execute CHECK_WALLET command and verify signature in the response.
* Set WalletPublicKeyValid in {@link TangemCard}
* See [1] 3.4, 8.4
*
* @throws Exception - if something went wrong
*/
public void run_CheckWalletWithSignatureVerify() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
@ -750,6 +965,15 @@ public class CardProtocol {
}
}
/**
* PURGE_WALLET command
* See [1] 3.4, 8.11
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization, the card changes state to Empty and a new wallet can be created by
* CREATE_WALLET command. If Is_Reusable flag is disabled, the card switches to Purged state. Purged state is final, it makes the card useless.
*
* @param PIN2 - PIN2 code to confirm operation
* @throws Exception - if something went wrong
*/
public void run_PurgeWallet(String PIN2) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.PurgeWallet);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
@ -774,7 +998,23 @@ public class CardProtocol {
}
}
public void run_SwapPIN(String PIN2, String newPin, String newPin2, boolean breakOnNeedPause) throws Exception {
/**
* Execute SET_PIN command
* This command changes PIN1 and PIN2 passwords if it is allowed by Allow_SET_PIN1 and Allow_SET_PIN2 flags in Settings_Mask. Host application can submit
* unchanged passwords (New_PIN1 = PIN1 and New_PIN2 = PIN2) in order to check its correctness. Depending on the result, Status_Word in the command response will have
* these values:
* SW_PINS_NOT_CHANGED = 0x9000
* SW_PIN1_CHANGED = 0x9001
* SW_PIN2_CHANGED = 0x9002
* SW_PINS_CHANGED = 0x9003
*
* @param PIN2 - PIN2 code to confirm operation
* @param newPin - new value of PIN code
* @param newPin2 - new value of PIN2 code
* @param breakOnNeedPause - flag that specify what
* @throws Exception - if something went wrong
*/
public void run_SetPIN(String PIN2, String newPin, String newPin2, boolean breakOnNeedPause) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.SwapPIN);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
rqApdu.addTLV(TLV.Tag.TAG_NewPIN, Util.calculateSHA256(newPin));
@ -803,11 +1043,18 @@ public class CardProtocol {
}
}
/**
* Verify if default PIN2 is set on the card and enable UseDefaultPIN2 flag {@link TangemCard}
* Works in firmware 1.12 and later, for older version UseDefaultPIN2 is always null
* This function NEVER triggers security delay
*
* @throws Exception - if something went wrong
*/
public void run_CheckPIN2isDefault() throws Exception {
// can obtain SetPIN(to default) answer without security delay - try check if PIN2 is default with card request
if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) {
// can obtain SwapPIN(to default) answer without security delay - try check if PIN2 is default with card request
try {
run_SwapPIN(DefaultPIN2, mPIN, DefaultPIN2, true);
run_SetPIN(DefaultPIN2, mPIN, DefaultPIN2, true);
mCard.setUseDefaultPIN2(true);
} catch (TangemException_NeedPause e) {
mCard.setUseDefaultPIN2(null);
@ -819,6 +1066,18 @@ public class CardProtocol {
}
}
/**
* SIGN command to sign hashes - SigningMethod=0,2,4 (see {@link TangemCard.SigningMethod})
* See [1] 8.6
* @param PIN2 - PIN2 code to confirm operation
* @param hashes - array of digests to sign (max 10 digest at a time)
* @param UseIssuerValidation - if card need issuer validation before sign (true for SigningMethod=2,4)
* @param issuerData - new issuerData to write on card (only for SigningMethod=4, null for other)
* @param issuer - issuer (need only for SigningMethod=2,4)
* @return TLVList with card answer contained wallet signatures of digests from hashes array (in case of success)
* @throws Exception - if something went wrong
*/
// TODO - change function to run_SignHashes(String PIN2, byte[][] hashes, byte[] issuerData, byte[] issuerTransactionSignature) because normally issuer signature can be obtained only by external server
public TLVList run_SignHashes(String PIN2, byte[][] hashes, boolean UseIssuerValidation, byte[] issuerData, Issuer issuer) throws Exception {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (hashes.length > 10) throw new Exception("To much hashes in one transaction!");
@ -865,6 +1124,15 @@ public class CardProtocol {
}
}
/**
* SIGN raw tx - SigningMethod=1 (see {@link TangemCard.SigningMethod})
* See [1] 8.6
* @param PIN2 - PIN2 code to confirm operation
* @param bTxOutData - part of raw transaction to sign
* @return TLVList with card answer contained wallet signatures of bTxOutData(in case of success)
* @throws Exception - if something went wrong
*/
// TODO - change function to run_SignRaw(String PIN2, String hashAlgID, byte[] bTxOutData, byte[] issuerData, byte[] issuerTransactionSignature) to support all signing methods
public TLVList run_SignRaw(String PIN2, byte[] bTxOutData) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.Sign);
@ -893,6 +1161,21 @@ public class CardProtocol {
}
}
/**
* VERIFY_CODE command
* See [1] 8.8
* This command challenges the card to prove integrity of COS binary code. For this purpose, the host application should have a special hash library publicly provided
* by Tangem. It may contains ~150.000 precalculated hashes of COS binary code segments.
* VERIFY_CODE command internally reads a segment of COS binary code beginning at Code_Page_Address and having length of [64 x Code_Page_Count] bytes.
* Then it appends Challenge to the code segment, calculates resulting hash and returns it in the response.
* The application needs to ensure that returned hash coincides with the one stored in the hash library (see {@link Firmwares}).
* @param hashAlgID - sha-256, sha-1, sha-224, sha-384, sha-512, crc-16
* @param codePageAddress - Value from 0 to ~3000, take from {@link Firmwares}
* @param codePageCount - Number of 32-byte pages to read: from 1 to 5, take from {@link Firmwares}
* @param challenge - Additional challenge value from 1 to 10, take from {@link Firmwares}
* @return digest bytes to compare with one stored in {@link Firmwares}
* @throws Exception - if something went wrong
*/
public byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode);
@ -913,6 +1196,21 @@ public class CardProtocol {
}
}
/**
* VALIDATE_CARD command
* See [1] 3.2, 8.10
* This is an optional command that the issuer can support if there is a real risk of mass counterfeiting by making multiple clones of a single card. This can be the case for
* transferrable Tangem cards that are almost never redeemed by users.
* The issuer has to have a back-end service storing and updating a counter value (Card_Validation_Counter) for each card (CID). This function should also be supported
* by the issuers application.
* The application may occasionally call VALIDATE_CARD command to ensure that theres only one card having this CID is circulating out there. VALIDATE_CARD
* will increase COS internal Card_Validation_Counter by 1 and sign the new value with CARD_PRIVATE_KEY. Then the application should submit increased
* Card_Validation_Counter and its signature to issuers card validation back-end (server). The server should verify the signature and update Card_Validation_Counter value if
* previous value is less than the new one. If the server reveals that submitted Card_Validation_Counter value is less than previous value, then the card having this CID is
* deemed compromised and should not be accepted by the application.
* @param PIN2 - PIN2 code to confirm operation
* @throws Exception - if something went wrong
*/
private void run_ValidateCard(String PIN2) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard);
@ -937,6 +1235,15 @@ public class CardProtocol {
}
}
/**
* WRITE_ISSUER_DATA command
* This command re-writes Issuer_Data data block (max 512 bytes) and its issuers signature.
* Issuer_Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, format and payload of Issuer_Data.
* For example, this field may contain information about wallet balance signed by the issuer or additional issuers attestation data
* @param issuerData - new issuerData
* @param issuerSignature - signature of issuerData with IssuerDataKey
* @throws Exception - if something went wrong
*/
private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
run_Read();
@ -955,7 +1262,14 @@ public class CardProtocol {
}
}
private TLVList run_ReadIssuerData() throws Exception {
/**
* GET_ISSUER_DATA command and verify verify issuer signature of returned data
* See [1] 3.3, 8.7
* This command returns Issuer_Data data block and its issuers signature.
* @return TLVList with issuerData (if success read and verify)
* @throws Exception - if something went wrong
*/
private TLVList run_GetIssuerData() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
@ -995,6 +1309,12 @@ public class CardProtocol {
}
}
/**
* 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
* Once READ is successfully executed - save answer to {@see readResult}, save current PIN and encryption mode to {@link TangemCard} and return
* @throws Exception - if something went wrong
*/
public void run_GetSupportedEncryption() throws Exception {
mCard.encryptionMode = TangemCard.EncryptionMode.None;
do {

View file

@ -12,7 +12,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class FW {
public class Firmwares {
private static JsonArray jaFirmwares = null;
public static boolean needInit() {

View file

@ -32,7 +32,7 @@ import com.tangem.data.nfc.DeviceNFCAntennaLocation
import com.tangem.data.nfc.ReadCardInfoTask
import com.tangem.di.Navigator
import com.tangem.domain.cardReader.CardProtocol
import com.tangem.domain.cardReader.FW
import com.tangem.domain.cardReader.Firmwares
import com.tangem.domain.cardReader.NfcManager
import com.tangem.domain.wallet.*
import com.tangem.presentation.dialog.NoExtendedLengthSupportDialog
@ -68,8 +68,8 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback, CardProtoco
if (Issuer.needInit())
Issuer.init(context)
if (FW.needInit())
FW.init(context)
if (Firmwares.needInit())
Firmwares.init(context)
}
}