Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-13 07:38:49 +00:00
commit 4b7cd3e99f
3 changed files with 112 additions and 48 deletions

View file

@ -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 cards 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)
)
}
}

View file

@ -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")

View file

@ -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