diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 280cc6ed9d..0000000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
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/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/build.gradle b/tangem-demo/build.gradle
index b1de1a90fd..614e6b9552 100644
--- a/tangem-demo/build.gradle
+++ b/tangem-demo/build.gradle
@@ -38,7 +38,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.core:core-ktx:1.1.0'
+ implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt
index 01207d703d..1a294a8897 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/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 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
index 2af3ad92de..d3bb8bbabe 100644
--- a/tangem-demo/src/main/res/layout/activity_main.xml
+++ b/tangem-demo/src/main/res/layout/activity_main.xml
@@ -111,6 +111,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_create_wallet" />
+
+
+
+
\ No newline at end of file
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