Updated on 2026-08-14
This commit is contained in:
parent
6b25de35e5
commit
2f125e24d9
15 changed files with 358 additions and 468 deletions
|
|
@ -105,7 +105,7 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) {
|
|||
class TooManyHashesInOneTransaction : TangemSdkError(40906)
|
||||
|
||||
//Write Extra Issuer Data Errors
|
||||
class ExendedDataSizeTooLarge : TangemSdkError(41101)
|
||||
class ExtendedDataSizeTooLarge : TangemSdkError(41101)
|
||||
|
||||
//General Errors
|
||||
class NotPersonalized() : TangemSdkError(40001)
|
||||
|
|
|
|||
|
|
@ -70,19 +70,14 @@ class CheckWalletCommand(
|
|||
}
|
||||
}
|
||||
|
||||
override fun performPreCheck(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<CheckWalletResponse>) -> Unit
|
||||
): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -41,47 +41,46 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
|
||||
Log.i("Command", "Initializing ${this::class.java.simpleName}")
|
||||
if (session.environment.handleErrors) {
|
||||
if (performPreCheck(session, callback)) return
|
||||
}
|
||||
transceive(session) { result ->
|
||||
if (session.environment.handleErrors) {
|
||||
if (performAfterCheck(session, result, callback)) return@transceive
|
||||
}
|
||||
callback(result)
|
||||
}
|
||||
transceive(session, callback)
|
||||
}
|
||||
|
||||
open fun performPreCheck(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<T>) -> Unit
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
open fun performPreCheck(card: Card): TangemSdkError? = null
|
||||
|
||||
open fun performAfterCheck(
|
||||
session: CardSession,
|
||||
result: CompletionResult<T>,
|
||||
callback: (result: CompletionResult<T>) -> Unit
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
open fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? = error
|
||||
|
||||
fun transceive(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
|
||||
try {
|
||||
val apdu = serialize(session.environment)
|
||||
transceiveApdu(apdu, session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
is CompletionResult.Success -> {
|
||||
|
||||
val card = session.environment.card
|
||||
if (session.environment.handleErrors && card != null) {
|
||||
performPreCheck(card)?.let { error ->
|
||||
callback(CompletionResult.Failure(error))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val apdu = serialize(session.environment)
|
||||
transceiveApdu(apdu, session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (session.environment.handleErrors) {
|
||||
performAfterCheck(session.environment.card, result.error)?.let {
|
||||
callback(CompletionResult.Failure(it))
|
||||
return@transceiveApdu
|
||||
}
|
||||
}
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
try {
|
||||
val response = deserialize(session.environment, result.data)
|
||||
callback(CompletionResult.Success(response))
|
||||
} catch (error: TangemSdkError) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: TangemSdkError) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun transceiveApdu(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -12,18 +10,18 @@ import com.tangem.common.tlv.TlvDecoder
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
|
||||
class CreateWalletResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
|
||||
*/
|
||||
val status: CardStatus,
|
||||
/**
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* Current status of the card [1 - Empty, 2 - Loaded, 3- Purged]
|
||||
*/
|
||||
val status: CardStatus,
|
||||
/**
|
||||
|
||||
*/
|
||||
val walletPublicKey: ByteArray
|
||||
*/
|
||||
val walletPublicKey: ByteArray
|
||||
) : CommandResponse
|
||||
|
||||
/**
|
||||
|
|
@ -39,39 +37,25 @@ class CreateWalletResponse(
|
|||
*/
|
||||
class CreateWalletCommand : Command<CreateWalletResponse>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
|
||||
return when (card.status) {
|
||||
CardStatus.Empty -> null
|
||||
CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized()
|
||||
CardStatus.Loaded -> TangemSdkError.AlreadyCreated()
|
||||
CardStatus.Purged -> TangemSdkError.CardIsPurged()
|
||||
null -> TangemSdkError.CardError()
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.Purged) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardIsPurged()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.Loaded) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.AlreadyCreated()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun performAfterCheck(session: CardSession,
|
||||
result: CompletionResult<CreateWalletResponse>,
|
||||
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
@ -83,15 +67,18 @@ class CreateWalletCommand : Command<CreateWalletResponse>() {
|
|||
return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse {
|
||||
override fun deserialize(
|
||||
environment: SessionEnvironment,
|
||||
apdu: ResponseApdu
|
||||
): CreateWalletResponse {
|
||||
val tlvData = apdu.getTlvData()
|
||||
?: throw TangemSdkError.DeserializeApduFailed()
|
||||
?: throw TangemSdkError.DeserializeApduFailed()
|
||||
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
return CreateWalletResponse(
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
status = decoder.decode(TlvTag.Status),
|
||||
walletPublicKey = decoder.decode(TlvTag.WalletPublicKey)
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
status = decoder.decode(TlvTag.Status),
|
||||
walletPublicKey = decoder.decode(TlvTag.WalletPublicKey)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -31,37 +29,33 @@ class PurgeWalletResponse(
|
|||
*/
|
||||
class PurgeWalletCommand : Command<PurgeWalletResponse>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<PurgeWalletResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (session.environment.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.PurgeWalletProhibited()))
|
||||
return true
|
||||
if (card.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true) {
|
||||
return TangemSdkError.PurgeWalletProhibited()
|
||||
}
|
||||
|
||||
return when (card.status) {
|
||||
CardStatus.Loaded -> null
|
||||
CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized()
|
||||
CardStatus.Empty, CardStatus.Purged -> TangemSdkError.CardIsEmpty()
|
||||
null -> TangemSdkError.CardError()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun performAfterCheck(session: CardSession,
|
||||
result: CompletionResult<PurgeWalletResponse>,
|
||||
callback: (result: CompletionResult<PurgeWalletResponse>) -> Unit): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, environment.pin1)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.common.CardDeserializer
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -317,6 +315,8 @@ class Card(
|
|||
|
||||
/**
|
||||
* Whether the card requires issuer’s confirmation of activation.
|
||||
* is "true" if the card requires activation,
|
||||
* is 'false" if the card is activated or does not require activation
|
||||
*/
|
||||
val isActivated: Boolean,
|
||||
|
||||
|
|
@ -367,17 +367,11 @@ class Card(
|
|||
*/
|
||||
class ReadCommand : Command<Card>() {
|
||||
|
||||
override fun performAfterCheck(session: CardSession, result: CompletionResult<Card>, callback: (result: CompletionResult<Card>) -> Unit): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.Pin1Required()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin1Required()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -17,32 +17,32 @@ import com.tangem.common.tlv.TlvTag
|
|||
|
||||
class ReadIssuerDataResponse(
|
||||
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
|
||||
/**
|
||||
* Data defined by issuer.
|
||||
*/
|
||||
val issuerData: ByteArray,
|
||||
/**
|
||||
* Data defined by issuer.
|
||||
*/
|
||||
val issuerData: ByteArray,
|
||||
|
||||
/**
|
||||
* Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card).
|
||||
* Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]:
|
||||
* SHA256([cardId] | [issuerData]).
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
|
||||
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
|
||||
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
|
||||
*/
|
||||
val issuerDataSignature: ByteArray,
|
||||
/**
|
||||
* Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card).
|
||||
* Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]:
|
||||
* SHA256([cardId] | [issuerData]).
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
|
||||
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
|
||||
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
|
||||
*/
|
||||
val issuerDataSignature: ByteArray,
|
||||
|
||||
/**
|
||||
* An optional counter that protect issuer data against replay attack.
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
|
||||
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
|
||||
*/
|
||||
val issuerDataCounter: Int?
|
||||
/**
|
||||
* An optional counter that protect issuer data against replay attack.
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
|
||||
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
|
||||
*/
|
||||
val issuerDataCounter: Int?
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
|
|
@ -54,21 +54,17 @@ class ReadIssuerDataResponse(
|
|||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class ReadIssuerDataCommand(
|
||||
val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : Command<ReadIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ReadIssuerDataResponse>) -> Unit) {
|
||||
val card = session.environment.card
|
||||
if (card == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return
|
||||
}
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey()))
|
||||
return
|
||||
}
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ReadIssuerDataResponse>) -> Unit
|
||||
) {
|
||||
|
||||
val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey
|
||||
|
||||
super.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> callback(result)
|
||||
|
|
@ -78,9 +74,9 @@ class ReadIssuerDataCommand(
|
|||
return@run
|
||||
}
|
||||
val issuerDataToVerify = IssuerDataToVerify(
|
||||
card.cardId, result.data.issuerData, result.data.issuerDataCounter
|
||||
result.data.cardId, result.data.issuerData, result.data.issuerDataCounter
|
||||
)
|
||||
if (verify(publicKey, result.data.issuerDataSignature, issuerDataToVerify)) {
|
||||
if (verify(publicKey!!, result.data.issuerDataSignature, issuerDataToVerify)) {
|
||||
callback(result)
|
||||
} else {
|
||||
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
|
||||
|
|
@ -90,12 +86,12 @@ class ReadIssuerDataCommand(
|
|||
}
|
||||
}
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<ReadIssuerDataResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
return false
|
||||
issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey()
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
@ -106,15 +102,18 @@ class ReadIssuerDataCommand(
|
|||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse {
|
||||
override fun deserialize(
|
||||
environment: SessionEnvironment,
|
||||
apdu: ResponseApdu
|
||||
): ReadIssuerDataResponse {
|
||||
val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed()
|
||||
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
return ReadIssuerDataResponse(
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
issuerData = decoder.decode(TlvTag.IssuerData),
|
||||
issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature),
|
||||
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
issuerData = decoder.decode(TlvTag.IssuerData),
|
||||
issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature),
|
||||
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,37 +18,37 @@ import java.io.ByteArrayOutputStream
|
|||
|
||||
class ReadIssuerExtraDataResponse(
|
||||
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
|
||||
/**
|
||||
* Size of all Issuer_Extra_Data field.
|
||||
*/
|
||||
val size: Int?,
|
||||
/**
|
||||
* Size of all Issuer_Extra_Data field.
|
||||
*/
|
||||
val size: Int?,
|
||||
|
||||
/**
|
||||
* Data defined by issuer.
|
||||
*/
|
||||
val issuerData: ByteArray,
|
||||
/**
|
||||
* Data defined by issuer.
|
||||
*/
|
||||
val issuerData: ByteArray,
|
||||
|
||||
/**
|
||||
* Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card).
|
||||
* Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]:
|
||||
* SHA256([cardId] | [issuerData]).
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
|
||||
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
|
||||
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
|
||||
*/
|
||||
val issuerDataSignature: ByteArray?,
|
||||
/**
|
||||
* Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card).
|
||||
* Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]:
|
||||
* SHA256([cardId] | [issuerData]).
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
|
||||
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
|
||||
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
|
||||
*/
|
||||
val issuerDataSignature: ByteArray?,
|
||||
|
||||
/**
|
||||
* An optional counter that protects issuer data against replay attack.
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
|
||||
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
|
||||
*/
|
||||
val issuerDataCounter: Int?
|
||||
/**
|
||||
* An optional counter that protects issuer data against replay attack.
|
||||
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
|
||||
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
|
||||
*/
|
||||
val issuerDataCounter: Int?
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
|
|
@ -60,42 +60,38 @@ class ReadIssuerExtraDataResponse(
|
|||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
*/
|
||||
class ReadIssuerExtraDataCommand(
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : Command<ReadIssuerExtraDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
private val issuerData = ByteArrayOutputStream()
|
||||
private var offset: Int = 0
|
||||
private var issuerDataSize: Int = 0
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
val card = session.environment.card
|
||||
if (card == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey()))
|
||||
return
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return
|
||||
}
|
||||
|
||||
readIssuerData(session, card.cardId, publicKey, callback)
|
||||
issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey()
|
||||
return null
|
||||
}
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit
|
||||
) {
|
||||
val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey
|
||||
readIssuerData(session, publicKey!!, callback)
|
||||
}
|
||||
|
||||
private fun readIssuerData(
|
||||
session: CardSession,
|
||||
cardId: String, publicKey: ByteArray,
|
||||
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
session: CardSession, publicKey: ByteArray,
|
||||
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit
|
||||
) {
|
||||
|
||||
if (issuerDataSize != 0) {
|
||||
session.viewDelegate.onDelay(
|
||||
issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
|
||||
issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -112,9 +108,9 @@ class ReadIssuerExtraDataCommand(
|
|||
issuerData.write(result.data.issuerData)
|
||||
if (result.data.issuerDataSignature == null) {
|
||||
offset = issuerData.size()
|
||||
readIssuerData(session, cardId, publicKey, callback)
|
||||
readIssuerData(session, publicKey, callback)
|
||||
} else {
|
||||
completeTask(result.data, cardId, publicKey, callback)
|
||||
completeTask(result.data, publicKey, callback)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -124,21 +120,22 @@ class ReadIssuerExtraDataCommand(
|
|||
}
|
||||
}
|
||||
|
||||
private fun completeTask(data: ReadIssuerExtraDataResponse,
|
||||
cardId: String, publicKey: ByteArray,
|
||||
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
private fun completeTask(
|
||||
data: ReadIssuerExtraDataResponse, publicKey: ByteArray,
|
||||
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit
|
||||
) {
|
||||
val dataToVerify = IssuerDataToVerify(
|
||||
cardId,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataCounter
|
||||
data.cardId,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataCounter
|
||||
)
|
||||
if (verify(publicKey, data.issuerDataSignature!!, dataToVerify)) {
|
||||
val finalResult = ReadIssuerExtraDataResponse(
|
||||
data.cardId,
|
||||
issuerDataSize,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataSignature,
|
||||
data.issuerDataCounter
|
||||
data.cardId,
|
||||
issuerDataSize,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataSignature,
|
||||
data.issuerDataCounter
|
||||
)
|
||||
callback(CompletionResult.Success(finalResult))
|
||||
} else {
|
||||
|
|
@ -155,16 +152,19 @@ class ReadIssuerExtraDataCommand(
|
|||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse {
|
||||
override fun deserialize(
|
||||
environment: SessionEnvironment,
|
||||
apdu: ResponseApdu
|
||||
): ReadIssuerExtraDataResponse {
|
||||
val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed()
|
||||
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
return ReadIssuerExtraDataResponse(
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
size = decoder.decodeOptional(TlvTag.Size),
|
||||
issuerData = decoder.decodeOptional(TlvTag.IssuerData) ?: byteArrayOf(),
|
||||
issuerDataSignature = decoder.decodeOptional(TlvTag.IssuerDataSignature),
|
||||
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
size = decoder.decodeOptional(TlvTag.Size),
|
||||
issuerData = decoder.decodeOptional(TlvTag.IssuerData) ?: byteArrayOf(),
|
||||
issuerDataSignature = decoder.decodeOptional(TlvTag.IssuerDataSignature),
|
||||
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -51,16 +49,14 @@ class ReadUserDataResponse(
|
|||
*/
|
||||
class ReadUserDataCommand : Command<ReadUserDataResponse>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<ReadUserDataResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -20,10 +18,10 @@ import com.tangem.crypto.sign
|
|||
* Sums up array elements within all SIGN commands
|
||||
*/
|
||||
class SignResponse(
|
||||
val cardId: String,
|
||||
val signature: ByteArray,
|
||||
val walletRemainingSignatures: Int,
|
||||
val walletSignedHashes: Int
|
||||
val cardId: String,
|
||||
val signature: ByteArray,
|
||||
val walletRemainingSignatures: Int,
|
||||
val walletSignedHashes: Int
|
||||
) : CommandResponse
|
||||
|
||||
/**
|
||||
|
|
@ -32,60 +30,44 @@ class SignResponse(
|
|||
* @property hashes Array of transaction hashes.
|
||||
* @property cardId CID, Unique Tangem card ID number
|
||||
*/
|
||||
class SignCommand(private val hashes: Array<ByteArray>)
|
||||
: Command<SignResponse>() {
|
||||
class SignCommand(private val hashes: Array<ByteArray>) : Command<SignResponse>() {
|
||||
|
||||
//TODO: Allow signing more than 10 hashes
|
||||
|
||||
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<SignResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.walletRemainingSignatures == 0) {
|
||||
return TangemSdkError.NoRemainingSignatures()
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.Purged) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardIsPurged()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.Empty) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CardIsEmpty()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.walletRemainingSignatures == 0) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NoRemainingSignatures()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.signingMethods?.contains(SigningMethod.SignHash) != true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.SignHashesNotAvailable()))
|
||||
return true
|
||||
if (card.signingMethods?.contains(SigningMethod.SignHash) != true) {
|
||||
return TangemSdkError.SignHashesNotAvailable()
|
||||
}
|
||||
if (hashSizes == 0) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.EmptyHashes()))
|
||||
return true
|
||||
return TangemSdkError.EmptyHashes()
|
||||
}
|
||||
if (hashes.any { it.size != hashSizes }) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.HashSizeMustBeEqual()))
|
||||
return true
|
||||
return TangemSdkError.HashSizeMustBeEqual()
|
||||
}
|
||||
|
||||
return when (card.status) {
|
||||
CardStatus.Loaded -> null
|
||||
CardStatus.Empty -> TangemSdkError.
|
||||
CardIsEmpty()
|
||||
CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized()
|
||||
CardStatus.Purged -> TangemSdkError.CardIsPurged()
|
||||
null -> TangemSdkError.CardError()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun performAfterCheck(session: CardSession,
|
||||
result: CompletionResult<SignResponse>,
|
||||
callback: (result: CompletionResult<SignResponse>) -> Unit): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
@ -103,16 +85,9 @@ class SignCommand(private val hashes: Array<ByteArray>)
|
|||
}
|
||||
|
||||
private fun flattenHashes(): ByteArray {
|
||||
checkForErrors()
|
||||
return hashes.reduce { arr1, arr2 -> arr1 + arr2 }
|
||||
}
|
||||
|
||||
private fun checkForErrors() {
|
||||
if (hashes.isEmpty()) throw TangemSdkError.EmptyHashes()
|
||||
if (hashes.size > 10) throw TangemSdkError.TooManyHashesInOneTransaction()
|
||||
if (hashes.any { it.size != hashSizes }) throw TangemSdkError.HashSizeMustBeEqual()
|
||||
}
|
||||
|
||||
/**
|
||||
* Application can optionally submit a public key Terminal_PublicKey in [SignCommand].
|
||||
* Submitted key is stored by the Tangem card if it differs from a previous submitted Terminal_PublicKey.
|
||||
|
|
@ -121,7 +96,8 @@ class SignCommand(private val hashes: Array<ByteArray>)
|
|||
* (this key should be generated and securily stored by the application).
|
||||
*/
|
||||
private fun addTerminalSignature(
|
||||
environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder) {
|
||||
environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder
|
||||
) {
|
||||
environment.terminalKeys?.let { terminalKeyPair ->
|
||||
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
|
||||
tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData)
|
||||
|
|
@ -134,10 +110,10 @@ class SignCommand(private val hashes: Array<ByteArray>)
|
|||
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
return SignResponse(
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
signature = decoder.decode(TlvTag.Signature),
|
||||
walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures),
|
||||
walletSignedHashes = decoder.decode(TlvTag.SignedHashes)
|
||||
cardId = decoder.decode(TlvTag.CardId),
|
||||
signature = decoder.decode(TlvTag.Signature),
|
||||
walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures),
|
||||
walletSignedHashes = decoder.decode(TlvTag.SignedHashes)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -16,10 +14,10 @@ import com.tangem.common.tlv.TlvDecoder
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
|
||||
class WriteIssuerDataResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String
|
||||
) : CommandResponse
|
||||
|
||||
/**
|
||||
|
|
@ -33,75 +31,53 @@ class WriteIssuerDataResponse(
|
|||
* @property issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
*/
|
||||
class WriteIssuerDataCommand(
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : Command<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit): Boolean {
|
||||
val card = session.environment.card
|
||||
if (card == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return true
|
||||
}
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey()))
|
||||
return true
|
||||
?: return TangemSdkError.MissingIssuerPubicKey()
|
||||
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (issuerData.size > MAX_SIZE) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.DataSizeTooLarge()))
|
||||
return true
|
||||
return TangemSdkError.DataSizeTooLarge()
|
||||
}
|
||||
if (!isCounterValid(issuerDataCounter, card)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingCounter()))
|
||||
return true
|
||||
return TangemSdkError.MissingCounter()
|
||||
}
|
||||
if (!verifySignature(publicKey, card.cardId)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
|
||||
return true
|
||||
return TangemSdkError.VerificationFailed()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(session: CardSession,
|
||||
result: CompletionResult<WriteIssuerDataResponse>,
|
||||
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit
|
||||
): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams &&
|
||||
isCounterRequired(session.environment.card)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.DataCannotBeWritten()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) {
|
||||
return TangemSdkError.DataCannotBeWritten()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
|
||||
if (isCounterRequired(card)) issuerDataCounter != null else true
|
||||
if (isCounterRequired(card)) issuerDataCounter != null else true
|
||||
|
||||
private fun isCounterRequired(card: Card?): Boolean =
|
||||
card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
|
||||
card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true
|
||||
|
||||
private fun verifySignature(publicKey: ByteArray, cardId: String): Boolean {
|
||||
return verify(
|
||||
publicKey,
|
||||
issuerDataSignature,
|
||||
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
publicKey,
|
||||
issuerDataSignature,
|
||||
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -117,12 +93,15 @@ class WriteIssuerDataCommand(
|
|||
return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse {
|
||||
override fun deserialize(
|
||||
environment: SessionEnvironment,
|
||||
apdu: ResponseApdu
|
||||
): WriteIssuerDataResponse {
|
||||
val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed()
|
||||
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
return WriteIssuerDataResponse(
|
||||
cardId = decoder.decode(TlvTag.CardId)
|
||||
cardId = decoder.decode(TlvTag.CardId)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,89 +32,64 @@ import com.tangem.common.tlv.TlvTag
|
|||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
*/
|
||||
class WriteIssuerExtraDataCommand(
|
||||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : Command<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData
|
||||
var offset: Int = 0
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
|
||||
val card = session.environment.card
|
||||
if (card == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return
|
||||
}
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey()))
|
||||
return
|
||||
}
|
||||
|
||||
writeIssuerData(session, card.cardId, publicKey) { response ->
|
||||
when (response) {
|
||||
is CompletionResult.Success -> callback(response)
|
||||
is CompletionResult.Failure -> {
|
||||
if (response.error is TangemSdkError.InvalidParams && isCounterRequired(card)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.DataCannotBeWritten()))
|
||||
return@writeIssuerData
|
||||
}
|
||||
if (response.error is TangemSdkError.InvalidState &&
|
||||
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.OverwritingDataIsProhibited()))
|
||||
return@writeIssuerData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit
|
||||
) {
|
||||
writeIssuerData(session, callback)
|
||||
}
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit): Boolean {
|
||||
val card = session.environment.card
|
||||
if (card == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return true
|
||||
}
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey
|
||||
if (publicKey == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey()))
|
||||
return true
|
||||
}
|
||||
?: return TangemSdkError.MissingIssuerPubicKey()
|
||||
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (issuerData.size > MAX_SIZE) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.ExendedDataSizeTooLarge()))
|
||||
return true
|
||||
return TangemSdkError.DataSizeTooLarge()
|
||||
}
|
||||
if (!isCounterValid(issuerDataCounter, card)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingCounter()))
|
||||
return true
|
||||
return TangemSdkError.MissingCounter()
|
||||
}
|
||||
if (!verifySignatures(card.cardId, publicKey)) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.VerificationFailed()))
|
||||
return true
|
||||
if (!verifySignatures(publicKey, card.cardId)) {
|
||||
return TangemSdkError.VerificationFailed()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) {
|
||||
return TangemSdkError.DataCannotBeWritten()
|
||||
}
|
||||
if (error is TangemSdkError.InvalidState &&
|
||||
card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true) {
|
||||
return TangemSdkError.OverwritingDataIsProhibited()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
|
||||
if (isCounterRequired(card)) issuerDataCounter != null else true
|
||||
if (isCounterRequired(card)) issuerDataCounter != null else true
|
||||
|
||||
private fun isCounterRequired(card: Card): Boolean =
|
||||
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
|
||||
private fun isCounterRequired(card: Card?): Boolean =
|
||||
card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true
|
||||
|
||||
private fun verifySignatures(cardId: String, publicKey: ByteArray): Boolean {
|
||||
private fun verifySignatures(publicKey: ByteArray, cardId: String): Boolean {
|
||||
|
||||
val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size)
|
||||
val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
|
|
@ -124,13 +99,16 @@ class WriteIssuerExtraDataCommand(
|
|||
}
|
||||
|
||||
private fun writeIssuerData(
|
||||
session: CardSession,
|
||||
cardId: String, publicKey: ByteArray,
|
||||
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit
|
||||
) {
|
||||
|
||||
if (mode == IssuerDataMode.WriteExtraData) {
|
||||
session.viewDelegate.onDelay(issuerData.size, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE)
|
||||
session.viewDelegate.onDelay(
|
||||
issuerData.size,
|
||||
offset,
|
||||
SINGLE_WRITE_SIZE
|
||||
)
|
||||
}
|
||||
transceive(session) { result ->
|
||||
when (result) {
|
||||
|
|
@ -138,7 +116,7 @@ class WriteIssuerExtraDataCommand(
|
|||
when (mode) {
|
||||
IssuerDataMode.InitializeWritingExtraData -> {
|
||||
mode = IssuerDataMode.WriteExtraData
|
||||
writeIssuerData(session, cardId, publicKey, callback)
|
||||
writeIssuerData(session, callback)
|
||||
return@transceive
|
||||
}
|
||||
IssuerDataMode.WriteExtraData -> {
|
||||
|
|
@ -146,7 +124,7 @@ class WriteIssuerExtraDataCommand(
|
|||
if (offset >= issuerData.size) {
|
||||
mode = IssuerDataMode.FinalizeExtraData
|
||||
}
|
||||
writeIssuerData(session, cardId, publicKey, callback)
|
||||
writeIssuerData(session, callback)
|
||||
return@transceive
|
||||
}
|
||||
IssuerDataMode.FinalizeExtraData -> {
|
||||
|
|
@ -155,6 +133,11 @@ class WriteIssuerExtraDataCommand(
|
|||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (session.environment.handleErrors) {
|
||||
performAfterCheck(session.environment.card, result.error)?.let {
|
||||
callback(CompletionResult.Failure(it))
|
||||
}
|
||||
}
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
|
|
@ -188,17 +171,21 @@ class WriteIssuerExtraDataCommand(
|
|||
}
|
||||
|
||||
private fun getDataToWrite(): ByteArray =
|
||||
issuerData.copyOfRange(offset, offset + calculatePartSize())
|
||||
issuerData.copyOfRange(offset, offset + calculatePartSize())
|
||||
|
||||
private fun calculatePartSize(): Int {
|
||||
val bytesLeft = issuerData.size - offset
|
||||
return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse {
|
||||
override fun deserialize(
|
||||
environment: SessionEnvironment,
|
||||
apdu: ResponseApdu
|
||||
): WriteIssuerDataResponse {
|
||||
val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed()
|
||||
|
||||
return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId)
|
||||
return WriteIssuerDataResponse(
|
||||
cardId = TlvDecoder(tlvData).decode(TlvTag.CardId)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -34,36 +32,24 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val
|
|||
private val userCounter: Int? = null,
|
||||
private val userProtectedCounter: Int? = null) : Command<WriteUserDataResponse>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<WriteUserDataResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status == CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.NotPersonalized()
|
||||
}
|
||||
if (session.environment.card?.isActivated == true) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotActivated()))
|
||||
return true
|
||||
if (card.isActivated) {
|
||||
return TangemSdkError.NotActivated()
|
||||
}
|
||||
if (userData?.size ?: 0 > MAX_SIZE || userProtectedData?.size ?: 0 > MAX_SIZE) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.DataSizeTooLarge()))
|
||||
return true
|
||||
return TangemSdkError.DataSizeTooLarge()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(session: CardSession,
|
||||
result: CompletionResult<WriteUserDataResponse>,
|
||||
callback: (result: CompletionResult<WriteUserDataResponse>) -> Unit
|
||||
): Boolean {
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.InvalidParams) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
else -> return false
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.commands.CardStatus
|
|||
import com.tangem.commands.Command
|
||||
import com.tangem.commands.common.CardDeserializer
|
||||
import com.tangem.commands.personalization.entities.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -38,12 +37,11 @@ class PersonalizeCommand(
|
|||
private val acquirer: Acquirer? = null
|
||||
) : Command<Card>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status != CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.AlreadyPersonalized()))
|
||||
return true
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status != CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.AlreadyPersonalized()
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ fun TangemSdkError.localizedDescription(): Int {
|
|||
is TangemSdkError.Pin2OrCvcRequired -> R.string.error_operation
|
||||
is TangemSdkError.VerificationFailed -> R.string.error_verification_failed
|
||||
is TangemSdkError.DataSizeTooLarge -> R.string.error_data_size_too_large
|
||||
is TangemSdkError.ExendedDataSizeTooLarge -> R.string.error_data_size_too_large_extended
|
||||
is TangemSdkError.ExtendedDataSizeTooLarge -> R.string.error_data_size_too_large_extended
|
||||
is TangemSdkError.MissingCounter -> R.string.error_missing_counter
|
||||
is TangemSdkError.OverwritingDataIsProhibited -> R.string.error_data_cannot_be_written
|
||||
is TangemSdkError.DataCannotBeWritten -> R.string.error_data_cannot_be_written
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue