Updated on 2026-08-14

This commit is contained in:
Tangem 2019-10-28 17:08:42 +03:00
parent 0c9dce4b23
commit 2ddc76b2c0
17 changed files with 148 additions and 207 deletions

View file

@ -9,10 +9,9 @@ import com.tangem.tasks.*
class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null,
dataStorage: DataStorage? = null) {
private val cardManagerDelegate: CardManagerDelegate? = null) {
var isBusy = false
private var isBusy = false
private set
private val cardEnvironmentRepository = mutableMapOf<String, CardEnvironment>()
@ -22,13 +21,13 @@ class CardManager(
}
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
val task = ScanTask(cardManagerDelegate, reader)
val task = ScanTask()
runTask(task, callback = callback)
}
fun sign(hashes: Array<ByteArray>, cardId: String,
callback: (result: TaskEvent<SignResponse>) -> Unit) {
var signCommand: SignCommand
val signCommand: SignCommand
try {
signCommand = SignCommand(hashes, cardId)
} catch (error: Exception) {
@ -39,16 +38,34 @@ class CardManager(
}
return
}
val task = SingleCommandTask(signCommand, cardManagerDelegate, reader)
val task = SingleCommandTask(signCommand)
runTask(task, cardId, callback)
}
fun <T> runTask(task: Task<T>, cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
// AppExecutors.diskIO().execute {
if (isBusy) {
callback(TaskEvent.Completion(TaskError.Busy()))
return
}
val environment = fetchCardEnvironment(cardId)
isBusy = true
task.run(environment, callback)
task.reader = reader
task.delegate = cardManagerDelegate
task.run(environment) {
when (it) {
is TaskEvent.Event -> callback(it)
is TaskEvent.Completion -> {
isBusy = false
callback(it)
}
}
}
// }
}
@ -59,7 +76,7 @@ class CardManager(
fun <T : CommandResponse> runCommand(commandSerializer: CommandSerializer<T>,
cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
val task = SingleCommandTask(commandSerializer, cardManagerDelegate, reader)
val task = SingleCommandTask(commandSerializer)
runTask(task, cardId, callback)
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.tasks.TaskError
interface CardManagerDelegate {
fun showSecurityDelay(ms: Int)
fun hideSecurityDelay()
fun requestPin(success: () -> String, error: (cardError: TaskError.CardError) -> TaskError.CardError)
fun openNfcPopup()

View file

@ -1,10 +1,11 @@
package com.tangem
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
interface CardReader {
var readingActive: Boolean
fun sendApdu(apduSerialized: ByteArray, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
fun setStartSession()
}

View file

@ -27,9 +27,9 @@ class CheckWalletCommand(
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = listOf(
Tlv(TlvTag.Pin, TlvTag.Pin.code, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, TlvTag.CardId.code, cid.hexToBytes()),
Tlv(TlvTag.Challenge, TlvTag.Challenge.code, challenge)
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cid.hexToBytes()),
Tlv(TlvTag.Challenge, challenge)
)
return CommandApdu(instructionCode, tlvData)

View file

@ -3,58 +3,23 @@ package com.tangem.commands
import com.tangem.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extentions.toInt
import com.tangem.common.tlv.TlvTag
import com.tangem.enums.Instruction
import com.tangem.tasks.TaskError
//interface TlvMappable {
// fun responseFromTlv(tlvList: List<Tlv>): CommandResponse?
//}
interface CommandResponse
// : TlvMappable {
//
//}
abstract class CommandSerializer<T : CommandResponse>() {
abstract class CommandSerializer<T : CommandResponse> {
abstract val instruction: Instruction
abstract val instructionCode: Int
abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu
abstract fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): T?
}
sealed class CommandEvent {
class Success(val response: Any) : CommandEvent()
class Failure(val taskError: TaskError) : CommandEvent()
class UserCancellation : CommandEvent()
}
//class CheckWalletResponse(
// val cardId: String,
// val salt: ByteArray,
// val walletSignature: ByteArray
//) : CommandResponse, TlvMappable by CheckWalletResponse {
//
// companion object : TlvMappable {
// override fun responseFromTlv(tlvList: List<Tlv>): CommandResponse? {
// val mapper = TlvMapper(tlvList)
//
// return try {
// CheckWalletResponse(
// mapper.map(TlvTag.CardId),
// mapper.map(TlvTag.Salt),
// mapper.map(TlvTag.Signature)
// )
// } catch (exception: TlvMapperException) {
// null
// }
// }
//
// }
//}
fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? {
val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey)
return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt()
}
}

View file

@ -100,13 +100,10 @@ class ReadCardCommand : CommandSerializer<ReadCardResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, TlvTag.Pin.code, cardEnvironment.pin1.calculateSha256())
)
val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()))
cardEnvironment.terminalKeys?.let {
Tlv(TlvTag.TerminalPublicKey,
TlvTag.TerminalPublicKey.code, it.publicKey)
Tlv(TlvTag.TerminalPublicKey, it.publicKey)
}
return CommandApdu(instructionCode, tlvData)

View file

@ -12,6 +12,14 @@ import com.tangem.crypto.sign
import com.tangem.enums.Instruction
import com.tangem.tasks.TaskError
class SignResponse(
val cardId: String,
val signature: ByteArray,
val remainingSignatures: Int,
val signedHashes: Int
) : CommandResponse
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
: CommandSerializer<SignResponse>() {
@ -34,12 +42,11 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, TlvTag.Pin.code, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.Pin2, TlvTag.Pin2.code, cardEnvironment.pin2.calculateSha256()),
Tlv(TlvTag.CardId, TlvTag.CardId.code, cardId.hexToBytes()),
Tlv(TlvTag.TransactionOutHashSize,
TlvTag.TransactionOutHashSize.code, byteArrayOf(hashSizes.toByte())),
Tlv(TlvTag.TransactionOutHash, TlvTag.TransactionOutHash.code, dataToSign)
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.TransactionOutHashSize,byteArrayOf(hashSizes.toByte())),
Tlv(TlvTag.TransactionOutHash, dataToSign)
)
addTerminalSignature(cardEnvironment, tlvData)
@ -50,9 +57,8 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
cardEnvironment.terminalKeys?.let {
val signedData = dataToSign.sign(it.privateKey)
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature,
TlvTag.TerminalTransactionSignature.code, signedData))
tlvData.add(Tlv(TlvTag.TerminalPublicKey, TlvTag.TerminalPublicKey.code, it.publicKey))
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData))
tlvData.add(Tlv(TlvTag.TerminalPublicKey, it.publicKey))
}
}
@ -67,11 +73,4 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
tlvMapper.map(TlvTag.SignedHashes)
)
}
}
class SignResponse(
val cardId: String,
val signature: ByteArray,
val remainingSignatures: Int,
val signedHashes: Int
) : CommandResponse
}

View file

@ -19,10 +19,14 @@ class CommandApdu(
private val encryptionMode: EncryptionMode = EncryptionMode.NONE,
private val encryptionKey: ByteArray? = null) {
val apduData: ByteArray
fun toBytes(): ByteArray {
init {
apduData = toBytes()
}
// var length = 4 // CLA, INS, P1, P2
private fun toBytes(): ByteArray {
val data = if (tlvList.isNotEmpty()) {
tlvList.toBytes()
@ -31,13 +35,6 @@ class CommandApdu(
}
val lc = data.size
//
// if (data.isNotEmpty()) {
// length += 1 // LC
// if (lc >= 256)
// length += 2
// length += data.size // DATA
// }
val byteStream = ByteArrayOutputStream()
@ -50,45 +47,13 @@ class CommandApdu(
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)
// }
stream.write(0)
stream.write(lc shr 8)
stream.write(lc and 0xFF)
}

View file

@ -5,8 +5,8 @@ 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()
private val sw1: Int = 0x00FF and data[data.size - 2].toInt()
private val sw2: Int = 0x00FF and data[data.size - 1].toInt()
val sw: Int = sw1 shl 8 or sw2

View file

@ -3,53 +3,68 @@ 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()) {
class Tlv {
val tag: TlvTag
val value: ByteArray
val tagCode: Int
constructor(tagCode: Int, value: ByteArray = byteArrayOf()) {
this.tag = TlvTag.byCode(tagCode)
this.tagCode = tagCode
this.value = value
}
constructor(tag: TlvTag, value: ByteArray = byteArrayOf()) {
this.tag = tag
this.tagCode = tag.code
this.value = value
}
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()
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")
len = len or (lenH shl 8)
}
val value = ByteArray(len)
if (len > 0) {
if (len != stream.read(value)) {
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 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)
val value = ByteArray(len)
if (len > 0) {
if (len != stream.read(value)) {
throw IOException("Can't read TLV")
}
}
val tag = TlvTag.byCode(code)
return if (tag == TlvTag.Unknown) Tlv(code, value) else Tlv(tag, value)
}
} while (tlv != null)
return tlvList
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
}
}
}
}

View file

@ -1,8 +1,6 @@
package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.CardManagerDelegate
import com.tangem.CardReader
import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ReadCardCommand
@ -16,7 +14,7 @@ sealed class ScanEvent {
}
internal class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task<ScanEvent>(delegate, reader) {
internal class ScanTask : Task<ScanEvent>() {
private lateinit var readCardData: ReadCardResponse
private lateinit var challenge: ByteArray

View file

@ -1,15 +1,11 @@
package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.CardManagerDelegate
import com.tangem.CardReader
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
class SingleCommandTask<Event : CommandResponse>(
private val commandSerializer: CommandSerializer<Event>,
delegate: CardManagerDelegate? = null,
reader: CardReader) : Task<Event>(delegate, reader) {
private val commandSerializer: CommandSerializer<Event>) : Task<Event>() {
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {

View file

@ -7,17 +7,18 @@ import com.tangem.Log
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
import com.tangem.common.extentions.toInt
import com.tangem.common.tlv.TlvTag
import com.tangem.enums.Status
abstract class Task<T>(protected val delegate: CardManagerDelegate? = null, protected val reader: CardReader) {
abstract class Task<T> {
var delegate: CardManagerDelegate? = null
var reader: CardReader? = null
fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<T>) -> Unit) {
delegate?.openNfcPopup()
reader.setStartSession()
reader?.setStartSession()
Log.i(this::class.simpleName!!, "Nfc task is started")
onRun(cardEnvironment, callback)
@ -34,8 +35,8 @@ abstract class Task<T>(protected val delegate: CardManagerDelegate? = null, prot
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated")
reader.sendApdu(
commandSerializer.serialize(cardEnvironment).toBytes()) { result ->
reader?.transceiveApdu(
commandSerializer.serialize(cardEnvironment)) { result ->
when (result) {
is CompletionResult.Success -> {
@ -61,10 +62,9 @@ abstract class Task<T>(protected val delegate: CardManagerDelegate? = null, prot
Status.InsNotSupported -> callback(TaskEvent.Completion(TaskError.InsNotSupported()))
Status.NeedEncryption -> callback(TaskEvent.Completion(TaskError.NeedEncryption()))
Status.NeedPause -> {
val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey)
val ms = tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt()
if (ms != null) delegate?.showSecurityDelay(ms)
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} triggered security delay of $ms milliseconds")
val remainingTime = commandSerializer.deserializeSecurityDelay(responseApdu, cardEnvironment)
if (remainingTime != null) delegate?.showSecurityDelay(remainingTime)
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
sendCommand(commandSerializer, cardEnvironment, callback)
}
}
@ -72,7 +72,7 @@ abstract class Task<T>(protected val delegate: CardManagerDelegate? = null, prot
is CompletionResult.Failure ->
if (result.error is TaskError.UserCancelledError) {
callback(TaskEvent.Completion(TaskError.UserCancelledError()))
reader.readingActive = false
reader?.readingActive = false
}
}
@ -85,6 +85,7 @@ sealed class TaskError(description: String? = null) : Exception(description) {
class MappingError : TaskError()
class GenericError(description: String? = null) : TaskError(description)
class UserCancelledError() : TaskError()
class Busy(): TaskError()
class ErrorProcessingCommand : TaskError()
class InvalidState : TaskError()
@ -104,12 +105,6 @@ sealed class TaskError(description: String? = null) : Exception(description) {
class HashSizeMustBeEqual() : TaskError()
}
sealed class TaskResult {
data class Success(val resultData: Any) : TaskResult()
data class CommandCompleted(val resultData: Any) : TaskResult()
data class Error(val error: TaskError) : TaskResult()
}
sealed class TaskEvent<T> {
class Event<T>(val data: T) : TaskEvent<T>()
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()

View file

@ -4,24 +4,23 @@ import androidx.fragment.app.FragmentActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.CardManagerDelegate
import com.tangem.Log
import com.tangem.tasks.Task
import com.tangem.tasks.TaskError
import kotlinx.android.synthetic.main.layout_touch_card.*
class BasicCardManagerDelegate(private val activity: FragmentActivity, private val reader: NfcReader) : CardManagerDelegate {
class DefaultCardManagerDelegate(private val activity: FragmentActivity, private val reader: NfcReader) : CardManagerDelegate {
private var dialog: BottomSheetDialog? = null
private var task: Task<*>? = null
override fun showSecurityDelay(ms: Int) {
// activity.runOnUiThread{
// activity.tv_security_delay?.text = "Security delay. $ms left."
// }
}
override fun hideSecurityDelay() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun requestPin(success: () -> String, error: (cardError: TaskError.CardError) -> TaskError.CardError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@ -48,6 +47,7 @@ class BasicCardManagerDelegate(private val activity: FragmentActivity, private v
}
dialog?.setOnDismissListener {
reader.readingCancelled = false
task = null
Log.i(this::class.simpleName!!, "readingCancelled is set to true")
}
dialog?.show()
@ -56,6 +56,7 @@ class BasicCardManagerDelegate(private val activity: FragmentActivity, private v
}
override fun closeNfcPopup() {
task = null
activity.runOnUiThread {
dialog?.dismiss()
}

View file

@ -5,6 +5,7 @@ import android.nfc.tech.IsoDep
import com.tangem.CardReader
import com.tangem.Log
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.tasks.TaskError
@ -18,7 +19,6 @@ class NfcReader : CardReader {
}
}
override var readingActive = false
var startSession = false
var readingCancelled = false
set(value) {
if (value) readingActive = false
@ -33,9 +33,8 @@ class NfcReader : CardReader {
readingCancelled = false
}
override fun sendApdu(apduSerialized: ByteArray, callback: (response: CompletionResult<ResponseApdu>) -> Unit) {
data = apduSerialized
// readingActive = true
override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit) {
data = apdu.apduData
this.callback = callback
if (isoDep != null) {
try {
@ -61,18 +60,12 @@ class NfcReader : CardReader {
}
fun onTagDiscovered(tag: Tag?) {
// if (readingActive) {
// if (startSession) {
// connect()
// startSession = false
// }
isoDep = IsoDep.get(tag)
transceiveData()
}
private fun connect() {
// val timeout = isoDep?.timeout
isoDep?.connect()
isoDep?.close()
isoDep?.connect()

View file

@ -5,7 +5,7 @@
android:layout_width="400dp"
android:layout_height="250dp"
android:gravity="center_vertical|center_horizontal"
tools:ignore="contentDescription">
>
<LinearLayout
android:layout_width="match_parent"

View file

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/bottom_sheet_behavior">
@ -11,7 +11,7 @@
<include
layout="@layout/layout_touch_card"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_height="250dp"
android:layout_gravity="center" />