From eb23c3be24659e3347a860472a07ab23eeb0e80d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2019 16:57:54 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/CardEnvironment.kt | 10 +- .../src/main/java/com/tangem/CardManager.kt | 60 ++++++---- .../java/com/tangem/CardManagerDelegate.kt | 2 +- .../src/main/java/com/tangem/CardReader.kt | 5 +- .../src/main/java/com/tangem/Executors.kt | 20 ++++ .../src/main/java/com/tangem/Log.kt | 29 +++++ .../com/tangem/commands/ReadCardCommand.kt | 11 +- .../com/tangem/common/CompletionResult.kt | 8 ++ .../main/java/com/tangem/tasks/ScanTask.kt | 52 ++++----- .../src/main/java/com/tangem/tasks/Task.kt | 104 +++++++++++------- 10 files changed, 203 insertions(+), 98 deletions(-) create mode 100644 tangem-card-new/src/main/java/com/tangem/Executors.kt create mode 100644 tangem-card-new/src/main/java/com/tangem/Log.kt create mode 100644 tangem-card-new/src/main/java/com/tangem/common/CompletionResult.kt diff --git a/tangem-card-new/src/main/java/com/tangem/CardEnvironment.kt b/tangem-card-new/src/main/java/com/tangem/CardEnvironment.kt index b0e5191c8d..e4fdb8ec16 100644 --- a/tangem-card-new/src/main/java/com/tangem/CardEnvironment.kt +++ b/tangem-card-new/src/main/java/com/tangem/CardEnvironment.kt @@ -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) -} \ No newline at end of file +} + +class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/CardManager.kt b/tangem-card-new/src/main/java/com/tangem/CardManager.kt index 4db05ae717..96cfb5bd03 100644 --- a/tangem-card-new/src/main/java/com/tangem/CardManager.kt +++ b/tangem-card-new/src/main/java/com/tangem/CardManager.kt @@ -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() init { initCrypto() } - fun scanCard(callback: (result: ScanEvent, cardEnvironment: CardEnvironment) -> Unit) { + fun scanCard(callback: (result: TaskEvent) -> 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(signCommandData, cardManagerDelegate, reader) -// runTask(task, cardEnvironmentRepository.cardEnvironment, callback) + fun sign(hashes: Array, cardId: String, + callback: (result: TaskEvent) -> 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 runTask(task: Task, environment: CardEnvironment? = null, - callback: (result: TaskEvent, cardEnvironment: CardEnvironment) -> Unit) { - task.run(environment ?: cardEnvironmentRepository.cardEnvironment, callback) + fun runTask(task: Task, cardId: String? = null, + callback: (result: TaskEvent) -> Unit) { +// AppExecutors.diskIO().execute { + val environment = fetchCardEnvironment(cardId) + isBusy = true + task.run(environment, callback) +// } } - fun runCommand(commandSerializer: CommandSerializer, - environment: CardEnvironment? = null, - callback: (result: TaskResult) -> Unit) { -// val task = BasicTask(commandSerializer, cardManagerDelegate, reader) -// runTask(task, environment, callback) + private fun fetchCardEnvironment(cardId: String?): CardEnvironment { + return cardEnvironmentRepository[cardId] ?: CardEnvironment() + } + + fun runCommand(commandSerializer: CommandSerializer, + cardId: String? = null, + callback: (result: TaskEvent) -> Unit) { + val task = SingleCommandTask(commandSerializer, cardManagerDelegate, reader) + runTask(task, cardId, callback) } } \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/CardManagerDelegate.kt b/tangem-card-new/src/main/java/com/tangem/CardManagerDelegate.kt index ff835bf94f..89973ad33d 100644 --- a/tangem-card-new/src/main/java/com/tangem/CardManagerDelegate.kt +++ b/tangem-card-new/src/main/java/com/tangem/CardManagerDelegate.kt @@ -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) diff --git a/tangem-card-new/src/main/java/com/tangem/CardReader.kt b/tangem-card-new/src/main/java/com/tangem/CardReader.kt index 784e084282..b0a9772e15 100644 --- a/tangem-card-new/src/main/java/com/tangem/CardReader.kt +++ b/tangem-card-new/src/main/java/com/tangem/CardReader.kt @@ -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) -> Unit) + fun setStartSession() } \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/Executors.kt b/tangem-card-new/src/main/java/com/tangem/Executors.kt new file mode 100644 index 0000000000..60fdb2380b --- /dev/null +++ b/tangem-card-new/src/main/java/com/tangem/Executors.kt @@ -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 + } + +} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/Log.kt b/tangem-card-new/src/main/java/com/tangem/Log.kt new file mode 100644 index 0000000000..8f77c9a854 --- /dev/null +++ b/tangem-card-new/src/main/java/com/tangem/Log.kt @@ -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) +} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/commands/ReadCardCommand.kt b/tangem-card-new/src/main/java/com/tangem/commands/ReadCardCommand.kt index 30d9b8f365..41e99c4b37 100644 --- a/tangem-card-new/src/main/java/com/tangem/commands/ReadCardCommand.kt +++ b/tangem-card-new/src/main/java/com/tangem/commands/ReadCardCommand.kt @@ -93,17 +93,22 @@ class ReadCardResponse( ) : CommandResponse -class ReadCardCommand(private val pin1: String) : CommandSerializer() { +class ReadCardCommand : CommandSerializer() { 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) } diff --git a/tangem-card-new/src/main/java/com/tangem/common/CompletionResult.kt b/tangem-card-new/src/main/java/com/tangem/common/CompletionResult.kt new file mode 100644 index 0000000000..aac64bbc28 --- /dev/null +++ b/tangem-card-new/src/main/java/com/tangem/common/CompletionResult.kt @@ -0,0 +1,8 @@ +package com.tangem.common + +import com.tangem.tasks.TaskError + +sealed class CompletionResult { + class Success(val data: T) : CompletionResult() + class Failure(val error: TaskError) : CompletionResult() +} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-card-new/src/main/java/com/tangem/tasks/ScanTask.kt index c4b552c12c..9f964224e8 100644 --- a/tangem-card-new/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-card-new/src/main/java/com/tangem/tasks/ScanTask.kt @@ -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(delegate, reader) { +internal class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task(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) -> 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( diff --git a/tangem-card-new/src/main/java/com/tangem/tasks/Task.kt b/tangem-card-new/src/main/java/com/tangem/tasks/Task.kt index 55683d9a6f..6d86e26ebf 100644 --- a/tangem-card-new/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-card-new/src/main/java/com/tangem/tasks/Task.kt @@ -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(protected val delegate: CardManagerDelegate? = null, private val reader: CardReader) { +abstract class Task(protected val delegate: CardManagerDelegate? = null, protected val reader: CardReader) { fun run(cardEnvironment: CardEnvironment, - callback: (result: T, cardEnvironment: CardEnvironment) -> Unit) { + callback: (result: TaskEvent) -> 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) -> Unit) + + protected fun sendCommand( + commandSerializer: CommandSerializer, + cardEnvironment: CardEnvironment, + callback: (result: TaskEvent) -> Unit) { + + Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated") - protected fun sendCommand(commandSerializer: CommandSerializer, 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( -// private val commandSerializer: CommandSerializer, -// 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 { + class Event(val data: T) : TaskEvent() + class Completion(val error: TaskError? = null) : TaskEvent() +}