Updated on 2026-08-14
This commit is contained in:
parent
44caf53216
commit
eb23c3be24
10 changed files with 203 additions and 98 deletions
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem
|
||||
|
||||
|
||||
|
||||
data class CardEnvironment(
|
||||
val pin1: String = DEFAULT_PIN,
|
||||
val pin2: String = DEFAULT_PIN2,
|
||||
val terminalPublicKey: ByteArray? = null,
|
||||
val terminalPrivateKey: ByteArray? = null
|
||||
val cardId: String? = null,
|
||||
val terminalKeys: KeyPair? = null,
|
||||
val encryptionKey: ByteArray? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
@ -19,4 +19,6 @@ enum class EncryptionMode(val code: Byte) {
|
|||
NONE(0x0),
|
||||
FAST(0x1),
|
||||
STRONG(0x2)
|
||||
}
|
||||
}
|
||||
|
||||
class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray)
|
||||
|
|
@ -2,44 +2,64 @@ package com.tangem
|
|||
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.SignCommandData
|
||||
import com.tangem.commands.SignCommand
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.crypto.initCrypto
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.ScanTask
|
||||
import com.tangem.tasks.Task
|
||||
import com.tangem.tasks.TaskResult
|
||||
import com.tangem.tasks.*
|
||||
|
||||
class CardManager(
|
||||
private val reader: CardReader,
|
||||
private val cardManagerDelegate: CardManagerDelegate? = null,
|
||||
dataStorage: DataStorage? = null) {
|
||||
|
||||
private val cardEnvironmentRepository = CardEnvironmentRepository(dataStorage)
|
||||
var isBusy = false
|
||||
private set
|
||||
|
||||
private val cardEnvironmentRepository = mutableMapOf<String, CardEnvironment>()
|
||||
|
||||
init {
|
||||
initCrypto()
|
||||
}
|
||||
|
||||
fun scanCard(callback: (result: ScanEvent, cardEnvironment: CardEnvironment) -> Unit) {
|
||||
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
val task = ScanTask(cardManagerDelegate, reader)
|
||||
runTask(task, cardEnvironmentRepository.cardEnvironment, callback)
|
||||
runTask(task, callback = callback)
|
||||
}
|
||||
|
||||
fun sign(signCommandData: SignCommandData,
|
||||
callback: (result: TaskResult) -> Unit) {
|
||||
// val task = SignTask<SignEvent>(signCommandData, cardManagerDelegate, reader)
|
||||
// runTask(task, cardEnvironmentRepository.cardEnvironment, callback)
|
||||
fun sign(hashes: Array<ByteArray>, cardId: String,
|
||||
callback: (result: TaskEvent<SignResponse>) -> Unit) {
|
||||
var signCommand: SignCommand
|
||||
try {
|
||||
signCommand = SignCommand(hashes, cardId)
|
||||
} catch (error: Exception) {
|
||||
if (error is TaskError) {
|
||||
callback(TaskEvent.Completion(error))
|
||||
} else {
|
||||
callback(TaskEvent.Completion(TaskError.GenericError(error.message)))
|
||||
}
|
||||
return
|
||||
}
|
||||
val task = SingleCommandTask(signCommand, cardManagerDelegate, reader)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
fun <TaskEvent>runTask(task: Task<TaskEvent>, environment: CardEnvironment? = null,
|
||||
callback: (result: TaskEvent, cardEnvironment: CardEnvironment) -> Unit) {
|
||||
task.run(environment ?: cardEnvironmentRepository.cardEnvironment, callback)
|
||||
fun <T> runTask(task: Task<T>, cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
// AppExecutors.diskIO().execute {
|
||||
val environment = fetchCardEnvironment(cardId)
|
||||
isBusy = true
|
||||
task.run(environment, callback)
|
||||
// }
|
||||
}
|
||||
|
||||
fun runCommand(commandSerializer: CommandSerializer<CommandResponse>,
|
||||
environment: CardEnvironment? = null,
|
||||
callback: (result: TaskResult) -> Unit) {
|
||||
// val task = BasicTask<CommandResponse>(commandSerializer, cardManagerDelegate, reader)
|
||||
// runTask(task, environment, callback)
|
||||
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
|
||||
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
|
||||
}
|
||||
|
||||
fun <T : CommandResponse> runCommand(commandSerializer: CommandSerializer<T>,
|
||||
cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
val task = SingleCommandTask(commandSerializer, cardManagerDelegate, reader)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.tasks.TaskError
|
|||
|
||||
interface CardManagerDelegate {
|
||||
|
||||
fun showSecurityDelay(seconds: Int)
|
||||
fun showSecurityDelay(ms: Int)
|
||||
fun hideSecurityDelay()
|
||||
fun requestPin(success: () -> String, error: (cardError: TaskError.CardError) -> TaskError.CardError)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
||||
interface CardReader {
|
||||
fun sendApdu(apduSerialized: ByteArray, callback: (responseApdu: ResponseApdu) -> Unit)
|
||||
var readingActive: Boolean
|
||||
fun sendApdu(apduSerialized: ByteArray, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
|
||||
fun setStartSession()
|
||||
}
|
||||
20
tangem-card-new/src/main/java/com/tangem/Executors.kt
Normal file
20
tangem-card-new/src/main/java/com/tangem/Executors.kt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem
|
||||
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
object AppExecutors {
|
||||
|
||||
private val diskIO = Executors.newSingleThreadExecutor()
|
||||
private val networkIO = Executors.newFixedThreadPool(3)
|
||||
|
||||
fun diskIO(): Executor {
|
||||
return diskIO
|
||||
}
|
||||
|
||||
fun networkIO(): Executor {
|
||||
return networkIO
|
||||
}
|
||||
|
||||
}
|
||||
29
tangem-card-new/src/main/java/com/tangem/Log.kt
Normal file
29
tangem-card-new/src/main/java/com/tangem/Log.kt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem
|
||||
|
||||
object Log {
|
||||
|
||||
private var loggerInstance: LoggerInterface? = null
|
||||
|
||||
fun i(logTag: String, message: String) {
|
||||
loggerInstance?.i(logTag, message)
|
||||
}
|
||||
|
||||
fun e(logTag: String, message: String) {
|
||||
loggerInstance?.e(logTag, message)
|
||||
}
|
||||
|
||||
fun v(logTag: String, message: String) {
|
||||
|
||||
loggerInstance?.v(logTag, message)
|
||||
}
|
||||
|
||||
fun setLogger(logger: LoggerInterface) {
|
||||
loggerInstance = logger
|
||||
}
|
||||
}
|
||||
|
||||
interface LoggerInterface {
|
||||
fun i(logTag: String, message: String)
|
||||
fun e(logTag: String, message: String)
|
||||
fun v(logTag: String, message: String)
|
||||
}
|
||||
|
|
@ -93,17 +93,22 @@ class ReadCardResponse(
|
|||
) : CommandResponse
|
||||
|
||||
|
||||
class ReadCardCommand(private val pin1: String) : CommandSerializer<ReadCardResponse>() {
|
||||
class ReadCardCommand : CommandSerializer<ReadCardResponse>() {
|
||||
|
||||
override val instruction = Instruction.Read
|
||||
override val instructionCode = instruction.code
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
|
||||
val tlvData = listOf(
|
||||
Tlv(TlvTag.Pin, TlvTag.Pin.code, pin1.calculateSha256())
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, TlvTag.Pin.code, cardEnvironment.pin1.calculateSha256())
|
||||
)
|
||||
|
||||
cardEnvironment.terminalKeys?.let {
|
||||
Tlv(TlvTag.TerminalPublicKey,
|
||||
TlvTag.TerminalPublicKey.code, it.publicKey)
|
||||
}
|
||||
|
||||
return CommandApdu(instructionCode, tlvData)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
sealed class CompletionResult<T> {
|
||||
class Success<T>(val data: T) : CompletionResult<T>()
|
||||
class Failure<T>(val error: TaskError) : CompletionResult<T>()
|
||||
}
|
||||
|
|
@ -3,19 +3,20 @@ package com.tangem.tasks
|
|||
import com.tangem.CardEnvironment
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.CheckWalletCommand
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.ReadCardCommand
|
||||
import com.tangem.commands.ReadCardResponse
|
||||
import com.tangem.crypto.generateRandomBytes
|
||||
import com.tangem.crypto.verify
|
||||
|
||||
sealed class ScanEvent {
|
||||
data class OnReadEvent(val result: ReadCardResponse) : ScanEvent()
|
||||
data class OnVerifyEvent(val verified: Boolean) : ScanEvent()
|
||||
object OnUserCancelledEvent : ScanEvent()
|
||||
data class Failure(val error: TaskError) : ScanEvent()
|
||||
}
|
||||
|
||||
|
||||
class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task<ScanEvent>(delegate, reader) {
|
||||
internal class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task<ScanEvent>(delegate, reader) {
|
||||
|
||||
private lateinit var readCardData: ReadCardResponse
|
||||
private lateinit var challenge: ByteArray
|
||||
|
|
@ -23,23 +24,23 @@ class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task
|
|||
private lateinit var walletPublickKey: ByteArray
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: ScanEvent, cardEnvironment: CardEnvironment) -> Unit) {
|
||||
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
|
||||
val readCommand = ReadCardCommand(cardEnvironment.pin1)
|
||||
sendCommand(readCommand, cardEnvironment) { readCommandEvent ->
|
||||
val readCommand = ReadCardCommand()
|
||||
sendCommand(readCommand, cardEnvironment) { readEvent ->
|
||||
|
||||
when (readCommandEvent) {
|
||||
is CommandEvent.Success -> {
|
||||
readCardData = readCommandEvent.response as ReadCardResponse
|
||||
when (readEvent) {
|
||||
is TaskEvent.Event -> {
|
||||
readCardData = readEvent.data
|
||||
|
||||
callback(ScanEvent.OnReadEvent(readCardData), cardEnvironment)
|
||||
callback(TaskEvent.Event(ScanEvent.OnReadEvent(readCardData)))
|
||||
|
||||
if (readCardData.curve != null && readCardData.walletPublicKey != null) {
|
||||
curve = readCardData.curve!!
|
||||
walletPublickKey = readCardData.walletPublicKey!!
|
||||
} else {
|
||||
delegate?.closeNfcPopup()
|
||||
callback(ScanEvent.Failure(TaskError.CardError()), cardEnvironment)
|
||||
callback(TaskEvent.Completion(TaskError.CardError()))
|
||||
}
|
||||
|
||||
val checkWalletCommand = prepareCheckWalletCommand(cardEnvironment)
|
||||
|
|
@ -48,39 +49,32 @@ class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task
|
|||
delegate?.closeNfcPopup()
|
||||
|
||||
when (checkWalletEvent) {
|
||||
is CommandEvent.Success -> {
|
||||
val checkWalletResponse = checkWalletEvent.response as CheckWalletResponse
|
||||
is TaskEvent.Event -> {
|
||||
val checkWalletResponse = checkWalletEvent.data
|
||||
val verified = verify(walletPublickKey,
|
||||
challenge + checkWalletResponse.salt,
|
||||
checkWalletResponse.walletSignature,
|
||||
curve)
|
||||
if (verified) {
|
||||
callback(ScanEvent.OnVerifyEvent(true), cardEnvironment)
|
||||
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
callback(ScanEvent.Failure(TaskError.VefificationFailed()), cardEnvironment)
|
||||
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
|
||||
}
|
||||
}
|
||||
is CommandEvent.Failure ->
|
||||
callback(ScanEvent.Failure(checkWalletEvent.taskError), cardEnvironment)
|
||||
is CommandEvent.UserCancellation ->
|
||||
callback(ScanEvent.OnUserCancelledEvent, cardEnvironment)
|
||||
is TaskEvent.Completion ->
|
||||
if (checkWalletEvent.error != null)
|
||||
callback(TaskEvent.Completion(checkWalletEvent.error))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
is CommandEvent.Failure ->
|
||||
callback(ScanEvent.Failure(readCommandEvent.taskError), cardEnvironment)
|
||||
is CommandEvent.UserCancellation ->
|
||||
callback(ScanEvent.OnUserCancelledEvent, cardEnvironment)
|
||||
is TaskEvent.Completion ->
|
||||
if (readEvent.error != null) callback(TaskEvent.Completion(readEvent.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEnvironment(): CardEnvironment {
|
||||
//TODO: set logic of changing CardEnvironment when needed
|
||||
return CardEnvironment()
|
||||
}
|
||||
|
||||
private fun prepareCheckWalletCommand(cardEnvironment: CardEnvironment): CheckWalletCommand {
|
||||
challenge = generateRandomBytes(16)
|
||||
return CheckWalletCommand(
|
||||
|
|
|
|||
|
|
@ -3,55 +3,88 @@ package com.tangem.tasks
|
|||
import com.tangem.CardEnvironment
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.commands.CommandEvent
|
||||
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, private val reader: CardReader) {
|
||||
abstract class Task<T>(protected val delegate: CardManagerDelegate? = null, protected val reader: CardReader) {
|
||||
|
||||
fun run(cardEnvironment: CardEnvironment,
|
||||
callback: (result: T, cardEnvironment: CardEnvironment) -> Unit) {
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
|
||||
delegate?.openNfcPopup()
|
||||
reader.setStartSession()
|
||||
Log.i(this::class.simpleName!!, "Nfc task is started")
|
||||
onRun(cardEnvironment, callback)
|
||||
|
||||
}
|
||||
|
||||
abstract fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: T, cardEnvironment: CardEnvironment) -> Unit)
|
||||
callback: (result: TaskEvent<T>) -> Unit)
|
||||
|
||||
protected fun <T : CommandResponse> sendCommand(
|
||||
commandSerializer: CommandSerializer<T>,
|
||||
cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated")
|
||||
|
||||
protected fun <T : CommandResponse> sendCommand(commandSerializer: CommandSerializer<T>, cardEnvironment: CardEnvironment,
|
||||
callback: (result: CommandEvent) -> Unit) {
|
||||
|
||||
reader.sendApdu(
|
||||
commandSerializer.serialize(cardEnvironment).toBytes()) { responseApdu ->
|
||||
commandSerializer.serialize(cardEnvironment).toBytes()) { result ->
|
||||
|
||||
when (responseApdu.status) {
|
||||
Status.ProcessCompleted, Status.Pin1Changed, Status.Pin2Changed, Status.PinsChanged
|
||||
-> {
|
||||
try {
|
||||
val responseData = commandSerializer.deserialize(cardEnvironment, responseApdu)
|
||||
callback(CommandEvent.Success(responseData as T))
|
||||
} catch (error: TaskError) {
|
||||
callback(CommandEvent.Failure(error))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val responseApdu = result.data
|
||||
when (responseApdu.status) {
|
||||
Status.ProcessCompleted, Status.Pin1Changed, Status.Pin2Changed, Status.PinsChanged
|
||||
-> {
|
||||
// reader.readingActive = false
|
||||
// reader.setStartSession()
|
||||
try {
|
||||
val responseData = commandSerializer.deserialize(cardEnvironment, responseApdu)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is completed")
|
||||
callback(TaskEvent.Event(responseData as T))
|
||||
callback(TaskEvent.Completion())
|
||||
} catch (error: TaskError) {
|
||||
callback(TaskEvent.Completion(error))
|
||||
}
|
||||
}
|
||||
Status.InvalidParams -> callback(TaskEvent.Completion(TaskError.InvalidParams()))
|
||||
Status.ErrorProcessingCommand -> callback(TaskEvent.Completion(TaskError.ErrorProcessingCommand()))
|
||||
Status.InvalidState -> callback(TaskEvent.Completion(TaskError.InvalidState()))
|
||||
|
||||
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")
|
||||
sendCommand(commandSerializer, cardEnvironment, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
Status.InvalidParams -> callback(CommandEvent.Failure(TaskError.InvalidParams()))
|
||||
Status.ErrorProcessingCommand -> callback(CommandEvent.Failure(TaskError.ErrorProcessingCommand()))
|
||||
Status.InvalidState -> callback(CommandEvent.Failure(TaskError.InvalidState()))
|
||||
is CompletionResult.Failure ->
|
||||
if (result.error is TaskError.UserCancelledError) {
|
||||
callback(TaskEvent.Completion(TaskError.UserCancelledError()))
|
||||
reader.readingActive = false
|
||||
}
|
||||
|
||||
Status.InsNotSupported -> callback(CommandEvent.Failure(TaskError.InsNotSupported()))
|
||||
Status.NeedEncryption -> callback(CommandEvent.Failure(TaskError.NeedEncryption()))
|
||||
Status.NeedPause -> callback(CommandEvent.Failure(TaskError.NeedPause()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TaskError : Exception() {
|
||||
sealed class TaskError(description: String? = null) : Exception(description) {
|
||||
class UnknownStatus(sw: Int) : TaskError()
|
||||
class MappingError : TaskError()
|
||||
class GenericError(description: String? = null) : TaskError(description)
|
||||
class UserCancelledError() : TaskError()
|
||||
|
||||
class ErrorProcessingCommand : TaskError()
|
||||
class InvalidState : TaskError()
|
||||
|
|
@ -64,6 +97,11 @@ sealed class TaskError : Exception() {
|
|||
class CardError : TaskError()
|
||||
class ReaderError() : TaskError()
|
||||
class SerializeCommandError() : TaskError()
|
||||
|
||||
class CardIsMissing() : TaskError()
|
||||
class EmptyHashes() : TaskError()
|
||||
class TooMuchHashes() : TaskError()
|
||||
class HashSizeMustBeEqual() : TaskError()
|
||||
}
|
||||
|
||||
sealed class TaskResult {
|
||||
|
|
@ -72,23 +110,9 @@ sealed class TaskResult {
|
|||
data class Error(val error: TaskError) : TaskResult()
|
||||
}
|
||||
|
||||
|
||||
//class BasicTask<T: CommandResponse>(
|
||||
// private val commandSerializer: CommandSerializer<CommandResponse>,
|
||||
// delegate: CardManagerDelegate? = null,
|
||||
// reader: CardReader) : Task(delegate, reader) {
|
||||
//
|
||||
// override fun onRun(cardEnvironment: CardEnvironment,
|
||||
// callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit) {
|
||||
// sendCommand(commandSerializer, cardEnvironment) {
|
||||
// callback.invoke(it, updateEnvironment())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun updateEnvironment(): CardEnvironment {
|
||||
// //TODO: set logic of changing CardEnvironment when needed
|
||||
// return CardEnvironment()
|
||||
// }
|
||||
//}
|
||||
sealed class TaskEvent<T> {
|
||||
class Event<T>(val data: T) : TaskEvent<T>()
|
||||
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue