Updated on 2026-08-14
This commit is contained in:
parent
5821fafe06
commit
73777c728a
8 changed files with 92 additions and 79 deletions
|
|
@ -113,7 +113,7 @@ class CardManager(
|
|||
*/
|
||||
fun readIssuerExtraData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
val task = ReadIssuerExtraDataTask()
|
||||
val task = ReadIssuerExtraDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +171,7 @@ class CardManager(
|
|||
val task = WriteIssuerExtraDataTask(
|
||||
issuerData,
|
||||
startingSignature, finalizingSignature,
|
||||
config.issuerPublicKey,
|
||||
issuerDataCounter
|
||||
)
|
||||
runTask(task, cardId, callback)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem
|
||||
|
||||
data class Config(
|
||||
val linkedTerminal: Boolean = true
|
||||
class Config(
|
||||
val linkedTerminal: Boolean = true,
|
||||
val issuerPublicKey: ByteArray? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
interface IssuerDataVerifier {
|
||||
fun verify(
|
||||
issuerPublicKey: ByteArray, signature: ByteArray, issuerDataToVerify: IssuerDataToVerify
|
||||
): Boolean
|
||||
}
|
||||
|
||||
class IssuerDataToVerify(
|
||||
val cardId: String,
|
||||
val issuerData: ByteArray?,
|
||||
val issuerDataCounter: Int? = null,
|
||||
val issuerExtraDataSize: Int? = null
|
||||
)
|
||||
|
||||
class DefaultIssuerDataVerifier : IssuerDataVerifier {
|
||||
override fun verify(
|
||||
issuerPublicKey: ByteArray,
|
||||
signature: ByteArray,
|
||||
issuerDataToVerify: IssuerDataToVerify
|
||||
): Boolean {
|
||||
|
||||
val tlvEncoder = TlvEncoder()
|
||||
val dataToVerify = ByteArrayOutputStream()
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, issuerDataToVerify.cardId))
|
||||
issuerDataToVerify.issuerData?.let { dataToVerify.write(it) }
|
||||
issuerDataToVerify.issuerDataCounter?.let { counter ->
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, counter))
|
||||
}
|
||||
issuerDataToVerify.issuerExtraDataSize?.let {
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.Size, it))
|
||||
}
|
||||
return CryptoUtils.verify(issuerPublicKey, dataToVerify.toByteArray(), signature)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.common.apdu.ResponseApdu
|
|||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.ReadIssuerExtraDataTask
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
class ReadIssuerExtraDataResponse(
|
||||
|
|
@ -53,7 +52,9 @@ class ReadIssuerExtraDataResponse(
|
|||
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
|
||||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
*/
|
||||
class ReadIssuerExtraDataCommand : CommandSerializer<ReadIssuerExtraDataResponse>() {
|
||||
class ReadIssuerExtraDataCommand(
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<ReadIssuerExtraDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
var offset: Int = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ class WriteIssuerExtraDataCommand(
|
|||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : CommandSerializer<WriteIssuerDataResponse>() {
|
||||
private val issuerDataCounter: Int? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
var mode: IssuerDataMode = IssuerDataMode.ExtraData
|
||||
var offset: Int = 0
|
||||
|
|
|
|||
|
|
@ -3,20 +3,19 @@ package com.tangem.tasks
|
|||
import com.tangem.commands.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* This task performs [ReadIssuerExtraDataCommand] repeatedly until
|
||||
* the issuer extra data is fully retrieved.
|
||||
*/
|
||||
internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
||||
internal class ReadIssuerExtraDataTask(
|
||||
private val issuerPublicKey: ByteArray?) : Task<ReadIssuerExtraDataResponse>() {
|
||||
|
||||
private val issuerData = ByteArrayOutputStream()
|
||||
private var issuerDataSize = 0
|
||||
private lateinit var card: Card
|
||||
private lateinit var cardEnvironment: CardEnvironment
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
|
|
@ -24,13 +23,13 @@ internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
|||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit
|
||||
) {
|
||||
card = currentCard!!
|
||||
this.cardEnvironment = cardEnvironment
|
||||
val command = ReadIssuerExtraDataCommand()
|
||||
readIssuerData(command, cardEnvironment, callback)
|
||||
readIssuerData(command, callback)
|
||||
}
|
||||
|
||||
private fun readIssuerData(
|
||||
command: ReadIssuerExtraDataCommand,
|
||||
cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
|
||||
if (issuerDataSize != 0) {
|
||||
|
|
@ -45,8 +44,9 @@ internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
|||
is CompletionResult.Success -> {
|
||||
if (result.data.size != null) {
|
||||
if (result.data.size == 0) {
|
||||
completeNfcSession(TaskError.NoData())
|
||||
callback(TaskEvent.Completion(TaskError.NoData()))
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
return@sendCommand
|
||||
}
|
||||
issuerDataSize = result.data.size
|
||||
|
|
@ -54,9 +54,9 @@ internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
|||
issuerData.write(result.data.issuerData)
|
||||
if (result.data.issuerDataSignature == null) {
|
||||
command.offset = issuerData.size()
|
||||
readIssuerData(command, cardEnvironment, callback)
|
||||
readIssuerData(command, callback)
|
||||
} else {
|
||||
completeTask(result.data, callback)
|
||||
completeTask(result.data, command, callback)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -69,9 +69,15 @@ internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun completeTask(data: ReadIssuerExtraDataResponse,
|
||||
private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
if (isIssuerDataValid(data.issuerDataCounter, data.issuerDataSignature!!)) {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
|
||||
val dataToVerify = IssuerDataToVerify(
|
||||
cardEnvironment.cardId!!,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataCounter
|
||||
)
|
||||
if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) {
|
||||
completeNfcSession()
|
||||
val finalResult = ReadIssuerExtraDataResponse(
|
||||
data.cardId,
|
||||
|
|
@ -87,21 +93,4 @@ internal class ReadIssuerExtraDataTask : Task<ReadIssuerExtraDataResponse>() {
|
|||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun isIssuerDataValid(issuerDataCounter: Int?, signature: ByteArray): Boolean {
|
||||
val tlvEncoder = TlvEncoder()
|
||||
val dataToVerify = ByteArrayOutputStream()
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, card.cardId))
|
||||
dataToVerify.write(issuerData.toByteArray())
|
||||
if (counterIncludedInSignature(issuerDataCounter)) {
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, issuerDataCounter))
|
||||
}
|
||||
return CryptoUtils.verify(card.issuerPublicKey!!, dataToVerify.toByteArray(), signature)
|
||||
}
|
||||
|
||||
private fun counterIncludedInSignature(issuerDataCounter: Int?): Boolean {
|
||||
return issuerDataCounter != null &&
|
||||
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.Log
|
||||
|
|
@ -8,6 +7,7 @@ import com.tangem.commands.Card
|
|||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
|
|
@ -52,9 +52,8 @@ sealed class TaskError(val code: Int): Exception() {
|
|||
|
||||
class UnknownError: TaskError(6000)
|
||||
|
||||
//Input Data Errors
|
||||
//Issuer Data Errors
|
||||
class MissingCounter: TaskError(7001)
|
||||
class NoData: TaskError(7002)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ package com.tangem.tasks
|
|||
import com.tangem.commands.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully
|
||||
|
|
@ -24,32 +20,35 @@ internal class WriteIssuerExtraDataTask(
|
|||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : Task<WriteIssuerDataResponse>() {
|
||||
|
||||
private lateinit var card: Card
|
||||
private lateinit var cardEnvironment: CardEnvironment
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
card = currentCard!!
|
||||
if (!isCounterValid(issuerDataCounter)) {
|
||||
completeNfcSession(TaskError.MissingCounter())
|
||||
callback(TaskEvent.Completion(TaskError.MissingCounter()))
|
||||
} else if (!verifySignatures()) {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
this.cardEnvironment = cardEnvironment
|
||||
val command = WriteIssuerExtraDataCommand(
|
||||
issuerData, startingSignature, finalizingSignature, issuerDataCounter
|
||||
)
|
||||
writeIssuerData(command, cardEnvironment, callback)
|
||||
if (!isCounterValid(issuerDataCounter)) {
|
||||
completeNfcSession(TaskError.MissingCounter())
|
||||
callback(TaskEvent.Completion(TaskError.MissingCounter()))
|
||||
} else if (!verifySignatures(command)) {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
|
||||
writeIssuerData(command, callback)
|
||||
}
|
||||
|
||||
private fun writeIssuerData(
|
||||
command: WriteIssuerExtraDataCommand,
|
||||
cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
if (command.mode == IssuerDataMode.WriteExtraData) {
|
||||
|
|
@ -62,7 +61,7 @@ internal class WriteIssuerExtraDataTask(
|
|||
when (command.mode) {
|
||||
IssuerDataMode.ExtraData -> {
|
||||
command.mode = IssuerDataMode.WriteExtraData
|
||||
writeIssuerData(command, cardEnvironment, callback)
|
||||
writeIssuerData(command, callback)
|
||||
return@sendCommand
|
||||
}
|
||||
IssuerDataMode.WriteExtraData -> {
|
||||
|
|
@ -70,7 +69,7 @@ internal class WriteIssuerExtraDataTask(
|
|||
if (command.offset >= issuerData.size) {
|
||||
command.mode = IssuerDataMode.FinalizeExtraData
|
||||
}
|
||||
writeIssuerData(command, cardEnvironment, callback)
|
||||
writeIssuerData(command, callback)
|
||||
return@sendCommand
|
||||
}
|
||||
IssuerDataMode.FinalizeExtraData -> {
|
||||
|
|
@ -96,32 +95,14 @@ internal class WriteIssuerExtraDataTask(
|
|||
private fun isCounterRequired(): Boolean =
|
||||
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
|
||||
|
||||
private fun verifySignatures(): Boolean = verifyFirstSignature() && verifyFinalizingSignature()
|
||||
private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
|
||||
val cardId = cardEnvironment.cardId!!
|
||||
|
||||
private fun verifyFirstSignature(): Boolean {
|
||||
val tlvEncoder = TlvEncoder()
|
||||
val dataToVerify = ByteArrayOutputStream()
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, card.cardId))
|
||||
if (isCounterRequired()) {
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, issuerDataCounter))
|
||||
}
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.Size, issuerData.size))
|
||||
val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size)
|
||||
val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
|
||||
return CryptoUtils.verify(
|
||||
card.issuerPublicKey!!, dataToVerify.toByteArray(), startingSignature
|
||||
)
|
||||
}
|
||||
|
||||
private fun verifyFinalizingSignature(): Boolean {
|
||||
val tlvEncoder = TlvEncoder()
|
||||
val dataToVerify = ByteArrayOutputStream()
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, card.cardId))
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerData, issuerData))
|
||||
if (isCounterRequired()) {
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, issuerDataCounter))
|
||||
}
|
||||
return CryptoUtils.verify(
|
||||
card.issuerPublicKey!!, dataToVerify.toByteArray(), finalizingSignature
|
||||
)
|
||||
return command.verify(publicKey, startingSignature, firstData) &&
|
||||
command.verify(publicKey, finalizingSignature, secondData)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue