From 8d716f97ede1782ae728fab4e626cfc73d99791f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 25 Nov 2019 13:53:30 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 27 ++++++- .../tangem/commands/ReadIssuerDataCommand.kt | 78 +++++++++++++++++++ .../tangem/commands/WriteIssuerDataCommand.kt | 61 +++++++++++++++ .../com/tangem/common/apdu/Instruction.kt | 2 +- .../com/tangem/common/extensions/ByteArray.kt | 1 + .../tangem/common/extensions/IntExtensions.kt | 10 +++ .../common/extensions/IntExtensionsTest.kt | 24 ++++++ .../java/com/tangem/common/tlv/TlvTest.kt | 35 +-------- .../com/tangem/tangemtest/MainActivity.kt | 35 ++++++++- .../src/main/res/layout/activity_main.xml | 22 ++++++ .../src/main/res/layout/nfc_bottom_sheet.xml | 2 +- 11 files changed, 256 insertions(+), 41 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt create mode 100644 tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index f8ac316151..241741724e 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -1,9 +1,6 @@ package com.tangem -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.commands.SignCommand -import com.tangem.commands.SignResponse +import com.tangem.commands.* import com.tangem.crypto.CryptoUtils import com.tangem.tasks.* import java.util.concurrent.Executors @@ -81,6 +78,28 @@ class CardManager( runTask(task, cardId, callback) } + fun readIssuerData(cardId: String, + callback: (result: TaskEvent) -> Unit) { + val getIssuerDataCommand = ReadIssuerDataCommand(cardId) + val task = SingleCommandTask(getIssuerDataCommand) + runTask(task, cardId, callback) + } + + + fun writeIssuerData(cardId: String, + issuerData: ByteArray, + issuerDataSignature: ByteArray, + issuerDataCounter: Int? = null, + callback: (result: TaskEvent) -> Unit) { + val writeIssuerDataCommand = WriteIssuerDataCommand( + cardId, + issuerData, + issuerDataSignature, + issuerDataCounter) + val task = SingleCommandTask(writeIssuerDataCommand) + runTask(task, cardId, callback) + } + /** */ diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt new file mode 100644 index 0000000000..a3324fdb8b --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -0,0 +1,78 @@ +package com.tangem.commands + +import com.tangem.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.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + +class ReadIssuerDataResponse( + + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + + /** + * 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 returns 512-byte Issuer Data field and its issuer’s signature. + * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. For example, this field may contain information about + * wallet balance signed by the issuer or additional issuer’s attestation data. + */ +class ReadIssuerDataCommand( + private val cardId: String +) : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvData = listOf( + Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), + Tlv(TlvTag.CardId, cardId.hexToBytes()) + ) + return CommandApdu(Instruction.ReadIssuerData, tlvData) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + ReadIssuerDataResponse( + cardId = mapper.map(TlvTag.CardId), + issuerData = mapper.map(TlvTag.IssuerData), + issuerDataSignature = mapper.map(TlvTag.IssuerDataSignature), + issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ 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 new file mode 100644 index 0000000000..b0d6a0bb02 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -0,0 +1,61 @@ +package com.tangem.commands + +import com.tangem.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.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + +class WriteIssuerDataResponse( + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String +) : CommandResponse + +/** + * This command writes 512-byte Issuer Data field and its issuer’s signature. + * Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, + * format and payload of Issuer Data. For example, this field may contain information about + * wallet balance signed by the issuer or additional issuer’s attestation data. + */ +class WriteIssuerDataCommand( + private val cardId: String, + private val issuerData: ByteArray, + private val issuerDataSignature: ByteArray, + private val issuerDataCounter: Int? = null +) : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvData = mutableListOf( + Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()), + Tlv(TlvTag.CardId, cardId.hexToBytes()), + Tlv(TlvTag.IssuerData, issuerData), + Tlv(TlvTag.IssuerDataSignature, issuerDataSignature) + ) + if (issuerDataCounter != null) { + tlvData.add(Tlv(TlvTag.IssuerDataCounter, issuerDataCounter.toByteArray())) + } + + return CommandApdu(Instruction.WriteIssuerData, tlvData) + } + + 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() + } + } +} \ 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 904c5437bd..f0743aa0dc 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 @@ -11,7 +11,7 @@ enum class Instruction(var code: Int) { ValidateCard(0xF4), VerifyCode(0xF5), WriteIssuerData(0xF6), - GetIssuerData(0xF7), + ReadIssuerData(0xF7), CreateWallet(0xF8), CheckWallet(0xF9), SwapPIN(0xFA), 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 7b611ad93b..5c65596c9d 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 @@ -33,3 +33,4 @@ fun ByteArray.toDate(): Date { fun ByteArray.calculateSha512(): ByteArray = MessageDigest.getInstance("SHA-512").digest(this) +fun ByteArray.calculateSha256(): ByteArray = MessageDigest.getInstance("SHA-256").digest(this) 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 new file mode 100644 index 0000000000..5565803830 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt @@ -0,0 +1,10 @@ +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() +} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt new file mode 100644 index 0000000000..710f817e28 --- /dev/null +++ b/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt @@ -0,0 +1,24 @@ +package com.tangem.common.extensions + +import com.google.common.truth.Truth.assertThat +import org.junit.jupiter.api.Test + + +class IntExtensionsTest { + + @Test + fun `small int toByteArray`() { + val int = 13 + val expected = byteArrayOf(0, 0, 0, 13) + assertThat(int.toByteArray()) + .isEqualTo(expected) + } + + @Test + fun `int toByteArray`() { + val int = 999 + val expected = byteArrayOf(0, 0, 3, -25) + assertThat(int.toByteArray()) + .isEqualTo(expected) + } +} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt index 11f7079ffe..4dcdecf66a 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt @@ -3,14 +3,8 @@ package com.tangem.common.tlv import com.google.common.truth.Truth.assertThat import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toHexString -import com.tangem.crypto.CryptoUtils import org.junit.Test -import org.junit.jupiter.api.Assertions.* -import javax.crypto.Cipher -import javax.crypto.SecretKey -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec + class TlvTest { @@ -106,31 +100,4 @@ class TlvTest { assertThat(tlvs1) .isNull() } - -// @Test - fun `sldfj dslkf`() { - val privateKey = "C9A8827B83D554C2ED6C92A958A75691221A951775C36EBA858C039564ECDC3E" - val password = "theXlPiP42".calculateSha256() - - val originalKey: SecretKey = SecretKeySpec(password, 0, password.size, "AES") - - CryptoUtils.initCrypto() - - fun decrypt(yourKey: SecretKey): ByteArray { - val decrypted: ByteArray - val cipher = Cipher.getInstance("AES", "SC") - cipher.init(Cipher.DECRYPT_MODE, yourKey, IvParameterSpec(ByteArray(cipher.blockSize))) - decrypted = cipher.doFinal(privateKey.hexToBytes()) - return decrypted - } - - - val publicKey = CryptoUtils.generatePublicKey( - decrypt(originalKey) - ) - - assertThat(publicKey.toHexString()) - .isEqualTo("041D020A29983E2BC36E6CEAD9D648A71AA54AD328F00D6336DD699BCEBD99B5F4DAAAAC5E8E6D72D0A94EA86CF0A41D367FE05DE4A1BD84C86D51CCA4AF357182") - } - } \ 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 f7c5dd6fe9..018ca60ce5 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -18,6 +18,8 @@ class MainActivity : AppCompatActivity() { private val cardManager = CardManager(nfcManager.reader, cardManagerDelegate) private lateinit var cardId: String + private lateinit var issuerData: ByteArray + private lateinit var issuerDataSignature: ByteArray override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -42,6 +44,7 @@ class MainActivity : AppCompatActivity() { runOnUiThread { tv_card_cid?.text = cardId btn_sign.isEnabled = true + btn_read_issuer_data.isEnabled = true } } } @@ -66,7 +69,37 @@ class MainActivity : AppCompatActivity() { is TaskEvent.Completion -> { if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + " used to sign sample hashes." } + is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } + } + } + } + btn_read_issuer_data?.setOnClickListener { _ -> + cardManager.readIssuerData(cardId) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { + btn_write_issuer_data.isEnabled = true + tv_card_cid?.text = it.data.issuerData.contentToString() + issuerData = it.data.issuerData + issuerDataSignature = it.data.issuerDataSignature + } + } + } + } + btn_write_issuer_data?.setOnClickListener { _ -> + cardManager.writeIssuerData( + cardId, + issuerData, + issuerDataSignature) { + 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 + } } } } diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml index ce8879e576..a1cecca79b 100644 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ b/tangem-demo/src/main/res/layout/activity_main.xml @@ -12,6 +12,7 @@ android:id="@+id/tv_card_cid" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:padding="16dp" android:layout_marginBottom="80dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" @@ -40,6 +41,27 @@ app:layout_constraintTop_toBottomOf="@id/btn_scan" android:enabled="false"/> +