Updated on 2026-08-14

This commit is contained in:
Tangem 2020-06-09 09:38:19 +03:00
parent 6b25de35e5
commit 2f125e24d9
15 changed files with 358 additions and 468 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
/**
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers 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,
/**
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers 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)
)
}
}

View file

@ -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,
/**
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers 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?,
/**
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers 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)
)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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