Updated on 2026-08-14
This commit is contained in:
commit
4b7cd3e99f
3 changed files with 112 additions and 48 deletions
|
|
@ -8,10 +8,12 @@ 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.*
|
||||
|
||||
/**
|
||||
* Determines which type of data is required for signing.
|
||||
*/
|
||||
enum class SigningMethod(val code: Int) {
|
||||
SignHash(0),
|
||||
SignRaw(1),
|
||||
|
|
@ -22,19 +24,27 @@ enum class SigningMethod(val code: Int) {
|
|||
SignPos(6);
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): SigningMethod? = values().find { it.code == code }
|
||||
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 {
|
||||
fun byName(curve: String): EllipticCurve? = values().find { it.curve == curve }
|
||||
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),
|
||||
|
|
@ -42,7 +52,8 @@ enum class CardStatus(val code: Int) {
|
|||
Purged(3);
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): CardStatus? = values().find { it.code == code }
|
||||
private val values = values()
|
||||
fun byCode(code: Int): CardStatus? = values.find { it.code == code }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,10 +63,67 @@ enum class ProductMask(val code: Byte) {
|
|||
Card(0x04);
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Byte): ProductMask? = values().find { it.code == code }
|
||||
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,
|
||||
|
|
@ -78,35 +146,30 @@ class Card(
|
|||
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?
|
||||
val cardData: CardData?
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
class ReadCardCommand : CommandSerializer<Card>() {
|
||||
|
||||
override val instruction = Instruction.Read
|
||||
override val instructionCode = instruction.code
|
||||
/**
|
||||
* 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<Card>() {
|
||||
|
||||
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 {
|
||||
Tlv(TlvTag.TerminalPublicKey, it.publicKey)
|
||||
cardEnvironment.terminalKeys?.let { terminalKeys ->
|
||||
Tlv(TlvTag.TerminalPublicKey, terminalKeys.publicKey)
|
||||
}
|
||||
|
||||
return CommandApdu(instructionCode, tlvData)
|
||||
return CommandApdu(Instruction.Read, tlvData)
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
|
||||
|
|
@ -138,21 +201,31 @@ class ReadCardCommand : CommandSerializer<Card>() {
|
|||
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)
|
||||
cardData = deserializeCardData(tlvData)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserializeCardData(tlvData: List<Tlv>): 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,22 +12,13 @@ class ResponseApdu(val data: ByteArray) {
|
|||
val statusWord: StatusWord = StatusWord.byCode(sw)
|
||||
|
||||
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
|
||||
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<Tlv>?): List<Tlv>? =
|
||||
tlvs?.flatMap {
|
||||
if (it.tag.hasNestedTlv()) {
|
||||
Tlv.fromBytes(it.value)
|
||||
} else {
|
||||
listOf(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decrypt(encryptionKey: ByteArray) {
|
||||
TODO("not implemented")
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class Tlv {
|
|||
}
|
||||
|
||||
|
||||
fun fromBytes(mData: ByteArray): List<Tlv> {
|
||||
fun tlvListFromBytes(mData: ByteArray): List<Tlv> {
|
||||
val tlvList = mutableListOf<Tlv>()
|
||||
val stream = ByteArrayInputStream(mData)
|
||||
var tlv: Tlv? = null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue