Updated on 2026-08-14
This commit is contained in:
commit
f77158c082
13 changed files with 208 additions and 47 deletions
|
|
@ -96,8 +96,7 @@ class CardManager(
|
|||
*/
|
||||
fun readIssuerData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
val getIssuerDataCommand = ReadIssuerDataCommand()
|
||||
val task = SingleCommandTask(getIssuerDataCommand)
|
||||
val task = ReadIssuerDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
|
|
@ -134,12 +133,12 @@ class CardManager(
|
|||
issuerDataSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
val writeIssuerDataCommand = WriteIssuerDataCommand(
|
||||
val task = WriteIssuerDataTask(
|
||||
issuerData,
|
||||
issuerDataSignature,
|
||||
issuerDataCounter
|
||||
issuerDataCounter,
|
||||
config.issuerPublicKey
|
||||
)
|
||||
val task = SingleCommandTask(writeIssuerDataCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
@ -50,12 +50,16 @@ class ReadIssuerDataResponse(
|
|||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class ReadIssuerDataCommand : CommandSerializer<ReadIssuerDataResponse>() {
|
||||
class ReadIssuerDataCommand(
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<ReadIssuerDataResponse>(),
|
||||
IssuerDataVerifier by verifier {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
|
|
@ -62,7 +65,7 @@ class ReadIssuerExtraDataCommand(
|
|||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, EXTRA_DATA_MODE)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData)
|
||||
tlvBuilder.append(TlvTag.Offset, offset)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
@ -33,13 +32,15 @@ class WriteIssuerDataResponse(
|
|||
class WriteIssuerDataCommand(
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : CommandSerializer<WriteIssuerDataResponse>() {
|
||||
private val issuerDataCounter: Int? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData)
|
||||
tlvBuilder.append(TlvTag.IssuerData, issuerData)
|
||||
tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature)
|
||||
tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
|
|
@ -9,32 +12,6 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* This enum specifies modes for [WriteIssuerExtraDataCommand].
|
||||
*/
|
||||
enum class IssuerDataMode(val code: Byte) {
|
||||
/**
|
||||
* This mode is required to initiate writing issuer extra data to the card.
|
||||
*/
|
||||
InitializeWritingExtraData(1),
|
||||
/**
|
||||
* With this mode, the command writes part of issuer extra data
|
||||
* (block of a size [WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE]) to the card.
|
||||
*/
|
||||
WriteExtraData(2),
|
||||
/**
|
||||
* This mode is used after the issuer extra data was fully written to the card.
|
||||
* Under this mode the command provides the issuer signature
|
||||
* to confirm the validity of data that was written to card.
|
||||
*/
|
||||
FinalizeExtraData(3);
|
||||
|
||||
companion object {
|
||||
private val values = CardStatus.values()
|
||||
fun byCode(code: Int): CardStatus? = values.find { it.code == code }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This command writes Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.commands.common
|
||||
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
|
||||
/**
|
||||
* This enum specifies modes for [WriteIssuerExtraDataCommand].
|
||||
*/
|
||||
enum class IssuerDataMode(val code: Byte) {
|
||||
|
||||
|
||||
/**
|
||||
* This mode is required to read issuer data from the card.
|
||||
*/
|
||||
ReadData(0),
|
||||
|
||||
/**
|
||||
* This mode is required to write issuer data to the card.
|
||||
*/
|
||||
WriteData(0),
|
||||
/**
|
||||
* This mode is required to read issuer extra data from the card.
|
||||
*/
|
||||
ReadExtraData(1),
|
||||
/**
|
||||
* This mode is required to initiate writing issuer extra data to the card.
|
||||
*/
|
||||
InitializeWritingExtraData(1),
|
||||
/**
|
||||
* With this mode, the command writes part of issuer extra data
|
||||
* (block of a size [WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE]) to the card.
|
||||
*/
|
||||
WriteExtraData(2),
|
||||
/**
|
||||
* This mode is used after the issuer extra data was fully written to the card.
|
||||
* Under this mode the command provides the issuer signature
|
||||
* to confirm the validity of data that was written to card.
|
||||
*/
|
||||
FinalizeExtraData(3);
|
||||
|
||||
companion object {
|
||||
private val values = values()
|
||||
fun byCode(code: Byte): IssuerDataMode? = values.find { it.code == code }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.commands
|
||||
package com.tangem.commands.common
|
||||
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
@ -2,7 +2,10 @@ package com.tangem.common.tlv
|
|||
|
||||
import com.tangem.Log
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.tasks.TaskError
|
||||
import java.util.*
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.common.tlv
|
|||
|
||||
import com.tangem.Log
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.common.extensions.toDate
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toInt
|
||||
|
|
@ -132,7 +133,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
|
|||
TlvValueType.IssuerDataMode -> {
|
||||
typeCheck<T, IssuerDataMode>(tag)
|
||||
try {
|
||||
IssuerDataMode.byCode(tlvValue.toInt()) as T
|
||||
IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T
|
||||
} catch (exception: Exception) {
|
||||
logException(tag, tlvValue.toInt().toString(), exception)
|
||||
throw TaskError.ConvertError()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.ReadIssuerDataCommand
|
||||
import com.tangem.commands.ReadIssuerDataResponse
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task<ReadIssuerDataResponse>() {
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
|
||||
val command = ReadIssuerDataCommand()
|
||||
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val data = result.data
|
||||
if (data.issuerData.isEmpty()) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
return@sendCommand
|
||||
}
|
||||
val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!!
|
||||
val issuerDataToVerify = IssuerDataToVerify(
|
||||
cardEnvironment.cardId!!,
|
||||
data.issuerData,
|
||||
data.issuerDataCounter
|
||||
)
|
||||
if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.ReadIssuerExtraDataCommand
|
||||
import com.tangem.commands.ReadIssuerExtraDataResponse
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.WriteIssuerDataCommand
|
||||
import com.tangem.commands.WriteIssuerDataResponse
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
class WriteIssuerDataTask(
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null
|
||||
) : Task<WriteIssuerDataResponse>() {
|
||||
|
||||
private lateinit var card: Card
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
card = currentCard!!
|
||||
val command = WriteIssuerDataCommand(
|
||||
issuerData, issuerDataSignature, issuerDataCounter
|
||||
)
|
||||
if (!isCounterValid(issuerDataCounter)) {
|
||||
completeNfcSession(TaskError.MissingCounter())
|
||||
callback(TaskEvent.Completion(TaskError.MissingCounter()))
|
||||
} else if (!verifySignature(command, cardEnvironment.cardId!!)) {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?): Boolean =
|
||||
if (isCounterRequired()) issuerDataCounter != null else true
|
||||
|
||||
private fun isCounterRequired(): Boolean =
|
||||
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
|
||||
|
||||
private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean {
|
||||
return command.verify(
|
||||
issuerPublicKey ?: card.issuerPublicKey!!,
|
||||
issuerDataSignature,
|
||||
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.WriteIssuerDataResponse
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue