package com.tangem.domain.cardReader; import android.content.Context; import android.nfc.Tag; import android.nfc.TagLostException; import android.nfc.tech.IsoDep; import android.util.Log; import com.tangem.domain.wallet.CoinEngine; import com.tangem.domain.wallet.CoinEngineFactory; import com.tangem.domain.wallet.Issuer; import com.tangem.domain.wallet.TangemCard; import com.tangem.domain.wallet.Manufacturer; import com.tangem.util.Util; import org.spongycastle.jce.ECNamedCurveTable; import org.spongycastle.jce.interfaces.ECPublicKey; import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; import org.spongycastle.math.ec.ECPoint; import java.io.ByteArrayOutputStream; import java.io.IOException; 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. */ public class CardProtocol { public interface Notifications { void OnReadStart(CardProtocol cardProtocol); void OnReadProgress(CardProtocol cardProtocol, int progress); void OnReadFinish(CardProtocol cardProtocol); void OnReadCancel(); void OnReadWait(int msec); void OnReadBeforeRequest(int timeout); void OnReadAfterRequest(); } public void setError(Exception error) { mError = error; } public TangemCard getCard() { return mCard; } public Exception getError() { return mError; } 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; public Tag getTag() { return mIsoDep.getTag(); } protected String mPIN; protected Notifications mNotifications; public void setPIN(String PIN) { mPIN = PIN; if (mCard != null) { mCard.setPIN(PIN); } } protected TangemCard mCard; protected Exception mError; protected Context mContext; static { Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); } public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) { mContext = context; mIsoDep = isoDep; mNotifications = notifications; mPIN = null; mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getTag().getId())); } public CardProtocol(Context context, IsoDep isoDep, TangemCard card, Notifications notifications) { mContext = context; mIsoDep = isoDep; mNotifications = notifications; mPIN = card.getPIN(); mCard = card; } public static class TangemException extends Exception { public TangemException(String message) { super(message); } } public static class TangemException_InvalidPIN extends TangemException { public TangemException_InvalidPIN(String message) { super(message); } } public static class TangemException_NeedPause extends TangemException { public TangemException_NeedPause(String message) { super(message); } } public static class TangemException_ExtendedLengthNotSupported extends TangemException { public TangemException_ExtendedLengthNotSupported(String message) { super(message); } } public byte[] GetUID() { if (mIsoDep == null || mIsoDep.getTag() == null) return null; return mIsoDep.getTag().getId(); } public int getTimeout() { if (mIsoDep == null || mIsoDep.getTag() == null) return 60000; return mIsoDep.getTimeout(); } protected byte[] protocolKey; public void resetProtocolKey() { protocolKey = null; } public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), mIsoDep.getTag().getId(), 50); //Log.e("Reader", String.format("PIN: %s, Protocol key: %s", mPIN, Util.bytesToHex(protocolKey))); if (sessionKey != null) { sessionKey = null; } } byte[] sessionKey = null; public 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: { byte[] baMyChallenge = Util.generateRandomBytes(16); cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyChallenge); Log.i(logTag, cmdApdu.getCommandName()); ResponseApdu rspApdu = null; try { if (mIsoDep == null) { throw new TagLostException(); } byte[] cmdBytes = cmdApdu.toBytes(); String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); byte[] rsp = mIsoDep.transceive(cmdBytes); rspApdu = new ResponseApdu(rsp); Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); if (rspApdu.isParsedWithError()) { throw new Exception("Can't parse answer"); } } catch (Exception E) { sessionKey = null; throw E; } if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); byte[] baTheirsChallenge = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; if (protocolKey == null) CreateProtocolKey(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(baMyChallenge); outputStream.write(baTheirsChallenge); outputStream.write(protocolKey); sessionKey = Util.calculateSHA256(outputStream.toByteArray()); //Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); } else { Log.e(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); } } break; case Strong: { KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "SC"); kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom()); KeyPair KP = kpgen.generateKeyPair(); KeyAgreement ka = KeyAgreement.getInstance("ECDH", "SC"); ka.init(KP.getPrivate()); ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); //return spec.getG().multiply(new BigInteger((ECPrivateKey) )).getEncoded(false); ECPublicKey eckey = (ECPublicKey) KP.getPublic(); byte[] baMyPublicKey = eckey.getQ().getEncoded(false); cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyPublicKey); Log.i(logTag, cmdApdu.getCommandName()); ResponseApdu rspApdu = null; try { if (mIsoDep == null) { throw new TagLostException(); } //mIsoDep.setTimeout(msTimeout); byte[] cmdBytes = cmdApdu.toBytes(); String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); byte[] rsp = mIsoDep.transceive(cmdBytes); rspApdu = new ResponseApdu(rsp); Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); if (rspApdu.isParsedWithError()) { throw new Exception("Can't parse answer"); } } catch (Exception E) { sessionKey = null; throw E; } if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); byte[] baTheirsPublicKey = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value; ka.doPhase(CardCrypto.LoadPublicKey(baTheirsPublicKey), true); if (protocolKey == null) CreateProtocolKey(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); outputStream.write(ka.generateSecret()); outputStream.write(protocolKey); sessionKey = Util.calculateSHA256(outputStream.toByteArray()); // Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey))); } else { Log.i(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description())); throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2())); } } break; default: throw new Exception("Unknown encryption mode"); } } catch (Exception e) { e.printStackTrace(); Log.e(logTag, String.format("Exception: %s", e.getMessage())); throw new Exception("Can't open session: " + e.getMessage()); } } // send command APDU, get response APDU, and display HEX data to user // private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception { // if (mCard.encryptionMode != TangemCard.EncryptionMode.None) { // if (sessionKey == null) { // run_OpenSession(mCard.encryptionMode); // } // cmdApdu.Crypt(sessionKey); // } // cmdApdu.setP1(mCard.encryptionMode.getP()); // byte[] cmdBytes = cmdApdu.toBytes(); // String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); // Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); // byte[] rsp; // ResponseApdu rspApdu; // try { // do { // try { // mNotifications.OnReadBeforeRequest(mIsoDep.getTimeout()); // try { // rsp = mIsoDep.transceive(cmdBytes); // } finally { // mNotifications.OnReadAfterRequest(); // } // } catch (IOException e) { // if (e.getMessage().contains("length exceeds supported maximum") && mIsoDep.isExtendedLengthApduSupported()) { // throw new TangemException_ExtendedLengthNotSupported(e.getMessage()); // } // throw e; // } // if (mCard.encryptionMode != TangemCard.EncryptionMode.None) { // rspApdu = ResponseApdu.Decrypt(rsp, sessionKey); // } else { // rspApdu = new ResponseApdu(rsp); // } // // if (rspApdu.isParsedWithError()) { // Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); // throw new TangemException(rspApdu.getParseErroMessage()); // } else if (rspApdu.getSW1SW2() == SW.NEED_PAUSE && mNotifications != null) { // int remainingPause = rspApdu.getTLVs().getTagAsInt(TLV.Tag.TAG_Pause) * 10; // Log.v("NFC", String.format(">> Security delay, remaining %f s", remainingPause / 1000.0)); // if (breakOnNeedPause) { // break; // } else { // mNotifications.OnReadWait(remainingPause); // } // } else { // Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); // } // } while (rspApdu.getSW1SW2() == SW.NEED_PAUSE); // } finally { // mNotifications.OnReadWait(0); // } // // return rspApdu; // } private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception { if (mCard.encryptionMode != TangemCard.EncryptionMode.None) { if (sessionKey == null) { run_OpenSession(mCard.encryptionMode); } cmdApdu.Crypt(sessionKey); } cmdApdu.setP1(mCard.encryptionMode.getP()); byte[] cmdBytes = cmdApdu.toBytes(); String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc()); Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr)); byte[] rsp; ResponseApdu rspApdu; try { do { try { mNotifications.OnReadBeforeRequest(mIsoDep.getTimeout()); try { rsp = mIsoDep.transceive(cmdBytes); } finally { mNotifications.OnReadAfterRequest(); } } catch (IOException e) { if (e.getMessage().contains("length exceeds supported maximum") && mIsoDep.isExtendedLengthApduSupported()) { throw new TangemException_ExtendedLengthNotSupported(e.getMessage()); } throw e; } if (mCard.encryptionMode != TangemCard.EncryptionMode.None && !ResponseApdu.isStatusWord(rsp, SW.NEED_PAUSE)) { rspApdu = ResponseApdu.Decrypt(rsp, sessionKey); } else { rspApdu = new ResponseApdu(rsp); } if (rspApdu.isParsedWithError()) { Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); throw new TangemException(rspApdu.getParseErroMessage()); } else if (rspApdu.getSW1SW2() == SW.NEED_PAUSE && mNotifications != null) { int remainingPause = 60000; TLV tlvPause = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Pause); if (tlvPause != null) { remainingPause = rspApdu.getTLVs().getTagAsInt(TLV.Tag.TAG_Pause) * 10; } Log.v("NFC", String.format(">> Security delay, remaining %f s", remainingPause / 1000.0)); if (breakOnNeedPause) { break; } else { mNotifications.OnReadWait(remainingPause); } } else { Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp))); } } while (rspApdu.getSW1SW2() == SW.NEED_PAUSE); } finally { mNotifications.OnReadWait(0); } return rspApdu; } private CommandApdu StartPrepareCommand(INS ins) throws NoSuchAlgorithmException { CommandApdu Apdu = new CommandApdu(ins); byte[] baPIN = Util.calculateSHA256(mPIN); Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN); if (ins != INS.Read) { Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID()); } return Apdu; } public void run_Read() throws Exception { CommandApdu rqApdu = StartPrepareCommand(INS.Read); 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(" "))); readResult = rspApdu.getTLVs(); parseReadResult(); run_ReadOrWriteIssuerDataAndDefineOfflineBalance(); } else if (rspApdu.isStatus(SW_PIN_ERROR)) { throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible PIN is invalid!\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("FAILED: [%04X]\n", rspApdu.getSW1SW2())); } } public void run_ReadOrWriteIssuerDataAndDefineOfflineBalance() throws Exception { TLVList tlvIssuerData; if (mCard.getNeedWriteIssuerData()) { run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature()); try { tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData()); } catch (TLVException e) { e.printStackTrace(); tlvIssuerData = null; } } else { try { tlvIssuerData = run_ReadIssuerData(); } catch (Exception e) { e.printStackTrace(); tlvIssuerData = null; } } if (mCard.getStatus() == TangemCard.Status.Loaded) { // try read offline balance data if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) { mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value); } else { mCard.clearOfflineBalance(); } } else { mCard.clearOfflineBalance(); } } private TLVList readResult = null; public void clearReadResult() { sessionKey = null; readResult = null; } public boolean haveReadResult() { return readResult != null; } public void parseReadResult() throws TangemException, NoSuchProviderException, NoSuchAlgorithmException { 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); mCard.setHealth(readResult.getTLV(TLV.Tag.TAG_Health).getAsInt()); 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); TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol); TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address); TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal); if (tokenSymbol != null) mCard.setTokenSymbol(tokenSymbol.getAsString()); if (contractAddress != null) mCard.setContractAddress(contractAddress.getAsString()); if (tokens_decimal != null) mCard.setTokensDecimal(tokens_decimal.getAsInt()); // this method has reflection TokenSymbol // you mast call setBlockchainIDFromCard after calling setTokensDecimal mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString()); byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value; int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF); int month = tlvPersonalizationDT[2] - 1; int day = tlvPersonalizationDT[3]; Calendar cd = Calendar.getInstance(); cd.set(year, month, day, 0, 0, 0); mCard.setPersonalizationDateTime(cd.getTime()); try { if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) { mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString()); } else { mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString()); } } catch (Exception e) { e.printStackTrace(); Log.e(logTag, "Cannot get firmware version"); mCard.setFirmwareVersion("0.00"); } try { if (mCard.getFirmwareVersion().compareTo("1.05") < 0) { mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); } else { mCard.setIssuer(Issuer.FindIssuer(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 { mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value)); } catch (Exception ee) { ee.printStackTrace(); Log.e(logTag, "Cannot get issuer"); mCard.setIssuer(Issuer.Unknown()); } } try { mCard.setSettingsMask(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask)); } catch (Exception e) { e.printStackTrace(); Log.e(logTag, "Can't get settings mask"); } if (readResult.getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) { mCard.setPauseBeforePIN2(10 * readResult.getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2)); } try { mCard.setSigningMethod(readResult.getTagAsInt(TLV.Tag.TAG_SigningMethod)); } catch (Exception e) { e.printStackTrace(); Log.e(logTag, "Can't get signing method"); mCard.setSigningMethod(0); } try { mCard.setMaxSignatures(readResult.getTagAsInt(TLV.Tag.TAG_MaxSignatures)); } catch (Exception e) { e.printStackTrace(); Log.e(logTag, "Can't get max signatures"); } } catch (Exception e) { e.printStackTrace(); throw new TangemException("Can't parse card data"); } } if (mCard.getStatus() == TangemCard.Status.Loaded) { TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1"); ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value); byte pkUncompressed[] = p1.getEncoded(false); byte pkCompresses[] = p1.getEncoded(true); mCard.setWalletPublicKey(pkUncompressed); mCard.setWalletPublicKeyRar(pkCompresses); CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain()); String wallet = engineCoin.calculateAddress(mCard, pkUncompressed); mCard.setWallet(wallet); //mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed)); mCard.setRemainingSignatures(readResult.getTagAsInt(TLV.Tag.TAG_RemainingSignatures)); } else { mCard.setWallet("N/A"); } } public TLVList run_VerifyCard() throws Exception { if (mCard.getCardPublicKey() == null || readResult == null) { run_Read(); } if (mCard.getStatus() == TangemCard.Status.NotPersonalized) { getCard().setManufacturer(Manufacturer.Unknown, false); return null; } if (mCard.getCardPublicKey() == null) { throw new TangemException("Not all data read, can't verify card!"); } CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCard); byte[] bChallenge = Util.generateRandomBytes(16); rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { TLVList verifyResult = rspApdu.getTLVs(); Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); verifyResult.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); 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!"); } try { ByteArrayOutputStream bs = new ByteArrayOutputStream(); bs.write(bChallenge); bs.write(tlvSalt.Value); byte[] dataArray = bs.toByteArray(); if (CardCrypto.VerifySignature(mCard.getCardPublicKey(), dataArray, tlvCardSignature.Value)) { getCard().setCardPublicKeyValid(true); Log.i(logTag, "Card signature verification OK"); } else { Log.e(logTag, "Card signature verification FAILED"); 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(); getCard().setManufacturer(Manufacturer.Unknown, false); } return verifyResult; } else if (rspApdu.isStatus(SW_PIN_ERROR)) { throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); } else { getCard().setManufacturer(Manufacturer.Unknown, false); throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public void run_CreateWallet(String PIN2) throws Exception { if (readResult == null) run_Read(); 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(" "))); 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(" "))); if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public TLVList run_CheckWallet() throws Exception { CommandApdu rqApdu = StartPrepareCommand(INS.CheckWallet); byte[] bChallenge = Util.generateRandomBytes(16); rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { TLVList Result = rspApdu.getTLVs(); Result.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge)); Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); return Result; } else if (rspApdu.isStatus(SW_PIN_ERROR)) { throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public void run_CheckWalletWithSignatureVerify() throws Exception { if (mCard.getCardPublicKey() == null || readResult == null) { run_Read(); } if (mCard.getStatus() == TangemCard.Status.NotPersonalized) { getCard().setManufacturer(Manufacturer.Unknown, false); return; } if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != TangemCard.Status.Loaded.getCode()) { throw new TangemException("Card must be loaded"); } TLVList checkResult = run_CheckWallet(); if (checkResult == null) return; TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey); TLV tlvChallenge = checkResult.getTLV(TLV.Tag.TAG_Challenge); TLV tlvSalt = checkResult.getTLV(TLV.Tag.TAG_Salt); TLV tlvSignature = checkResult.getTLV(TLV.Tag.TAG_Signature); if (tlvPublicKey == null || tlvChallenge == null || tlvSalt == null || tlvSignature == null) { throw new TangemException("Not all data read, can't check signature!"); } ByteArrayOutputStream bs = new ByteArrayOutputStream(); bs.write(tlvChallenge.Value); bs.write(tlvSalt.Value); byte[] dataArray = bs.toByteArray(); if (CardCrypto.VerifySignature(tlvPublicKey.Value, dataArray, tlvSignature.Value)) { Log.i(logTag, "Signature verification OK"); mCard.setWalletPublicKeyValid(true); } else { mCard.setWalletPublicKeyValid(false); } } public void run_PurgeWallet(String PIN2) throws Exception { CommandApdu rqApdu = StartPrepareCommand(INS.PurgeWallet); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); 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(" "))); if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public void run_SwapPIN(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)); rqApdu.addTLV(TLV.Tag.TAG_NewPIN2, Util.calculateSHA256(newPin2)); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, breakOnNeedPause); if (rspApdu.isStatus(SW.PIN1_CHANGED) || rspApdu.isStatus(SW.PIN2_CHANGED) || rspApdu.isStatus(SW.PINS_CHANGED) || rspApdu.isStatus(SW.PINS_NOT_CHANGED)) { Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); if (newPin2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } else { mCard.setUseDefaultPIN2(false); } } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2())); } else if (breakOnNeedPause && rspApdu.isStatus(SW.NEED_PAUSE)) { throw new TangemException_NeedPause(String.format("FAILED: [%04X] - Need pause\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public void run_CheckPIN2isDefault() throws Exception { 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); mCard.setUseDefaultPIN2(true); } catch (TangemException_NeedPause e) { mCard.setUseDefaultPIN2(null); } catch (TangemException_InvalidPIN e) { mCard.setUseDefaultPIN2(false); } } else { mCard.setUseDefaultPIN2(null); } } 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!"); for (int i = 0; i < hashes.length; i++) { if (i != 0 && hashes[0].length != hashes[i].length) throw new Exception("Hashes length must be identical!"); bs.write(hashes[i]); } 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()); if (UseIssuerValidation) { byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), bs.toByteArray()); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature); } if (issuerData != null) { if (issuer == null || issuer == Issuer.Unknown()) throw new Exception("Need known Issuer to write issuer Data"); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); } Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { TLVList Result = rspApdu.getTLVs(); Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } return Result; } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public TLVList run_SignRaw(String PIN2, byte[] bTxOutData) throws Exception { CommandApdu rqApdu = StartPrepareCommand(INS.Sign); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); rqApdu.addTLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData); rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, "sha-256x2".getBytes("US-ASCII")); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { TLVList Result = rspApdu.getTLVs(); Result.add(new TLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData)); Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } return Result; } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception { if (readResult == null) run_Read(); CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode); rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII")); rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress); rqApdu.addTLV_U16(TLV.Tag.TAG_CodePageCount, codePageCount); rqApdu.addTLV(TLV.Tag.TAG_Challenge, challenge); 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(" "))); return rspApdu.getTLVs().getTLV(TLV.Tag.TAG_CodeHash).Value; } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } private void run_ValidateCard(String PIN2) throws Exception { if (readResult == null) run_Read(); CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard); rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2)); 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(" "))); if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(true); } } else if (rspApdu.isStatus(SW_PIN_ERROR)) { if (PIN2.equals(DefaultPIN2)) { mCard.setUseDefaultPIN2(false); } throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2())); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception { run_Read(); CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData); rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature); 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(" "))); } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } private TLVList run_ReadIssuerData() throws Exception { CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData); 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); TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature); if (issuerData == null || issuerDataSignature == null) throw new TangemException("Invalid answer format (GetIssuerData)"); ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream(); bsDataToVerify.write(mCard.getCID()); bsDataToVerify.write(issuerData.Value); try { if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) { mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value); return TLVList.fromBytes(issuerData.Value); } else { throw new TangemException("Invalid issuer data read (signature verification failed)"); } } catch (Exception e) { e.printStackTrace(); throw new TangemException("Invalid issuer data read"); } } else { throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2())); } } public void run_GetSupportedEncryption() throws Exception { mCard.encryptionMode = TangemCard.EncryptionMode.None; do { CommandApdu rqApdu = StartPrepareCommand(INS.Read); Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" "))); ResponseApdu rspApdu = SendAndReceive(rqApdu, false); if (rspApdu.isStatus(SW.NEED_ENCRYPTION)) { if (mCard.encryptionMode == TangemCard.EncryptionMode.None) { mCard.encryptionMode = TangemCard.EncryptionMode.Fast; } else if (mCard.encryptionMode == TangemCard.EncryptionMode.Fast) { mCard.encryptionMode = TangemCard.EncryptionMode.Strong; } else { throw new Exception("Can't get supported encryption methods"); } } else if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) { Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" "))); readResult = rspApdu.getTLVs(); mCard.setPIN(mPIN); break; } else { break; } } while (true); } }