Updated on 2026-08-14

This commit is contained in:
Tangem 2019-10-18 07:29:29 +00:00
commit 45b640dcec
7 changed files with 323 additions and 0 deletions

View 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()
}
}

View file

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

View 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()
}
}

View 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) {
}
}

View file

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

View 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
}
}

View 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
}
}