From 0d5946908502123247c7856881a409bdebf8a90d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Nov 2019 10:31:39 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../java/com/tangem/commands/ReadCommand.kt | 231 ++++++++++++++++++ .../com/tangem/common/apdu/ResponseApdu.kt | 13 +- .../main/java/com/tangem/common/tlv/Tlv.kt | 2 +- 3 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt new file mode 100644 index 0000000000..f4eb833de6 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -0,0 +1,231 @@ +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.extentions.calculateSha256 +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError +import java.util.* + +/** + * Determines which type of data is required for signing. + */ +enum class SigningMethod(val code: Int) { + SignHash(0), + SignRaw(1), + SignHashValidatedByIssuer(2), + SignRawValidatedByIssuer(3), + SignHashValidatedByIssuerAndWriteIssuerData(4), + SignRawValidatedByIssuerAndWriteIssuerData(5), + SignPos(6); + + companion object { + private val values = values() + fun byCode(code: Int): SigningMethod? = values.find { it.code == code } + } +} + +/** + * Elliptic curve used for wallet key operations. + */ +enum class EllipticCurve(val curve: String) { + Secp256k1("secp256k1"), + Ed25519("ed25519"); + + companion object { + private val values = values() + fun byName(curve: String): EllipticCurve? = values.find { it.curve == curve } + } +} + +/** + * Status of the card and its wallet. + */ +enum class CardStatus(val code: Int) { + NotPersonalized(0), + Empty(1), + Loaded(2), + Purged(3); + + companion object { + private val values = values() + fun byCode(code: Int): CardStatus? = values.find { it.code == code } + } +} + +enum class ProductMask(val code: Byte) { + Note(0x01), + Tag(0x02), + Card(0x04); + + companion object { + private val values = values() + fun byCode(code: Byte): ProductMask? = values.find { it.code == code } + } +} + +/** + * Stores and maps Tangem card settings. + * + * @property rawValue are card settings in a form of flags, + * while flags definitions and values are in [SettingsMask.Companion] as constants. + */ +data class SettingsMask(val rawValue: Int) { + + companion object { + const val isReusable = 0x0001 + const val useActivation = 0x0002 + const val forbidPurgeWallet = 0x0004 + const val useBlock = 0x0008 + + const val allowSwapPIN = 0x0010 + const val allowSwapPIN2 = 0x0020 + const val useCVC = 0x0040 + const val forbidDefaultPIN = 0x0080 + + const val useOneCommandAtTime = 0x0100 + const val useNdef = 0x0200 + const val useDynamicNdef = 0x0400 + const val smartSecurityDelay = 0x0800 + + const val protocolAllowUnencrypted = 0x1000 + const val protocolAllowStaticEncryption = 0x2000 + + const val protectIssuerDataAgainstReplay = 0x4000 + + const val allowSelectBlockchain = 0x8000 + + const val disablePrecomputedNdef = 0x00010000 + + const val skipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000 + } +} + +/** + * Detailed information about card contents. + */ +class CardData( + val batchId: String?, + val manufactureDateTime: Date?, + val issuerName: String?, + val blockchainName: String?, + val manufacturerSignature: ByteArray?, + val productMask: ProductMask?, + + val tokenSymbol: String?, + val tokenContractAddress: String?, + val tokenDecimal: Int? +) + +/** + * Response for [ReadCommand]. Contains detailed card information. + */ +class Card( + val cardId: String, + val manufacturerName: String, + val status: CardStatus, + + val firmwareVersion: String?, + val cardPublicKey: ByteArray?, + val settingsMask: SettingsMask?, + val issuerPublicKey: ByteArray?, + val curve: EllipticCurve?, + val maxSignatures: Int?, + val signingMethod: SigningMethod?, + val pauseBeforePin2: Int?, + val walletPublicKey: ByteArray?, + val walletRemainingSignatures: Int?, + val walletSignedHashes: Int?, + val health: Int?, + val isActivated: Boolean, + val activationSeed: ByteArray?, + val paymentFlowVersion: ByteArray?, + val userCounter: Int?, + val terminalIsLinked: Boolean, + val cardData: CardData? +) : CommandResponse + +/** + * This command receives from the Tangem Card all the data about the card and the wallet, + * including unique card number (CID or cardId) that has to be submitted while calling all other commands. + * + */ +class ReadCommand : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + /** + * [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain + * default value of ‘000000’. + * In order to obtain card’s data, [ReadCommand] should use the correct pin 1 value. + * The card will not respond if wrong pin 1 has been submitted. + */ + val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256())) + + cardEnvironment.terminalKeys?.let { terminalKeys -> + Tlv(TlvTag.TerminalPublicKey, terminalKeys.publicKey) + } + + return CommandApdu(Instruction.Read, tlvData) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val tlvMapper = TlvMapper(tlvData) + + Card( + cardId = tlvMapper.map(TlvTag.CardId), + manufacturerName = tlvMapper.map(TlvTag.ManufactureId), + status = tlvMapper.map(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.tlvListFromBytes(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) + ) + } +} \ No newline at end of file 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 8d65b0422f..9eecdc4448 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 @@ -12,22 +12,13 @@ class ResponseApdu(val data: ByteArray) { val statusWord: StatusWord = StatusWord.byCode(sw) fun getTlvData(encryptionKey: ByteArray? = null): List? { - val tlvs = when { + return when { data.size < 2 -> null data.size == 2 -> emptyList() - else -> Tlv.fromBytes(data.copyOf(data.size - 2)) + else -> Tlv.tlvListFromBytes(data.copyOf(data.size - 2)) } - return flattenNestedTlvs(tlvs) } - private fun flattenNestedTlvs(tlvs: List?): List? = - tlvs?.flatMap { - if (it.tag.hasNestedTlv()) { - Tlv.fromBytes(it.value) - } else { - listOf(it) - } - } private fun decrypt(encryptionKey: ByteArray) { TODO("not implemented") diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt index 2f58f5d3e8..a22dad60c1 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt @@ -49,7 +49,7 @@ class Tlv { } - fun fromBytes(mData: ByteArray): List { + fun tlvListFromBytes(mData: ByteArray): List { val tlvList = mutableListOf() val stream = ByteArrayInputStream(mData) var tlv: Tlv? = null From cfe8686ad677bbec8b8f90c0dda3a769bfbaf198 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 13 Nov 2019 10:34:00 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../com/tangem/commands/ReadCardCommand.kt | 158 ------------------ 1 file changed, 158 deletions(-) delete mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadCardCommand.kt diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCardCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCardCommand.kt deleted file mode 100644 index e7309a33f1..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCardCommand.kt +++ /dev/null @@ -1,158 +0,0 @@ -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.extentions.calculateSha256 -import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.TlvMapper -import com.tangem.common.tlv.TlvTag -import com.tangem.data.SettingsMask -import com.tangem.tasks.TaskError -import java.util.* - -enum class SigningMethod(val code: Int) { - SignHash(0), - SignRaw(1), - SignHashValidatedByIssuer(2), - SignRawValidatedByIssuer(3), - SignHashValidatedByIssuerAndWriteIssuerData(4), - SignRawValidatedByIssuerAndWriteIssuerData(5), - SignPos(6); - - companion object { - fun byCode(code: Int): SigningMethod? = values().find { it.code == code } - } -} - -enum class EllipticCurve(val curve: String) { - Secp256k1("secp256k1"), - Ed25519("ed25519"); - - companion object { - fun byName(curve: String): EllipticCurve? = values().find { it.curve == curve } - } -} - -enum class CardStatus(val code: Int) { - NotPersonalized(0), - Empty(1), - Loaded(2), - Purged(3); - - companion object { - fun byCode(code: Int): CardStatus? = values().find { it.code == code } - } -} - -enum class ProductMask(val code: Byte) { - Note(0x01), - Tag(0x02), - Card(0x04); - - companion object { - fun byCode(code: Byte): ProductMask? = values().find { it.code == code } - } -} - -class Card( - val cardId: String, - val manufacturerName: String, - val status: CardStatus, - - val firmwareVersion: String?, - val cardPublicKey: ByteArray?, - val settingsMask: SettingsMask?, - val issuerPublicKey: ByteArray?, - val curve: EllipticCurve?, - val maxSignatures: Int?, - val signingMethod: SigningMethod?, - val pauseBeforePin2: Int?, - val walletPublicKey: ByteArray?, - val walletRemainingSignatures: Int?, - val walletSignedHashes: Int?, - val health: Int?, - val isActivated: Boolean, - val activationSeed: ByteArray?, - val paymentFlowVersion: ByteArray?, - val userCounter: Int?, - val terminalIsLinked: Boolean, - - //Card Data - val batchId: String?, - val manufactureDateTime: Date?, - val issuerName: String?, - val blockchainName: String?, - val manufacturerSignature: ByteArray?, - val productMask: ProductMask?, - - val tokenSymbol: String?, - val tokenContractAddress: String?, - val tokenDecimal: Int? -) : CommandResponse - - -class ReadCardCommand : CommandSerializer() { - - override val instruction = Instruction.Read - override val instructionCode = instruction.code - - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - - val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256())) - - cardEnvironment.terminalKeys?.let { - Tlv(TlvTag.TerminalPublicKey, it.publicKey) - } - - return CommandApdu(instructionCode, tlvData) - } - - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData() ?: return null - - return try { - val tlvMapper = TlvMapper(tlvData) - - Card( - cardId = tlvMapper.map(TlvTag.CardId), - manufacturerName = tlvMapper.map(TlvTag.ManufactureId), - status = tlvMapper.map(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), - - 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) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } - } - - -} \ No newline at end of file