diff --git a/app/src/main/java/com/tangem/data/network/AdaliteApi.java b/app/src/main/java/com/tangem/data/network/AdaliteApi.java index 8af94a92c5..3a6b380945 100644 --- a/app/src/main/java/com/tangem/data/network/AdaliteApi.java +++ b/app/src/main/java/com/tangem/data/network/AdaliteApi.java @@ -26,5 +26,5 @@ public interface AdaliteApi { @Headers("Content-Type: application/json") @POST(ServerApiAdalite.ADALITE_SEND) - Call adaliteSend(@Body AdaliteBody adaliteBody); + Call adaliteSend(@Body AdaliteBody adaliteBody); } 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 0e51596e15..7dab3374a2 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java @@ -27,7 +27,7 @@ public class ServerApiAdalite { private int requestsCount = 0; - private final String adaliteURL1 = "https://explorer2.adalite.io"; //TODO: make random selection, add more?, move + private final String adaliteURL1 = "https://explorer3.adalite.io"; //TODO: make random selection, add more?, move private final String adaliteURL2 = "https://nodes.southeastasia.cloudapp.azure.com"; private String currentURL = adaliteURL1; @@ -48,7 +48,7 @@ public class ServerApiAdalite { void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo); - void onSuccess(String method, List listResponse); + void onSuccess(String method, String stringResponse); void onFail(String method, String message); } @@ -145,10 +145,10 @@ public class ServerApiAdalite { break; case ADALITE_SEND: - Call sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx)); - sendCall.enqueue(new Callback() { + Call sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx)); + sendCall.enqueue(new Callback() { @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { + public void onResponse(@NonNull Call call, @NonNull Response response) { requestsCount--; if (response.code() == 200) { @@ -166,7 +166,7 @@ public class ServerApiAdalite { } @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { + public void onFailure(@NonNull Call call, @NonNull Throwable t) { Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); requestsCount--; diff --git a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java index e51226a99b..1614b760ca 100644 --- a/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java +++ b/app/src/main/java/com/tangem/wallet/cardano/CardanoEngine.java @@ -671,7 +671,7 @@ public class CardanoEngine extends CoinEngine { } @Override - public void onSuccess(String method, List listResponse) { + public void onSuccess(String method, String stringResponse) { Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions"); blockchainRequestsCallbacks.onComplete(false); @@ -731,30 +731,19 @@ public class CardanoEngine extends CoinEngine { } @Override - public void onSuccess(String method, List listResponse) { + public void onSuccess(String method, String stringResponse) { if (method.equals(ServerApiAdalite.ADALITE_SEND)) { - String resultString = listResponse.toString(); - try { - if (resultString.isEmpty()) { - ctx.setError("No response from node"); - blockchainRequestsCallbacks.onComplete(false); - } else { // TODO: Make check for a valid send response - ctx.setError(null); - blockchainRequestsCallbacks.onComplete(true); - } - } catch (Exception e) { - if (e.getMessage() != null) { - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } else { - ctx.setError(e.getClass().getName()); - blockchainRequestsCallbacks.onComplete(false); - Log.e(TAG, resultString); - } + if (stringResponse.equals("\"Transaction sent successfully!\"")) { + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } else { // TODO: Make check for a valid send response + ctx.setError(stringResponse); + blockchainRequestsCallbacks.onComplete(false); } } } + @Override public void onFail(String method, String message) { Log.e(TAG, "onFail: " + method + " " + message); diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index e65d1aa050..62c88bf19f 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -96,8 +96,23 @@ class CardManager( */ fun readIssuerData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val getIssuerDataCommand = ReadIssuerDataCommand() - val task = SingleCommandTask(getIssuerDataCommand) + val task = ReadIssuerDataTask(config.issuerPublicKey) + runTask(task, cardId, callback) + } + + /** + * This task retrieves Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. . For example, this field may contain photo or + * biometric information for ID card product. Because of the large size of Issuer_Extra_Data, + * a series of these commands have to be executed to read the entire Issuer_Extra_Data. + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [ReadIssuerExtraDataTask], + * provides card response in the form of [ReadIssuerExtraDataResponse]. + */ + fun readIssuerExtraData(cardId: String, + callback: (result: TaskEvent) -> Unit) { + val task = ReadIssuerExtraDataTask(config.issuerPublicKey) runTask(task, cardId, callback) } @@ -108,7 +123,7 @@ class CardManager( * wallet balance signed by the issuer or additional issuer’s attestation data. * @param cardId CID, Unique Tangem card ID number. * @param issuerData Data provided by issuer. - * @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). + * @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key. * @param issuerDataCounter An optional counter that protect issuer data against replay attack. * @param callback is triggered on the completion of the [WriteIssuerDataCommand], * provides card response in the form of [WriteIssuerDataResponse]. @@ -118,12 +133,46 @@ class CardManager( issuerDataSignature: ByteArray, issuerDataCounter: Int? = null, callback: (result: TaskEvent) -> Unit) { - val writeIssuerDataCommand = WriteIssuerDataCommand( + val task = WriteIssuerDataTask( issuerData, issuerDataSignature, + issuerDataCounter, + config.issuerPublicKey + ) + runTask(task, cardId, callback) + } + + /** + * This task writes Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. + * The issuer defines purpose of use, format and payload of Issuer Data. + * For example, this field may contain a photo or biometric information for ID card products. + * Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed + * to write entire Issuer_Extra_Data. + * @param cardId CID, Unique Tangem card ID number. + * @param issuerData Data provided by issuer. + * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and + * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. + * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay + * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). + * @param issuerDataCounter An optional counter that protect issuer data against replay attack. + * @param callback is triggered on the completion of the [WriteIssuerDataCommand], + * provides card response in the form of [WriteIssuerDataResponse]. + */ + fun writeIssuerExtraData(cardId: String, + issuerData: ByteArray, + startingSignature: ByteArray, + finalizingSignature: ByteArray, + issuerDataCounter: Int? = null, + callback: (result: TaskEvent) -> Unit) { + val task = WriteIssuerExtraDataTask( + issuerData, + startingSignature, finalizingSignature, + config.issuerPublicKey, issuerDataCounter ) - val task = SingleCommandTask(writeIssuerDataCommand) runTask(task, cardId, callback) } diff --git a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt b/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt index da742378a2..5fce77186d 100644 --- a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt @@ -21,6 +21,12 @@ interface CardManagerDelegate { */ fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) + /** + * It is called when long tasks are performed. + * A user is expected to hold the card until the task is complete. + */ + fun onDelay(total: Int, current: Int, step: Int) + /** * It is called when user takes the card away from the Android device during the scanning * (for example when security delay is in progress) and the TagLostException is received. diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 7689e77697..2d1193e176 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -1,5 +1,6 @@ package com.tangem -data class Config( - val linkedTerminal: Boolean = true +class Config( + val linkedTerminal: Boolean = true, + val issuerPublicKey: ByteArray? = null ) \ No newline at end of file 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 a045692a10..c681f5fed9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -1,12 +1,12 @@ package com.tangem.commands +import com.tangem.commands.common.DefaultIssuerDataVerifier +import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataVerifier 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 @@ -50,12 +50,16 @@ class ReadIssuerDataResponse( * wallet balance signed by the issuer or additional issuer’s attestation data. * @property cardId CID, Unique Tangem card ID number. */ -class ReadIssuerDataCommand : CommandSerializer() { +class ReadIssuerDataCommand( + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() +) : CommandSerializer(), + IssuerDataVerifier by verifier { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt new file mode 100644 index 0000000000..4e3f757395 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -0,0 +1,97 @@ +package com.tangem.commands + +import com.tangem.commands.common.DefaultIssuerDataVerifier +import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataVerifier +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 ReadIssuerExtraDataResponse( + + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + + /** + * Size of all Issuer_Extra_Data field. + */ + val size: Int?, + + /** + * Data defined by issuer. + */ + val issuerData: ByteArray, + + /** + * 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 + * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: + * SHA256([cardId] | [issuerData] | [issuerDataCounter]). + */ + val issuerDataSignature: ByteArray?, + + /** + * An optional counter that protect issuer data against replay attack. + * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] + * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. + */ + val issuerDataCounter: Int? +) : CommandResponse + + +/** + * This command retrieves Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. . For example, this field may contain photo or + * biometric information for ID card product. Because of the large size of Issuer_Extra_Data, + * a series of these commands have to be executed to read the entire Issuer_Extra_Data. + */ +class ReadIssuerExtraDataCommand( + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() +) : CommandSerializer(), IssuerDataVerifier by verifier { + + var offset: Int = 0 + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) + tlvBuilder.append(TlvTag.Offset, offset) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + ReadIssuerExtraDataResponse( + cardId = mapper.map(TlvTag.CardId), + size = mapper.mapOptional(TlvTag.Size), + issuerData = mapper.mapOptional(TlvTag.IssuerData) ?: byteArrayOf(), + issuerDataSignature = mapper.mapOptional(TlvTag.IssuerDataSignature), + issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } + + companion object { + /** + * This mode value specifies that this command retrieves Issuer EXTRA data from the card + * (with value 0 the command will get instead simple Issuer Data from the card). + */ + const val EXTRA_DATA_MODE = 1 + } +} \ No newline at end of file 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 9066040e81..2ca018f2a0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -1,13 +1,12 @@ package com.tangem.commands +import com.tangem.commands.common.DefaultIssuerDataVerifier +import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataVerifier 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.extensions.toByteArray -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -33,13 +32,15 @@ class WriteIssuerDataResponse( class WriteIssuerDataCommand( private val issuerData: ByteArray, private val issuerDataSignature: ByteArray, - private val issuerDataCounter: Int? = null -) : CommandSerializer() { + private val issuerDataCounter: Int? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() +) : CommandSerializer(), IssuerDataVerifier by verifier { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) tlvBuilder.append(TlvTag.IssuerData, issuerData) tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt new file mode 100644 index 0000000000..7a1b987cd5 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -0,0 +1,90 @@ +package com.tangem.commands + +import com.tangem.commands.common.DefaultIssuerDataVerifier +import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataVerifier +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 + +/** + * This command writes Issuer Extra Data field and its issuer’s signature. + * Issuer Extra Data is never changed or parsed from within the Tangem COS. + * The issuer defines purpose of use, format and payload of Issuer Data. + * For example, this field may contain a photo or biometric information for ID card products. + * Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed + * to write entire Issuer_Extra_Data. + * @param issuerData Data provided by issuer. + * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and + * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. + * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay + * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). + * @param issuerDataCounter An optional counter that protect issuer data against replay attack. + */ +class WriteIssuerExtraDataCommand( + private val issuerData: ByteArray, + private val startingSignature: ByteArray, + private val finalizingSignature: ByteArray, + private val issuerDataCounter: Int? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() +) : CommandSerializer(), IssuerDataVerifier by verifier { + + var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData + var offset: Int = 0 + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + + tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Mode, mode) + + when (mode) { + IssuerDataMode.InitializeWritingExtraData -> { + tlvBuilder.append(TlvTag.Size, issuerData.size) + tlvBuilder.append(TlvTag.IssuerDataSignature, startingSignature) + tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) + } + IssuerDataMode.WriteExtraData -> { + tlvBuilder.append(TlvTag.IssuerData, getDataToWrite()) + tlvBuilder.append(TlvTag.Offset, offset) + } + IssuerDataMode.FinalizeExtraData -> { + tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) + } + } + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + } + + private fun getDataToWrite(): ByteArray = + issuerData.copyOfRange(offset, offset + calculatePartSize()) + + private fun calculatePartSize(): Int { + val bytesLeft = issuerData.size - offset + return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + WriteIssuerDataResponse( + cardId = mapper.map(TlvTag.CardId) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } + + companion object { + const val SINGLE_WRITE_SIZE = 1524 + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt new file mode 100644 index 0000000000..2ebded1b6d --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt @@ -0,0 +1,44 @@ +package com.tangem.commands.common + +import com.tangem.commands.WriteIssuerExtraDataCommand + +/** + * This enum specifies modes for [WriteIssuerExtraDataCommand]. + */ +enum class IssuerDataMode(val code: Byte) { + + + /** + * This mode is required to read issuer data from the card. + */ + ReadData(0), + + /** + * This mode is required to write issuer data to the card. + */ + WriteData(0), + /** + * This mode is required to read issuer extra data from the card. + */ + ReadExtraData(1), + /** + * This mode is required to initiate writing issuer extra data to the card. + */ + InitializeWritingExtraData(1), + /** + * With this mode, the command writes part of issuer extra data + * (block of a size [WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE]) to the card. + */ + WriteExtraData(2), + /** + * This mode is used after the issuer extra data was fully written to the card. + * Under this mode the command provides the issuer signature + * to confirm the validity of data that was written to card. + */ + FinalizeExtraData(3); + + companion object { + private val values = values() + fun byCode(code: Byte): IssuerDataMode? = values.find { it.code == code } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt new file mode 100644 index 0000000000..03bf58d56e --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt @@ -0,0 +1,40 @@ +package com.tangem.commands.common + +import com.tangem.common.tlv.TlvEncoder +import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.CryptoUtils +import java.io.ByteArrayOutputStream + +interface IssuerDataVerifier { + fun verify( + issuerPublicKey: ByteArray, signature: ByteArray, issuerDataToVerify: IssuerDataToVerify + ): Boolean +} + +class IssuerDataToVerify( + val cardId: String, + val issuerData: ByteArray?, + val issuerDataCounter: Int? = null, + val issuerExtraDataSize: Int? = null +) + +class DefaultIssuerDataVerifier : IssuerDataVerifier { + override fun verify( + issuerPublicKey: ByteArray, + signature: ByteArray, + issuerDataToVerify: IssuerDataToVerify + ): Boolean { + + val tlvEncoder = TlvEncoder() + val dataToVerify = ByteArrayOutputStream() + dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, issuerDataToVerify.cardId)) + issuerDataToVerify.issuerData?.let { dataToVerify.write(it) } + issuerDataToVerify.issuerDataCounter?.let { counter -> + dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, counter)) + } + issuerDataToVerify.issuerExtraDataSize?.let { + dataToVerify.write(tlvEncoder.encodeValue(TlvTag.Size, it)) + } + return CryptoUtils.verify(issuerPublicKey, dataToVerify.toByteArray(), signature) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt b/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt index 5565803830..0e8aa998da 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt @@ -2,9 +2,15 @@ package com.tangem.common.extensions import java.nio.ByteBuffer -fun Int.toByteArray(): ByteArray { - val bufferSize = Int.SIZE_BYTES - val buffer = ByteBuffer.allocate(bufferSize) - buffer.putInt(this) - return buffer.array() +fun Int.toByteArray(size: Int = Int.SIZE_BYTES): ByteArray { + if (size == Int.SIZE_BYTES) { + val buffer = ByteBuffer.allocate(size) + buffer.putInt(this) + return buffer.array() + } else if (size == Short.SIZE_BYTES){ + return byteArrayOf( + (this ushr 8).toByte(), + this.toByte()) + } + return byteArrayOf() } \ 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 561603e335..e2dce7d743 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 @@ -2,22 +2,33 @@ package com.tangem.common.tlv import com.tangem.Log import com.tangem.commands.* -import com.tangem.common.extensions.* +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.tasks.TaskError -import java.time.Year import java.util.* +/** + * Encodes information that is to be written on the card from parsed classes into [ByteArray] + * (according to the provided [TlvTag] and corresponding [TlvValueType]) + * and then forms [Tlv] with the encoded values. + */ class TlvEncoder { + /** + + * @param value information that is to be encoded into [Tlv]. + */ internal inline fun encode(tag: TlvTag, value: T?): Tlv { if (value != null) { - return Tlv(tag, encodeValue(value, tag)) + return Tlv(tag, encodeValue(tag, value)) } else { Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null") throw TaskError.SerializeCommandError() } } - private inline fun encodeValue(value: T, tag: TlvTag): ByteArray { + internal inline fun encodeValue(tag: TlvTag, value: T): ByteArray { return when (tag.valueType()) { TlvValueType.HexString -> { typeCheck(tag) @@ -31,7 +42,11 @@ class TlvEncoder { typeCheck(tag) (value as String).toByteArray() } - TlvValueType.IntValue -> { + TlvValueType.Uint16 -> { + typeCheck(tag) + (value as Int).toByteArray(2) + } + TlvValueType.Uint32 -> { typeCheck(tag) (value as Int).toByteArray() } @@ -74,6 +89,10 @@ class TlvEncoder { typeCheck(tag) (value as SigningMethod).rawValue.toByteArray() } + TlvValueType.IssuerDataMode -> { + typeCheck(tag) + byteArrayOf((value as IssuerDataMode).code) + } } } 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 90e218c5fa..ead8085ebe 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 @@ -2,6 +2,7 @@ package com.tangem.common.tlv import com.tangem.Log import com.tangem.commands.* +import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.toDate import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toInt @@ -34,7 +35,7 @@ class TlvMapper(val tlvList: List) { /** * Finds [Tlv] by its [TlvTag]. - * Throws [MissingTagException] if [Tlv] is not found, + * Throws [TaskError.MissingTag] if [Tlv] is not found, * otherwise converts [Tlv] value to [T]. * * @param tag [TlvTag] of a [Tlv] which value is to be returned. @@ -61,7 +62,7 @@ class TlvMapper(val tlvList: List) { typeCheck(tag) tlvValue.toUtf8() as T } - TlvValueType.IntValue -> { + TlvValueType.Uint16, TlvValueType.Uint32 -> { typeCheck(tag) try { tlvValue.toInt() as T @@ -129,6 +130,15 @@ class TlvMapper(val tlvList: List) { throw TaskError.ConvertError() } } + TlvValueType.IssuerDataMode -> { + typeCheck(tag) + try { + IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T + } catch (exception: Exception) { + logException(tag, tlvValue.toInt().toString(), exception) + throw TaskError.ConvertError() + } + } } } 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 92ca2a8885..e5320b77b1 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 @@ -6,7 +6,8 @@ package com.tangem.common.tlv enum class TlvValueType { HexString, Utf8String, - IntValue, + Uint16, + Uint32, BoolValue, ByteArray, EllipticCurve, @@ -14,7 +15,8 @@ enum class TlvValueType { ProductMask, SettingsMask, CardStatus, - SigningMethod + SigningMethod, + IssuerDataMode } /** @@ -60,6 +62,10 @@ enum class TlvTag(val code: Int) { IssuerDataSignature(0x33), IssuerTransactionSignature(0x34), IssuerDataCounter(0x35), + Size(0x25), + Mode(0x23), + Offset(0x24), + IsActivated(0x3A), ActivationSeed(0x3B), @@ -113,13 +119,16 @@ enum class TlvTag(val code: Int) { TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve MaxSignatures, PauseBeforePin2, RemainingSignatures, - SignedHashes, Health, TokenDecimal, UserCounter, IssuerDataCounter -> TlvValueType.IntValue + SignedHashes, Health, TokenDecimal, + Offset, Size -> TlvValueType.Uint16 + UserCounter, IssuerDataCounter -> TlvValueType.Uint32 IsActivated, TerminalIsLinked -> TlvValueType.BoolValue ManufactureDateTime -> TlvValueType.DateTime ProductMask -> TlvValueType.ProductMask SettingsMask -> TlvValueType.SettingsMask Status -> TlvValueType.CardStatus SigningMethod -> TlvValueType.SigningMethod + Mode -> TlvValueType.IssuerDataMode else -> TlvValueType.ByteArray } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt new file mode 100644 index 0000000000..d7c50dde31 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt @@ -0,0 +1,53 @@ +package com.tangem.tasks + +import com.tangem.commands.Card +import com.tangem.commands.ReadIssuerDataCommand +import com.tangem.commands.ReadIssuerDataResponse +import com.tangem.commands.common.IssuerDataToVerify +import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult + +class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task() { + + override fun onRun( + cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit) { + + val command = ReadIssuerDataCommand() + + sendCommand(command, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + val data = result.data + if (data.issuerData.isEmpty()) { + completeNfcSession() + callback(TaskEvent.Event(result.data)) + callback(TaskEvent.Completion()) + return@sendCommand + } + val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!! + val issuerDataToVerify = IssuerDataToVerify( + cardEnvironment.cardId!!, + data.issuerData, + data.issuerDataCounter + ) + if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) { + completeNfcSession() + callback(TaskEvent.Event(result.data)) + callback(TaskEvent.Completion()) + } else { + completeNfcSession(TaskError.VerificationFailed()) + callback(TaskEvent.Completion(TaskError.VerificationFailed())) + } + } + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelled) { + completeNfcSession(result.error) + } + callback(TaskEvent.Completion(result.error)) + } + } + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt new file mode 100644 index 0000000000..4f1397b8db --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt @@ -0,0 +1,100 @@ +package com.tangem.tasks + +import com.tangem.commands.Card +import com.tangem.commands.ReadIssuerExtraDataCommand +import com.tangem.commands.ReadIssuerExtraDataResponse +import com.tangem.commands.WriteIssuerExtraDataCommand +import com.tangem.commands.common.IssuerDataToVerify +import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult +import java.io.ByteArrayOutputStream + +/** + * This task performs [ReadIssuerExtraDataCommand] repeatedly until + * the issuer extra data is fully retrieved. + */ +internal class ReadIssuerExtraDataTask( + private val issuerPublicKey: ByteArray?) : Task() { + + private val issuerData = ByteArrayOutputStream() + private var issuerDataSize = 0 + private lateinit var card: Card + private lateinit var cardEnvironment: CardEnvironment + + override fun onRun( + cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit + ) { + card = currentCard!! + this.cardEnvironment = cardEnvironment + val command = ReadIssuerExtraDataCommand() + readIssuerData(command, callback) + } + + private fun readIssuerData( + command: ReadIssuerExtraDataCommand, + callback: (result: TaskEvent) -> Unit) { + + if (issuerDataSize != 0) { + delegate?.onDelay( + issuerDataSize, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + ) + } + + sendCommand(command, cardEnvironment) { result -> + when (result) { + + is CompletionResult.Success -> { + if (result.data.size != null) { + if (result.data.size == 0) { + completeNfcSession() + callback(TaskEvent.Event(result.data)) + callback(TaskEvent.Completion()) + return@sendCommand + } + issuerDataSize = result.data.size + } + issuerData.write(result.data.issuerData) + if (result.data.issuerDataSignature == null) { + command.offset = issuerData.size() + readIssuerData(command, callback) + } else { + completeTask(result.data, command, callback) + } + } + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelled) { + completeNfcSession(result.error) + } + callback(TaskEvent.Completion(result.error)) + } + } + } + } + + private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand, + callback: (result: TaskEvent) -> Unit) { + val publicKey = issuerPublicKey ?: card.issuerPublicKey!! + val dataToVerify = IssuerDataToVerify( + cardEnvironment.cardId!!, + issuerData.toByteArray(), + data.issuerDataCounter + ) + if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { + completeNfcSession() + val finalResult = ReadIssuerExtraDataResponse( + data.cardId, + issuerDataSize, + issuerData.toByteArray(), + data.issuerDataSignature, + data.issuerDataCounter + ) + callback(TaskEvent.Event(finalResult)) + callback(TaskEvent.Completion()) + } else { + completeNfcSession(TaskError.VerificationFailed()) + callback(TaskEvent.Completion(TaskError.VerificationFailed())) + } + } +} \ No newline at end of file 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 2f749238f5..2e193ecc5e 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -1,6 +1,5 @@ package com.tangem.tasks -import com.tangem.common.CardEnvironment import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log @@ -8,6 +7,7 @@ import com.tangem.commands.Card import com.tangem.commands.CommandResponse import com.tangem.commands.CommandSerializer import com.tangem.commands.ReadCommand +import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord @@ -51,6 +51,9 @@ sealed class TaskError(val code: Int): Exception() { class TagLost: TaskError(5003) class UnknownError: TaskError(6000) + + //Issuer Data Errors + class MissingCounter: TaskError(7001) } /** @@ -105,7 +108,6 @@ abstract class Task { /** * Should be called on [Task] completion, whether it was successful or with failure. * - * @param withError True when there is an error * @param taskError The error to be shown by [CardManagerDelegate] */ protected fun completeNfcSession(taskError: TaskError? = null) { diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt new file mode 100644 index 0000000000..c8b3bcb083 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt @@ -0,0 +1,67 @@ +package com.tangem.tasks + +import com.tangem.commands.Card +import com.tangem.commands.SettingsMask +import com.tangem.commands.WriteIssuerDataCommand +import com.tangem.commands.WriteIssuerDataResponse +import com.tangem.commands.common.IssuerDataToVerify +import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult + +class WriteIssuerDataTask( + private val issuerData: ByteArray, + private val issuerDataSignature: ByteArray, + private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null +) : Task() { + + private lateinit var card: Card + + override fun onRun( + cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit) { + + card = currentCard!! + val command = WriteIssuerDataCommand( + issuerData, issuerDataSignature, issuerDataCounter + ) + if (!isCounterValid(issuerDataCounter)) { + completeNfcSession(TaskError.MissingCounter()) + callback(TaskEvent.Completion(TaskError.MissingCounter())) + } else if (!verifySignature(command, cardEnvironment.cardId!!)) { + completeNfcSession(TaskError.VerificationFailed()) + callback(TaskEvent.Completion(TaskError.VerificationFailed())) + } + + sendCommand(command, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + completeNfcSession() + callback(TaskEvent.Event(result.data)) + callback(TaskEvent.Completion()) + } + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelled) { + completeNfcSession(result.error) + } + callback(TaskEvent.Completion(result.error)) + } + } + } + } + + private fun isCounterValid(issuerDataCounter: Int?): Boolean = + if (isCounterRequired()) issuerDataCounter != null else true + + private fun isCounterRequired(): Boolean = + card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + + private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean { + return command.verify( + issuerPublicKey ?: card.issuerPublicKey!!, + issuerDataSignature, + IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + ) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt new file mode 100644 index 0000000000..f56ac1c5d5 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt @@ -0,0 +1,113 @@ +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.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify +import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult + +/** + * This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully + * written on the card. + * @param issuerData Data provided by issuer. + * @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and + * Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData]. + * @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId], + * [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay + * andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]). + * @param issuerDataCounter An optional counter that protect issuer data against replay attack. + */ +internal class WriteIssuerExtraDataTask( + private val issuerData: ByteArray, + private val startingSignature: ByteArray, + private val finalizingSignature: ByteArray, + private val issuerPublicKey: ByteArray? = null, + private val issuerDataCounter: Int? = null +) : Task() { + + private lateinit var card: Card + private lateinit var cardEnvironment: CardEnvironment + + override fun onRun(cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit) { + + card = currentCard!! + this.cardEnvironment = cardEnvironment + val command = WriteIssuerExtraDataCommand( + issuerData, startingSignature, finalizingSignature, issuerDataCounter + ) + if (!isCounterValid(issuerDataCounter)) { + completeNfcSession(TaskError.MissingCounter()) + callback(TaskEvent.Completion(TaskError.MissingCounter())) + } else if (!verifySignatures(command)) { + completeNfcSession(TaskError.VerificationFailed()) + callback(TaskEvent.Completion(TaskError.VerificationFailed())) + } + + writeIssuerData(command, callback) + } + + private fun writeIssuerData( + command: WriteIssuerExtraDataCommand, + callback: (result: TaskEvent) -> Unit) { + + if (command.mode == IssuerDataMode.WriteExtraData) { + delegate?.onDelay(issuerData.size, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) + } + sendCommand(command, cardEnvironment) { result -> + when (result) { + + is CompletionResult.Success -> { + when (command.mode) { + IssuerDataMode.InitializeWritingExtraData -> { + command.mode = IssuerDataMode.WriteExtraData + writeIssuerData(command, callback) + return@sendCommand + } + IssuerDataMode.WriteExtraData -> { + command.offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + if (command.offset >= issuerData.size) { + command.mode = IssuerDataMode.FinalizeExtraData + } + writeIssuerData(command, callback) + return@sendCommand + } + IssuerDataMode.FinalizeExtraData -> { + completeNfcSession() + callback(TaskEvent.Event(result.data)) + callback(TaskEvent.Completion()) + } + } + } + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelled) { + completeNfcSession(result.error) + } + callback(TaskEvent.Completion(result.error)) + } + } + } + } + + private fun isCounterValid(issuerDataCounter: Int?): Boolean = + if (isCounterRequired()) issuerDataCounter != null else true + + private fun isCounterRequired(): Boolean = + card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + + private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean { + val publicKey = issuerPublicKey ?: card.issuerPublicKey!! + val cardId = cardEnvironment.cardId!! + + val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) + val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + + return command.verify(publicKey, startingSignature, firstData) && + command.verify(publicKey, finalizingSignature, secondData) + } +} \ No newline at end of file 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 739ca2c95a..967c62c688 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -3,6 +3,11 @@ package com.tangem.tangemtest import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toByteArray +import com.tangem.common.extensions.toHexString +import com.tangem.crypto.CryptoUtils +import com.tangem.crypto.sign import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError @@ -15,6 +20,7 @@ class MainActivity : AppCompatActivity() { private lateinit var cardId: String private lateinit var issuerData: ByteArray private lateinit var issuerDataSignature: ByteArray + private var issuerDataCounter: Int = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,6 +47,8 @@ class MainActivity : AppCompatActivity() { tv_card_cid?.text = cardId btn_sign.isEnabled = true btn_read_issuer_data.isEnabled = true + btn_read_issuer_extra_data.isEnabled = true + btn_write_issuer_data.isEnabled = true btn_purge_wallet.isEnabled = true btn_create_wallet.isEnabled = true } @@ -101,6 +109,20 @@ class MainActivity : AppCompatActivity() { } } } + btn_read_issuer_extra_data?.setOnClickListener { _ -> + cardManager.readIssuerExtraData(cardId) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { + issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1 + btn_write_issuer_data.isEnabled = true + tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data." + } + } + } + } btn_purge_wallet?.setOnClickListener { _ -> cardManager.purgeWallet( cardId) { diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml index 5759633c21..f11cbdbd35 100644 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ b/tangem-demo/src/main/res/layout/activity_main.xml @@ -33,7 +33,7 @@