Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-05 12:30:57 +03:00
parent b7f323fd14
commit 7c70545ae7
10 changed files with 117 additions and 77 deletions

View file

@ -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<CheckWalletResponse>() {
class CheckWalletCommand : CommandSerializer<CheckWalletResponse>() {
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())
}

View file

@ -38,14 +38,12 @@ class CreateWalletResponse(
*
* @property cardId CID, Unique Tangem card ID number.
*/
class CreateWalletCommand(
private val cardId: String
) : CommandSerializer<CreateWalletResponse>() {
class CreateWalletCommand : CommandSerializer<CreateWalletResponse>() {
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())

View file

@ -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<PurgeWalletResponse>() {
class PurgeWalletCommand : CommandSerializer<PurgeWalletResponse>() {
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())
}

View file

@ -50,14 +50,12 @@ class ReadIssuerDataResponse(
* wallet balance signed by the issuer or additional issuers attestation data.
* @property cardId CID, Unique Tangem card ID number.
*/
class ReadIssuerDataCommand(
private val cardId: String
) : CommandSerializer<ReadIssuerDataResponse>() {
class ReadIssuerDataCommand : CommandSerializer<ReadIssuerDataResponse>() {
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())
}

View file

@ -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<ByteArray>, private val cardId: String)
class SignCommand(private val hashes: Array<ByteArray>)
: CommandSerializer<SignResponse>() {
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
@ -54,7 +54,7 @@ class SignCommand(private val hashes: Array<ByteArray>, 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)

View file

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

View file

@ -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<ScanEvent>() {
override fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<ScanEvent>) -> 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())
}
}
}
}
}

View file

@ -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<Event : CommandResponse>(
private val command: CommandSerializer<Event>
) : Task<Event>() {
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<Event>) -> Unit
) {
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {

View file

@ -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<T> {
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<T> {
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<T> {
* In this method the individual Tasks' logic should be implemented.
*/
protected abstract fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<T>) -> Unit)
/**
@ -165,6 +177,34 @@ abstract class Task<T> {
}
}
}
private fun runWithPreflightRead(
environment: CardEnvironment, callback: (result: TaskEvent<T>) -> 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)
}
}
}
}
}

View file

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