diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 97fb0ab7e3..c31e4cf46b 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -150,12 +150,13 @@ public class Server { public static class ApiBlockchair { public static final String URL_BLOCKCHAIR = ServerURL.API_BLOCKCHAIR + "{blockchain}/"; + private static final String API_KEY = "?key=A___0Shpsu4KagE7oSabrw20DfXAqWlT"; public static class Method { - static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}"; - static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}"; - static final String STATS = URL_BLOCKCHAIR + "stats"; - static final String PUSH = URL_BLOCKCHAIR + "push/transaction"; + static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}" + API_KEY; + static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}" + API_KEY; + static final String STATS = URL_BLOCKCHAIR + "stats" + API_KEY; + static final String PUSH = URL_BLOCKCHAIR + "push/transaction" + API_KEY; } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java index 7dab3374a2..4d531d0743 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java @@ -189,7 +189,7 @@ public class ServerApiAdalite { } private void retryRequest(String method, String wallet, String tx) { - currentURL = adaliteURL2; +// currentURL = adaliteURL2; requestData(method, wallet, tx, true); } } diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt index 307a9186b7..421f780cc1 100644 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt @@ -472,10 +472,14 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. private fun doPurge() { requestPIN2Count = 0 - val engine = CoinEngineFactory.create(ctx) - if (!engine!!.hasBalanceInfo()) { + val engine = CoinEngineFactory.create(ctx) ?: return + if (!engine.hasBalanceInfo()) { return - } else if (engine.isBalanceNotZero) { + } + if (engine.isBalanceNotZero) { + Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() + return + } else if (engine.awaitingConfirmation()) { Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() return } diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index ca0505d7aa..4de8ee31c8 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -163,7 +163,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte when (items[which]) { getString(R.string.loaded_wallet_load_via_app) -> { try { - val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUri) + val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUriEx) intent.addCategory(Intent.CATEGORY_DEFAULT) startActivity(intent) } catch (e: ActivityNotFoundException) { @@ -176,7 +176,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte } getString(R.string.loaded_wallet_load_via_qr) -> { - ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUri.toString()) + ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUriEx.toString()) } getString(R.string.loaded_wallet_load_via_cryptonit) -> { @@ -297,16 +297,15 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte serverApiTangem.setArtworkListener(artworkListener) refresh() startVerify(lastTag) - } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) // set rate info to CoinData viewModel.getRateInfo().observe(this, Observer { rate -> ctx.coinData.rate = rate ctx.coinData.rateAlter = rate + updateViews() }) viewModel.requestRateInfo(ctx) } @@ -886,8 +885,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte private fun doShareWallet(useURI: Boolean) { if (useURI) { - val engine = CoinEngineFactory.create(ctx) - val txtShare = engine?.shareWalletUri.toString() + val txtShare = ctx.coinData.wallet val intent = Intent(Intent.ACTION_SEND) intent.type = Constant.INTENT_TYPE_TEXT_PLAIN intent.putExtra(Intent.EXTRA_SUBJECT, Constant.WALLET_ADDRESS) diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java index 25b6a47ed2..5fce47c602 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -3,6 +3,7 @@ package com.tangem.wallet; import android.net.Uri; import android.text.InputFilter; +import com.tangem.data.Blockchain; import com.tangem.tangem_card.reader.CardProtocol; import com.tangem.tangem_card.tasks.SignTask; @@ -195,6 +196,13 @@ public abstract class CoinEngine { public abstract Uri getShareWalletUri(); + public Uri getShareWalletUriEx(){ + if (ctx.getBlockchain() == Blockchain.BitcoinCash) + return getShareWalletUri(); + else + return Uri.parse(ctx.getBlockchain().name().toLowerCase() + ":" + getShareWalletUri().toString()); + } + public abstract boolean checkNewTransactionAmount(Amount amount); public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded); diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java index edc471e86b..4c126fbc3f 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -141,7 +141,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse("https://explorer.bitcoin.com/bch/address/" + ctx.getCoinData().getWallet()); + return Uri.parse("https://blockchair.com/bitcoin-cash/address/" + ctx.getCoinData().getWallet()); } @Override diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 4bfa331eda..05b489ba07 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -219,9 +219,9 @@ public class BtcEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null && !ctx.getCard().getDenominationText().equals("0.00")) { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 7175f68f73..8c24e30480 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -228,10 +228,12 @@ public class EthEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); +// if (ctx.getCard().getDenomination() != null) { +// return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + if (ctx.getBlockchain() == Blockchain.Ethereum) { + return Uri.parse(ctx.getCoinData().getWallet()); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java index ebf42f4e21..3c9e6c0017 100644 --- a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java @@ -27,7 +27,6 @@ import com.tangem.wallet.EthTransaction; import com.tangem.wallet.Keccak256; import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; -import com.tangem.wallet.eth.EthData; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.SignatureDecodeException; @@ -185,9 +184,9 @@ public class EthIdEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java index 801ed7673e..e8ebfa3fd3 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -179,9 +179,9 @@ public class LtcEngine extends BtcEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java index 859d8f3d24..151e631fbd 100644 --- a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java @@ -189,7 +189,7 @@ public class NftTokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index ac453d5ff9..d36abf0480 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -305,9 +305,9 @@ public class TokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java index 493b45cc01..8476b33a37 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java @@ -361,10 +361,14 @@ public class XlmEngine extends CoinEngine { } Operation operation; - if (coinData.isTargetAccountCreated()) + if (coinData.isTargetAccountCreated()) { operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build(); - else + } else { + if (amountValue.compareTo(coinData.getReserve()) < 0) { + throw new IllegalArgumentException("Target account is not created. Send " + coinData.getReserve().toDescriptionString(getDecimals()) + " or more to create"); + } operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build(); + } TransactionEx transaction = TransactionEx.buildEx(120, coinData.getAccountResponse(), operation); @@ -432,7 +436,7 @@ public class XlmEngine extends CoinEngine { if (request.errorResponse != null && request.errorResponse.getCode() == 404) { coinData.setTargetAccountCreated(false); - if (amount.compareTo(coinData.getReserve()) >= 0) { //TODO: take fee inclusion in account, now 1 XLM with fee included will fail after transaction is sent + if (amount.compareTo(coinData.getReserve()) >= 0) { blockchainRequestsCallbacks.onComplete(true); } else { ctx.setError(R.string.confirm_transaction_error_not_enough_xlm_for_create); diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 3df5e26532..1b10a221a3 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -17,7 +17,7 @@ public class XrpData extends CoinData { private Long reserve = 20000000L; - private Boolean accountNotFound = false; + private Boolean accountNotFound, targetAccountCreated = false; @Override public void loadFromBundle(Bundle B) { @@ -32,7 +32,9 @@ public class XrpData extends CoinData { if (B.containsKey("Reserve")) reserve = B.getLong("Reserve"); else reserve = 20000000L; if (B.containsKey("AccoundNotFound")) accountNotFound = B.getBoolean("AccoundNotFound"); - else reserve = 20000000L; + else accountNotFound = false; + if (B.containsKey("TargetAccountCreated")) targetAccountCreated = B.getBoolean("TargetAccountCreated"); + else targetAccountCreated = false; } @Override @@ -44,6 +46,7 @@ public class XrpData extends CoinData { if (sequence != null) B.putLong("Sequence", sequence); if (reserve != null) B.putLong("Reserve", reserve); if (accountNotFound != null) B.putBoolean("AccoundNotFound", accountNotFound); + if (targetAccountCreated != null) B.putBoolean("TargetAccountCreated", targetAccountCreated); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -57,6 +60,7 @@ public class XrpData extends CoinData { sequence = null; reserve = 20000000L; accountNotFound = false; + targetAccountCreated = false; } // balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present @@ -105,6 +109,14 @@ public class XrpData extends CoinData { this.accountNotFound = accountFound; } + public Boolean isTargetAccountCreated() { + return targetAccountCreated; + } + + public void setTargetAccountCreated(boolean targetAccountCreated) { + this.targetAccountCreated = targetAccountCreated; + } + public boolean hasBalanceInfo() { return balanceConfirmed != null || balanceUnconfirmed != null; } @@ -112,5 +124,4 @@ public class XrpData extends CoinData { public boolean hasUnconfirmed() { return !balanceConfirmed.equals(balanceUnconfirmed); } - } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 73db5449a5..ff06160ba2 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -148,7 +148,7 @@ public class XrpEngine extends CoinEngine { } public Uri getShareWalletUri() { - return Uri.parse("ripple:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override @@ -347,11 +347,18 @@ public class XrpEngine extends CoinEngine { if (IncFee) { amount = convertToInternalAmount(amountValue).subtract(convertToInternalAmount(feeValue)).setScale(0).toPlainString(); } else { - amount = Long.toString(convertToInternalAmount(amountValue).longValueExact()); + amount = Long.toString(convertToInternalAmount(amountValue).longValue()); } fee = Long.toString(convertToInternalAmount(feeValue).longValueExact()); + if (!coinData.isTargetAccountCreated() && + Long.valueOf(amount).compareTo(coinData.getReserveInInternalUnits().longValue()) < 0 + ) { + String reserveDescription = convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()); + throw new IllegalArgumentException("Target account is not created. Send " + reserveDescription + " or more to create"); + } + XrpPayment payment = new XrpPayment(); // Put `as` AccountID field Account, `Object` o @@ -522,19 +529,50 @@ public class XrpEngine extends CoinEngine { ServerApiRipple.ResponseListener rippleListener = new ServerApiRipple.ResponseListener() { @Override public void onSuccess(String method, RippleResponse rippleResponse) { - try { - InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); - InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); - InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + Log.i(TAG, "onSuccess: " + method); + switch (method) { + case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { + try { + if (rippleResponse.getResult().getError_code().equals(19)) { // "Account not found" + coinData.setTargetAccountCreated(false); + } else { + coinData.setTargetAccountCreated(true); + } + } catch (Exception e) { + coinData.setTargetAccountCreated(true); //expected behaviour, if account exists, there should be no error code -> null pointer + } - coinData.minFee = convertToAmount(minFee); - coinData.normalFee = convertToAmount(normalFee); - coinData.maxFee = convertToAmount(maxFee); + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; - blockchainRequestsCallbacks.onComplete(true); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + case ServerApiRipple.RIPPLE_FEE: { + try { + InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); + InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); + InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + + coinData.minFee = convertToAmount(minFee); + coinData.normalFee = convertToAmount(normalFee); + coinData.maxFee = convertToAmount(maxFee); + + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + ctx.setError(e.getMessage()); + } + + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; } } @@ -548,6 +586,7 @@ public class XrpEngine extends CoinEngine { serverApiRipple.setResponseListener(rippleListener); + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); serverApiRipple.requestData(ServerApiRipple.RIPPLE_FEE, "", ""); } diff --git a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt index e09a207c57..cdea28160d 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt @@ -13,6 +13,7 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import com.tangem.Constant +import com.tangem.data.Blockchain import com.tangem.ui.activity.MainActivity import com.tangem.ui.fragment.BaseFragment import com.tangem.ui.fragment.qr.CameraPermissionManager @@ -151,6 +152,9 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc // etAmount?.setText(amount) // rgIncFee.check(R.id.rbFeeOut) // } + } else if (ctx.blockchain == Blockchain.Ripple && schemeSplit[0] == "ripple") { + val uri = Uri.parse(schemeSplit[1]) + etWallet?.setText(uri.path) } else { etWallet?.setText(code) } diff --git a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt index 2117c6a9a6..8429ec1e2e 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt @@ -34,6 +34,7 @@ import com.tangem.wallet.TangemContext import kotlinx.android.synthetic.main.layout_progress_horizontal.* import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.* +import java.lang.IllegalArgumentException class SignTransactionFragment : BaseFragment(), NavigationResultListener, @@ -148,6 +149,10 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener, } catch (e: Exception) { e.printStackTrace() } + } catch(e: IllegalArgumentException) { + val data = Bundle() + data.putString(Constant.EXTRA_MESSAGE, e.message) + navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment) } catch (e: Exception) { e.printStackTrace() } diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index c76707df84..fe6ed6638a 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -12,9 +12,9 @@ dependencies { implementation "com.madgag.spongycastle:core:1.58.0.0" implementation "com.madgag.spongycastle:prov:1.58.0.0" implementation 'net.i2p.crypto:eddsa:0.3.0' + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testImplementation "com.google.truth:truth:1.0" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" } sourceCompatibility = "8" diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..f5d064d4e9 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -1,11 +1,14 @@ package com.tangem import com.tangem.commands.* +import com.tangem.commands.personalization.CardConfig +import com.tangem.commands.personalization.DepersonalizeCommand +import com.tangem.commands.personalization.DepersonalizeResponse +import com.tangem.commands.personalization.PersonalizeCommand import com.tangem.common.CardEnvironment import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils import com.tangem.tasks.* -import java.util.concurrent.Executors /** * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. @@ -24,7 +27,6 @@ class CardManager( private var terminalKeysService: TerminalKeysService? = null private var isBusy = false - private val cardManagerExecutor = Executors.newSingleThreadExecutor() init { CryptoUtils.initCrypto() @@ -69,18 +71,7 @@ class CardManager( */ fun sign(hashes: Array, cardId: String, callback: (result: TaskEvent) -> Unit) { - val signCommand: SignCommand - try { - signCommand = SignCommand(hashes) - } catch (error: Exception) { - if (error is TaskError) { - callback(TaskEvent.Completion(error)) - } else { - Log.e(this::class.simpleName!!, error.message ?: "") - callback(TaskEvent.Completion(TaskError.UnknownError())) - } - return - } + val signCommand = SignCommand(hashes) val task = SingleCommandTask(signCommand) runTask(task, cardId, callback) } @@ -189,16 +180,16 @@ class CardManager( * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ fun writeUserData( - cardId: String, - userData: ByteArray? = null, - userProtectedData: ByteArray? = null, - userCounter: Int? = null, - userProtectedCounter: Int? = null, - callback: (result: TaskEvent) -> Unit + cardId: String, + userData: ByteArray? = null, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + callback: (result: TaskEvent) -> Unit ) { - val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) - val task = SingleCommandTask(writeUserDataCommand) - runTask(task, cardId, callback) + val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + val task = SingleCommandTask(writeUserDataCommand) + runTask(task, cardId, callback) } /** @@ -212,8 +203,8 @@ class CardManager( * For example, this fields may contain blockchain nonce value. */ fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) + val task = SingleCommandTask(ReadUserDataCommand()) + runTask(task, cardId, callback) } /** @@ -247,6 +238,43 @@ class CardManager( runTask(task, cardId, callback) } + /** + * Command available on SDK cards only + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * @param cardId CID, Unique Tangem card ID number. + */ + fun depersonalize(cardId: String, + callback: (result: TaskEvent) -> Unit) { + val depersonalizeCommand = DepersonalizeCommand() + val task = SingleCommandTask(depersonalizeCommand) + runTask(task, cardId, callback) + } + + /** + * Command available on SDK cards only + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param cardId this parameter will set up CID, Unique Tangem card ID. + */ + fun personalize(config: CardConfig, + cardId: String, + callback: (result: TaskEvent) -> Unit) { + if (this.config.issuer == null) { + callback(TaskEvent.Completion(TaskError.IssuerIsRequired())) + return + } + val personalizationCommand = PersonalizeCommand(config, cardId) + val task = SingleCommandTask(personalizationCommand) + task.performPreflightRead = false + runTask(task, callback = callback) + } + /** */ @@ -262,7 +290,7 @@ class CardManager( task.reader = reader task.delegate = cardManagerDelegate - cardManagerExecutor.execute { + Thread().run { task.run(environment) { taskEvent -> if (taskEvent is TaskEvent.Completion) isBusy = false callback(taskEvent) @@ -292,7 +320,10 @@ class CardManager( val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null return CardEnvironment( cardId = cardId, - terminalKeys = terminalKeys + terminalKeys = terminalKeys, + manufacturerKeyPair = config.manufacturerKeyPair, + acquirerKeyPair = config.acquirerKeyPair, + issuer = config.issuer ) } diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 2d1193e176..2067f61920 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -1,6 +1,12 @@ package com.tangem +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.common.KeyPair + class Config( val linkedTerminal: Boolean = true, - val issuerPublicKey: ByteArray? = null + val issuerPublicKey: ByteArray? = null, + val manufacturerKeyPair: KeyPair? = null, + val acquirerKeyPair: KeyPair? = null, + val issuer: Issuer? = null ) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e5376f2cc7..28f0cd2d43 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -52,11 +49,14 @@ class CheckWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CheckWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt index 92ee3d0d90..134474fb23 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt @@ -38,7 +38,7 @@ abstract class CommandSerializer { * @return Remaining security delay in milliseconds. */ fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey) + val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 0c142ee296..18b20f1394 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -46,11 +43,14 @@ class CreateWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CreateWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt new file mode 100644 index 0000000000..85d3ddf866 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -0,0 +1,46 @@ +package com.tangem.commands + +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + +class OpenSessionResponse( + val sessionKeyB: ByteArray, + val uid: ByteArray +) : CommandResponse + +/** + * In case of encrypted communication, App should setup a session before calling any further command. + * [OpenSessionCommand] generates secret session_key that is used by both host and card + * to encrypt and decrypt commands’ payload. + + */ +class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) + return CommandApdu( + Instruction.OpenSession, tlvBuilder.serialize(), + encryptionMode = cardEnvironment.encryptionMode + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + OpenSessionResponse( + sessionKeyB = mapper.map(TlvTag.SessionKeyB), + uid = mapper.map(TlvTag.Uid) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index cef8aca229..4eb379e521 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -37,11 +34,14 @@ class PurgeWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.PurgeWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 0a161f392c..1e55cd404b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -32,6 +32,49 @@ data class SigningMethod(val rawValue: Int) { const val signHashValidatedByIssuerAndWriteIssuerData = 4 const val signRawValidatedByIssuerAndWriteIssuerData = 5 const val signPos = 6 + + fun build( + signHash: Boolean = false, + signRaw: Boolean = false, + signHashValidatedByIssuer: Boolean = false, + signRawValidatedByIssuer: Boolean = false, + signHashValidatedByIssuerAndWriteIssuerData: Boolean = false, + signRawValidatedByIssuerAndWriteIssuerData: Boolean = false, + signPos: Boolean = false + + ): SigningMethod { + fun Boolean.toInt() = if (this) 1 else 0 + + val signingMethodsCount = 0 + + signHash.toInt() + + signRaw.toInt() + + signHashValidatedByIssuer.toInt() + + signRawValidatedByIssuer.toInt() + + signHashValidatedByIssuerAndWriteIssuerData.toInt() + + signRawValidatedByIssuerAndWriteIssuerData.toInt() + + signPos.toInt() + + var signingMethod: Int = 0 + if (signingMethodsCount == 1) { + if (signHash) signingMethod += SigningMethod.signHash + if (signRaw) signingMethod += SigningMethod.signRaw + if (signHashValidatedByIssuer) signingMethod += SigningMethod.signHashValidatedByIssuer + if (signRawValidatedByIssuer) signingMethod += SigningMethod.signRawValidatedByIssuer + if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signHashValidatedByIssuerAndWriteIssuerData + if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signRawValidatedByIssuerAndWriteIssuerData + if (signPos) signingMethod += SigningMethod.signPos + } else if (signingMethodsCount > 1) { + signingMethod = 0x80 + if (signHash) signingMethod += 0x01 + if (signRaw) signingMethod += 0x01 shl SigningMethod.signRaw + if (signHashValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuer + if (signRawValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuer + if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuerAndWriteIssuerData + if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData + if (signPos) signingMethod += 0x01 shl SigningMethod.signPos + } + return SigningMethod(signingMethod) + } } } @@ -79,43 +122,74 @@ data class ProductMask(val rawValue: Int) { } } +class ProductMaskBuilder() { + + private var productMaskValue = 0 + + fun add(productCode: Int) { + productMaskValue = productMaskValue or productCode + } + + fun build() = ProductMask(productMaskValue) + +} + /** * Stores and maps Tangem card settings. * * @property rawValue Card settings in a form of flags, - * while flags definitions and values are in [SettingsMask.Companion] as constants. + * while flags definitions and possible values are in [Settings]. */ data class SettingsMask(val rawValue: Int) { + fun contains(settings: Settings): Boolean = (rawValue and settings.code) != 0 +} - fun contains(value: Int): Boolean = (rawValue and value) != 0 +enum class Settings(val code: Int) { + IsReusable(0x0001), + UseActivation(0x0002), + ForbidPurgeWallet(0x0004), + UseBlock(0x0008), - companion object { - const val isReusable = 0x0001 - const val useActivation = 0x0002 - const val forbidPurgeWallet = 0x0004 - const val useBlock = 0x0008 + AllowSwapPIN(0x0010), + AllowSwapPIN2(0x0020), + UseCVC(0x0040), + ForbidDefaultPIN(0x0080), - const val allowSwapPIN = 0x0010 - const val allowSwapPIN2 = 0x0020 - const val useCVC = 0x0040 - const val forbidDefaultPIN = 0x0080 + UseOneCommandAtTime(0x0100), + UseNdef(0x0200), + UseDynamicNdef(0x0400), + SmartSecurityDelay(0x0800), - const val useOneCommandAtTime = 0x0100 - const val useNdef = 0x0200 - const val useDynamicNdef = 0x0400 - const val smartSecurityDelay = 0x0800 + ProtocolAllowUnencrypted(0x1000), + ProtocolAllowStaticEncryption(0x2000), - const val protocolAllowUnencrypted = 0x1000 - const val protocolAllowStaticEncryption = 0x2000 + ProtectIssuerDataAgainstReplay(0x4000), + RestrictOverwriteIssuerDataEx(0x00100000), - const val protectIssuerDataAgainstReplay = 0x4000 + AllowSelectBlockchain(0x8000), - const val allowSelectBlockchain = 0x8000 + DisablePrecomputedNdef(0x00010000), - const val disablePrecomputedNdef = 0x00010000 + SkipSecurityDelayIfValidatedByLinkedTerminal(0x00080000), + SkipCheckPin2andCvcIfValidatedByIssuer(0x00040000), + SkipSecurityDelayIfValidatedByIssuer(0x00020000), - const val skipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000 + RequireTermTxSignature(0x01000000), + RequireTermCertSignature(0x02000000), + CheckPIN3onCard(0x04000000) +} + + +class SettingsMaskBuilder() { + + private var settingsMaskValue = 0 + + fun add(settings: Settings) { + settingsMaskValue = settingsMaskValue or settings.code } + + fun build() = SettingsMask(settingsMaskValue) + } /** @@ -310,11 +384,14 @@ class ReadCommand : CommandSerializer() { */ tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) - return CommandApdu(Instruction.Read, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Read, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val tlvMapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index c681f5fed9..cb95fd0899 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -28,7 +28,7 @@ class ReadIssuerDataResponse( * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: * SHA256([cardId] | [issuerData]). - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] then signature of + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: * SHA256([cardId] | [issuerData] | [issuerDataCounter]). */ @@ -36,7 +36,7 @@ class ReadIssuerDataResponse( /** * An optional counter that protect issuer data against replay attack. - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. */ val issuerDataCounter: Int? @@ -60,11 +60,14 @@ class ReadIssuerDataCommand( tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 4e3f757395..5672003477 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -33,7 +33,7 @@ class ReadIssuerExtraDataResponse( * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: * SHA256([cardId] | [issuerData]). - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] then signature of + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: * SHA256([cardId] | [issuerData] | [issuerDataCounter]). */ @@ -41,7 +41,7 @@ class ReadIssuerExtraDataResponse( /** * An optional counter that protect issuer data against replay attack. - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. */ val issuerDataCounter: Int? @@ -67,11 +67,14 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 3628d9cad7..ba5954fe01 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -57,11 +57,14 @@ class ReadUserDataCommand: CommandSerializer() { builder.append(TlvTag.CardId, cardEnvironment.cardId) builder.append(TlvTag.Pin, cardEnvironment.pin1) - return CommandApdu(Instruction.ReadUserData, builder.serialize()) + return CommandApdu( + Instruction.ReadUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index d76722e96c..6f0b7bfacc 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -60,7 +57,10 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } /** @@ -79,7 +79,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null val tlvMapper = TlvMapper(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2ca018f2a0..dbc7f15640 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -45,11 +45,14 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 7a1b987cd5..753607f0a9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -60,7 +60,10 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } private fun getDataToWrite(): ByteArray = @@ -72,7 +75,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index d512c4ded7..804abf6601 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -47,11 +47,14 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.WriteUserData, builder.serialize()) + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt new file mode 100644 index 0000000000..15548f9871 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt @@ -0,0 +1,112 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.* + +data class NdefRecord( + val type: Type, + val value: String +) { + enum class Type { + URI, AAR, TEXT + } + + val valueInBytes: ByteArray by lazy { value.toByteArray() } +} + +/** + * It is a configuration file with all the card settings that are written on the card + * during [PersonalizeCommand]. + */ +data class CardConfig( + val issuerName: String? = null, + val acquirerName: String? = null, + val series: String? = null, + val startNumber: Long = 0, + val count: Int = 0, + val pin: String, + val pin2: String, + val pin3: String, + val hexCrExKey: String?, + val cvc: String, + val pauseBeforePin2: Int, + val smartSecurityDelay: Boolean, + val curveID: EllipticCurve, + val signingMethod: SigningMethod, + val maxSignatures: Int, + val isReusable: Boolean, + val allowSwapPin: Boolean, + val allowSwapPin2: Boolean, + val useActivation: Boolean, + val useCvc: Boolean, + val useNdef: Boolean, + val useDynamicNdef: Boolean, + val useOneCommandAtTime: Boolean, + val useBlock: Boolean, + val allowSelectBlockchain: Boolean, + val forbidPurgeWallet: Boolean, + val protocolAllowUnencrypted: Boolean, + val protocolAllowStaticEncryption: Boolean, + val protectIssuerDataAgainstReplay: Boolean, + val forbidDefaultPin: Boolean, + val disablePrecomputedNdef: Boolean, + val skipSecurityDelayIfValidatedByIssuer: Boolean, + val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean, + val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean, + + val restrictOverwriteIssuerDataEx: Boolean, + + val requireTerminalTxSignature: Boolean, + val requireTerminalCertSignature: Boolean, + val checkPin3onCard: Boolean, + + val createWallet: Boolean, + + val cardData: CardData, + val ndefRecords: List +) { + + fun getSettingsMask(): SettingsMask { + val builder = SettingsMaskBuilder() + + if (allowSwapPin) builder.add(Settings.AllowSwapPIN) + if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2) + if (useCvc) builder.add(Settings.UseCVC) + if (isReusable) builder.add(Settings.IsReusable) + + if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime) + if (useNdef) builder.add(Settings.UseNdef) + if (useDynamicNdef) builder.add(Settings.UseDynamicNdef) + if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef) + + if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted) + if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption) + + if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN) + + if (useActivation) builder.add(Settings.UseActivation) + + if (useBlock) builder.add(Settings.UseBlock) + if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay) + + if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay) + + if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet) + if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain) + + if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer) + if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer) + + if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal) + if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx) + + if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature) + + if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature) + + if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard) + + return builder.build() + } + + companion object +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt new file mode 100644 index 0000000000..46239ea4ea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -0,0 +1,31 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.CommandResponse +import com.tangem.commands.CommandSerializer +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu + +data class DepersonalizeResponse(val success: Boolean) : CommandResponse + +/** + * Command available on SDK cards only + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * @param cardId CID, Unique Tangem card ID number. + */ +class DepersonalizeCommand : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + return CommandApdu( + Instruction.Depersonalize, byteArrayOf(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? { + return DepersonalizeResponse(true) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt new file mode 100644 index 0000000000..a034d171cb --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt @@ -0,0 +1,87 @@ +package com.tangem.commands.personalization + +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets + +/** + * Encodes information that is to be written on the card as an Ndef Tag. + */ +class NdefEncoder(private val ndefRecords: List, private val useDinamicNdef: Boolean) { + + fun encode(): ByteArray { + val bs = ByteArrayOutputStream() + // space for size + bs.write(0) + bs.write(0) + + for (i in ndefRecords.indices) { + val headerValue = (if (i == 0) 0x80 else 0x00) or (if (!useDinamicNdef && i == ndefRecords.size - 1) 0x40 else 0x00) + var value: ByteArray = ndefRecords[i].value.toByteArray(StandardCharsets.UTF_8) + encodeValue(ndefRecords[i], headerValue, bs) + } + + val result = bs.toByteArray() + result[0] = (result.size - 2 shr 8).toByte() + result[1] = (result.size - 2 and 0xFF).toByte() + return result + + } + + + private fun encodeValue(ndefRecord: NdefRecord, headerValue: Int, bs: ByteArrayOutputStream) { + when (ndefRecord.type) { + NdefRecord.Type.AAR -> { + bs.write((headerValue or 0x14)) // NDEF Header + bs.write(0x0F) // Length of the record type + bs.write(ndefRecord.valueInBytes.size) // Length of the payload data + bs.write(byteArrayOf(0x61.toByte(), 0x6E.toByte(), 0x64.toByte(), 0x72.toByte(), 0x6F.toByte(), 0x69.toByte(), 0x64.toByte(), 0x2E.toByte(), 0x63.toByte(), 0x6F.toByte(), 0x6D.toByte(), 0x3A.toByte(), + 0x70.toByte(), 0x6B.toByte(), 0x67.toByte())) // type name + bs.write(ndefRecord.valueInBytes) + } + NdefRecord.Type.URI -> { + bs.write((headerValue or 0x11)) // NDEF Header + bs.write(0x01) // Length of the record type + val uriIdentifierCode: Byte + val prefix: String + when { + ndefRecord.value.startsWith("http://www.") -> { + uriIdentifierCode = 0x01.toByte() + prefix = "http://www." + } + ndefRecord.value.startsWith("https://www.") -> { + uriIdentifierCode = 0x02.toByte() + prefix = "https://www." + } + ndefRecord.value.startsWith("http://") -> { + uriIdentifierCode = 0x03.toByte() + prefix = "http://" + } + ndefRecord.value.startsWith("https://") -> { + uriIdentifierCode = 0x04.toByte() + prefix = "https://" + } + else -> { + throw Exception() + } + } + val value = ndefRecord.value.substring(prefix.length).toByteArray() + bs.write(value.size + 1) // Length of the payload data + bs.write(0x55) // URI + bs.write(uriIdentifierCode.toInt()) // ? + bs.write(value) + } + NdefRecord.Type.TEXT -> { + bs.write((headerValue or 0x11)) // NDEF Header + bs.write(0x01) // Length of the record type + bs.write(ndefRecord.valueInBytes.size.toByte() + 1 + "en".length) // Length of the payload data + bs.write(0x54) // Text + bs.write(0x02) // UTF8(MSB=0)|"en".length + bs.write("en".toByteArray(StandardCharsets.US_ASCII)) + bs.write(ndefRecord.valueInBytes) + } + else -> throw Exception("Invalid NDEF record in config!") + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt new file mode 100644 index 0000000000..6bcdd815a5 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -0,0 +1,165 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.Card +import com.tangem.commands.CardData +import com.tangem.commands.CommandSerializer +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.sign +import com.tangem.tasks.TaskError + +/** + * Command available on SDK cards only + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param cardId this parameter will set up CID, Unique Tangem card ID. + */ +class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + if (cardEnvironment.issuer == null || cardEnvironment.manufacturerKeyPair == null) { + throw TaskError.SerializeCommandError() + } + return CommandApdu( + Instruction.Personalize, + serializePersonalizationData( + cardId, config, + cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey, + cardEnvironment.acquirerKeyPair?.publicKey), + encryptionKey = devPersonalizationKey + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { + val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null + + return try { + val tlvMapper = TlvMapper(tlvData) + Card( + cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", + manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", + status = tlvMapper.mapOptional(TlvTag.Status), + + firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), + cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), + settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), + issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), + curve = tlvMapper.mapOptional(TlvTag.CurveId), + maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), + signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), + pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), + walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), + walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), + health = tlvMapper.mapOptional(TlvTag.Health), + isActivated = tlvMapper.map(TlvTag.IsActivated), + activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), + paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), + userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), + terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + + cardData = deserializeCardData(tlvData) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } + + private fun deserializeCardData(tlvData: List): CardData? { + val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { + Tlv.deserialize(it.value) + } + if (cardDataTlvs.isNullOrEmpty()) return null + + val tlvMapper = TlvMapper(cardDataTlvs) + return CardData( + batchId = tlvMapper.mapOptional(TlvTag.Batch), + manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), + issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), + blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), + manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), + productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + + tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), + tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), + tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + ) + } + + private fun serializePersonalizationData(cardId: String, config: CardConfig, + issuer: Issuer, manufacturerPrivateKey: ByteArray, + acquirePublicKey: ByteArray? + ): ByteArray { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.CardId, cardId) + + tlvBuilder.append(TlvTag.CurveId, config.curveID) + tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures) + tlvBuilder.append(TlvTag.SigningMethod, config.signingMethod) + tlvBuilder.append(TlvTag.SettingsMask, config.getSettingsMask()) + tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10) + tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray()) + if (!config.ndefRecords.isNullOrEmpty()) tlvBuilder.append(TlvTag.NdefData, serializeNdef(config.ndefRecords)) + + tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet) + + tlvBuilder.append(TlvTag.NewPin, config.pin) + tlvBuilder.append(TlvTag.NewPin2, config.pin2) + tlvBuilder.append(TlvTag.NewPin3, config.pin3) + tlvBuilder.append(TlvTag.CrExKey, config.hexCrExKey) + tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey) + tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey) + + tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirePublicKey) + + tlvBuilder.append( + TlvTag.CardData, serializeCardData(cardId, config.cardData, issuer, manufacturerPrivateKey) + ) + return tlvBuilder.serialize() + } + + private fun serializeCardData( + cardId: String, cardData: CardData, + issuer: Issuer, manufacturerPrivateKey: ByteArray): ByteArray { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Batch, cardData.batchId) + tlvBuilder.append(TlvTag.ProductMask, cardData.productMask) + + tlvBuilder.append(TlvTag.ManufactureDateTime, cardData.manufactureDateTime) + + tlvBuilder.append(TlvTag.IssuerId, issuer.id) + + tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName) + + if (cardData.tokenSymbol != null) { + tlvBuilder.append(TlvTag.TokenSymbol, cardData.tokenSymbol) + tlvBuilder.append(TlvTag.TokenContractAddress, cardData.tokenContractAddress) + tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal) + } + tlvBuilder.append( + TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(manufacturerPrivateKey) + ) + return tlvBuilder.serialize() + } + + private fun serializeNdef(ndefRecords: List): ByteArray { + return NdefEncoder(ndefRecords, config.useDynamicNdef).encode() + } + + companion object { + val devPersonalizationKey = "1234".calculateSha256().copyOf(32) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt new file mode 100644 index 0000000000..f82889be6a --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt @@ -0,0 +1,10 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.KeyPair + +data class Issuer( + val name: String, + val id: String, + val dataKeyPair: KeyPair, + val transactionKeyPair: KeyPair +) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index 21728a8c44..bc507bf9a2 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -1,5 +1,7 @@ package com.tangem.common +import com.tangem.commands.personalization.entities.Issuer + /** * Contains data relating to a Tangem card. It is used in constructing all the commands, @@ -10,8 +12,12 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, - val encryptionKey: ByteArray? = null, - val cvc: ByteArray? = null + var encryptionMode: EncryptionMode = EncryptionMode.NONE, + var encryptionKey: ByteArray? = null, + val cvc: ByteArray? = null, + val manufacturerKeyPair: KeyPair? = null, + val acquirerKeyPair: KeyPair? = null, + val issuer: Issuer? = null ) { companion object { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index 450618ced5..1a2c2be076 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu import com.tangem.common.EncryptionMode +import com.tangem.common.extensions.calculateCrc16 +import com.tangem.common.extensions.toByteArray +import com.tangem.crypto.encrypt import java.io.ByteArrayOutputStream /** @@ -8,21 +11,19 @@ import java.io.ByteArrayOutputStream * to a raw data that can be sent to the card. * * @property ins Instruction code that determines the type of request for the card. - * @property tlvList A list of TLVs that are to be sent to the card + * @property tlvs Tlvs encoded to a [ByteArray] that are to be sent to the card. */ class CommandApdu( private val ins: Int, private val tlvs: ByteArray, - private val cla: Byte = ISO_CLA, - private val p1: Byte = 0x00, - private val p2: Byte = 0x00, - private val le: Int = 0x00, private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null) { + private val encryptionKey: ByteArray? = null, + + private val cla: Int = ISO_CLA) { constructor( instruction: Instruction, @@ -36,6 +37,19 @@ class CommandApdu( encryptionKey = encryptionKey ) + private val p1: Int + private val p2: Int + + init { + if (ins == Instruction.OpenSession.code) { + p1 = 0x00 + p2 = encryptionMode.code.toInt() + } else { + p1 = encryptionMode.code.toInt() + p2 = 0x00 + } + } + /** * Request converted to a raw data @@ -48,33 +62,37 @@ class CommandApdu( private fun toBytes(): ByteArray { - - val lc = tlvs.size + val data = if (encryptionKey != null) tlvs.encrypt() else tlvs val byteStream = ByteArrayOutputStream() - byteStream.write(cla.toInt()) + byteStream.write(cla) byteStream.write(ins) - byteStream.write(p1.toInt()) - byteStream.write(p2.toInt()) - if (lc != 0) { - writeLength(byteStream, lc) - byteStream.write(tlvs) + byteStream.write(p1) + byteStream.write(p2) + if (data.isNotEmpty()) { + byteStream.writeLength(data.size) + byteStream.write(data) } return byteStream.toByteArray() } - private fun writeLength(stream: ByteArrayOutputStream, lc: Int) { - stream.write(0) - stream.write(lc shr 8) - stream.write(lc and 0xFF) + private fun ByteArrayOutputStream.writeLength(lc: Int) { + this.write(0) + this.write(lc shr 8) + this.write(lc and 0xFF) } - private fun encrypt() { - TODO("not implemented") - } + private fun ByteArray.encrypt(): ByteArray { + val crc: ByteArray = tlvs.calculateCrc16() + val stream = ByteArrayOutputStream() + stream.write(this.size.toByteArray(2)) + stream.write(crc) + stream.write(this) + return stream.toByteArray().encrypt(encryptionKey!!) + } companion object { - const val ISO_CLA = 0x00.toByte() + const val ISO_CLA = 0x00 } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index a913813eb2..46c8989535 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -6,6 +6,7 @@ package com.tangem.common.apdu */ enum class Instruction(var code: Int) { Unknown(0x00), + Personalize(0xF1), Read(0xF2), VerifyCard(0xF3), ValidateCard(0xF4), @@ -20,7 +21,8 @@ enum class Instruction(var code: Int) { Activate(0xFE), OpenSession(0xFF), WriteUserData(0xE0), - ReadUserData(0xE1); + ReadUserData(0xE1), + Depersonalize(0xE3); companion object { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e98c78874b..e7e8625384 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu +import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv +import com.tangem.crypto.decrypt +import java.io.ByteArrayInputStream /** * Stores response data from the card and parses it to [Tlv] and [StatusWord]. @@ -25,15 +28,39 @@ class ResponseApdu(private val data: ByteArray) { * (Encryption / decryption functionality is not implemented yet.) */ fun getTlvData(encryptionKey: ByteArray? = null): List? { - return when { - data.size <= 2 -> null - else -> Tlv.deserialize(data.copyOf(data.size - 2)) + return if (data.size <= 2) { + null + } else { + val responseData = data.copyOf(data.size - 2) + return if (encryptionKey != null) { + if (data.size >= 18) { + val decryptedData = decrypt(responseData, encryptionKey) + Tlv.deserialize(decryptedData) + } else { + null + } + } else { + Tlv.deserialize(responseData) + } } } + private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) - private fun decrypt(encryptionKey: ByteArray) { - TODO("not implemented") + val inputStream = ByteArrayInputStream(decryptedData) + val baLength = ByteArray(2) + inputStream.read(baLength) + val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) + if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + val baCRC = ByteArray(2) + inputStream.read(baCRC) + val answerData = ByteArray(length) + inputStream.read(answerData) + val crc: ByteArray = answerData.calculateCrc16() + if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + + return answerData } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index f7e416d700..ac201f5fcb 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -6,6 +6,7 @@ import java.nio.ByteBuffer import java.security.MessageDigest import java.util.* import kotlin.experimental.and +import kotlin.experimental.xor /** * Extension functions for [ByteArray]. @@ -53,4 +54,21 @@ fun ByteArray.toCompressedPublicKey(): ByteArray { } else { this } +} + +fun ByteArray.calculateCrc16(): ByteArray { + var chBlock: Byte + // STEP 1 Initialize the CRC-16 value + var wCRC = 0x6363 // ITU-V.41 + var i = 0 + // STEP 2 Update data and Calucuate their CRC + do { + chBlock = this.get(i++) + chBlock = chBlock xor (wCRC and 0x00FF).toByte() + val chBlockInt = (chBlock.toInt() xor (chBlock.toInt() shl 4)) + wCRC = wCRC shr 8 xor (chBlockInt and 0xFF shl 8) and 0xFFFF xor (chBlockInt and 0xFF shl 3 and 0xFFFF) xor (chBlockInt and 0xFF shr 4 and 0xFFFF) + // (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); + } while (i < this.size) + + return byteArrayOf((wCRC and 0xFF).toByte(), (wCRC and 0xFFFF shr 8).toByte()) } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt index 1186dceeb3..58e4043450 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -6,6 +6,7 @@ import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toByteArray +import com.tangem.common.extensions.toHexString import com.tangem.tasks.TaskError import java.util.* @@ -32,11 +33,11 @@ class TlvEncoder { return when (tag.valueType()) { TlvValueType.HexString -> { typeCheck(tag) - return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) { - (value as String).calculateSha256() - } else { - (value as String).hexToBytes() - } + (value as String).hexToBytes() + } + TlvValueType.HexStringToHash -> { + typeCheck(tag) + (value as String).calculateSha256() } TlvValueType.Utf8String -> { typeCheck(tag) @@ -52,8 +53,8 @@ class TlvEncoder { } TlvValueType.BoolValue -> { typeCheck(tag) - Log.e(this::class.simpleName!!, "Unsupported operation: Boolean to ByteArray for tag $tag") - throw TaskError.ConvertError() + val booleanValue = value as Boolean + if (booleanValue) byteArrayOf(1) else byteArrayOf(0) } TlvValueType.ByteArray -> { typeCheck(tag) @@ -61,7 +62,7 @@ class TlvEncoder { } TlvValueType.EllipticCurve -> { typeCheck(tag) - (value as EllipticCurve).curve.plus("\\0").toByteArray() + (value as EllipticCurve).curve.toByteArray() } TlvValueType.DateTime -> { typeCheck(tag) @@ -69,7 +70,7 @@ class TlvEncoder { val year = calendar.get(Calendar.YEAR) val month = calendar.get(Calendar.MONTH) + 1 val day = calendar.get(Calendar.DAY_OF_MONTH) - return year.toByteArray() + month.toByteArray() + day.toByteArray() + return year.toByteArray(2) + month.toByte() + day.toByte() } TlvValueType.ProductMask -> { typeCheck(tag) @@ -79,7 +80,8 @@ class TlvEncoder { } TlvValueType.SettingsMask -> { typeCheck(tag) - (value as SettingsMask).rawValue.toByteArray(2) + val rawValue = (value as SettingsMask).rawValue + rawValue.toByteArray(determineByteArraySize(rawValue)) } TlvValueType.CardStatus -> { typeCheck(tag) @@ -87,7 +89,7 @@ class TlvEncoder { } TlvValueType.SigningMethod -> { typeCheck(tag) - (value as SigningMethod).rawValue.toByteArray() + byteArrayOf((value as SigningMethod).rawValue.toByte()) } TlvValueType.IssuerDataMode -> { typeCheck(tag) @@ -96,6 +98,11 @@ class TlvEncoder { } } + private fun determineByteArraySize(value: Int): Int { + val mask = 0xFFFF0000.toInt() + return if ((value and mask) != 0) 4 else 2 + } + private inline fun typeCheck(tag: TlvTag) { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt index 184f2fb49d..1713fd2e2d 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt @@ -54,7 +54,7 @@ class TlvMapper(val tlvList: List) { } return when (tag.valueType()) { - TlvValueType.HexString -> { + TlvValueType.HexString, TlvValueType.HexStringToHash -> { typeCheck(tag) tlvValue.toHexString() as T } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index d37500e221..44200cd20a 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -5,6 +5,7 @@ package com.tangem.common.tlv */ enum class TlvValueType { HexString, + HexStringToHash, Utf8String, Uint16, Uint32, @@ -36,6 +37,7 @@ enum class TlvTag(val code: Int) { SettingsMask(0x0A), CardData(0x0C), NdefData(0x0D), + CreateWalletAtPersonalize(0x0E), Health(0x0F), Pin(0x10), @@ -52,6 +54,10 @@ enum class TlvTag(val code: Int) { SessionKeyA(0x1A), SessionKeyB(0x1B), Pause(0x1C), + NewPin3(0x1E), + CrExKey(0x1F), + + Uid(0x0B), ManufactureId(0x20), ManufacturerSignature(0x86), @@ -62,11 +68,12 @@ enum class TlvTag(val code: Int) { IssuerDataSignature(0x33), IssuerTransactionSignature(0x34), IssuerDataCounter(0x35), + AcquirerPublicKey(0x37), + Size(0x25), Mode(0x23), Offset(0x24), - IsActivated(0x3A), ActivationSeed(0x3B), ResetPin(0x36), @@ -95,7 +102,6 @@ enum class TlvTag(val code: Int) { ProductMask(0x8A), PaymentFlowVersion(0x54), - TokenSymbol(0xA0), TokenContractAddress(0xA1), TokenDecimal(0xA2), @@ -118,15 +124,15 @@ enum class TlvTag(val code: Int) { */ fun valueType(): TlvValueType { return when (this) { - CardId, Pin, Pin2, Batch -> TlvValueType.HexString + CardId, Batch, CrExKey -> TlvValueType.HexString + Pin, Pin2, NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve - MaxSignatures, PauseBeforePin2, RemainingSignatures, - SignedHashes, Health, TokenDecimal, + PauseBeforePin2, RemainingSignatures, SignedHashes, Health, TokenDecimal, Offset, Size -> TlvValueType.Uint16 - UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 - IsActivated, TerminalIsLinked -> TlvValueType.BoolValue + MaxSignatures, UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 + IsActivated, TerminalIsLinked, CreateWalletAtPersonalize -> TlvValueType.BoolValue ManufactureDateTime -> TlvValueType.DateTime ProductMask -> TlvValueType.ProductMask SettingsMask -> TlvValueType.SettingsMask diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index 734b3d8f7a..af6f5e2b76 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -2,14 +2,19 @@ package com.tangem.crypto import com.tangem.commands.EllipticCurve import net.i2p.crypto.eddsa.EdDSASecurityProvider +import org.spongycastle.jce.provider.BouncyCastleProvider +import java.security.PublicKey import java.security.SecureRandom import java.security.Security +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec object CryptoUtils { fun initCrypto() { - Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1) + Security.insertProviderAt(BouncyCastleProvider(), 1) Security.addProvider(EdDSASecurityProvider()) } @@ -62,6 +67,16 @@ object CryptoUtils { EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray) } } + + fun loadPublicKey( + publicKey: ByteArray, + curve: EllipticCurve = EllipticCurve.Secp256k1 + ): PublicKey { + return when (curve) { + EllipticCurve.Secp256k1 -> Secp256k1.loadPublicKey(publicKey) + EllipticCurve.Ed25519 -> Ed25519.loadPublicKey(publicKey) + } + } } /** @@ -79,4 +94,28 @@ fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCu } } +fun ByteArray.encrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec, "SC") + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this) +} + +fun ByteArray.decrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this.copyOfRange(0, this.size)) +} + +fun ByteArray.pbkdf2Hash(salt: ByteArray, iterations: Int): ByteArray { + return Pbkdf2().deriveKey(this, salt, iterations) +} + +private const val ENCRYPTION_SPEC_PKCS7 = "AES/CBC/PKCS7PADDING" +private const val ENCRYPTION_SPEC_NO_PADDING = "AES/CBC/NOPADDING" + + diff --git a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt index fd0cf14cc1..5c07b5a4e9 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt @@ -24,7 +24,7 @@ object Ed25519 { return signatureInstance.verify(signature) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) return EdDSAPublicKey(pubKey) diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt new file mode 100644 index 0000000000..ab3d97445c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt @@ -0,0 +1,50 @@ +package com.tangem.crypto + +import org.spongycastle.jce.interfaces.ECPublicKey +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.spec.ECGenParameterSpec +import javax.crypto.KeyAgreement + +interface EncryptionHelper { + val keyA: ByteArray + + fun generateSecret(keyB: ByteArray): ByteArray +} + +class StrongEncryptionHelper : EncryptionHelper { + private val keyPair = generateKeyPair() + private val keyAgreement = generateKeyAgreement(keyPair) + override val keyA = provideKeyA(keyPair) + + override fun generateSecret(keyB: ByteArray): ByteArray { + keyAgreement.doPhase(CryptoUtils.loadPublicKey(keyB), true) + return keyAgreement.generateSecret() + } + + private fun generateKeyPair(): KeyPair { + val kpgen = KeyPairGenerator.getInstance("ECDH", "SC") + kpgen.initialize(ECGenParameterSpec("secp256k1"), SecureRandom()) + return kpgen.generateKeyPair() + } + + private fun generateKeyAgreement(keyPair: KeyPair): KeyAgreement { + val keyAgreement = KeyAgreement.getInstance("ECDH", "SC") + keyAgreement.init(keyPair.private) + return keyAgreement + } + + private fun provideKeyA(keyPair: KeyPair): ByteArray { + val eckey = keyPair.public as ECPublicKey + return eckey.q.getEncoded(false) + } +} + +class FastEncryptionHelper : EncryptionHelper { + override val keyA = CryptoUtils.generateRandomBytes(16) + + override fun generateSecret(keyB: ByteArray): ByteArray { + return keyA + keyB + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt new file mode 100644 index 0000000000..4c532b3692 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt @@ -0,0 +1,88 @@ +package com.tangem.crypto + +import org.spongycastle.crypto.CipherParameters +import org.spongycastle.crypto.digests.SHA256Digest +import org.spongycastle.crypto.macs.HMac +import org.spongycastle.crypto.params.KeyParameter +import java.security.InvalidKeyException +import java.util.* +import kotlin.experimental.xor +import kotlin.math.min +import kotlin.math.pow + +class Pbkdf2 { + private val F: HMac = HMac(SHA256Digest()) + + fun deriveKey(password: ByteArray, salt: ByteArray, iterations: Int): ByteArray { + + val macSize = F.macSize + // Check key length + if (macSize > (2.0.pow(32.0) - 1) * macSize) throw InvalidKeyException("Derived key to long") + + val derivedKey = ByteArray(macSize) + + val J = 0 + val K: Int = macSize + val U: Int = macSize shl 1 + val B = K + U + val workingArray = ByteArray(K + U + 4) + + // Initialize F + val macParams: CipherParameters = KeyParameter(password) + F.init(macParams) + + // Perform iterations + var kpos = 0 + var blk = 1 + while (kpos < macSize) { + storeInt32BE(blk, workingArray, B) + F.update(salt, 0, salt.size) + F.reset() + F.update(salt, 0, salt.size) + F.update(workingArray, B, 4) + F.doFinal(workingArray, U) + System.arraycopy(workingArray, U, workingArray, J, K) + var i = 1 + var j = J + var k = K + while (i < iterations) { + F.init(macParams) + F.update(workingArray, j, K) + F.doFinal(workingArray, k) + var u = U + var v = k + while (u < B) { + workingArray[u] = workingArray[u] xor workingArray[v] + u++ + v++ + } + val swp = k + k = j + j = swp + i++ + } + val tocpy = min(macSize - kpos, K) + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy) + kpos += K + blk++ + } + Arrays.fill(workingArray, 0.toByte()) + return derivedKey + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + private fun storeInt32BE(value: Int, bytes: ByteArray, offSet: Int) { + bytes[offSet + 3] = value.toByte() + bytes[offSet + 2] = (value ushr 8).toByte() + bytes[offSet + 1] = (value ushr 16).toByte() + bytes[offSet] = (value ushr 24).toByte() + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt index 2a62979bed..97eba2a81c 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt @@ -29,7 +29,7 @@ object Secp256k1 { return signatureInstance.verify(sigDer) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = ECNamedCurveTable.getParameterSpec("secp256k1") val factory = KeyFactory.getInstance("EC", "SC") diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..b585cd3023 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -3,57 +3,150 @@ package com.tangem.tasks import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.commands.ReadCommand +import com.tangem.commands.* import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult +import com.tangem.common.EncryptionMode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash /** * An error class that represent typical errors that may occur when performing Tangem SDK tasks. * Errors are propagated back to the caller in callbacks. */ -sealed class TaskError(val code: Int): Exception() { +sealed class TaskError(val code: Int) : Exception() { //Errors in serializing APDU - class SerializeCommandError: TaskError(1001) - class EncodingError: TaskError(1002) - class MissingTag: TaskError(1003) - class WrongType: TaskError(1004) - class ConvertError: TaskError(1005) + /** + * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] + * (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors). + */ + class SerializeCommandError : TaskError(1001) - //Card errors - class UnknownStatus: TaskError(2001) - class ErrorProcessingCommand: TaskError(2002) - class MissingPreflightRead: TaskError(2003) - class InvalidState: TaskError(2004) - class InsNotSupported: TaskError(2005) - class InvalidParams: TaskError(2006) - class NeedEncryption: TaskError(2007) + class EncodingError : TaskError(1002) + class MissingTag : TaskError(1003) + class WrongType : TaskError(1004) + class ConvertError : TaskError(1005) + + /** + * This error is returned when unknown [StatusWord] is received from a card. + */ + class UnknownStatus : TaskError(2001) + + /** + * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. + * The card sends this status in case of internal card error. + */ + class ErrorProcessingCommand : TaskError(2002) + + /** + * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] + * is executed before performing other commands. + */ + class MissingPreflightRead : TaskError(2003) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidState]. + * The card sends this status when command can not be executed in the current state of a card. + */ + class InvalidState : TaskError(2004) + + /** + * This error is returned when a card's reply is [StatusWord.InsNotSupported]. + * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. + */ + class InsNotSupported : TaskError(2005) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidParams]. + * The card sends this status when there are wrong or not sufficient parameters in TLV request, + * or wrong PIN1/PIN2. + * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], + * mapping or serialization errors. + */ + class InvalidParams : TaskError(2006) + + /** + * This error is returned when a card's reply is [StatusWord.NeedEncryption] + * and the encryption was not established by TangemSdk. + */ + class NeedEncryption : TaskError(2007) //Scan errors - class VerificationFailed: TaskError(3000) - class CardError: TaskError(3001) - class WrongCard: TaskError(3002) - class TooMuchHashesInOneTransaction: TaskError(3003) - class EmptyHashes: TaskError(3004) - class HashSizeMustBeEqual: TaskError(3005) + /** + * This error is returned when a [Task] checks unsuccessfully either + * a card's ability to sign with its private key, or the validity of issuer data. + */ + class VerificationFailed : TaskError(3000) - class Busy: TaskError(4000) - class UserCancelled: TaskError(4001) - class UnsupportedDevice: TaskError(4002) + /** + * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. + */ + class CardError : TaskError(3001) - //NFC error - class NfcReaderError: TaskError(5002) - class TagLost: TaskError(5003) + /** + * This error is returned when a [Task] expects a user to use a particular card, + * and a user tries to use a different card. + */ + class WrongCard : TaskError(3002) - class UnknownError: TaskError(6000) + /** + * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. + * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. + */ + class TooMuchHashesInOneTransaction : TaskError(3003) - //Issuer Data Errors - class MissingCounter: TaskError(7001) + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives only empty hashes for signature. + */ + class EmptyHashes : TaskError(3004) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives hashes of different lengths for signature. + */ + class HashSizeMustBeEqual : TaskError(3005) + + /** + * This error is returned when [com.tangem.CardManager] was called with a new [Task], + * while a previous [Task] is still in progress. + */ + class Busy : TaskError(4000) + + /** + * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. + */ + class UserCancelled : TaskError(4001) + + //NFC errors + class NfcReaderError : TaskError(5002) + + /** + * This error is returned when Android NFC reader loses a tag + * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. + */ + class TagLost : TaskError(5003) + + class UnknownError : TaskError(6000) + + //Specific Command Errors + /** + * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter + * (when the card's requires it), but the counter is missing. + */ + class MissingCounter : TaskError(7001) + + /** + * This error is returned when [com.tangem.commands.personalization.PersonalizeCommand] is attempted + * without [com.tangem.commands.personalization.entities.Issuer] being set in the [com.tangem.Config]. + */ + class IssuerIsRequired : TaskError(7002) } /** @@ -137,8 +230,41 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + } + EncryptionMode.FAST, EncryptionMode.STRONG -> { + if (cardEnvironment.encryptionKey != null ) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } + val encryptionHelper: EncryptionHelper = + if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) + val openSessionApdu = openSessionCommand.serialize(cardEnvironment) + sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + cardEnvironment.encryptionKey = sessionKey + + sendCommand(command, cardEnvironment, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + } } private fun sendRequest(command: CommandSerializer, @@ -171,9 +297,26 @@ abstract class Task { StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption())) + StatusWord.NeedEncryption -> { + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TaskError.NeedEncryption())) + return@transceiveApdu + } + } + sendCommand(command, cardEnvironment, callback) + } StatusWord.NeedPause -> { - // When NeedPause is returned from the card whenever security delay is triggered. + // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") @@ -201,7 +344,6 @@ abstract class Task { callback(TaskEvent.Completion(readResult.error)) } is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 @@ -215,10 +357,7 @@ abstract class Task { onRun(newEnvironment, readResult.data, callback) } } - - } - } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt index c8b3bcb083..b22ed95049 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt @@ -1,7 +1,7 @@ package com.tangem.tasks import com.tangem.commands.Card -import com.tangem.commands.SettingsMask +import com.tangem.commands.Settings import com.tangem.commands.WriteIssuerDataCommand import com.tangem.commands.WriteIssuerDataResponse import com.tangem.commands.common.IssuerDataToVerify @@ -55,7 +55,7 @@ class WriteIssuerDataTask( if (isCounterRequired()) issuerDataCounter != null else true private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean { return command.verify( diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt index f56ac1c5d5..887e832548 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt @@ -1,9 +1,6 @@ package com.tangem.tasks -import com.tangem.commands.Card -import com.tangem.commands.SettingsMask -import com.tangem.commands.WriteIssuerDataResponse -import com.tangem.commands.WriteIssuerExtraDataCommand +import com.tangem.commands.* import com.tangem.commands.common.IssuerDataMode import com.tangem.commands.common.IssuerDataToVerify import com.tangem.common.CardEnvironment @@ -98,7 +95,7 @@ internal class WriteIssuerExtraDataTask( if (isCounterRequired()) issuerDataCounter != null else true private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean { val publicKey = issuerPublicKey ?: card.issuerPublicKey!! diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt index b1ebcf143e..64cb5ce15d 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt @@ -68,15 +68,15 @@ class TlvMapperTest { .isNotNull() assertThat(settingsMask.rawValue) .isEqualTo(32289) - assertThat(settingsMask.contains(SettingsMask.skipSecurityDelayIfValidatedByLinkedTerminal)) + assertThat(settingsMask.contains(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal)) .isFalse() - assertThat(settingsMask.contains(SettingsMask.isReusable)) + assertThat(settingsMask.contains(Settings.IsReusable)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.allowSwapPIN2)) + assertThat(settingsMask.contains(Settings.AllowSwapPIN2)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.useDynamicNdef)) + assertThat(settingsMask.contains(Settings.UseDynamicNdef)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.forbidPurgeWallet)) + assertThat(settingsMask.contains(Settings.ForbidPurgeWallet)) .isFalse() } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt index f2c89bc9b4..36f3b2adfd 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt @@ -4,7 +4,9 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager +import com.tangem.commands.personalization.CardConfig import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tangemtest.extensions.init import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError import com.tangem.tasks.TaskEvent @@ -151,6 +153,30 @@ class Old_MainActivity : AppCompatActivity() { } } btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) } + btn_personalize?.setOnClickListener { _ -> + cardManager.personalize( + CardConfig.init(application), "BB00000000000395" + ) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = it.data.cardId } + } + } + } + btn_depersonalize?.setOnClickListener { _ -> + cardManager.depersonalize(cardId) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { + tv_card_cid?.text = "Depersonalized: ${it.data.success.toString()}" + } + } + } + } } private fun createSampleHashes(): Array { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt new file mode 100644 index 0000000000..5412ab7c15 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt @@ -0,0 +1,125 @@ +package com.tangem.tangemtest.extensions + +import android.app.Application +import android.content.Context +import com.tangem.commands.* +import com.tangem.commands.personalization.CardConfig +import com.tangem.commands.personalization.NdefRecord +import java.util.* + +fun CardConfig.Companion.init(application: Application): CardConfig { + + val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE) + + val signingMethod = SigningMethod.build( + signHash = preferences.getBoolean("personalization_SigningMethod_0", false), + signRaw = preferences.getBoolean("personalization_SigningMethod_1", false), + signHashValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_2", false), + signRawValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_3", false), + signHashValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_4", false), + signRawValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_5", false), + signPos = preferences.getBoolean("personalization_SigningMethod_6", false) + ) + + val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true) + val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false) + val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false) + + val productMaskBuilder = ProductMaskBuilder() + if (isNote) productMaskBuilder.add(ProductMask.note) + if (isTag) productMaskBuilder.add(ProductMask.tag) + if (isIdCard) productMaskBuilder.add(ProductMask.idCard) + val productMask = productMaskBuilder.build() + + var tokenSymbol: String? = null + var tokenContractAddress: String? = null + var tokenDecimal: Int? = null + if (preferences.getBoolean("personalization_isToken", false)) { + tokenSymbol = preferences.getString("personalization_token_symbol", "") + tokenContractAddress = preferences.getString("personalization_token_contract_address", "") + tokenDecimal = preferences.getString("personalization_token_decimal", "")!!.toInt() + } + + val cardData = CardData( + blockchainName = preferences.getString("personalization_Blockchain", "BTC"), + batchId = preferences.getString("personalization_card_batch", "FFFF"), + productMask = productMask, + tokenSymbol = tokenSymbol, + tokenContractAddress = tokenContractAddress, + tokenDecimal = tokenDecimal, + issuerName = null, + manufactureDateTime = Calendar.getInstance().time, + manufacturerSignature = null) + + + val ndefAar = preferences.getString("personalization_NDEF_AAR", "Release APP") + val ndefUri = preferences.getString("personalization_NDEF_URI", "https://tangem.com") + + val ndefs = mutableListOf() + if (!ndefUri.isNullOrEmpty()) { + ndefs.add(NdefRecord(NdefRecord.Type.URI, ndefUri)) + } + if (ndefAar != "None") { + val type = NdefRecord.Type.AAR + val value = when (ndefAar) { + "Debug APP" -> { + "com.tangem.wallet.debug" + } + "Release APP" -> { + "com.tangem.wallet" + } + "--- CUSTOM ---" -> { + preferences.getString("personalization_NDEF_CUSTOM_AAR", "com.tangem.wallet")!! + } + else -> "" + } + ndefs.add(NdefRecord(type, value)) + } + + return CardConfig( + cardData = cardData, + curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!) + ?: EllipticCurve.Secp256k1, + signingMethod = signingMethod, + createWallet = preferences.getBoolean("personalization_CreateWallet", true), + maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(), + isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true), + protocolAllowUnencrypted = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_None", true), + protocolAllowStaticEncryption = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_Fast", true), + useActivation = preferences.getBoolean("personalization_SettingsMask_NeedActivation", false), + + useOneCommandAtTime = preferences.getBoolean("personalization_SettingsMask_OneApduAtOnce", false), + useCvc = preferences.getBoolean("personalization_SettingsMask_UseCVC", false), + useBlock = preferences.getBoolean("personalization_SettingsMask_UseBlock", false), + allowSwapPin = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN", true), + allowSwapPin2 = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN2", true), + useNdef = preferences.getBoolean("personalization_SettingsMask_UseNDEF", true), + useDynamicNdef = preferences.getBoolean("personalization_SettingsMask_UseDynamicNDEF", true), + protectIssuerDataAgainstReplay = preferences.getBoolean("personalization_SettingsMask_ProtectIssuerDataAgainstReplay", true), + forbidDefaultPin = preferences.getBoolean("personalization_SettingsMask_ForbidDefaultPIN", false), + smartSecurityDelay = preferences.getBoolean("personalization_SettingsMask_SmartSecurityDelay", false), + pauseBeforePin2 = preferences.getString("personalization_PauseBeforePIN2", "15")!!.toInt() * 1000, + allowSelectBlockchain = preferences.getBoolean("personalization_SettingsMask_AllowSelectBlockchain", false), + forbidPurgeWallet = preferences.getBoolean("personalization_SettingsMask_ForbidPurgeWallet", false) + ?: false, + disablePrecomputedNdef = preferences.getBoolean("personalization_SettingsMask_DisablePrecomputedNDEF", false) + ?: false, + skipSecurityDelayIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByIssuer", true), + skipCheckPIN2andCVCIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipCheckPIN2andCVCIfValidatedByIssuer", true), + + skipSecurityDelayIfValidatedByLinkedTerminal = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByLinkedTerminal", true), + restrictOverwriteIssuerDataEx = preferences.getBoolean("personalization_SettingsMask_RestrictOverwriteIssuerDataEx", true), + + requireTerminalTxSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalTxSignature", false), + requireTerminalCertSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalCertSignature", false), + checkPin3onCard = preferences.getBoolean("personalization_SettingsMask_CheckPIN3onCard", true), + + cvc = preferences.getString("personalization_cvc", "000") ?: "000", + pin = preferences.getString("personalization_pin", "000000") ?: "000000", + pin2 = preferences.getString("personalization_pin2", "000") ?: "000", + pin3 = preferences.getString("personalization_pin3", "123") ?: "123", + hexCrExKey = preferences.getString("personalization_CrEx_Key", "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"), + + ndefRecords = ndefs + ) +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt index d87b466385..ed3ba8bf1c 100644 --- a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt +++ b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt @@ -43,7 +43,7 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model40("HWVTR", "Huawei P10", 0, 50, 0, 0), model41("HWWAS-H", "Huawei P10 lite", 0, 50, 0, 0), model42("HWVKY", "Huawei P10 Plus", 0, 50, 0, 0), - model43("HWEML", "Huawei P20", 0, 50, 50, 0), + model43("HWEML", "Huawei P20", 0, 40, 5, 0), model44("HWANE", "Huawei P20 Lite", 0, 50, 20, 0), model45("HWCLT", "Huawei P20 Pro", 0, 50, 50, 0), model46("HW-01K", "Huawei P20 Pro", 0, 50, 50, 0), @@ -231,8 +231,8 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model228("sagit", "Xiaomi Mi 6", 0, 50, 20, 0), model229("dipper", "Xiaomi Mi 8", 0, 45, 20, 0), model230("ursa", "Xiaomi MI 8 Explorer Edition", 0, 50, 40, 0), - model231("cepheus", "Xiaomi MI 9", 0, 40, 20, 0), - model232("grus", "Xiaomi MI 9 SE", 0, 40, 20, 0), + model231("cepheus", "Xiaomi MI 9", 0, 40, 5, 0), + model232("grus", "Xiaomi MI 9 SE", 0, 40, 5, 0), model233("lithium", "Xiaomi Mi MIX", 0, 20, 20, 0), model234("chiron", "Xiaomi Mi MIX 2", 0, 45, 20, 0), model235("polaris", "Xiaomi Mi MIX 2S", 0, 45, 20, 0), diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index 7c17de9137..14d86acb0d 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -47,11 +47,11 @@ dependencies { implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" - implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61" + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" implementation 'at.favre.lib:armadillo:0.9.0' diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt index 400ab3f07f..e1dd5bb69b 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt @@ -40,7 +40,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel } private fun showReadingDialog(activity: FragmentActivity, cardId: String?) { - val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null) + val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) readingDialog = BottomSheetDialog(activity) readingDialog?.setContentView(dialogView) readingDialog?.dismissWithAnimation = true