Updated on 2026-08-14
This commit is contained in:
commit
78f819cf01
25 changed files with 839 additions and 69 deletions
|
|
@ -26,5 +26,5 @@ public interface AdaliteApi {
|
|||
|
||||
@Headers("Content-Type: application/json")
|
||||
@POST(ServerApiAdalite.ADALITE_SEND)
|
||||
Call<List> adaliteSend(@Body AdaliteBody adaliteBody);
|
||||
Call<String> adaliteSend(@Body AdaliteBody adaliteBody);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public class ServerApiAdalite {
|
|||
|
||||
private int requestsCount = 0;
|
||||
|
||||
private final String adaliteURL1 = "https://explorer2.adalite.io"; //TODO: make random selection, add more?, move
|
||||
private final String adaliteURL1 = "https://explorer3.adalite.io"; //TODO: make random selection, add more?, move
|
||||
private final String adaliteURL2 = "https://nodes.southeastasia.cloudapp.azure.com";
|
||||
|
||||
private String currentURL = adaliteURL1;
|
||||
|
|
@ -48,7 +48,7 @@ public class ServerApiAdalite {
|
|||
|
||||
void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo);
|
||||
|
||||
void onSuccess(String method, List listResponse);
|
||||
void onSuccess(String method, String stringResponse);
|
||||
|
||||
void onFail(String method, String message);
|
||||
}
|
||||
|
|
@ -145,10 +145,10 @@ public class ServerApiAdalite {
|
|||
break;
|
||||
|
||||
case ADALITE_SEND:
|
||||
Call<List> sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx));
|
||||
sendCall.enqueue(new Callback<List>() {
|
||||
Call<String> sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx));
|
||||
sendCall.enqueue(new Callback<String>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<List> call, @NonNull Response<List> response) {
|
||||
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
|
||||
requestsCount--;
|
||||
|
||||
if (response.code() == 200) {
|
||||
|
|
@ -166,7 +166,7 @@ public class ServerApiAdalite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<List> call, @NonNull Throwable t) {
|
||||
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage());
|
||||
requestsCount--;
|
||||
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ public class CardanoEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(String method, List listResponse) {
|
||||
public void onSuccess(String method, String stringResponse) {
|
||||
Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
|
|
@ -731,30 +731,19 @@ public class CardanoEngine extends CoinEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(String method, List listResponse) {
|
||||
public void onSuccess(String method, String stringResponse) {
|
||||
if (method.equals(ServerApiAdalite.ADALITE_SEND)) {
|
||||
String resultString = listResponse.toString();
|
||||
try {
|
||||
if (resultString.isEmpty()) {
|
||||
ctx.setError("No response from node");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else { // TODO: Make check for a valid send response
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null) {
|
||||
ctx.setError(e.getMessage());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
} else {
|
||||
ctx.setError(e.getClass().getName());
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
Log.e(TAG, resultString);
|
||||
}
|
||||
if (stringResponse.equals("\"Transaction sent successfully!\"")) {
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
} else { // TODO: Make check for a valid send response
|
||||
ctx.setError(stringResponse);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.e(TAG, "onFail: " + method + " " + message);
|
||||
|
|
|
|||
|
|
@ -96,8 +96,23 @@ class CardManager(
|
|||
*/
|
||||
fun readIssuerData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
val getIssuerDataCommand = ReadIssuerDataCommand()
|
||||
val task = SingleCommandTask(getIssuerDataCommand)
|
||||
val task = ReadIssuerDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This task retrieves Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. . For example, this field may contain photo or
|
||||
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
|
||||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param callback is triggered on the completion of the [ReadIssuerExtraDataTask],
|
||||
* provides card response in the form of [ReadIssuerExtraDataResponse].
|
||||
*/
|
||||
fun readIssuerExtraData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
val task = ReadIssuerExtraDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +123,7 @@ class CardManager(
|
|||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card).
|
||||
* @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key.
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
|
||||
* provides card response in the form of [WriteIssuerDataResponse].
|
||||
|
|
@ -118,12 +133,46 @@ class CardManager(
|
|||
issuerDataSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
val writeIssuerDataCommand = WriteIssuerDataCommand(
|
||||
val task = WriteIssuerDataTask(
|
||||
issuerData,
|
||||
issuerDataSignature,
|
||||
issuerDataCounter,
|
||||
config.issuerPublicKey
|
||||
)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This task writes Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
|
||||
* The issuer defines purpose of use, format and payload of Issuer Data.
|
||||
* For example, this field may contain a photo or biometric information for ID card products.
|
||||
* Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed
|
||||
* to write entire Issuer_Extra_Data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
|
||||
* provides card response in the form of [WriteIssuerDataResponse].
|
||||
*/
|
||||
fun writeIssuerExtraData(cardId: String,
|
||||
issuerData: ByteArray,
|
||||
startingSignature: ByteArray,
|
||||
finalizingSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
val task = WriteIssuerExtraDataTask(
|
||||
issuerData,
|
||||
startingSignature, finalizingSignature,
|
||||
config.issuerPublicKey,
|
||||
issuerDataCounter
|
||||
)
|
||||
val task = SingleCommandTask(writeIssuerDataCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ interface CardManagerDelegate {
|
|||
*/
|
||||
fun onSecurityDelay(ms: Int, totalDurationSeconds: Int)
|
||||
|
||||
/**
|
||||
* It is called when long tasks are performed.
|
||||
* A user is expected to hold the card until the task is complete.
|
||||
*/
|
||||
fun onDelay(total: Int, current: Int, step: Int)
|
||||
|
||||
/**
|
||||
* It is called when user takes the card away from the Android device during the scanning
|
||||
* (for example when security delay is in progress) and the TagLostException is received.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem
|
||||
|
||||
data class Config(
|
||||
val linkedTerminal: Boolean = true
|
||||
class Config(
|
||||
val linkedTerminal: Boolean = true,
|
||||
val issuerPublicKey: ByteArray? = null
|
||||
)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
@ -50,12 +50,16 @@ class ReadIssuerDataResponse(
|
|||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class ReadIssuerDataCommand : CommandSerializer<ReadIssuerDataResponse>() {
|
||||
class ReadIssuerDataCommand(
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<ReadIssuerDataResponse>(),
|
||||
IssuerDataVerifier by verifier {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
class ReadIssuerExtraDataResponse(
|
||||
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
*/
|
||||
val cardId: String,
|
||||
|
||||
/**
|
||||
* Size of all Issuer_Extra_Data field.
|
||||
*/
|
||||
val size: Int?,
|
||||
|
||||
/**
|
||||
* 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 [SettingsMask.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 [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask]
|
||||
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
|
||||
*/
|
||||
val issuerDataCounter: Int?
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
/**
|
||||
* This command retrieves Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. . For example, this field may contain photo or
|
||||
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
|
||||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
*/
|
||||
class ReadIssuerExtraDataCommand(
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<ReadIssuerExtraDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
var offset: Int = 0
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData)
|
||||
tlvBuilder.append(TlvTag.Offset, offset)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? {
|
||||
val tlvData = responseApdu.getTlvData() ?: return null
|
||||
|
||||
return try {
|
||||
val mapper = TlvMapper(tlvData)
|
||||
ReadIssuerExtraDataResponse(
|
||||
cardId = mapper.map(TlvTag.CardId),
|
||||
size = mapper.mapOptional(TlvTag.Size),
|
||||
issuerData = mapper.mapOptional(TlvTag.IssuerData) ?: byteArrayOf(),
|
||||
issuerDataSignature = mapper.mapOptional(TlvTag.IssuerDataSignature),
|
||||
issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* This mode value specifies that this command retrieves Issuer EXTRA data from the card
|
||||
* (with value 0 the command will get instead simple Issuer Data from the card).
|
||||
*/
|
||||
const val EXTRA_DATA_MODE = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
@ -33,13 +32,15 @@ class WriteIssuerDataResponse(
|
|||
class WriteIssuerDataCommand(
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : CommandSerializer<WriteIssuerDataResponse>() {
|
||||
private val issuerDataCounter: Int? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData)
|
||||
tlvBuilder.append(TlvTag.IssuerData, issuerData)
|
||||
tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature)
|
||||
tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* This command writes Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
|
||||
* The issuer defines purpose of use, format and payload of Issuer Data.
|
||||
* For example, this field may contain a photo or biometric information for ID card products.
|
||||
* Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed
|
||||
* to write entire Issuer_Extra_Data.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @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,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
|
||||
|
||||
var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData
|
||||
var offset: Int = 0
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Mode, mode)
|
||||
|
||||
when (mode) {
|
||||
IssuerDataMode.InitializeWritingExtraData -> {
|
||||
tlvBuilder.append(TlvTag.Size, issuerData.size)
|
||||
tlvBuilder.append(TlvTag.IssuerDataSignature, startingSignature)
|
||||
tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter)
|
||||
}
|
||||
IssuerDataMode.WriteExtraData -> {
|
||||
tlvBuilder.append(TlvTag.IssuerData, getDataToWrite())
|
||||
tlvBuilder.append(TlvTag.Offset, offset)
|
||||
}
|
||||
IssuerDataMode.FinalizeExtraData -> {
|
||||
tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature)
|
||||
}
|
||||
}
|
||||
return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
private fun getDataToWrite(): ByteArray =
|
||||
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(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? {
|
||||
val tlvData = responseApdu.getTlvData() ?: return null
|
||||
|
||||
return try {
|
||||
val mapper = TlvMapper(tlvData)
|
||||
WriteIssuerDataResponse(
|
||||
cardId = mapper.map(TlvTag.CardId)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SINGLE_WRITE_SIZE = 1524
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.commands.common
|
||||
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
|
||||
/**
|
||||
* This enum specifies modes for [WriteIssuerExtraDataCommand].
|
||||
*/
|
||||
enum class IssuerDataMode(val code: Byte) {
|
||||
|
||||
|
||||
/**
|
||||
* This mode is required to read issuer data from the card.
|
||||
*/
|
||||
ReadData(0),
|
||||
|
||||
/**
|
||||
* This mode is required to write issuer data to the card.
|
||||
*/
|
||||
WriteData(0),
|
||||
/**
|
||||
* This mode is required to read issuer extra data from the card.
|
||||
*/
|
||||
ReadExtraData(1),
|
||||
/**
|
||||
* This mode is required to initiate writing issuer extra data to the card.
|
||||
*/
|
||||
InitializeWritingExtraData(1),
|
||||
/**
|
||||
* With this mode, the command writes part of issuer extra data
|
||||
* (block of a size [WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE]) to the card.
|
||||
*/
|
||||
WriteExtraData(2),
|
||||
/**
|
||||
* This mode is used after the issuer extra data was fully written to the card.
|
||||
* Under this mode the command provides the issuer signature
|
||||
* to confirm the validity of data that was written to card.
|
||||
*/
|
||||
FinalizeExtraData(3);
|
||||
|
||||
companion object {
|
||||
private val values = values()
|
||||
fun byCode(code: Byte): IssuerDataMode? = values.find { it.code == code }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.commands.common
|
||||
|
||||
import com.tangem.common.tlv.TlvEncoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
interface IssuerDataVerifier {
|
||||
fun verify(
|
||||
issuerPublicKey: ByteArray, signature: ByteArray, issuerDataToVerify: IssuerDataToVerify
|
||||
): Boolean
|
||||
}
|
||||
|
||||
class IssuerDataToVerify(
|
||||
val cardId: String,
|
||||
val issuerData: ByteArray?,
|
||||
val issuerDataCounter: Int? = null,
|
||||
val issuerExtraDataSize: Int? = null
|
||||
)
|
||||
|
||||
class DefaultIssuerDataVerifier : IssuerDataVerifier {
|
||||
override fun verify(
|
||||
issuerPublicKey: ByteArray,
|
||||
signature: ByteArray,
|
||||
issuerDataToVerify: IssuerDataToVerify
|
||||
): Boolean {
|
||||
|
||||
val tlvEncoder = TlvEncoder()
|
||||
val dataToVerify = ByteArrayOutputStream()
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, issuerDataToVerify.cardId))
|
||||
issuerDataToVerify.issuerData?.let { dataToVerify.write(it) }
|
||||
issuerDataToVerify.issuerDataCounter?.let { counter ->
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, counter))
|
||||
}
|
||||
issuerDataToVerify.issuerExtraDataSize?.let {
|
||||
dataToVerify.write(tlvEncoder.encodeValue(TlvTag.Size, it))
|
||||
}
|
||||
return CryptoUtils.verify(issuerPublicKey, dataToVerify.toByteArray(), signature)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,15 @@ package com.tangem.common.extensions
|
|||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
fun Int.toByteArray(): ByteArray {
|
||||
val bufferSize = Int.SIZE_BYTES
|
||||
val buffer = ByteBuffer.allocate(bufferSize)
|
||||
buffer.putInt(this)
|
||||
return buffer.array()
|
||||
fun Int.toByteArray(size: Int = Int.SIZE_BYTES): ByteArray {
|
||||
if (size == Int.SIZE_BYTES) {
|
||||
val buffer = ByteBuffer.allocate(size)
|
||||
buffer.putInt(this)
|
||||
return buffer.array()
|
||||
} else if (size == Short.SIZE_BYTES){
|
||||
return byteArrayOf(
|
||||
(this ushr 8).toByte(),
|
||||
this.toByte())
|
||||
}
|
||||
return byteArrayOf()
|
||||
}
|
||||
|
|
@ -2,22 +2,33 @@ package com.tangem.common.tlv
|
|||
|
||||
import com.tangem.Log
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.tasks.TaskError
|
||||
import java.time.Year
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Encodes information that is to be written on the card from parsed classes into [ByteArray]
|
||||
* (according to the provided [TlvTag] and corresponding [TlvValueType])
|
||||
* and then forms [Tlv] with the encoded values.
|
||||
*/
|
||||
class TlvEncoder {
|
||||
/**
|
||||
|
||||
* @param value information that is to be encoded into [Tlv].
|
||||
*/
|
||||
internal inline fun <reified T> encode(tag: TlvTag, value: T?): Tlv {
|
||||
if (value != null) {
|
||||
return Tlv(tag, encodeValue(value, tag))
|
||||
return Tlv(tag, encodeValue(tag, value))
|
||||
} else {
|
||||
Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null")
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> encodeValue(value: T, tag: TlvTag): ByteArray {
|
||||
internal inline fun <reified T> encodeValue(tag: TlvTag, value: T): ByteArray {
|
||||
return when (tag.valueType()) {
|
||||
TlvValueType.HexString -> {
|
||||
typeCheck<T, String>(tag)
|
||||
|
|
@ -31,7 +42,11 @@ class TlvEncoder {
|
|||
typeCheck<T, String>(tag)
|
||||
(value as String).toByteArray()
|
||||
}
|
||||
TlvValueType.IntValue -> {
|
||||
TlvValueType.Uint16 -> {
|
||||
typeCheck<T, Int>(tag)
|
||||
(value as Int).toByteArray(2)
|
||||
}
|
||||
TlvValueType.Uint32 -> {
|
||||
typeCheck<T, Int>(tag)
|
||||
(value as Int).toByteArray()
|
||||
}
|
||||
|
|
@ -74,6 +89,10 @@ class TlvEncoder {
|
|||
typeCheck<T, SigningMethod>(tag)
|
||||
(value as SigningMethod).rawValue.toByteArray()
|
||||
}
|
||||
TlvValueType.IssuerDataMode -> {
|
||||
typeCheck<T, IssuerDataMode>(tag)
|
||||
byteArrayOf((value as IssuerDataMode).code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.common.tlv
|
|||
|
||||
import com.tangem.Log
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.common.extensions.toDate
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toInt
|
||||
|
|
@ -34,7 +35,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
|
|||
|
||||
/**
|
||||
* Finds [Tlv] by its [TlvTag].
|
||||
* Throws [MissingTagException] if [Tlv] is not found,
|
||||
* Throws [TaskError.MissingTag] if [Tlv] is not found,
|
||||
* otherwise converts [Tlv] value to [T].
|
||||
*
|
||||
* @param tag [TlvTag] of a [Tlv] which value is to be returned.
|
||||
|
|
@ -61,7 +62,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
|
|||
typeCheck<T, String>(tag)
|
||||
tlvValue.toUtf8() as T
|
||||
}
|
||||
TlvValueType.IntValue -> {
|
||||
TlvValueType.Uint16, TlvValueType.Uint32 -> {
|
||||
typeCheck<T, Int>(tag)
|
||||
try {
|
||||
tlvValue.toInt() as T
|
||||
|
|
@ -129,6 +130,15 @@ class TlvMapper(val tlvList: List<Tlv>) {
|
|||
throw TaskError.ConvertError()
|
||||
}
|
||||
}
|
||||
TlvValueType.IssuerDataMode -> {
|
||||
typeCheck<T, IssuerDataMode>(tag)
|
||||
try {
|
||||
IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T
|
||||
} catch (exception: Exception) {
|
||||
logException(tag, tlvValue.toInt().toString(), exception)
|
||||
throw TaskError.ConvertError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ package com.tangem.common.tlv
|
|||
enum class TlvValueType {
|
||||
HexString,
|
||||
Utf8String,
|
||||
IntValue,
|
||||
Uint16,
|
||||
Uint32,
|
||||
BoolValue,
|
||||
ByteArray,
|
||||
EllipticCurve,
|
||||
|
|
@ -14,7 +15,8 @@ enum class TlvValueType {
|
|||
ProductMask,
|
||||
SettingsMask,
|
||||
CardStatus,
|
||||
SigningMethod
|
||||
SigningMethod,
|
||||
IssuerDataMode
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,6 +62,10 @@ enum class TlvTag(val code: Int) {
|
|||
IssuerDataSignature(0x33),
|
||||
IssuerTransactionSignature(0x34),
|
||||
IssuerDataCounter(0x35),
|
||||
Size(0x25),
|
||||
Mode(0x23),
|
||||
Offset(0x24),
|
||||
|
||||
|
||||
IsActivated(0x3A),
|
||||
ActivationSeed(0x3B),
|
||||
|
|
@ -113,13 +119,16 @@ enum class TlvTag(val code: Int) {
|
|||
TlvValueType.Utf8String
|
||||
CurveId -> TlvValueType.EllipticCurve
|
||||
MaxSignatures, PauseBeforePin2, RemainingSignatures,
|
||||
SignedHashes, Health, TokenDecimal, UserCounter, IssuerDataCounter -> TlvValueType.IntValue
|
||||
SignedHashes, Health, TokenDecimal,
|
||||
Offset, Size -> TlvValueType.Uint16
|
||||
UserCounter, IssuerDataCounter -> TlvValueType.Uint32
|
||||
IsActivated, TerminalIsLinked -> TlvValueType.BoolValue
|
||||
ManufactureDateTime -> TlvValueType.DateTime
|
||||
ProductMask -> TlvValueType.ProductMask
|
||||
SettingsMask -> TlvValueType.SettingsMask
|
||||
Status -> TlvValueType.CardStatus
|
||||
SigningMethod -> TlvValueType.SigningMethod
|
||||
Mode -> TlvValueType.IssuerDataMode
|
||||
else -> TlvValueType.ByteArray
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.ReadIssuerDataCommand
|
||||
import com.tangem.commands.ReadIssuerDataResponse
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task<ReadIssuerDataResponse>() {
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
|
||||
val command = ReadIssuerDataCommand()
|
||||
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val data = result.data
|
||||
if (data.issuerData.isEmpty()) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
return@sendCommand
|
||||
}
|
||||
val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!!
|
||||
val issuerDataToVerify = IssuerDataToVerify(
|
||||
cardEnvironment.cardId!!,
|
||||
data.issuerData,
|
||||
data.issuerDataCounter
|
||||
)
|
||||
if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.ReadIssuerExtraDataCommand
|
||||
import com.tangem.commands.ReadIssuerExtraDataResponse
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* This task performs [ReadIssuerExtraDataCommand] repeatedly until
|
||||
* the issuer extra data is fully retrieved.
|
||||
*/
|
||||
internal class ReadIssuerExtraDataTask(
|
||||
private val issuerPublicKey: ByteArray?) : Task<ReadIssuerExtraDataResponse>() {
|
||||
|
||||
private val issuerData = ByteArrayOutputStream()
|
||||
private var issuerDataSize = 0
|
||||
private lateinit var card: Card
|
||||
private lateinit var cardEnvironment: CardEnvironment
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit
|
||||
) {
|
||||
card = currentCard!!
|
||||
this.cardEnvironment = cardEnvironment
|
||||
val command = ReadIssuerExtraDataCommand()
|
||||
readIssuerData(command, callback)
|
||||
}
|
||||
|
||||
private fun readIssuerData(
|
||||
command: ReadIssuerExtraDataCommand,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
|
||||
if (issuerDataSize != 0) {
|
||||
delegate?.onDelay(
|
||||
issuerDataSize, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
|
||||
)
|
||||
}
|
||||
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
|
||||
is CompletionResult.Success -> {
|
||||
if (result.data.size != null) {
|
||||
if (result.data.size == 0) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
return@sendCommand
|
||||
}
|
||||
issuerDataSize = result.data.size
|
||||
}
|
||||
issuerData.write(result.data.issuerData)
|
||||
if (result.data.issuerDataSignature == null) {
|
||||
command.offset = issuerData.size()
|
||||
readIssuerData(command, callback)
|
||||
} else {
|
||||
completeTask(result.data, command, callback)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
|
||||
val dataToVerify = IssuerDataToVerify(
|
||||
cardEnvironment.cardId!!,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataCounter
|
||||
)
|
||||
if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) {
|
||||
completeNfcSession()
|
||||
val finalResult = ReadIssuerExtraDataResponse(
|
||||
data.cardId,
|
||||
issuerDataSize,
|
||||
issuerData.toByteArray(),
|
||||
data.issuerDataSignature,
|
||||
data.issuerDataCounter
|
||||
)
|
||||
callback(TaskEvent.Event(finalResult))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.Log
|
||||
|
|
@ -8,6 +7,7 @@ import com.tangem.commands.Card
|
|||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
|
|
@ -51,6 +51,9 @@ sealed class TaskError(val code: Int): Exception() {
|
|||
class TagLost: TaskError(5003)
|
||||
|
||||
class UnknownError: TaskError(6000)
|
||||
|
||||
//Issuer Data Errors
|
||||
class MissingCounter: TaskError(7001)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -105,7 +108,6 @@ abstract class Task<T> {
|
|||
/**
|
||||
* Should be called on [Task] completion, whether it was successful or with failure.
|
||||
*
|
||||
* @param withError True when there is an error
|
||||
* @param taskError The error to be shown by [CardManagerDelegate]
|
||||
*/
|
||||
protected fun completeNfcSession(taskError: TaskError? = null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.WriteIssuerDataCommand
|
||||
import com.tangem.commands.WriteIssuerDataResponse
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
class WriteIssuerDataTask(
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null
|
||||
) : Task<WriteIssuerDataResponse>() {
|
||||
|
||||
private lateinit var card: Card
|
||||
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
card = currentCard!!
|
||||
val command = WriteIssuerDataCommand(
|
||||
issuerData, issuerDataSignature, issuerDataCounter
|
||||
)
|
||||
if (!isCounterValid(issuerDataCounter)) {
|
||||
completeNfcSession(TaskError.MissingCounter())
|
||||
callback(TaskEvent.Completion(TaskError.MissingCounter()))
|
||||
} else if (!verifySignature(command, cardEnvironment.cardId!!)) {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?): Boolean =
|
||||
if (isCounterRequired()) issuerDataCounter != null else true
|
||||
|
||||
private fun isCounterRequired(): Boolean =
|
||||
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
|
||||
|
||||
private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean {
|
||||
return command.verify(
|
||||
issuerPublicKey ?: card.issuerPublicKey!!,
|
||||
issuerDataSignature,
|
||||
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.WriteIssuerDataResponse
|
||||
import com.tangem.commands.WriteIssuerExtraDataCommand
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
/**
|
||||
* This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully
|
||||
* written on the card.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
*/
|
||||
internal class WriteIssuerExtraDataTask(
|
||||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : Task<WriteIssuerDataResponse>() {
|
||||
|
||||
private lateinit var card: Card
|
||||
private lateinit var cardEnvironment: CardEnvironment
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
card = currentCard!!
|
||||
this.cardEnvironment = cardEnvironment
|
||||
val command = WriteIssuerExtraDataCommand(
|
||||
issuerData, startingSignature, finalizingSignature, issuerDataCounter
|
||||
)
|
||||
if (!isCounterValid(issuerDataCounter)) {
|
||||
completeNfcSession(TaskError.MissingCounter())
|
||||
callback(TaskEvent.Completion(TaskError.MissingCounter()))
|
||||
} else if (!verifySignatures(command)) {
|
||||
completeNfcSession(TaskError.VerificationFailed())
|
||||
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
|
||||
}
|
||||
|
||||
writeIssuerData(command, callback)
|
||||
}
|
||||
|
||||
private fun writeIssuerData(
|
||||
command: WriteIssuerExtraDataCommand,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
|
||||
if (command.mode == IssuerDataMode.WriteExtraData) {
|
||||
delegate?.onDelay(issuerData.size, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE)
|
||||
}
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
|
||||
is CompletionResult.Success -> {
|
||||
when (command.mode) {
|
||||
IssuerDataMode.InitializeWritingExtraData -> {
|
||||
command.mode = IssuerDataMode.WriteExtraData
|
||||
writeIssuerData(command, callback)
|
||||
return@sendCommand
|
||||
}
|
||||
IssuerDataMode.WriteExtraData -> {
|
||||
command.offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
|
||||
if (command.offset >= issuerData.size) {
|
||||
command.mode = IssuerDataMode.FinalizeExtraData
|
||||
}
|
||||
writeIssuerData(command, callback)
|
||||
return@sendCommand
|
||||
}
|
||||
IssuerDataMode.FinalizeExtraData -> {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelled) {
|
||||
completeNfcSession(result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?): Boolean =
|
||||
if (isCounterRequired()) issuerDataCounter != null else true
|
||||
|
||||
private fun isCounterRequired(): Boolean =
|
||||
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
|
||||
|
||||
private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean {
|
||||
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
|
||||
val cardId = cardEnvironment.cardId!!
|
||||
|
||||
val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size)
|
||||
val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
|
||||
|
||||
return command.verify(publicKey, startingSignature, firstData) &&
|
||||
command.verify(publicKey, finalizingSignature, secondData)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,11 @@ package com.tangem.tangemtest
|
|||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.crypto.sign
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -15,6 +20,7 @@ class MainActivity : AppCompatActivity() {
|
|||
private lateinit var cardId: String
|
||||
private lateinit var issuerData: ByteArray
|
||||
private lateinit var issuerDataSignature: ByteArray
|
||||
private var issuerDataCounter: Int = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -41,6 +47,8 @@ class MainActivity : AppCompatActivity() {
|
|||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_read_issuer_extra_data.isEnabled = true
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
|
|
@ -101,6 +109,20 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_extra_data?.setOnClickListener { _ ->
|
||||
cardManager.readIssuerExtraData(cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_purge_wallet?.setOnClickListener { _ ->
|
||||
cardManager.purgeWallet(
|
||||
cardId) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<Button
|
||||
android:id="@+id/btn_sign"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sign transaction"
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<Button
|
||||
android:id="@+id/btn_read_issuer_data"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Read issuer data"
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
<Button
|
||||
android:id="@+id/btn_write_issuer_data"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Write issuer data"
|
||||
|
|
@ -65,19 +65,30 @@
|
|||
android:enabled="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_purge_wallet"
|
||||
android:layout_marginTop="24dp"
|
||||
android:id="@+id/btn_read_issuer_extra_data"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Purge wallet"
|
||||
android:text="Read issuer extra data"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_write_issuer_data"
|
||||
android:enabled="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_purge_wallet"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Purge wallet"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_read_issuer_extra_data"
|
||||
android:enabled="false"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_create_wallet"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Create wallet"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tangem_sdk_new
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
|
|
@ -77,7 +78,8 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay)
|
||||
readingDialog?.tvTaskText?.text =
|
||||
activity.getText(R.string.dialog_security_delay_description)
|
||||
// readingDialog?.pbSecurityDelay?.isIndeterminate = false
|
||||
|
||||
performHapticFeedback()
|
||||
|
||||
if (readingDialog?.pbSecurityDelay?.max != totalDurationSeconds) {
|
||||
readingDialog?.pbSecurityDelay?.max = totalDurationSeconds
|
||||
|
|
@ -95,6 +97,32 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
}
|
||||
}
|
||||
|
||||
override fun onDelay(total: Int, current: Int, step: Int) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.show()
|
||||
readingDialog?.tvRemainingTime?.text = (((total - current) / step) + 1).toString()
|
||||
readingDialog?.tvTaskTitle?.text = "Operation in process"
|
||||
readingDialog?.tvTaskText?.text = "Please hold the card firmly until the operation is completed…"
|
||||
|
||||
performHapticFeedback()
|
||||
|
||||
if (readingDialog?.pbSecurityDelay?.max != total) {
|
||||
readingDialog?.pbSecurityDelay?.max = total
|
||||
}
|
||||
readingDialog?.pbSecurityDelay?.progress = current
|
||||
|
||||
val animation = ObjectAnimator.ofInt(
|
||||
readingDialog?.pbSecurityDelay,
|
||||
"progress",
|
||||
current,
|
||||
current + step)
|
||||
animation.duration = 300
|
||||
animation.interpolator = DecelerateInterpolator()
|
||||
animation.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagLost() {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.show()
|
||||
|
|
@ -110,6 +138,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.show()
|
||||
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp))
|
||||
performHapticFeedback()
|
||||
}
|
||||
postUI(300) { readingDialog?.dismiss() }
|
||||
}
|
||||
|
|
@ -122,6 +151,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
readingDialog?.flError?.show()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
|
||||
readingDialog?.tvTaskText?.text = "${error::class.simpleName}: ${error.code}"
|
||||
performHapticFeedback()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +159,12 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
private fun performHapticFeedback() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
readingDialog?.llHeader?.isHapticFeedbackEnabled = true
|
||||
readingDialog?.llHeader?.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLogger() {
|
||||
Log.setLogger(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
app:layout_behavior="@string/bottom_sheet_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="#e2e5e6"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue