Updated on 2026-08-14
This commit is contained in:
parent
da003bf0d3
commit
6ed8c7d0fb
17 changed files with 543 additions and 203 deletions
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
import com.tangem.EncryptionMode
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.toBytes
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class CommandApdu(
|
||||
|
||||
private val instruction: Int,
|
||||
private val tlvList: List<Tlv>,
|
||||
|
||||
private val cla: Byte = ISO_CLA,
|
||||
private val p1: Byte = 0x00,
|
||||
private val p2: Byte = 0x00,
|
||||
|
||||
private val le: Int = 0x00,
|
||||
|
||||
private val encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
private val encryptionKey: ByteArray? = null) {
|
||||
|
||||
|
||||
fun toBytes(): 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 byteStream = ByteArrayOutputStream()
|
||||
byteStream.write(cla.toInt())
|
||||
byteStream.write(instruction)
|
||||
byteStream.write(p1.toInt())
|
||||
byteStream.write(p2.toInt())
|
||||
if (lc != 0) {
|
||||
writeLength(byteStream, lc)
|
||||
byteStream.write(data)
|
||||
}
|
||||
return byteStream.toByteArray()
|
||||
|
||||
// val apdu = ByteArray(length)
|
||||
//
|
||||
// var index = 0
|
||||
// apdu[index] = cla
|
||||
// index++
|
||||
// apdu[index] = instruction
|
||||
// index++
|
||||
// apdu[index] = p1
|
||||
// index++
|
||||
// apdu[index] = p2
|
||||
// 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 writeLength(stream: ByteArrayOutputStream, lc: Int) {
|
||||
// if (lc < 256) {
|
||||
// stream.write(lc)
|
||||
// } else {
|
||||
stream.write(0)
|
||||
stream.write(lc shr 8)
|
||||
stream.write(lc and 0xFF)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
private fun encrypt() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ISO_CLA = 0x00.toByte()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.enums.Status
|
||||
|
||||
class ResponseApdu(val data: ByteArray) {
|
||||
|
||||
val sw1: Int = 0x00FF and data[data.size - 2].toInt()
|
||||
val sw2: Int = 0x00FF and data[data.size - 1].toInt()
|
||||
|
||||
val sw: Int = sw1 shl 8 or sw2
|
||||
|
||||
val status: Status = Status.byCode(sw)
|
||||
|
||||
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
|
||||
val tlvs = when {
|
||||
data.size < 2 -> null
|
||||
data.size == 2 -> emptyList()
|
||||
else -> Tlv.fromBytes(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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.common.extentions
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.experimental.and
|
||||
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
|
||||
|
||||
|
||||
fun ByteArray.toInt(): Int {
|
||||
return when (this.size) {
|
||||
1 -> (this[0] and 0xFF.toByte()).toInt()
|
||||
2 -> ByteBuffer.wrap(this).short.toInt()
|
||||
4 -> ByteBuffer.wrap(this).int
|
||||
else -> throw IllegalArgumentException("Length must be 1,2 or 4. Length = " + this.size)
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.toDate(): Date {
|
||||
val year = ((this[0] and 0xFF.toByte()).toInt() shl 8) or (this[1] and 0xFF.toByte()).toInt()
|
||||
val month = this[2] - 1
|
||||
val day = this[3].toInt()
|
||||
val cd = Calendar.getInstance()
|
||||
cd.set(year, month, day, 0, 0, 0)
|
||||
return cd.time
|
||||
}
|
||||
|
||||
fun ByteArray.calculateSha512(): ByteArray = MessageDigest.getInstance("SHA-512").digest(this)
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.common.extentions
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
||||
fun String.calculateSha256(): ByteArray {
|
||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||
val data = this.toByteArray(Charset.forName("UTF-8"))
|
||||
return sha256.digest(data)
|
||||
}
|
||||
|
||||
fun String.hexToBytes(): ByteArray {
|
||||
val bytes = ByteArray(this.length / 2)
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = Integer.parseInt(this.substring(2 * i, 2 * i + 2),
|
||||
16).toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
80
tangem-card-new/src/main/java/com/tangem/common/tlv/Tlv.kt
Normal file
80
tangem-card-new/src/main/java/com/tangem/common/tlv/Tlv.kt
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class Tlv(val tag: TlvTag, val tagCode: Int, val value: ByteArray = byteArrayOf()) {
|
||||
|
||||
|
||||
companion object {
|
||||
fun tlvFromBytes(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(mData: ByteArray): List<Tlv> {
|
||||
val tlvList = mutableListOf<Tlv>()
|
||||
val stream = ByteArrayInputStream(mData)
|
||||
var tlv: Tlv? = null
|
||||
do {
|
||||
try {
|
||||
tlv = Tlv.tlvFromBytes(stream)
|
||||
if (tlv != null) tlvList.add(tlv)
|
||||
} catch (e: IOException) {
|
||||
throw TlvMapperException("TLVError: " + e.message)
|
||||
}
|
||||
|
||||
} while (tlv != null)
|
||||
return tlvList
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.ProductMask
|
||||
import com.tangem.commands.SigningMethod
|
||||
import com.tangem.common.extentions.toDate
|
||||
import com.tangem.common.extentions.toHexString
|
||||
import com.tangem.common.extentions.toInt
|
||||
import com.tangem.data.SettingsMask
|
||||
import java.util.*
|
||||
|
||||
|
||||
open class TlvMapperException(message: String?) : Exception(message)
|
||||
|
||||
class MissingTagException(message: String? = null) : TlvMapperException(message)
|
||||
class WrongTypeException(message: String? = null) : TlvMapperException(message)
|
||||
class ConvertionException(message: String? = null) : TlvMapperException(message)
|
||||
|
||||
class TlvMapper(val tlvList: List<Tlv>) {
|
||||
|
||||
inline fun <reified T> mapOptional(tag: TlvTag): T? =
|
||||
try {
|
||||
map<T>(tag)
|
||||
} catch (exception: MissingTagException) {
|
||||
null
|
||||
}
|
||||
|
||||
inline fun <reified T> map(tag: TlvTag): T {
|
||||
val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value
|
||||
?: throw MissingTagException()
|
||||
|
||||
return when (tag.valueType()) {
|
||||
TlvValueType.HexString -> {
|
||||
if (T::class != String::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
tlvValue.toHexString() as T
|
||||
}
|
||||
TlvValueType.Utf8String -> {
|
||||
if (T::class != String::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
String(tlvValue) as T
|
||||
}
|
||||
TlvValueType.IntValue -> {
|
||||
if (T::class != Integer::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
tlvValue.toInt() as T
|
||||
}
|
||||
TlvValueType.BoolValue -> {
|
||||
if (T::class != Boolean::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
true as T
|
||||
}
|
||||
TlvValueType.ByteArray -> {
|
||||
if (T::class != ByteArray::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
tlvValue as T
|
||||
}
|
||||
TlvValueType.EllipticCurve -> {
|
||||
if (T::class != EllipticCurve::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
EllipticCurve.byName(String(tlvValue)) as T
|
||||
}
|
||||
TlvValueType.DateTime -> {
|
||||
if (T::class != Date::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
tlvValue.toDate() as T
|
||||
}
|
||||
TlvValueType.ProductMask -> {
|
||||
if (T::class != ProductMask::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
ProductMask.byCode(tlvValue.first()) as T
|
||||
|
||||
}
|
||||
TlvValueType.SettingsMask -> {
|
||||
if (T::class != SettingsMask::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
SettingsMask(tlvValue.toInt()) as T
|
||||
|
||||
}
|
||||
TlvValueType.CardStatus -> {
|
||||
if (T::class != CardStatus::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
CardStatus.byCode(tlvValue.toInt()) as T
|
||||
}
|
||||
TlvValueType.SigningMethod -> {
|
||||
if (T::class != SigningMethod::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
SigningMethod.byCode(tlvValue.toInt()) as T
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.enums
|
||||
package com.tangem.common.tlv
|
||||
|
||||
enum class TlvTag(val code: Int) {
|
||||
Unknown(0x00),
|
||||
CardID(0x01),
|
||||
CardId(0x01),
|
||||
Status(0x02),
|
||||
CardPublicKey(0x03),
|
||||
CardSignature(0x04),
|
||||
CurveID(0x05),
|
||||
CurveId(0x05),
|
||||
HashAlgID(0x06),
|
||||
SigningMethod(0x07),
|
||||
MaxSignatures(0x08),
|
||||
|
|
@ -49,9 +49,9 @@ enum class TlvTag(val code: Int) {
|
|||
CodePageCount(0x41),
|
||||
CodeHash(0x42),
|
||||
|
||||
TrOutHash(0x50),
|
||||
TrOutHashSize(0x51),
|
||||
TrOutRaw(0x52),
|
||||
TransactionOutHash(0x50),
|
||||
TransactionOutHashSize(0x51),
|
||||
TransactionOutRaw(0x52),
|
||||
|
||||
WalletPublicKey(0x60),
|
||||
Signature(0x61),
|
||||
|
|
@ -66,6 +66,11 @@ enum class TlvTag(val code: Int) {
|
|||
ManufacturerPublicKey(0x85),
|
||||
CardIdManufacturerSignature(0x86),
|
||||
|
||||
ProductMask(0x8A),
|
||||
PaymentFlowVersion(0x54),
|
||||
UserCounter(0x2C),
|
||||
|
||||
|
||||
TokenSymbol(0xA0),
|
||||
TokenContractAddress(0xA1),
|
||||
TokenDecimal(0xA2),
|
||||
|
|
@ -78,7 +83,34 @@ enum class TlvTag(val code: Int) {
|
|||
TerminalPublicKey(0x5C),
|
||||
TerminalTransactionSignature(0x57);
|
||||
|
||||
fun hasNestedTlv(): Boolean {
|
||||
return when (this) {
|
||||
TlvTag.CardData -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun valueType(): TlvValueType {
|
||||
return when (this) {
|
||||
CardId, Pin, Batch -> TlvValueType.HexString
|
||||
ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress ->
|
||||
TlvValueType.Utf8String
|
||||
CurveId -> TlvValueType.EllipticCurve
|
||||
MaxSignatures, PauseBeforePin2, RemainingSignatures,
|
||||
SignedHashes, Health, TokenDecimal, UserCounter -> TlvValueType.IntValue
|
||||
IsActivated, TerminalIsLinked -> TlvValueType.BoolValue
|
||||
ManufactureDateTime -> TlvValueType.DateTime
|
||||
ProductMask -> TlvValueType.ProductMask
|
||||
SettingsMask -> TlvValueType.SettingsMask
|
||||
Status -> TlvValueType.CardStatus
|
||||
SigningMethod -> TlvValueType.SigningMethod
|
||||
else -> TlvValueType.ByteArray
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): TlvTag = values().find { it.code == code } ?: Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
enum class TlvValueType {
|
||||
HexString,
|
||||
Utf8String,
|
||||
IntValue,
|
||||
BoolValue,
|
||||
ByteArray,
|
||||
EllipticCurve,
|
||||
DateTime,
|
||||
ProductMask,
|
||||
SettingsMask,
|
||||
CardStatus,
|
||||
SigningMethod
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import java.security.SecureRandom
|
||||
|
||||
fun generateRandomBytes(length: Int): ByteArray {
|
||||
val bytes = ByteArray(length)
|
||||
SecureRandom().nextBytes(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt
Normal file
27
tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.common.extentions.calculateSha512
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import java.security.MessageDigest
|
||||
import java.security.PublicKey
|
||||
|
||||
internal fun verifyEd25519(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val messageSha512 = message.calculateSha512()
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
|
||||
signatureInstance.update(messageSha512)
|
||||
|
||||
return signatureInstance.verify(signature)
|
||||
}
|
||||
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec)
|
||||
return EdDSAPublicKey(pubKey)
|
||||
}
|
||||
43
tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt
Normal file
43
tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import org.spongycastle.asn1.ASN1EncodableVector
|
||||
import org.spongycastle.asn1.ASN1Integer
|
||||
import org.spongycastle.asn1.DERSequence
|
||||
import org.spongycastle.jce.ECNamedCurveTable
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
|
||||
internal fun verifySecp256k1(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val signatureInstance = Signature.getInstance("SHA256withECDSA")
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
signatureInstance.update(message)
|
||||
|
||||
val v = ASN1EncodableVector()
|
||||
val size = signature.size / 2
|
||||
v.add(calculateR(signature, size))
|
||||
v.add(calculateS(signature, size))
|
||||
val sigDer = DERSequence(v).encoded
|
||||
|
||||
return signatureInstance.verify(sigDer)
|
||||
}
|
||||
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
val factory = KeyFactory.getInstance("EC", "SC")
|
||||
|
||||
val p1 = spec.curve.decodePoint(publicKeyArray)
|
||||
val keySpec = ECPublicKeySpec(p1, spec)
|
||||
|
||||
return factory.generatePublic(keySpec)
|
||||
}
|
||||
|
||||
private fun calculateR(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(0, size)))
|
||||
|
||||
private fun calculateS(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2)))
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.data
|
||||
|
||||
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 Protocol_AllowUnencrypted = 0x1000
|
||||
const val Protocol_AllowStaticEncryption = 0x2000
|
||||
|
||||
const val ProtectIssuerDataAgainstReplay = 0x4000
|
||||
|
||||
const val AllowSelectBlockchain = 0x8000
|
||||
|
||||
const val DisablePrecomputedNDEF = 0x00010000
|
||||
|
||||
const val SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ enum class Status (val code: Int, val description: String){
|
|||
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
|
||||
ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"),
|
||||
InvalidState(0x6985, "SW_INVALID_STATE"),
|
||||
// PinsNotChanged(ProcessCompleted.code, ProcessCompleted.description),
|
||||
// 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"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue