From 7c70545ae774a0d1d00d6b82418bd9a623056e1e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 5 Feb 2020 12:30:57 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/commands/CheckWalletCommand.kt | 21 +++-- .../tangem/commands/CreateWalletCommand.kt | 6 +- .../com/tangem/commands/PurgeWalletCommand.kt | 6 +- .../tangem/commands/ReadIssuerDataCommand.kt | 6 +- .../java/com/tangem/commands/SignCommand.kt | 4 +- .../tangem/commands/WriteIssuerDataCommand.kt | 3 +- .../main/java/com/tangem/tasks/ScanTask.kt | 80 ++++++++----------- .../com/tangem/tasks/SingleCommandTask.kt | 10 ++- .../src/main/java/com/tangem/tasks/Task.kt | 46 ++++++++++- .../com/tangem/tangemtest/MainActivity.kt | 12 ++- 10 files changed, 117 insertions(+), 77 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index a6b99184ec..e5376f2cc7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -10,6 +10,7 @@ import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.CryptoUtils import com.tangem.tasks.TaskError /** @@ -23,7 +24,16 @@ class CheckWalletResponse( val cardId: String, val salt: ByteArray, val walletSignature: ByteArray -) : CommandResponse +) : CommandResponse { + + fun verify(curve: EllipticCurve, publicKey: ByteArray, challenge: ByteArray): Boolean { + return CryptoUtils.verify( + publicKey, + challenge + salt, + walletSignature, + curve) + } +} /** * This command proves that the wallet private key from the card corresponds to the wallet public key. @@ -33,15 +43,14 @@ class CheckWalletResponse( * @property cardId Unique Tangem card ID number * @property challenge Random challenge generated by application */ -class CheckWalletCommand( - private val cardId: String, - private val challenge: ByteArray -) : CommandSerializer() { +class CheckWalletCommand : CommandSerializer() { + + val challenge = CryptoUtils.generateRandomBytes(16) override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 3a30ae478b..0c142ee296 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -38,14 +38,12 @@ class CreateWalletResponse( * * @property cardId CID, Unique Tangem card ID number. */ -class CreateWalletCommand( - private val cardId: String -) : CommandSerializer() { +class CreateWalletCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index 5c44515412..cef8aca229 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -30,14 +30,12 @@ class PurgeWalletResponse( * ‘Purged’ state is final, it makes the card useless. * @property cardId CID, Unique Tangem card ID number. */ -class PurgeWalletCommand( - private val cardId: String -) : CommandSerializer() { +class PurgeWalletCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index 076d89f66d..a045692a10 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -50,14 +50,12 @@ class ReadIssuerDataResponse( * wallet balance signed by the issuer or additional issuer’s attestation data. * @property cardId CID, Unique Tangem card ID number. */ -class ReadIssuerDataCommand( - private val cardId: String -) : CommandSerializer() { +class ReadIssuerDataCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index 0b8848c4d5..9a473aa9c6 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -33,7 +33,7 @@ class SignResponse( * @property hashes Array of transaction hashes. * @property cardId CID, Unique Tangem card ID number */ -class SignCommand(private val hashes: Array, private val cardId: String) +class SignCommand(private val hashes: Array) : CommandSerializer() { private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 @@ -54,7 +54,7 @@ class SignCommand(private val hashes: Array, private val cardId: Stri val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index dc36a1a927..9066040e81 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -31,7 +31,6 @@ class WriteIssuerDataResponse( * @property issuerDataCounter An optional counter that protect issuer data against replay attack. */ class WriteIssuerDataCommand( - private val cardId: String, private val issuerData: ByteArray, private val issuerDataSignature: ByteArray, private val issuerDataCounter: Int? = null @@ -40,7 +39,7 @@ class WriteIssuerDataCommand( override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardId) + tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.IssuerData, issuerData) tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index e80da5189f..b179960ddb 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -1,7 +1,8 @@ package com.tangem.tasks -import com.tangem.CardEnvironment +import com.tangem.common.CardEnvironment import com.tangem.commands.Card +import com.tangem.commands.CardStatus import com.tangem.commands.CheckWalletCommand import com.tangem.commands.ReadCommand import com.tangem.common.CompletionResult @@ -31,64 +32,47 @@ sealed class ScanEvent { internal class ScanTask : Task() { override fun onRun(cardEnvironment: CardEnvironment, + currentCard: Card?, callback: (result: TaskEvent) -> Unit) { - val readCommand = ReadCommand() - sendCommand(readCommand, cardEnvironment) { readResult -> + if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard))) - when (readResult) { + if (currentCard == null) { + completeNfcSession(true, TaskError.MissingPreflightRead()) + callback(TaskEvent.Completion(TaskError.MissingPreflightRead())) - is CompletionResult.Failure -> { - if (readResult.error !is TaskError.UserCancelledError) { - completeNfcSession(true, readResult.error) - } - callback(TaskEvent.Completion(readResult.error)) - } + } else if (currentCard.status != CardStatus.Loaded) { + completeNfcSession() + callback(TaskEvent.Completion()) - is CompletionResult.Success -> { - val card = readResult.data + } else if (currentCard.curve == null || currentCard.walletPublicKey == null) { + completeNfcSession(true, TaskError.CardError()) + callback(TaskEvent.Completion(TaskError.CardError())) - callback(TaskEvent.Event(ScanEvent.OnReadEvent(card))) + } else { - if (card.curve == null || card.walletPublicKey == null) { - completeNfcSession(true) - callback(TaskEvent.Completion(TaskError.CardError())) - return@sendCommand - } + val checkWalletCommand = CheckWalletCommand() - val challenge = CryptoUtils.generateRandomBytes(16) - val checkWalletCommand = CheckWalletCommand( - card.cardId, - challenge) - - sendCommand(checkWalletCommand, cardEnvironment) { result -> - when (result) { - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelledError) { - completeNfcSession(true, result.error) - } - callback(TaskEvent.Completion(result.error)) - } - - is CompletionResult.Success -> { - completeNfcSession() - val checkWalletResponse = result.data - val verified = CryptoUtils.verify( - card.walletPublicKey, - challenge + checkWalletResponse.salt, - checkWalletResponse.walletSignature, - card.curve) - if (verified) { - callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true))) - callback(TaskEvent.Completion()) - } else { - callback(TaskEvent.Completion(TaskError.VefificationFailed())) - } - } + sendCommand(checkWalletCommand, cardEnvironment) { result -> + when (result) { + is CompletionResult.Failure -> { + if (result.error !is TaskError.UserCancelledError) { + completeNfcSession(true, result.error) } - + callback(TaskEvent.Completion(result.error)) + } + is CompletionResult.Success -> { + completeNfcSession() + val verified = result.data.verify( + currentCard.curve, + currentCard.walletPublicKey, + checkWalletCommand.challenge + ) + callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified))) + callback(TaskEvent.Completion()) } } + } } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt index 34ed9b0a69..60b0129aad 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt @@ -1,6 +1,7 @@ package com.tangem.tasks -import com.tangem.CardEnvironment +import com.tangem.commands.Card +import com.tangem.common.CardEnvironment import com.tangem.commands.CommandResponse import com.tangem.commands.CommandSerializer import com.tangem.common.CompletionResult @@ -14,8 +15,11 @@ class SingleCommandTask( private val command: CommandSerializer ) : Task() { - override fun onRun(cardEnvironment: CardEnvironment, - callback: (result: TaskEvent) -> Unit) { + override fun onRun( + cardEnvironment: CardEnvironment, + currentCard: Card?, + callback: (result: TaskEvent) -> Unit + ) { sendCommand(command, cardEnvironment) { result -> when (result) { is CompletionResult.Success -> { diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 3998586946..bbcf068f4e 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -1,11 +1,13 @@ package com.tangem.tasks -import com.tangem.CardEnvironment +import com.tangem.common.CardEnvironment import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log +import com.tangem.commands.Card import com.tangem.commands.CommandResponse import com.tangem.commands.CommandSerializer +import com.tangem.commands.ReadCommand import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord @@ -32,12 +34,15 @@ sealed class TaskError(description: String? = null) : Exception(description) { class VefificationFailed : TaskError() class CardError : TaskError() class ReaderError() : TaskError() - class SerializeCommandError() : TaskError() + class SerializeCommandError(description: String? = null) : TaskError(description) class CardIsMissing() : TaskError() class EmptyHashes() : TaskError() class TooMuchHashes() : TaskError() class HashSizeMustBeEqual() : TaskError() + + class WrongCard() : TaskError() + class MissingPreflightRead() : TaskError() } /** @@ -67,6 +72,7 @@ abstract class Task { var delegate: CardManagerDelegate? = null var reader: CardReader? = null + var performPreflightRead: Boolean = true /** * This method should be called to run the [Task] and perform all its operations. @@ -79,7 +85,12 @@ abstract class Task { delegate?.onNfcSessionStarted() reader?.openSession() Log.i(this::class.simpleName!!, "Nfc task is started") - onRun(cardEnvironment, callback) + + if (performPreflightRead) { + runWithPreflightRead(cardEnvironment, callback) + } else { + onRun(cardEnvironment, null, callback) + } } /** @@ -101,6 +112,7 @@ abstract class Task { * In this method the individual Tasks' logic should be implemented. */ protected abstract fun onRun(cardEnvironment: CardEnvironment, + currentCard: Card?, callback: (result: TaskEvent) -> Unit) /** @@ -165,6 +177,34 @@ abstract class Task { } } } + + private fun runWithPreflightRead( + environment: CardEnvironment, callback: (result: TaskEvent) -> Unit) { + sendCommand(ReadCommand(), environment) { readResult -> + when (readResult) { + is CompletionResult.Failure -> { + completeNfcSession(true, readResult.error) + callback(TaskEvent.Completion(readResult.error)) + } + is CompletionResult.Success -> { + + val receivedCardId = readResult.data.cardId + + if (environment.cardId != null && environment.cardId != receivedCardId) { + completeNfcSession(true, TaskError.WrongCard()) + callback(TaskEvent.Completion(TaskError.WrongCard())) + return@sendCommand + } + + val newEnvironment = environment.copy(cardId = receivedCardId) + onRun(newEnvironment, readResult.data, callback) + } + } + + + } + + } } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt index 52b2951309..11f067114f 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate import com.tangem.tangem_sdk_new.NfcLifecycleObserver +import com.tangem.tangem_sdk_new.TerminalKeysStorage import com.tangem.tangem_sdk_new.nfc.NfcManager import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError @@ -15,7 +16,7 @@ class MainActivity : AppCompatActivity() { private val nfcManager = NfcManager() private val cardManagerDelegate: DefaultCardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader) - private val cardManager = CardManager(nfcManager.reader, cardManagerDelegate) + private val cardManager = CardManager(nfcManager.reader, cardManagerDelegate, TerminalKeysStorage()) private lateinit var cardId: String private lateinit var issuerData: ByteArray @@ -38,6 +39,10 @@ class MainActivity : AppCompatActivity() { is ScanEvent.OnReadEvent -> { // Handle returned card data cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId + runOnUiThread { + tv_card_cid?.text = cardId + btn_create_wallet.isEnabled = true + } } is ScanEvent.OnVerifyEvent -> { //Handle card verification @@ -127,6 +132,11 @@ class MainActivity : AppCompatActivity() { } is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = it.data.status.name + btn_sign.isEnabled = true + btn_read_issuer_data.isEnabled = true + btn_purge_wallet.isEnabled = true + btn_create_wallet.isEnabled = false + } } }