Updated on 2026-08-14

This commit is contained in:
Tangem 2019-10-25 16:57:54 +03:00
parent 44caf53216
commit eb23c3be24
10 changed files with 203 additions and 98 deletions

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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