Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-20 12:11:36 +03:00
parent 5821fafe06
commit 73777c728a
8 changed files with 92 additions and 79 deletions

View file

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

View file

@ -1,5 +1,6 @@
package com.tangem
data class Config(
val linkedTerminal: Boolean = true
class Config(
val linkedTerminal: Boolean = true,
val issuerPublicKey: ByteArray? = null
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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