Updated on 2026-08-14
This commit is contained in:
commit
45b640dcec
7 changed files with 323 additions and 0 deletions
75
tangem-card-new/src/main/java/com/tangem/data/CommandApdu.kt
Normal file
75
tangem-card-new/src/main/java/com/tangem/data/CommandApdu.kt
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.tangem.data
|
||||||
|
|
||||||
|
import com.tangem.enums.Instruction
|
||||||
|
|
||||||
|
class CommandApdu(
|
||||||
|
val tlvList: List<Tlv>,
|
||||||
|
val instruction: Instruction,
|
||||||
|
val p1: Int = 0x00,
|
||||||
|
val p2: Int = 0x00,
|
||||||
|
val encryptionKey: ByteArray? = null,
|
||||||
|
val rawInstruction: ByteArray = byteArrayOf()) {
|
||||||
|
|
||||||
|
val cla = ISO_CLA
|
||||||
|
|
||||||
|
|
||||||
|
fun serialize(): ByteArray {
|
||||||
|
|
||||||
|
var length = 4 // CLA, INS, P1, P2
|
||||||
|
|
||||||
|
val data = if (tlvList.isNotEmpty()) {
|
||||||
|
tlvList.toBytes()
|
||||||
|
} else {
|
||||||
|
byteArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
val lc = data.size
|
||||||
|
|
||||||
|
if (data.isNotEmpty()) {
|
||||||
|
length += 1 // LC
|
||||||
|
if (lc >= 256)
|
||||||
|
length += 2
|
||||||
|
length += data.size // DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
val apdu = ByteArray(length)
|
||||||
|
|
||||||
|
var index = 0
|
||||||
|
apdu[index] = cla
|
||||||
|
index++
|
||||||
|
apdu[index] = instruction.code.toByte()
|
||||||
|
index++
|
||||||
|
apdu[index] = p1.toByte()
|
||||||
|
index++
|
||||||
|
apdu[index] = p2.toByte()
|
||||||
|
index++
|
||||||
|
if (lc != 0) {
|
||||||
|
if (lc < 256) {
|
||||||
|
apdu[index] = lc.toByte()
|
||||||
|
index++
|
||||||
|
} else {
|
||||||
|
apdu[index] = 0
|
||||||
|
index++
|
||||||
|
apdu[index] = (lc shr 8).toByte()
|
||||||
|
index++
|
||||||
|
apdu[index] = (lc and 0xFF).toByte()
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(data, 0, apdu, index, data.size)
|
||||||
|
index += data.size
|
||||||
|
}
|
||||||
|
return apdu
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun encrypt() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ISO_CLA = 0x00.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.tangem.data
|
||||||
|
|
||||||
|
import com.tangem.enums.Status
|
||||||
|
|
||||||
|
class ResponseApdu(val data: ByteArray) {
|
||||||
|
|
||||||
|
private val tlvParser = TlvParser()
|
||||||
|
|
||||||
|
fun deserialize(encryptionKey: ByteArray? = null): ResponseApduParsed? {
|
||||||
|
|
||||||
|
if (data.size < 2) return null
|
||||||
|
|
||||||
|
if (data.size == 2) return ResponseApduParsed(parseStatus(data[0], data[1]), emptyList())
|
||||||
|
|
||||||
|
val tlvList = tlvParser.fromBytes(data.copyOf(data.size - 2))
|
||||||
|
val sw1 = data[data.size - 2]
|
||||||
|
val sw2 = data[data.size -1]
|
||||||
|
return ResponseApduParsed(parseStatus(sw1, sw2), tlvList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStatus(sw1: Byte, sw2: Byte): Status {
|
||||||
|
val code = (0x00FF and sw1.toInt()) shl 8 or (0x00FF and sw2.toInt())
|
||||||
|
return Status.byCode(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decrypt(encryptionKey: ByteArray) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ResponseApduParsed(val status: Status, val tlvList: List<Tlv>? = null, val parsingError: String? = null) {
|
||||||
|
fun statusCompleted(): Boolean = status == Status.ProcessCompleted
|
||||||
|
}
|
||||||
31
tangem-card-new/src/main/java/com/tangem/data/Tlv.kt
Normal file
31
tangem-card-new/src/main/java/com/tangem/data/Tlv.kt
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.tangem.data
|
||||||
|
|
||||||
|
import com.tangem.enums.TlvTag
|
||||||
|
|
||||||
|
class Tlv(val tag: TlvTag, val tagCode: Int, val value: ByteArray = byteArrayOf())
|
||||||
|
|
||||||
|
fun List<Tlv>.toBytes(): ByteArray =
|
||||||
|
this.map { it.toBytes() }.reduce { arr1, arr2 -> arr1 + arr2 }
|
||||||
|
|
||||||
|
fun Tlv.toBytes(): ByteArray {
|
||||||
|
val tag = byteArrayOf(this.tag.code.toByte())
|
||||||
|
val length = getLengthInBytes(this.value.size)
|
||||||
|
val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00)
|
||||||
|
return tag + length + value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLengthInBytes(tlvLength: Int): ByteArray {
|
||||||
|
return if (tlvLength < 0) {
|
||||||
|
if (tlvLength > 0xFE) {
|
||||||
|
byteArrayOf(
|
||||||
|
0xFF.toByte(),
|
||||||
|
(tlvLength shr 8 and 0xFF).toByte(),
|
||||||
|
(tlvLength and 0xFF).toByte()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
byteArrayOf((tlvLength and 0xFF).toByte())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byteArrayOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
55
tangem-card-new/src/main/java/com/tangem/data/TlvParser.kt
Normal file
55
tangem-card-new/src/main/java/com/tangem/data/TlvParser.kt
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.data
|
||||||
|
|
||||||
|
import com.tangem.enums.TlvTag
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class TlvParser {
|
||||||
|
|
||||||
|
fun readFromStream(stream: ByteArrayInputStream): Tlv? {
|
||||||
|
val code = stream.read()
|
||||||
|
if (code == -1) return null
|
||||||
|
var len = stream.read()
|
||||||
|
if (len == -1)
|
||||||
|
throw IOException("Can't read TLV")
|
||||||
|
if (len == 0xFF) {
|
||||||
|
val lenH = stream.read()
|
||||||
|
if (lenH == -1)
|
||||||
|
throw IOException("Can't read TLV")
|
||||||
|
len = stream.read()
|
||||||
|
if (len == -1)
|
||||||
|
throw IOException("Can't read TLV")
|
||||||
|
len = len or (lenH shl 8)
|
||||||
|
}
|
||||||
|
val value = ByteArray(len)
|
||||||
|
if (len > 0) {
|
||||||
|
if (len != stream.read(value)) {
|
||||||
|
throw IOException("Can't read TLV")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val tag = TlvTag.byCode(code)
|
||||||
|
return Tlv(tag, code, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromBytes(data: ByteArray): List<Tlv> {
|
||||||
|
val tlvList = mutableListOf<Tlv>()
|
||||||
|
val stream = ByteArrayInputStream(data)
|
||||||
|
var tlv: Tlv? = null
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
tlv = readFromStream(stream)
|
||||||
|
if (tlv != null) tlvList.add(tlv)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw IOException("TLVError: " + e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (tlv != null)
|
||||||
|
return tlvList
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun parseTlv(data: ByteArray) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.enums
|
||||||
|
|
||||||
|
enum class Instruction(var code: Int) {
|
||||||
|
Unknown(0x00),
|
||||||
|
Read(0xF2),
|
||||||
|
VerifyCard(0xF3),
|
||||||
|
ValidateCard(0xF4),
|
||||||
|
VerifyCode(0xF5),
|
||||||
|
WriteIssuerData(0xF6),
|
||||||
|
GetIssuerData(0xF7),
|
||||||
|
CreateWallet(0xF8),
|
||||||
|
CheckWallet(0xF9),
|
||||||
|
SwapPIN(0xFA),
|
||||||
|
Sign(0xFB),
|
||||||
|
PurgeWallet(0xFC),
|
||||||
|
Activate(0xFE),
|
||||||
|
OpenSession(0xFF);
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun byCode(code: Int): Instruction = values().find { it.code == code } ?: Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
21
tangem-card-new/src/main/java/com/tangem/enums/Status.kt
Normal file
21
tangem-card-new/src/main/java/com/tangem/enums/Status.kt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.enums
|
||||||
|
|
||||||
|
enum class Status (val code: Int, val description: String){
|
||||||
|
|
||||||
|
ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"),
|
||||||
|
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
|
||||||
|
ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"),
|
||||||
|
InvalidState(0x6985, "SW_INVALID_STATE"),
|
||||||
|
// PinsNotChanged(ProcessCompleted.code, ProcessCompleted.description),
|
||||||
|
Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"),
|
||||||
|
Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"),
|
||||||
|
PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"),
|
||||||
|
InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"),
|
||||||
|
NeedEnctryption(0x6982, "SW_NEED_ENCRYPTION"),
|
||||||
|
NeedPause(0x9789, "SW_NEED_PAUSE");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun byCode(code: Int): Status = values().find { it.code == code } ?: InvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
tangem-card-new/src/main/java/com/tangem/enums/TlvTag.kt
Normal file
84
tangem-card-new/src/main/java/com/tangem/enums/TlvTag.kt
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.tangem.enums
|
||||||
|
|
||||||
|
enum class TlvTag(val code: Int) {
|
||||||
|
Unknown(0x00),
|
||||||
|
CardID(0x01),
|
||||||
|
Status(0x02),
|
||||||
|
CardPublicKey(0x03),
|
||||||
|
CardSignature(0x04),
|
||||||
|
CurveID(0x05),
|
||||||
|
HashAlgID(0x06),
|
||||||
|
SigningMethod(0x07),
|
||||||
|
MaxSignatures(0x08),
|
||||||
|
PauseBeforePin2(0x09),
|
||||||
|
SettingsMask(0x0A),
|
||||||
|
CardData(0x0C),
|
||||||
|
NdefData(0x0D),
|
||||||
|
Health(0x0F),
|
||||||
|
|
||||||
|
Pin(0x10),
|
||||||
|
Pin2(0x11),
|
||||||
|
NewPin(0x12),
|
||||||
|
NewPin2(0x13),
|
||||||
|
NewPinHash(0x14),
|
||||||
|
NewPin2Hash(0x15),
|
||||||
|
Challenge(0x16),
|
||||||
|
Salt(0x17),
|
||||||
|
ValidationCounter(0x18),
|
||||||
|
Cvc(0x19),
|
||||||
|
|
||||||
|
SessionKeyA(0x1A),
|
||||||
|
SessionKeyB(0x1B),
|
||||||
|
Pause(0x1C),
|
||||||
|
|
||||||
|
ManufactureId(0x20),
|
||||||
|
ManufacturerSignature(0x21),
|
||||||
|
|
||||||
|
IssuerDataPublicKey(0x30),
|
||||||
|
IssuerTransactionPublicKey(0x31),
|
||||||
|
IssuerData(0x32),
|
||||||
|
IssuerDataSignature(0x33),
|
||||||
|
IssuerTransactionSignature(0x34),
|
||||||
|
IssuerDataCounter(0x35),
|
||||||
|
|
||||||
|
IsActivated(0x3A),
|
||||||
|
ActivationSeed(0x3B),
|
||||||
|
ResetPin(0x36),
|
||||||
|
|
||||||
|
CodePageAddress(0x40),
|
||||||
|
CodePageCount(0x41),
|
||||||
|
CodeHash(0x42),
|
||||||
|
|
||||||
|
TrOutHash(0x50),
|
||||||
|
TrOutHashSize(0x51),
|
||||||
|
TrOutRaw(0x52),
|
||||||
|
|
||||||
|
WalletPublicKey(0x60),
|
||||||
|
Signature(0x61),
|
||||||
|
RemainingSignatures(0x62),
|
||||||
|
SignedHashes(0x63),
|
||||||
|
|
||||||
|
Firmware(0x80),
|
||||||
|
Batch(0x81),
|
||||||
|
ManufactureDateTime(0x82),
|
||||||
|
IssuerId(0x83),
|
||||||
|
BlockchainId(0x84),
|
||||||
|
ManufacturerPublicKey(0x85),
|
||||||
|
CardIdManufacturerSignature(0x86),
|
||||||
|
|
||||||
|
TokenSymbol(0xA0),
|
||||||
|
TokenContractAddress(0xA1),
|
||||||
|
TokenDecimal(0xA2),
|
||||||
|
Denomination(0xC0),
|
||||||
|
ValidatedBalance(0xC1),
|
||||||
|
LastSignDate(0xC2),
|
||||||
|
DenominationText(0xC3),
|
||||||
|
|
||||||
|
TerminalIsLinked(0x58),
|
||||||
|
TerminalPublicKey(0x5C),
|
||||||
|
TerminalTransactionSignature(0x57);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun byCode(code: Int): TlvTag = values().find { it.code == code } ?: Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue