From effd4025eeff4526323854ddaa3e62f5fb8774c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Mar 2020 12:13:24 +0300 Subject: [PATCH 01/25] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 13 +-- .../com/tangem/commands/CheckWalletCommand.kt | 10 +-- .../com/tangem/commands/CommandSerializer.kt | 2 +- .../tangem/commands/CreateWalletCommand.kt | 10 +-- .../com/tangem/commands/OpenSessionCommand.kt | 41 +++++++++ .../com/tangem/commands/PurgeWalletCommand.kt | 10 +-- .../java/com/tangem/commands/ReadCommand.kt | 7 +- .../tangem/commands/ReadIssuerDataCommand.kt | 7 +- .../commands/ReadIssuerExtraDataCommand.kt | 7 +- .../tangem/commands/ReadUserDataCommand.kt | 7 +- .../java/com/tangem/commands/SignCommand.kt | 10 +-- .../tangem/commands/WriteIssuerDataCommand.kt | 7 +- .../commands/WriteIssuerExtraDataCommand.kt | 7 +- .../tangem/commands/WriteUserDataCommand.kt | 7 +- .../java/com/tangem/common/CardEnvironment.kt | 1 + .../com/tangem/common/apdu/CommandApdu.kt | 62 ++++++++----- .../com/tangem/common/apdu/ResponseApdu.kt | 37 ++++++-- .../com/tangem/common/extensions/ByteArray.kt | 18 ++++ .../main/java/com/tangem/common/tlv/TlvTag.kt | 3 +- .../java/com/tangem/crypto/CryptoUtils.kt | 38 ++++++++ .../main/java/com/tangem/crypto/Ed25519.kt | 2 +- .../com/tangem/crypto/EncryptionHelper.kt | 50 +++++++++++ .../src/main/java/com/tangem/crypto/Pbkdf2.kt | 88 +++++++++++++++++++ .../main/java/com/tangem/crypto/Secp256k1.kt | 2 +- .../src/main/java/com/tangem/tasks/Task.kt | 75 +++++++++++++--- 25 files changed, 431 insertions(+), 90 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt create mode 100644 tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..1503f8dc12 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -69,18 +69,7 @@ class CardManager( */ fun sign(hashes: Array, cardId: String, callback: (result: TaskEvent) -> Unit) { - val signCommand: SignCommand - try { - signCommand = SignCommand(hashes) - } catch (error: Exception) { - if (error is TaskError) { - callback(TaskEvent.Completion(error)) - } else { - Log.e(this::class.simpleName!!, error.message ?: "") - callback(TaskEvent.Completion(TaskError.UnknownError())) - } - return - } + val signCommand = SignCommand(hashes) val task = SingleCommandTask(signCommand) runTask(task, cardId, callback) } diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e5376f2cc7..28f0cd2d43 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -4,9 +4,6 @@ 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 @@ -52,11 +49,14 @@ class CheckWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CheckWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt index 92ee3d0d90..134474fb23 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt @@ -38,7 +38,7 @@ abstract class CommandSerializer { * @return Remaining security delay in milliseconds. */ fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey) + val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 0c142ee296..18b20f1394 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -4,9 +4,6 @@ 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 @@ -46,11 +43,14 @@ class CreateWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CreateWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt new file mode 100644 index 0000000000..9b78cb8bea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -0,0 +1,41 @@ +package com.tangem.commands + +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 OpenSessionResponse( + val sessionKeyB: ByteArray, + val uid: ByteArray +) : CommandResponse + + +class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) + return CommandApdu( + Instruction.OpenSession, tlvBuilder.serialize(), + encryptionMode = cardEnvironment.encryptionMode + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + OpenSessionResponse( + sessionKeyB = mapper.map(TlvTag.SessionKeyB), + uid = mapper.map(TlvTag.Uid) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index cef8aca229..4eb379e521 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -4,9 +4,6 @@ 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 @@ -37,11 +34,14 @@ class PurgeWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.PurgeWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 0a161f392c..b735e54d8f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -310,11 +310,14 @@ class ReadCommand : CommandSerializer() { */ tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) - return CommandApdu(Instruction.Read, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Read, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val tlvMapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index c681f5fed9..fea83e979f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -60,11 +60,14 @@ class ReadIssuerDataCommand( tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 4e3f757395..3a6e8309ff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -67,11 +67,14 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 3628d9cad7..ba5954fe01 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -57,11 +57,14 @@ class ReadUserDataCommand: CommandSerializer() { builder.append(TlvTag.CardId, cardEnvironment.cardId) builder.append(TlvTag.Pin, cardEnvironment.pin1) - return CommandApdu(Instruction.ReadUserData, builder.serialize()) + return CommandApdu( + Instruction.ReadUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index d76722e96c..6f0b7bfacc 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -4,9 +4,6 @@ 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 @@ -60,7 +57,10 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } /** @@ -79,7 +79,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null val tlvMapper = TlvMapper(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2ca018f2a0..dbc7f15640 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -45,11 +45,14 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 7a1b987cd5..753607f0a9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -60,7 +60,10 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } private fun getDataToWrite(): ByteArray = @@ -72,7 +75,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index d512c4ded7..804abf6601 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -47,11 +47,14 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.WriteUserData, builder.serialize()) + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index 21728a8c44..c30efb5d30 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -10,6 +10,7 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, + val encryptionMode: EncryptionMode = EncryptionMode.NONE, val encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index 450618ced5..1a2c2be076 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu import com.tangem.common.EncryptionMode +import com.tangem.common.extensions.calculateCrc16 +import com.tangem.common.extensions.toByteArray +import com.tangem.crypto.encrypt import java.io.ByteArrayOutputStream /** @@ -8,21 +11,19 @@ import java.io.ByteArrayOutputStream * to a raw data that can be sent to the card. * * @property ins Instruction code that determines the type of request for the card. - * @property tlvList A list of TLVs that are to be sent to the card + * @property tlvs Tlvs encoded to a [ByteArray] that are to be sent to the card. */ class CommandApdu( private val ins: Int, private val tlvs: ByteArray, - private val cla: Byte = ISO_CLA, - private val p1: Byte = 0x00, - private val p2: Byte = 0x00, - private val le: Int = 0x00, private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null) { + private val encryptionKey: ByteArray? = null, + + private val cla: Int = ISO_CLA) { constructor( instruction: Instruction, @@ -36,6 +37,19 @@ class CommandApdu( encryptionKey = encryptionKey ) + private val p1: Int + private val p2: Int + + init { + if (ins == Instruction.OpenSession.code) { + p1 = 0x00 + p2 = encryptionMode.code.toInt() + } else { + p1 = encryptionMode.code.toInt() + p2 = 0x00 + } + } + /** * Request converted to a raw data @@ -48,33 +62,37 @@ class CommandApdu( private fun toBytes(): ByteArray { - - val lc = tlvs.size + val data = if (encryptionKey != null) tlvs.encrypt() else tlvs val byteStream = ByteArrayOutputStream() - byteStream.write(cla.toInt()) + byteStream.write(cla) byteStream.write(ins) - byteStream.write(p1.toInt()) - byteStream.write(p2.toInt()) - if (lc != 0) { - writeLength(byteStream, lc) - byteStream.write(tlvs) + byteStream.write(p1) + byteStream.write(p2) + if (data.isNotEmpty()) { + byteStream.writeLength(data.size) + byteStream.write(data) } return byteStream.toByteArray() } - private fun writeLength(stream: ByteArrayOutputStream, lc: Int) { - stream.write(0) - stream.write(lc shr 8) - stream.write(lc and 0xFF) + private fun ByteArrayOutputStream.writeLength(lc: Int) { + this.write(0) + this.write(lc shr 8) + this.write(lc and 0xFF) } - private fun encrypt() { - TODO("not implemented") - } + private fun ByteArray.encrypt(): ByteArray { + val crc: ByteArray = tlvs.calculateCrc16() + val stream = ByteArrayOutputStream() + stream.write(this.size.toByteArray(2)) + stream.write(crc) + stream.write(this) + return stream.toByteArray().encrypt(encryptionKey!!) + } companion object { - const val ISO_CLA = 0x00.toByte() + const val ISO_CLA = 0x00 } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e98c78874b..e7e8625384 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu +import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv +import com.tangem.crypto.decrypt +import java.io.ByteArrayInputStream /** * Stores response data from the card and parses it to [Tlv] and [StatusWord]. @@ -25,15 +28,39 @@ class ResponseApdu(private val data: ByteArray) { * (Encryption / decryption functionality is not implemented yet.) */ fun getTlvData(encryptionKey: ByteArray? = null): List? { - return when { - data.size <= 2 -> null - else -> Tlv.deserialize(data.copyOf(data.size - 2)) + return if (data.size <= 2) { + null + } else { + val responseData = data.copyOf(data.size - 2) + return if (encryptionKey != null) { + if (data.size >= 18) { + val decryptedData = decrypt(responseData, encryptionKey) + Tlv.deserialize(decryptedData) + } else { + null + } + } else { + Tlv.deserialize(responseData) + } } } + private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) - private fun decrypt(encryptionKey: ByteArray) { - TODO("not implemented") + val inputStream = ByteArrayInputStream(decryptedData) + val baLength = ByteArray(2) + inputStream.read(baLength) + val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) + if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + val baCRC = ByteArray(2) + inputStream.read(baCRC) + val answerData = ByteArray(length) + inputStream.read(answerData) + val crc: ByteArray = answerData.calculateCrc16() + if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + + return answerData } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index f7e416d700..ac201f5fcb 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -6,6 +6,7 @@ import java.nio.ByteBuffer import java.security.MessageDigest import java.util.* import kotlin.experimental.and +import kotlin.experimental.xor /** * Extension functions for [ByteArray]. @@ -53,4 +54,21 @@ fun ByteArray.toCompressedPublicKey(): ByteArray { } else { this } +} + +fun ByteArray.calculateCrc16(): ByteArray { + var chBlock: Byte + // STEP 1 Initialize the CRC-16 value + var wCRC = 0x6363 // ITU-V.41 + var i = 0 + // STEP 2 Update data and Calucuate their CRC + do { + chBlock = this.get(i++) + chBlock = chBlock xor (wCRC and 0x00FF).toByte() + val chBlockInt = (chBlock.toInt() xor (chBlock.toInt() shl 4)) + wCRC = wCRC shr 8 xor (chBlockInt and 0xFF shl 8) and 0xFFFF xor (chBlockInt and 0xFF shl 3 and 0xFFFF) xor (chBlockInt and 0xFF shr 4 and 0xFFFF) + // (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); + } while (i < this.size) + + return byteArrayOf((wCRC and 0xFF).toByte(), (wCRC and 0xFFFF shr 8).toByte()) } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index d37500e221..c9f3275dfe 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -51,6 +51,7 @@ enum class TlvTag(val code: Int) { SessionKeyA(0x1A), SessionKeyB(0x1B), + Uid(0x0B), Pause(0x1C), ManufactureId(0x20), @@ -66,7 +67,6 @@ enum class TlvTag(val code: Int) { Mode(0x23), Offset(0x24), - IsActivated(0x3A), ActivationSeed(0x3B), ResetPin(0x36), @@ -95,7 +95,6 @@ enum class TlvTag(val code: Int) { ProductMask(0x8A), PaymentFlowVersion(0x54), - TokenSymbol(0xA0), TokenContractAddress(0xA1), TokenDecimal(0xA2), diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index 734b3d8f7a..d7643bdaae 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -2,8 +2,12 @@ package com.tangem.crypto import com.tangem.commands.EllipticCurve import net.i2p.crypto.eddsa.EdDSASecurityProvider +import java.security.PublicKey import java.security.SecureRandom import java.security.Security +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec object CryptoUtils { @@ -62,6 +66,16 @@ object CryptoUtils { EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray) } } + + fun loadPublicKey( + publicKey: ByteArray, + curve: EllipticCurve = EllipticCurve.Secp256k1 + ): PublicKey { + return when (curve) { + EllipticCurve.Secp256k1 -> Secp256k1.loadPublicKey(publicKey) + EllipticCurve.Ed25519 -> Ed25519.loadPublicKey(publicKey) + } + } } /** @@ -79,4 +93,28 @@ fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCu } } +fun ByteArray.encrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec, "SC") + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this) +} + +fun ByteArray.decrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this.copyOfRange(0, this.size)) +} + +fun ByteArray.pbkdf2Hash(salt: ByteArray, iterations: Int): ByteArray { + return Pbkdf2().deriveKey(this, salt, iterations) +} + +private const val ENCRYPTION_SPEC_PKCS7 = "AES/CBC/PKCS7PADDING" +private const val ENCRYPTION_SPEC_NO_PADDING = "AES/CBC/NOPADDING" + + diff --git a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt index fd0cf14cc1..5c07b5a4e9 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt @@ -24,7 +24,7 @@ object Ed25519 { return signatureInstance.verify(signature) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) return EdDSAPublicKey(pubKey) diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt new file mode 100644 index 0000000000..ab3d97445c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt @@ -0,0 +1,50 @@ +package com.tangem.crypto + +import org.spongycastle.jce.interfaces.ECPublicKey +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.spec.ECGenParameterSpec +import javax.crypto.KeyAgreement + +interface EncryptionHelper { + val keyA: ByteArray + + fun generateSecret(keyB: ByteArray): ByteArray +} + +class StrongEncryptionHelper : EncryptionHelper { + private val keyPair = generateKeyPair() + private val keyAgreement = generateKeyAgreement(keyPair) + override val keyA = provideKeyA(keyPair) + + override fun generateSecret(keyB: ByteArray): ByteArray { + keyAgreement.doPhase(CryptoUtils.loadPublicKey(keyB), true) + return keyAgreement.generateSecret() + } + + private fun generateKeyPair(): KeyPair { + val kpgen = KeyPairGenerator.getInstance("ECDH", "SC") + kpgen.initialize(ECGenParameterSpec("secp256k1"), SecureRandom()) + return kpgen.generateKeyPair() + } + + private fun generateKeyAgreement(keyPair: KeyPair): KeyAgreement { + val keyAgreement = KeyAgreement.getInstance("ECDH", "SC") + keyAgreement.init(keyPair.private) + return keyAgreement + } + + private fun provideKeyA(keyPair: KeyPair): ByteArray { + val eckey = keyPair.public as ECPublicKey + return eckey.q.getEncoded(false) + } +} + +class FastEncryptionHelper : EncryptionHelper { + override val keyA = CryptoUtils.generateRandomBytes(16) + + override fun generateSecret(keyB: ByteArray): ByteArray { + return keyA + keyB + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt new file mode 100644 index 0000000000..4c532b3692 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt @@ -0,0 +1,88 @@ +package com.tangem.crypto + +import org.spongycastle.crypto.CipherParameters +import org.spongycastle.crypto.digests.SHA256Digest +import org.spongycastle.crypto.macs.HMac +import org.spongycastle.crypto.params.KeyParameter +import java.security.InvalidKeyException +import java.util.* +import kotlin.experimental.xor +import kotlin.math.min +import kotlin.math.pow + +class Pbkdf2 { + private val F: HMac = HMac(SHA256Digest()) + + fun deriveKey(password: ByteArray, salt: ByteArray, iterations: Int): ByteArray { + + val macSize = F.macSize + // Check key length + if (macSize > (2.0.pow(32.0) - 1) * macSize) throw InvalidKeyException("Derived key to long") + + val derivedKey = ByteArray(macSize) + + val J = 0 + val K: Int = macSize + val U: Int = macSize shl 1 + val B = K + U + val workingArray = ByteArray(K + U + 4) + + // Initialize F + val macParams: CipherParameters = KeyParameter(password) + F.init(macParams) + + // Perform iterations + var kpos = 0 + var blk = 1 + while (kpos < macSize) { + storeInt32BE(blk, workingArray, B) + F.update(salt, 0, salt.size) + F.reset() + F.update(salt, 0, salt.size) + F.update(workingArray, B, 4) + F.doFinal(workingArray, U) + System.arraycopy(workingArray, U, workingArray, J, K) + var i = 1 + var j = J + var k = K + while (i < iterations) { + F.init(macParams) + F.update(workingArray, j, K) + F.doFinal(workingArray, k) + var u = U + var v = k + while (u < B) { + workingArray[u] = workingArray[u] xor workingArray[v] + u++ + v++ + } + val swp = k + k = j + j = swp + i++ + } + val tocpy = min(macSize - kpos, K) + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy) + kpos += K + blk++ + } + Arrays.fill(workingArray, 0.toByte()) + return derivedKey + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + private fun storeInt32BE(value: Int, bytes: ByteArray, offSet: Int) { + bytes[offSet + 3] = value.toByte() + bytes[offSet + 2] = (value ushr 8).toByte() + bytes[offSet + 1] = (value ushr 16).toByte() + bytes[offSet] = (value ushr 24).toByte() + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt index 2a62979bed..97eba2a81c 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt @@ -29,7 +29,7 @@ object Secp256k1 { return signatureInstance.verify(sigDer) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = ECNamedCurveTable.getParameterSpec("secp256k1") val factory = KeyFactory.getInstance("EC", "SC") diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..20dee606ee 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -3,14 +3,17 @@ package com.tangem.tasks import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.commands.ReadCommand +import com.tangem.commands.* import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult +import com.tangem.common.EncryptionMode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash /** * An error class that represent typical errors that may occur when performing Tangem SDK tasks. @@ -85,6 +88,7 @@ abstract class Task { var reader: CardReader? = null var performPreflightRead: Boolean = true var securityDelayDuration: Int = 0 + private var openSessionRequired = false /** * This method should be called to run the [Task] and perform all its operations. @@ -137,8 +141,43 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) + if (cardEnvironment.encryptionKey != null && !openSessionRequired) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } + + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + } + EncryptionMode.FAST, EncryptionMode.STRONG -> { + val encryptionHelper: EncryptionHelper = + if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) + val openSessionApdu = openSessionCommand.serialize(cardEnvironment) + sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + + val newEnvironment = cardEnvironment.copy(encryptionKey = sessionKey) + openSessionRequired = false + sendCommand(command, newEnvironment, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + } } private fun sendRequest(command: CommandSerializer, @@ -171,9 +210,25 @@ abstract class Task { StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption())) + StatusWord.NeedEncryption -> { + openSessionRequired = true + val newEnvironment = when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + cardEnvironment.copy(encryptionMode = EncryptionMode.FAST) + } + EncryptionMode.FAST -> { + cardEnvironment.copy(encryptionMode = EncryptionMode.STRONG) + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TaskError.NeedEncryption())) + return@transceiveApdu + } + } + sendCommand(command, newEnvironment, callback) + } StatusWord.NeedPause -> { - // When NeedPause is returned from the card whenever security delay is triggered. + // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") @@ -201,7 +256,6 @@ abstract class Task { callback(TaskEvent.Completion(readResult.error)) } is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 @@ -215,10 +269,7 @@ abstract class Task { onRun(newEnvironment, readResult.data, callback) } } - - } - } } From ec2821e222fedd9a1b2fd3ee6fe60b33af893202 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 10 Mar 2020 16:23:42 +0300 Subject: [PATCH 02/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/eth/EthEngine.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 7175f68f73..0435406297 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -228,10 +228,12 @@ public class EthEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); - } else { +// if (ctx.getCard().getDenomination() != null) { +// return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + if (ctx.getBlockchain() == Blockchain.Ethereum) { return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + } else { + return Uri.parse(ctx.getCoinData().getWallet()); } } From e19785b759b96616df68c141621152ea68456f6d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 16:56:04 +0300 Subject: [PATCH 03/25] Updated on 2026-08-14 --- .../src/main/java/com/tangem/tasks/Task.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 20dee606ee..9b5c13df6f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -88,7 +88,6 @@ abstract class Task { var reader: CardReader? = null var performPreflightRead: Boolean = true var securityDelayDuration: Int = 0 - private var openSessionRequired = false /** * This method should be called to run the [Task] and perform all its operations. @@ -141,18 +140,17 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - if (cardEnvironment.encryptionKey != null && !openSessionRequired) { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - return - } - when (cardEnvironment.encryptionMode) { EncryptionMode.NONE -> { val commandApdu = command.serialize(cardEnvironment) sendRequest(command, commandApdu, cardEnvironment, callback) } EncryptionMode.FAST, EncryptionMode.STRONG -> { + if (cardEnvironment.encryptionKey != null ) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } val encryptionHelper: EncryptionHelper = if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { StrongEncryptionHelper() @@ -168,10 +166,9 @@ abstract class Task { val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) val sessionKey = (secret + protocolKey).calculateSha256() + cardEnvironment.encryptionKey = sessionKey - val newEnvironment = cardEnvironment.copy(encryptionKey = sessionKey) - openSessionRequired = false - sendCommand(command, newEnvironment, callback) + sendCommand(command, cardEnvironment, callback) } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } @@ -211,13 +208,14 @@ abstract class Task { StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) StatusWord.NeedEncryption -> { - openSessionRequired = true - val newEnvironment = when (cardEnvironment.encryptionMode) { + when (cardEnvironment.encryptionMode) { EncryptionMode.NONE -> { - cardEnvironment.copy(encryptionMode = EncryptionMode.FAST) + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.FAST } EncryptionMode.FAST -> { - cardEnvironment.copy(encryptionMode = EncryptionMode.STRONG) + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.STRONG } EncryptionMode.STRONG -> { Log.e(this::class.simpleName!!, "Encryption doesn't work") @@ -225,7 +223,7 @@ abstract class Task { return@transceiveApdu } } - sendCommand(command, newEnvironment, callback) + sendCommand(command, cardEnvironment, callback) } StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. From 2badf92d8621f2fc8ff9539382a2aff14871f7c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 17:14:07 +0300 Subject: [PATCH 04/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/data/network/Server.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/data/network/Server.java b/app/src/main/java/com/tangem/data/network/Server.java index 97fb0ab7e3..c31e4cf46b 100644 --- a/app/src/main/java/com/tangem/data/network/Server.java +++ b/app/src/main/java/com/tangem/data/network/Server.java @@ -150,12 +150,13 @@ public class Server { public static class ApiBlockchair { public static final String URL_BLOCKCHAIR = ServerURL.API_BLOCKCHAIR + "{blockchain}/"; + private static final String API_KEY = "?key=A___0Shpsu4KagE7oSabrw20DfXAqWlT"; public static class Method { - static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}"; - static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}"; - static final String STATS = URL_BLOCKCHAIR + "stats"; - static final String PUSH = URL_BLOCKCHAIR + "push/transaction"; + static final String ADDRESS = URL_BLOCKCHAIR + "dashboards/address/{address}" + API_KEY; + static final String TRANSACTION = URL_BLOCKCHAIR + "dashboards/transaction/{transaction}" + API_KEY; + static final String STATS = URL_BLOCKCHAIR + "stats" + API_KEY; + static final String PUSH = URL_BLOCKCHAIR + "push/transaction" + API_KEY; } } } \ No newline at end of file From 279ec277f9b3fde8d13c50be55ce084a09c08ae0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 11 Mar 2020 17:20:19 +0300 Subject: [PATCH 05/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java index edc471e86b..2be3abdcac 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -141,7 +141,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse("https://explorer.bitcoin.com/bch/address/" + ctx.getCoinData().getWallet()); + return Uri.parse("https://api.blockchair.com/bitcoin-cash/dashboards/address/" + ctx.getCoinData().getWallet()); } @Override From 2b2ab9888c449f2be5bc2cba21872672710bed9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 12 Mar 2020 09:15:05 +0300 Subject: [PATCH 06/25] Updated on 2026-08-14 --- .../src/main/java/com/tangem/common/CardEnvironment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index c30efb5d30..c556f427b3 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -10,8 +10,8 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, - val encryptionMode: EncryptionMode = EncryptionMode.NONE, - val encryptionKey: ByteArray? = null, + var encryptionMode: EncryptionMode = EncryptionMode.NONE, + var encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { From 15399958314fe1836dd2b230927b1808d491b20e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 10:19:31 +0300 Subject: [PATCH 07/25] Updated on 2026-08-14 --- tangem-core/src/main/java/com/tangem/CardManager.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..c79a65d3f3 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -5,7 +5,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils import com.tangem.tasks.* -import java.util.concurrent.Executors /** * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. @@ -24,7 +23,6 @@ class CardManager( private var terminalKeysService: TerminalKeysService? = null private var isBusy = false - private val cardManagerExecutor = Executors.newSingleThreadExecutor() init { CryptoUtils.initCrypto() @@ -262,7 +260,7 @@ class CardManager( task.reader = reader task.delegate = cardManagerDelegate - cardManagerExecutor.execute { + Thread().run { task.run(environment) { taskEvent -> if (taskEvent is TaskEvent.Completion) isBusy = false callback(taskEvent) From 155283a8f4a757f9564890919a172d82abb3fe52 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 10:43:21 +0300 Subject: [PATCH 08/25] Updated on 2026-08-14 --- .../src/main/java/com/tangem/tasks/Task.kt | 140 ++++++++++++++---- 1 file changed, 112 insertions(+), 28 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..8be8b1a1a9 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -16,44 +16,128 @@ import com.tangem.common.apdu.StatusWord * An error class that represent typical errors that may occur when performing Tangem SDK tasks. * Errors are propagated back to the caller in callbacks. */ -sealed class TaskError(val code: Int): Exception() { +sealed class TaskError(val code: Int) : Exception() { //Errors in serializing APDU - class SerializeCommandError: TaskError(1001) - class EncodingError: TaskError(1002) - class MissingTag: TaskError(1003) - class WrongType: TaskError(1004) - class ConvertError: TaskError(1005) + /** + * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] + * (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors). + */ + class SerializeCommandError : TaskError(1001) - //Card errors - class UnknownStatus: TaskError(2001) - class ErrorProcessingCommand: TaskError(2002) - class MissingPreflightRead: TaskError(2003) - class InvalidState: TaskError(2004) - class InsNotSupported: TaskError(2005) - class InvalidParams: TaskError(2006) - class NeedEncryption: TaskError(2007) + class EncodingError : TaskError(1002) + class MissingTag : TaskError(1003) + class WrongType : TaskError(1004) + class ConvertError : TaskError(1005) + + /** + * This error is returned when unknown [StatusWord] is received from a card. + */ + class UnknownStatus : TaskError(2001) + + /** + * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. + * The card sends this status in case of internal card error. + */ + class ErrorProcessingCommand : TaskError(2002) + + /** + * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] + * is executed before performing other commands. + */ + class MissingPreflightRead : TaskError(2003) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidState]. + * The card sends this status when command can not be executed in the current state of a card. + */ + class InvalidState : TaskError(2004) + + /** + * This error is returned when a card's reply is [StatusWord.InsNotSupported]. + * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. + */ + class InsNotSupported : TaskError(2005) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidParams]. + * The card sends this status when there are wrong or not sufficient parameters in TLV request, + * or wrong PIN1/PIN2. + * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], + * mapping or serialization errors. + */ + class InvalidParams : TaskError(2006) + + /** + * This error is returned when a card's reply is [StatusWord.NeedEncryption] + * and the encryption was not established by TangemSdk. + */ + class NeedEncryption : TaskError(2007) //Scan errors - class VerificationFailed: TaskError(3000) - class CardError: TaskError(3001) - class WrongCard: TaskError(3002) - class TooMuchHashesInOneTransaction: TaskError(3003) - class EmptyHashes: TaskError(3004) - class HashSizeMustBeEqual: TaskError(3005) + /** + * This error is returned when a [Task] checks unsuccessfully either + * a card's ability to sign with its private key, or the validity of issuer data. + */ + class VerificationFailed : TaskError(3000) - class Busy: TaskError(4000) - class UserCancelled: TaskError(4001) - class UnsupportedDevice: TaskError(4002) + /** + * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. + */ + class CardError : TaskError(3001) - //NFC error - class NfcReaderError: TaskError(5002) - class TagLost: TaskError(5003) + /** + * This error is returned when a [Task] expects a user to use a particular card, + * and a user tries to use a different card. + */ + class WrongCard : TaskError(3002) - class UnknownError: TaskError(6000) + /** + * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. + * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. + */ + class TooMuchHashesInOneTransaction : TaskError(3003) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives only empty hashes for signature. + */ + class EmptyHashes : TaskError(3004) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives hashes of different lengths for signature. + */ + class HashSizeMustBeEqual : TaskError(3005) + + /** + * This error is returned when [com.tangem.CardManager] was called with a new [Task], + * while a previous [Task] is still in progress. + */ + class Busy : TaskError(4000) + + /** + * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. + */ + class UserCancelled : TaskError(4001) + + //NFC errors + class NfcReaderError : TaskError(5002) + + /** + * This error is returned when Android NFC reader loses a tag + * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. + */ + class TagLost : TaskError(5003) + + class UnknownError : TaskError(6000) //Issuer Data Errors - class MissingCounter: TaskError(7001) + /** + * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter + * (when the card's requires it), but the counter is missing. + */ + class MissingCounter : TaskError(7001) } /** From 1e0f11bcbf7757d0abfd256ba84dbbdede7638b7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 14:50:45 +0300 Subject: [PATCH 09/25] Updated on 2026-08-14 --- .idea/gradle.xml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .idea/gradle.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 280cc6ed9d..0000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - \ No newline at end of file From 8fa60efcfcc93b228d38aa6c2e57322b45a69039 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 14:52:49 +0300 Subject: [PATCH 10/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/btc/BtcEngine.java | 4 ++-- app/src/main/java/com/tangem/wallet/eth/EthEngine.java | 2 +- app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java | 5 ++--- app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java | 4 ++-- .../main/java/com/tangem/wallet/nftToken/NftTokenEngine.java | 2 +- app/src/main/java/com/tangem/wallet/token/TokenEngine.java | 4 ++-- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 4bfa331eda..05b489ba07 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -219,9 +219,9 @@ public class BtcEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null && !ctx.getCard().getDenominationText().equals("0.00")) { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 0435406297..8c24e30480 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -231,7 +231,7 @@ public class EthEngine extends CoinEngine { // if (ctx.getCard().getDenomination() != null) { // return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); if (ctx.getBlockchain() == Blockchain.Ethereum) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } else { return Uri.parse(ctx.getCoinData().getWallet()); } diff --git a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java index ebf42f4e21..3c9e6c0017 100644 --- a/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java +++ b/app/src/main/java/com/tangem/wallet/ethID/EthIdEngine.java @@ -27,7 +27,6 @@ import com.tangem.wallet.EthTransaction; import com.tangem.wallet.Keccak256; import com.tangem.wallet.R; import com.tangem.wallet.TangemContext; -import com.tangem.wallet.eth.EthData; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.SignatureDecodeException; @@ -185,9 +184,9 @@ public class EthIdEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java index 801ed7673e..e8ebfa3fd3 100644 --- a/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/ltc/LtcEngine.java @@ -179,9 +179,9 @@ public class LtcEngine extends BtcEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); + return Uri.parse(ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { - return Uri.parse("litecoin:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java index 859d8f3d24..151e631fbd 100644 --- a/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/nftToken/NftTokenEngine.java @@ -189,7 +189,7 @@ public class NftTokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index ac453d5ff9..d36abf0480 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -305,9 +305,9 @@ public class TokenEngine extends CoinEngine { @Override public Uri getShareWalletUri() { if (ctx.getCard().getDenomination() != null) { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); + return Uri.parse(ctx.getCoinData().getWallet());// + "?value=" + mCard.getDenomination() +"e18"); } else { - return Uri.parse("ethereum:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 73db5449a5..c5bd2f5815 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -148,7 +148,7 @@ public class XrpEngine extends CoinEngine { } public Uri getShareWalletUri() { - return Uri.parse("ripple:" + ctx.getCoinData().getWallet()); + return Uri.parse(ctx.getCoinData().getWallet()); } @Override From 8a22cf415e24aa91bfe00350ff4f29b10fda9ab9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 17:22:52 +0300 Subject: [PATCH 11/25] Updated on 2026-08-14 --- .../com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index ca0505d7aa..0f304ba5db 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -307,7 +307,10 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte viewModel.getRateInfo().observe(this, Observer { rate -> ctx.coinData.rate = rate ctx.coinData.rateAlter = rate + requestCounter-- + updateViews() }) + requestCounter++ viewModel.requestRateInfo(ctx) } @@ -717,8 +720,10 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte requestBalanceAndUnspentTransactions() - if (::viewModel.isInitialized) + if (::viewModel.isInitialized) { + requestCounter++ viewModel.requestRateInfo(ctx) + } if (requestCounter == 0) { // if no connection and no requests posted From 6e1c51584bdf735076ed26e6d611f2c7b88f0824 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:04:16 +0300 Subject: [PATCH 12/25] Updated on 2026-08-14 --- .../fragment/wallet/LoadedWalletFragment.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 0f304ba5db..806431837a 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -124,6 +124,16 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG) if (tag != null) onTagDiscovered(tag) } + + viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) + + // set rate info to CoinData + viewModel.getRateInfo().observe(this, Observer { rate -> + ctx.coinData.rate = rate + ctx.coinData.rateAlter = rate + updateViews() + }) + viewModel.requestRateInfo(ctx) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -299,21 +309,6 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte startVerify(lastTag) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) - - // set rate info to CoinData - viewModel.getRateInfo().observe(this, Observer { rate -> - ctx.coinData.rate = rate - ctx.coinData.rateAlter = rate - requestCounter-- - updateViews() - }) - requestCounter++ - viewModel.requestRateInfo(ctx) - } - override fun onPause() { super.onPause() if (timerHideErrorAndMessage != null) { @@ -720,10 +715,8 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte requestBalanceAndUnspentTransactions() - if (::viewModel.isInitialized) { - requestCounter++ + if (::viewModel.isInitialized) viewModel.requestRateInfo(ctx) - } if (requestCounter == 0) { // if no connection and no requests posted From 38943c837096629e8a80726813c7297882014f76 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:42:18 +0300 Subject: [PATCH 13/25] Updated on 2026-08-14 --- .../java/com/tangem/wallet/xrp/XrpData.java | 17 +++++- .../java/com/tangem/wallet/xrp/XrpEngine.java | 58 +++++++++++++++---- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 3df5e26532..1b10a221a3 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -17,7 +17,7 @@ public class XrpData extends CoinData { private Long reserve = 20000000L; - private Boolean accountNotFound = false; + private Boolean accountNotFound, targetAccountCreated = false; @Override public void loadFromBundle(Bundle B) { @@ -32,7 +32,9 @@ public class XrpData extends CoinData { if (B.containsKey("Reserve")) reserve = B.getLong("Reserve"); else reserve = 20000000L; if (B.containsKey("AccoundNotFound")) accountNotFound = B.getBoolean("AccoundNotFound"); - else reserve = 20000000L; + else accountNotFound = false; + if (B.containsKey("TargetAccountCreated")) targetAccountCreated = B.getBoolean("TargetAccountCreated"); + else targetAccountCreated = false; } @Override @@ -44,6 +46,7 @@ public class XrpData extends CoinData { if (sequence != null) B.putLong("Sequence", sequence); if (reserve != null) B.putLong("Reserve", reserve); if (accountNotFound != null) B.putBoolean("AccoundNotFound", accountNotFound); + if (targetAccountCreated != null) B.putBoolean("TargetAccountCreated", targetAccountCreated); } catch (Exception e) { Log.e("Can't save to bundle ", e.getMessage()); } @@ -57,6 +60,7 @@ public class XrpData extends CoinData { sequence = null; reserve = 20000000L; accountNotFound = false; + targetAccountCreated = false; } // balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present @@ -105,6 +109,14 @@ public class XrpData extends CoinData { this.accountNotFound = accountFound; } + public Boolean isTargetAccountCreated() { + return targetAccountCreated; + } + + public void setTargetAccountCreated(boolean targetAccountCreated) { + this.targetAccountCreated = targetAccountCreated; + } + public boolean hasBalanceInfo() { return balanceConfirmed != null || balanceUnconfirmed != null; } @@ -112,5 +124,4 @@ public class XrpData extends CoinData { public boolean hasUnconfirmed() { return !balanceConfirmed.equals(balanceUnconfirmed); } - } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 73db5449a5..0e0cc82884 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -342,6 +342,10 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); + if (!coinData.isTargetAccountCreated() && amountValue.compareTo(BigDecimal.valueOf(20)) < 0) { + throw new Exception("Target account is not created. Amount should be 20 XRP or more"); + } + String amount, fee; if (IncFee) { @@ -522,19 +526,50 @@ public class XrpEngine extends CoinEngine { ServerApiRipple.ResponseListener rippleListener = new ServerApiRipple.ResponseListener() { @Override public void onSuccess(String method, RippleResponse rippleResponse) { - try { - InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); - InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); - InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + Log.i(TAG, "onSuccess: " + method); + switch (method) { + case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { + try { + if (rippleResponse.getResult().getError_code().equals(19)) { // "Account not found" + coinData.setTargetAccountCreated(false); + } else { + coinData.setTargetAccountCreated(true); + } + } catch (Exception e) { + coinData.setTargetAccountCreated(true); //expected behaviour, if account exists, there should be no error code -> null pointer + } - coinData.minFee = convertToAmount(minFee); - coinData.normalFee = convertToAmount(normalFee); - coinData.maxFee = convertToAmount(maxFee); + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; - blockchainRequestsCallbacks.onComplete(true); - } catch (Exception e) { - e.printStackTrace(); - Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + case ServerApiRipple.RIPPLE_FEE: { + try { + InternalAmount minFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMinimum_fee()), "Drops"); + InternalAmount normalFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getOpen_ledger_fee()), "Drops"); + InternalAmount maxFee = new InternalAmount(Long.valueOf(rippleResponse.getResult().getDrops().getMedian_fee()), "Drops"); + + coinData.minFee = convertToAmount(minFee); + coinData.normalFee = convertToAmount(normalFee); + coinData.maxFee = convertToAmount(maxFee); + + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL RIPPLE_FEE Exception"); + ctx.setError(e.getMessage()); + } + + if (serverApiRipple.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + break; } } @@ -548,6 +583,7 @@ public class XrpEngine extends CoinEngine { serverApiRipple.setResponseListener(rippleListener); + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); serverApiRipple.requestData(ServerApiRipple.RIPPLE_FEE, "", ""); } From fa340346d71255ad64d117639cd3e3ea42b30c17 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 16 Mar 2020 20:51:21 +0300 Subject: [PATCH 14/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 0e0cc82884..f34e34c015 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -342,8 +342,9 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); - if (!coinData.isTargetAccountCreated() && amountValue.compareTo(BigDecimal.valueOf(20)) < 0) { - throw new Exception("Target account is not created. Amount should be 20 XRP or more"); + Amount reserve = convertToAmount(coinData.getReserveInInternalUnits()); + if (!coinData.isTargetAccountCreated() && amountValue.compareTo(reserve) < 0) { + throw new Exception("Target account is not created. Amount should be " + reserve.toDescriptionString(getDecimals()) + " or more"); } String amount, fee; From 6a20df79293f84cc67bba5029d22f46913033134 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 08:30:12 +0300 Subject: [PATCH 15/25] Updated on 2026-08-14 --- .../java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt index d87b466385..ed3ba8bf1c 100644 --- a/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt +++ b/tangem-sdk-old/src/main/java/com/tangem/tangem_sdk/android/nfc/NfcLocation.kt @@ -43,7 +43,7 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model40("HWVTR", "Huawei P10", 0, 50, 0, 0), model41("HWWAS-H", "Huawei P10 lite", 0, 50, 0, 0), model42("HWVKY", "Huawei P10 Plus", 0, 50, 0, 0), - model43("HWEML", "Huawei P20", 0, 50, 50, 0), + model43("HWEML", "Huawei P20", 0, 40, 5, 0), model44("HWANE", "Huawei P20 Lite", 0, 50, 20, 0), model45("HWCLT", "Huawei P20 Pro", 0, 50, 50, 0), model46("HW-01K", "Huawei P20 Pro", 0, 50, 50, 0), @@ -231,8 +231,8 @@ enum class NfcLocation(val codename: String, val fullName: String, val orientati model228("sagit", "Xiaomi Mi 6", 0, 50, 20, 0), model229("dipper", "Xiaomi Mi 8", 0, 45, 20, 0), model230("ursa", "Xiaomi MI 8 Explorer Edition", 0, 50, 40, 0), - model231("cepheus", "Xiaomi MI 9", 0, 40, 20, 0), - model232("grus", "Xiaomi MI 9 SE", 0, 40, 20, 0), + model231("cepheus", "Xiaomi MI 9", 0, 40, 5, 0), + model232("grus", "Xiaomi MI 9 SE", 0, 40, 5, 0), model233("lithium", "Xiaomi Mi MIX", 0, 20, 20, 0), model234("chiron", "Xiaomi Mi MIX 2", 0, 45, 20, 0), model235("polaris", "Xiaomi Mi MIX 2S", 0, 45, 20, 0), From 8b6261007d37d4900cae6845dce08329fc61ad33 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 09:42:27 +0300 Subject: [PATCH 16/25] Updated on 2026-08-14 --- .../fragment/wallet/LoadedWalletFragment.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 806431837a..2fdf9776d8 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -124,16 +124,6 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte val tag = arguments!!.getParcelable(NfcAdapter.EXTRA_TAG) if (tag != null) onTagDiscovered(tag) } - - viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) - - // set rate info to CoinData - viewModel.getRateInfo().observe(this, Observer { rate -> - ctx.coinData.rate = rate - ctx.coinData.rateAlter = rate - updateViews() - }) - viewModel.requestRateInfo(ctx) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -307,6 +297,17 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte serverApiTangem.setArtworkListener(artworkListener) refresh() startVerify(lastTag) + + + viewModel = ViewModelProviders.of(this).get(LoadedWalletViewModel::class.java) + + // set rate info to CoinData + viewModel.getRateInfo().observe(this, Observer { rate -> + ctx.coinData.rate = rate + ctx.coinData.rateAlter = rate + updateViews() + }) + viewModel.requestRateInfo(ctx) } override fun onPause() { From e78270ada3fa9d1a8db8bbb70cfadcf69913fdac Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 17 Mar 2020 15:59:21 +0300 Subject: [PATCH 17/25] Updated on 2026-08-14 --- .../java/com/tangem/ui/fragment/VerifyCardFragment.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt index 307a9186b7..421f780cc1 100644 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt @@ -472,10 +472,14 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. private fun doPurge() { requestPIN2Count = 0 - val engine = CoinEngineFactory.create(ctx) - if (!engine!!.hasBalanceInfo()) { + val engine = CoinEngineFactory.create(ctx) ?: return + if (!engine.hasBalanceInfo()) { return - } else if (engine.isBalanceNotZero) { + } + if (engine.isBalanceNotZero) { + Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() + return + } else if (engine.awaitingConfirmation()) { Toast.makeText(context, R.string.general_error_cannot_erase_wallet_with_non_zero_balance, Toast.LENGTH_LONG).show() return } From 15082abb65c8cffea995c27885fc6fa6b0aad89a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Mar 2020 14:19:05 +0300 Subject: [PATCH 18/25] Updated on 2026-08-14 --- .../main/java/com/tangem/wallet/xrp/XrpEngine.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index f6941d71fc..975155be4e 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -342,21 +342,23 @@ public class XrpEngine extends CoinEngine { public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { checkBlockchainDataExists(); - Amount reserve = convertToAmount(coinData.getReserveInInternalUnits()); - if (!coinData.isTargetAccountCreated() && amountValue.compareTo(reserve) < 0) { - throw new Exception("Target account is not created. Amount should be " + reserve.toDescriptionString(getDecimals()) + " or more"); - } - String amount, fee; if (IncFee) { amount = convertToInternalAmount(amountValue).subtract(convertToInternalAmount(feeValue)).setScale(0).toPlainString(); } else { - amount = Long.toString(convertToInternalAmount(amountValue).longValueExact()); + amount = Long.toString(convertToInternalAmount(amountValue).longValue()); } fee = Long.toString(convertToInternalAmount(feeValue).longValueExact()); + if (!coinData.isTargetAccountCreated() && + Long.valueOf(amount).compareTo(coinData.getReserveInInternalUnits().longValue()) < 0 + ) { + String reserveDescription = convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()); + throw new Exception("Target account is not created. Amount should be " + reserveDescription + " or more"); + } + XrpPayment payment = new XrpPayment(); // Put `as` AccountID field Account, `Object` o From 337d58c8d3ea51c26e95ae68decdbbc5214b589f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Mar 2020 15:10:22 +0300 Subject: [PATCH 19/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java index 2be3abdcac..4c126fbc3f 100644 --- a/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java +++ b/app/src/main/java/com/tangem/wallet/bch/BtcCashEngine.java @@ -141,7 +141,7 @@ public class BtcCashEngine extends CoinEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse("https://api.blockchair.com/bitcoin-cash/dashboards/address/" + ctx.getCoinData().getWallet()); + return Uri.parse("https://blockchair.com/bitcoin-cash/address/" + ctx.getCoinData().getWallet()); } @Override From c995e6cfe792a2aca62b7d212b8d39da3dfa71d6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 18 Mar 2020 18:36:56 +0300 Subject: [PATCH 20/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java | 2 +- .../java/com/tangem/ui/SignTransactionFragment.kt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index 975155be4e..ff06160ba2 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -356,7 +356,7 @@ public class XrpEngine extends CoinEngine { Long.valueOf(amount).compareTo(coinData.getReserveInInternalUnits().longValue()) < 0 ) { String reserveDescription = convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()); - throw new Exception("Target account is not created. Amount should be " + reserveDescription + " or more"); + throw new IllegalArgumentException("Target account is not created. Send " + reserveDescription + " or more to create"); } XrpPayment payment = new XrpPayment(); diff --git a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt index 2117c6a9a6..8429ec1e2e 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt @@ -34,6 +34,7 @@ import com.tangem.wallet.TangemContext import kotlinx.android.synthetic.main.layout_progress_horizontal.* import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.* +import java.lang.IllegalArgumentException class SignTransactionFragment : BaseFragment(), NavigationResultListener, @@ -148,6 +149,10 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener, } catch (e: Exception) { e.printStackTrace() } + } catch(e: IllegalArgumentException) { + val data = Bundle() + data.putString(Constant.EXTRA_MESSAGE, e.message) + navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment) } catch (e: Exception) { e.printStackTrace() } From 15b520db93e5f17dfac1542e64f4bc0770c0ac49 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Mar 2020 15:42:48 +0300 Subject: [PATCH 21/25] Updated on 2026-08-14 --- .../com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt | 7 +++---- app/src/main/java/com/tangem/wallet/CoinEngine.java | 8 ++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 2fdf9776d8..4de8ee31c8 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -163,7 +163,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte when (items[which]) { getString(R.string.loaded_wallet_load_via_app) -> { try { - val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUri) + val intent = Intent(Intent.ACTION_VIEW, engine.shareWalletUriEx) intent.addCategory(Intent.CATEGORY_DEFAULT) startActivity(intent) } catch (e: ActivityNotFoundException) { @@ -176,7 +176,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte } getString(R.string.loaded_wallet_load_via_qr) -> { - ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUri.toString()) + ShowQRCodeDialog.show(activity as AppCompatActivity?, engine.shareWalletUriEx.toString()) } getString(R.string.loaded_wallet_load_via_cryptonit) -> { @@ -885,8 +885,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte private fun doShareWallet(useURI: Boolean) { if (useURI) { - val engine = CoinEngineFactory.create(ctx) - val txtShare = engine?.shareWalletUri.toString() + val txtShare = ctx.coinData.wallet val intent = Intent(Intent.ACTION_SEND) intent.type = Constant.INTENT_TYPE_TEXT_PLAIN intent.putExtra(Intent.EXTRA_SUBJECT, Constant.WALLET_ADDRESS) diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java index 25b6a47ed2..5fce47c602 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -3,6 +3,7 @@ package com.tangem.wallet; import android.net.Uri; import android.text.InputFilter; +import com.tangem.data.Blockchain; import com.tangem.tangem_card.reader.CardProtocol; import com.tangem.tangem_card.tasks.SignTask; @@ -195,6 +196,13 @@ public abstract class CoinEngine { public abstract Uri getShareWalletUri(); + public Uri getShareWalletUriEx(){ + if (ctx.getBlockchain() == Blockchain.BitcoinCash) + return getShareWalletUri(); + else + return Uri.parse(ctx.getBlockchain().name().toLowerCase() + ":" + getShareWalletUri().toString()); + } + public abstract boolean checkNewTransactionAmount(Amount amount); public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded); From 46d3efaea7dc4bc75a2776ce0dbd5e7a09a3ffc3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Mar 2020 15:38:42 +0300 Subject: [PATCH 22/25] Updated on 2026-08-14 --- app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java index 493b45cc01..8476b33a37 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmEngine.java @@ -361,10 +361,14 @@ public class XlmEngine extends CoinEngine { } Operation operation; - if (coinData.isTargetAccountCreated()) + if (coinData.isTargetAccountCreated()) { operation = new PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), new AssetTypeNative(), amountValue.toValueString()).build(); - else + } else { + if (amountValue.compareTo(coinData.getReserve()) < 0) { + throw new IllegalArgumentException("Target account is not created. Send " + coinData.getReserve().toDescriptionString(getDecimals()) + " or more to create"); + } operation = new CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build(); + } TransactionEx transaction = TransactionEx.buildEx(120, coinData.getAccountResponse(), operation); @@ -432,7 +436,7 @@ public class XlmEngine extends CoinEngine { if (request.errorResponse != null && request.errorResponse.getCode() == 404) { coinData.setTargetAccountCreated(false); - if (amount.compareTo(coinData.getReserve()) >= 0) { //TODO: take fee inclusion in account, now 1 XLM with fee included will fail after transaction is sent + if (amount.compareTo(coinData.getReserve()) >= 0) { blockchainRequestsCallbacks.onComplete(true); } else { ctx.setError(R.string.confirm_transaction_error_not_enough_xlm_for_create); From ec51c84a7863bf1c59a7ac75623d79cf0986dff7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Mar 2020 15:59:50 +0300 Subject: [PATCH 23/25] Updated on 2026-08-14 --- tangem-core/build.gradle | 2 +- .../src/main/java/com/tangem/CardManager.kt | 59 +++++-- .../com/tangem/commands/OpenSessionCommand.kt | 5 + .../java/com/tangem/commands/ReadCommand.kt | 116 ++++++++++--- .../tangem/commands/ReadIssuerDataCommand.kt | 4 +- .../commands/ReadIssuerExtraDataCommand.kt | 4 +- .../commands/personalization/CardConfig.kt | 112 +++++++++++++ .../personalization/DepersonalizeCommand.kt | 31 ++++ .../commands/personalization/NdefEncoder.kt | 87 ++++++++++ .../personalization/PersonalizeCommand.kt | 155 ++++++++++++++++++ .../personalization/entities/Acquirer.kt | 9 + .../personalization/entities/Issuer.kt | 18 ++ .../personalization/entities/Manufacturer.kt | 21 +++ .../com/tangem/common/apdu/Instruction.kt | 4 +- .../java/com/tangem/common/tlv/TlvEncoder.kt | 29 ++-- .../java/com/tangem/common/tlv/TlvMapper.kt | 2 +- .../main/java/com/tangem/common/tlv/TlvTag.kt | 19 ++- .../java/com/tangem/crypto/CryptoUtils.kt | 3 +- .../com/tangem/tasks/WriteIssuerDataTask.kt | 4 +- .../tangem/tasks/WriteIssuerExtraDataTask.kt | 7 +- .../com/tangem/common/tlv/TlvMapperTest.kt | 10 +- tangem-demo/build.gradle | 2 +- .../com/tangem/tangemtest/MainActivity.kt | 26 +++ .../tangemtest/extensions/CardConfig.kt | 129 +++++++++++++++ .../src/main/res/layout/activity_main.xml | 20 +++ tangem-sdk/build.gradle | 4 +- .../DefaultCardManagerDelegate.kt | 2 +- 27 files changed, 811 insertions(+), 73 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt create mode 100644 tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index c76707df84..fe6ed6638a 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -12,9 +12,9 @@ dependencies { implementation "com.madgag.spongycastle:core:1.58.0.0" implementation "com.madgag.spongycastle:prov:1.58.0.0" implementation 'net.i2p.crypto:eddsa:0.3.0' + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testImplementation "com.google.truth:truth:1.0" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" } sourceCompatibility = "8" diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 83fcd22aeb..7a453944fb 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -1,6 +1,10 @@ package com.tangem import com.tangem.commands.* +import com.tangem.commands.personalization.CardConfig +import com.tangem.commands.personalization.DepersonalizeCommand +import com.tangem.commands.personalization.DepersonalizeResponse +import com.tangem.commands.personalization.PersonalizeCommand import com.tangem.common.CardEnvironment import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -176,16 +180,16 @@ class CardManager( * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ fun writeUserData( - cardId: String, - userData: ByteArray? = null, - userProtectedData: ByteArray? = null, - userCounter: Int? = null, - userProtectedCounter: Int? = null, - callback: (result: TaskEvent) -> Unit + cardId: String, + userData: ByteArray? = null, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + callback: (result: TaskEvent) -> Unit ) { - val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) - val task = SingleCommandTask(writeUserDataCommand) - runTask(task, cardId, callback) + val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + val task = SingleCommandTask(writeUserDataCommand) + runTask(task, cardId, callback) } /** @@ -199,8 +203,8 @@ class CardManager( * For example, this fields may contain blockchain nonce value. */ fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) + val task = SingleCommandTask(ReadUserDataCommand()) + runTask(task, cardId, callback) } /** @@ -234,6 +238,39 @@ class CardManager( runTask(task, cardId, callback) } + /** + * Command available on SDK cards only + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * @param cardId CID, Unique Tangem card ID number. + */ + fun depersonalize(cardId: String, + callback: (result: TaskEvent) -> Unit) { + val depersonalizeCommand = DepersonalizeCommand() + val task = SingleCommandTask(depersonalizeCommand) + runTask(task, cardId, callback) + } + + /** + * Command available on SDK cards only + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param cardId this parameter will set up CID, Unique Tangem card ID. + */ + fun personalize(config: CardConfig, + cardId: String, + callback: (result: TaskEvent) -> Unit) { + val personalizationCommand = PersonalizeCommand(config, cardId) + val task = SingleCommandTask(personalizationCommand) + task.performPreflightRead = false + runTask(task, callback = callback) + } + /** */ diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 9b78cb8bea..85d3ddf866 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -14,7 +14,12 @@ class OpenSessionResponse( val uid: ByteArray ) : CommandResponse +/** + * In case of encrypted communication, App should setup a session before calling any further command. + * [OpenSessionCommand] generates secret session_key that is used by both host and card + * to encrypt and decrypt commands’ payload. + */ class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index b735e54d8f..1e55cd404b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -32,6 +32,49 @@ data class SigningMethod(val rawValue: Int) { const val signHashValidatedByIssuerAndWriteIssuerData = 4 const val signRawValidatedByIssuerAndWriteIssuerData = 5 const val signPos = 6 + + fun build( + signHash: Boolean = false, + signRaw: Boolean = false, + signHashValidatedByIssuer: Boolean = false, + signRawValidatedByIssuer: Boolean = false, + signHashValidatedByIssuerAndWriteIssuerData: Boolean = false, + signRawValidatedByIssuerAndWriteIssuerData: Boolean = false, + signPos: Boolean = false + + ): SigningMethod { + fun Boolean.toInt() = if (this) 1 else 0 + + val signingMethodsCount = 0 + + signHash.toInt() + + signRaw.toInt() + + signHashValidatedByIssuer.toInt() + + signRawValidatedByIssuer.toInt() + + signHashValidatedByIssuerAndWriteIssuerData.toInt() + + signRawValidatedByIssuerAndWriteIssuerData.toInt() + + signPos.toInt() + + var signingMethod: Int = 0 + if (signingMethodsCount == 1) { + if (signHash) signingMethod += SigningMethod.signHash + if (signRaw) signingMethod += SigningMethod.signRaw + if (signHashValidatedByIssuer) signingMethod += SigningMethod.signHashValidatedByIssuer + if (signRawValidatedByIssuer) signingMethod += SigningMethod.signRawValidatedByIssuer + if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signHashValidatedByIssuerAndWriteIssuerData + if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signRawValidatedByIssuerAndWriteIssuerData + if (signPos) signingMethod += SigningMethod.signPos + } else if (signingMethodsCount > 1) { + signingMethod = 0x80 + if (signHash) signingMethod += 0x01 + if (signRaw) signingMethod += 0x01 shl SigningMethod.signRaw + if (signHashValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuer + if (signRawValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuer + if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuerAndWriteIssuerData + if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData + if (signPos) signingMethod += 0x01 shl SigningMethod.signPos + } + return SigningMethod(signingMethod) + } } } @@ -79,43 +122,74 @@ data class ProductMask(val rawValue: Int) { } } +class ProductMaskBuilder() { + + private var productMaskValue = 0 + + fun add(productCode: Int) { + productMaskValue = productMaskValue or productCode + } + + fun build() = ProductMask(productMaskValue) + +} + /** * Stores and maps Tangem card settings. * * @property rawValue Card settings in a form of flags, - * while flags definitions and values are in [SettingsMask.Companion] as constants. + * while flags definitions and possible values are in [Settings]. */ data class SettingsMask(val rawValue: Int) { + fun contains(settings: Settings): Boolean = (rawValue and settings.code) != 0 +} - fun contains(value: Int): Boolean = (rawValue and value) != 0 +enum class Settings(val code: Int) { + IsReusable(0x0001), + UseActivation(0x0002), + ForbidPurgeWallet(0x0004), + UseBlock(0x0008), - companion object { - const val isReusable = 0x0001 - const val useActivation = 0x0002 - const val forbidPurgeWallet = 0x0004 - const val useBlock = 0x0008 + AllowSwapPIN(0x0010), + AllowSwapPIN2(0x0020), + UseCVC(0x0040), + ForbidDefaultPIN(0x0080), - const val allowSwapPIN = 0x0010 - const val allowSwapPIN2 = 0x0020 - const val useCVC = 0x0040 - const val forbidDefaultPIN = 0x0080 + UseOneCommandAtTime(0x0100), + UseNdef(0x0200), + UseDynamicNdef(0x0400), + SmartSecurityDelay(0x0800), - const val useOneCommandAtTime = 0x0100 - const val useNdef = 0x0200 - const val useDynamicNdef = 0x0400 - const val smartSecurityDelay = 0x0800 + ProtocolAllowUnencrypted(0x1000), + ProtocolAllowStaticEncryption(0x2000), - const val protocolAllowUnencrypted = 0x1000 - const val protocolAllowStaticEncryption = 0x2000 + ProtectIssuerDataAgainstReplay(0x4000), + RestrictOverwriteIssuerDataEx(0x00100000), - const val protectIssuerDataAgainstReplay = 0x4000 + AllowSelectBlockchain(0x8000), - const val allowSelectBlockchain = 0x8000 + DisablePrecomputedNdef(0x00010000), - const val disablePrecomputedNdef = 0x00010000 + SkipSecurityDelayIfValidatedByLinkedTerminal(0x00080000), + SkipCheckPin2andCvcIfValidatedByIssuer(0x00040000), + SkipSecurityDelayIfValidatedByIssuer(0x00020000), - const val skipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000 + RequireTermTxSignature(0x01000000), + RequireTermCertSignature(0x02000000), + CheckPIN3onCard(0x04000000) +} + + +class SettingsMaskBuilder() { + + private var settingsMaskValue = 0 + + fun add(settings: Settings) { + settingsMaskValue = settingsMaskValue or settings.code } + + fun build() = SettingsMask(settingsMaskValue) + } /** diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index fea83e979f..cb95fd0899 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -28,7 +28,7 @@ class ReadIssuerDataResponse( * 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 + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: * SHA256([cardId] | [issuerData] | [issuerDataCounter]). */ @@ -36,7 +36,7 @@ class ReadIssuerDataResponse( /** * An optional counter that protect issuer data against replay attack. - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. */ val issuerDataCounter: Int? diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 3a6e8309ff..5672003477 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -33,7 +33,7 @@ class ReadIssuerExtraDataResponse( * 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 + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: * SHA256([cardId] | [issuerData] | [issuerDataCounter]). */ @@ -41,7 +41,7 @@ class ReadIssuerExtraDataResponse( /** * An optional counter that protect issuer data against replay attack. - * When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. */ val issuerDataCounter: Int? diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt new file mode 100644 index 0000000000..15548f9871 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt @@ -0,0 +1,112 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.* + +data class NdefRecord( + val type: Type, + val value: String +) { + enum class Type { + URI, AAR, TEXT + } + + val valueInBytes: ByteArray by lazy { value.toByteArray() } +} + +/** + * It is a configuration file with all the card settings that are written on the card + * during [PersonalizeCommand]. + */ +data class CardConfig( + val issuerName: String? = null, + val acquirerName: String? = null, + val series: String? = null, + val startNumber: Long = 0, + val count: Int = 0, + val pin: String, + val pin2: String, + val pin3: String, + val hexCrExKey: String?, + val cvc: String, + val pauseBeforePin2: Int, + val smartSecurityDelay: Boolean, + val curveID: EllipticCurve, + val signingMethod: SigningMethod, + val maxSignatures: Int, + val isReusable: Boolean, + val allowSwapPin: Boolean, + val allowSwapPin2: Boolean, + val useActivation: Boolean, + val useCvc: Boolean, + val useNdef: Boolean, + val useDynamicNdef: Boolean, + val useOneCommandAtTime: Boolean, + val useBlock: Boolean, + val allowSelectBlockchain: Boolean, + val forbidPurgeWallet: Boolean, + val protocolAllowUnencrypted: Boolean, + val protocolAllowStaticEncryption: Boolean, + val protectIssuerDataAgainstReplay: Boolean, + val forbidDefaultPin: Boolean, + val disablePrecomputedNdef: Boolean, + val skipSecurityDelayIfValidatedByIssuer: Boolean, + val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean, + val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean, + + val restrictOverwriteIssuerDataEx: Boolean, + + val requireTerminalTxSignature: Boolean, + val requireTerminalCertSignature: Boolean, + val checkPin3onCard: Boolean, + + val createWallet: Boolean, + + val cardData: CardData, + val ndefRecords: List +) { + + fun getSettingsMask(): SettingsMask { + val builder = SettingsMaskBuilder() + + if (allowSwapPin) builder.add(Settings.AllowSwapPIN) + if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2) + if (useCvc) builder.add(Settings.UseCVC) + if (isReusable) builder.add(Settings.IsReusable) + + if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime) + if (useNdef) builder.add(Settings.UseNdef) + if (useDynamicNdef) builder.add(Settings.UseDynamicNdef) + if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef) + + if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted) + if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption) + + if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN) + + if (useActivation) builder.add(Settings.UseActivation) + + if (useBlock) builder.add(Settings.UseBlock) + if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay) + + if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay) + + if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet) + if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain) + + if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer) + if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer) + + if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal) + if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx) + + if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature) + + if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature) + + if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard) + + return builder.build() + } + + companion object +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt new file mode 100644 index 0000000000..46239ea4ea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -0,0 +1,31 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.CommandResponse +import com.tangem.commands.CommandSerializer +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu + +data class DepersonalizeResponse(val success: Boolean) : CommandResponse + +/** + * Command available on SDK cards only + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * @param cardId CID, Unique Tangem card ID number. + */ +class DepersonalizeCommand : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + return CommandApdu( + Instruction.Depersonalize, byteArrayOf(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? { + return DepersonalizeResponse(true) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt new file mode 100644 index 0000000000..a034d171cb --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt @@ -0,0 +1,87 @@ +package com.tangem.commands.personalization + +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets + +/** + * Encodes information that is to be written on the card as an Ndef Tag. + */ +class NdefEncoder(private val ndefRecords: List, private val useDinamicNdef: Boolean) { + + fun encode(): ByteArray { + val bs = ByteArrayOutputStream() + // space for size + bs.write(0) + bs.write(0) + + for (i in ndefRecords.indices) { + val headerValue = (if (i == 0) 0x80 else 0x00) or (if (!useDinamicNdef && i == ndefRecords.size - 1) 0x40 else 0x00) + var value: ByteArray = ndefRecords[i].value.toByteArray(StandardCharsets.UTF_8) + encodeValue(ndefRecords[i], headerValue, bs) + } + + val result = bs.toByteArray() + result[0] = (result.size - 2 shr 8).toByte() + result[1] = (result.size - 2 and 0xFF).toByte() + return result + + } + + + private fun encodeValue(ndefRecord: NdefRecord, headerValue: Int, bs: ByteArrayOutputStream) { + when (ndefRecord.type) { + NdefRecord.Type.AAR -> { + bs.write((headerValue or 0x14)) // NDEF Header + bs.write(0x0F) // Length of the record type + bs.write(ndefRecord.valueInBytes.size) // Length of the payload data + bs.write(byteArrayOf(0x61.toByte(), 0x6E.toByte(), 0x64.toByte(), 0x72.toByte(), 0x6F.toByte(), 0x69.toByte(), 0x64.toByte(), 0x2E.toByte(), 0x63.toByte(), 0x6F.toByte(), 0x6D.toByte(), 0x3A.toByte(), + 0x70.toByte(), 0x6B.toByte(), 0x67.toByte())) // type name + bs.write(ndefRecord.valueInBytes) + } + NdefRecord.Type.URI -> { + bs.write((headerValue or 0x11)) // NDEF Header + bs.write(0x01) // Length of the record type + val uriIdentifierCode: Byte + val prefix: String + when { + ndefRecord.value.startsWith("http://www.") -> { + uriIdentifierCode = 0x01.toByte() + prefix = "http://www." + } + ndefRecord.value.startsWith("https://www.") -> { + uriIdentifierCode = 0x02.toByte() + prefix = "https://www." + } + ndefRecord.value.startsWith("http://") -> { + uriIdentifierCode = 0x03.toByte() + prefix = "http://" + } + ndefRecord.value.startsWith("https://") -> { + uriIdentifierCode = 0x04.toByte() + prefix = "https://" + } + else -> { + throw Exception() + } + } + val value = ndefRecord.value.substring(prefix.length).toByteArray() + bs.write(value.size + 1) // Length of the payload data + bs.write(0x55) // URI + bs.write(uriIdentifierCode.toInt()) // ? + bs.write(value) + } + NdefRecord.Type.TEXT -> { + bs.write((headerValue or 0x11)) // NDEF Header + bs.write(0x01) // Length of the record type + bs.write(ndefRecord.valueInBytes.size.toByte() + 1 + "en".length) // Length of the payload data + bs.write(0x54) // Text + bs.write(0x02) // UTF8(MSB=0)|"en".length + bs.write("en".toByteArray(StandardCharsets.US_ASCII)) + bs.write(ndefRecord.valueInBytes) + } + else -> throw Exception("Invalid NDEF record in config!") + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt new file mode 100644 index 0000000000..25bd22b4ba --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -0,0 +1,155 @@ +package com.tangem.commands.personalization + +import com.tangem.commands.Card +import com.tangem.commands.CardData +import com.tangem.commands.CommandSerializer +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer +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 +import com.tangem.crypto.sign +import com.tangem.tasks.TaskError + +/** + * Command available on SDK cards only + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param cardId this parameter will set up CID, Unique Tangem card ID. + */ +class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + return CommandApdu( + Instruction.Personalize, + serializePersonalizationData(cardId, config), + encryptionKey = devPersonalizationKey + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { + val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null + + return try { + val tlvMapper = TlvMapper(tlvData) + Card( + cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", + manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", + status = tlvMapper.mapOptional(TlvTag.Status), + + firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), + cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), + settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), + issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), + curve = tlvMapper.mapOptional(TlvTag.CurveId), + maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), + signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), + pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), + walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), + walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), + health = tlvMapper.mapOptional(TlvTag.Health), + isActivated = tlvMapper.map(TlvTag.IsActivated), + activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), + paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), + userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), + terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + + cardData = deserializeCardData(tlvData) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } + + private fun deserializeCardData(tlvData: List): CardData? { + val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { + Tlv.deserialize(it.value) + } + if (cardDataTlvs.isNullOrEmpty()) return null + + val tlvMapper = TlvMapper(cardDataTlvs) + return CardData( + batchId = tlvMapper.mapOptional(TlvTag.Batch), + manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), + issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), + blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), + manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), + productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + + tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), + tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), + tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + ) + } + + private fun serializePersonalizationData(cardId: String, config: CardConfig): ByteArray { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.CardId, cardId) + + tlvBuilder.append(TlvTag.CurveId, config.curveID) + tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures) + tlvBuilder.append(TlvTag.SigningMethod, config.signingMethod) + tlvBuilder.append(TlvTag.SettingsMask, config.getSettingsMask()) + tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10) + tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray()) + if (!config.ndefRecords.isNullOrEmpty()) tlvBuilder.append(TlvTag.NdefData, serializeNdef(config.ndefRecords)) + + tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet) + + tlvBuilder.append(TlvTag.NewPin, config.pin) + tlvBuilder.append(TlvTag.NewPin2, config.pin2) + tlvBuilder.append(TlvTag.NewPin3, config.pin3) + tlvBuilder.append(TlvTag.CrExKey, config.hexCrExKey) + tlvBuilder.append(TlvTag.IssuerDataPublicKey, Issuer.dataPublicKey) + tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, Issuer.transactionPublicKey) + + tlvBuilder.append(TlvTag.AcquirerPublicKey, Acquirer.publicKey) + + tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData)) + + return tlvBuilder.serialize() + } + + private fun serializeCardData(cardId: String, cardData: CardData): ByteArray { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Batch, cardData.batchId) + tlvBuilder.append(TlvTag.ProductMask, cardData.productMask) + + tlvBuilder.append(TlvTag.ManufactureDateTime, cardData.manufactureDateTime) + + tlvBuilder.append(TlvTag.IssuerId, Issuer.id) + + tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName) + + if (cardData.tokenSymbol != null) { + tlvBuilder.append(TlvTag.TokenSymbol, cardData.tokenSymbol) + tlvBuilder.append(TlvTag.TokenContractAddress, cardData.tokenContractAddress) + tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal) + } + + tlvBuilder.append(TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(Manufacturer.devPrivateKey)) + + return tlvBuilder.serialize() + } + + private fun serializeNdef(ndefRecords: List): ByteArray { + return NdefEncoder(ndefRecords, config.useDynamicNdef).encode() + } + + companion object { + val devPersonalizationKey = "1234".calculateSha256().copyOf(32) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt new file mode 100644 index 0000000000..60bf33e774 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt @@ -0,0 +1,9 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.CryptoUtils + +object Acquirer { + val privateKey = "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() + val publicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(privateKey) } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt new file mode 100644 index 0000000000..50ada2281c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt @@ -0,0 +1,18 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.CryptoUtils + +object Issuer { + + val name = "TANGEM SDK" + val id = name + "\u0000" + val dataPrivateKey = "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() + + val dataPublicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(dataPrivateKey) } + + val transactionPrivateKey = + "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".hexToBytes() + + val transactionPublicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(transactionPrivateKey) } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt new file mode 100644 index 0000000000..cbe77110ab --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt @@ -0,0 +1,21 @@ +package com.tangem.commands.personalization.entities + +object Manufacturer { + val devPrivateKey = byteArrayOf( + 0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(), + 0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(), + 0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(), + 0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte() + ) + + val devPublicKey = byteArrayOf(0x04, + 0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(), + 0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(), + 0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(), + 0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(), + 0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(), + 0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(), + 0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(), + 0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte() + ) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index a913813eb2..46c8989535 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -6,6 +6,7 @@ package com.tangem.common.apdu */ enum class Instruction(var code: Int) { Unknown(0x00), + Personalize(0xF1), Read(0xF2), VerifyCard(0xF3), ValidateCard(0xF4), @@ -20,7 +21,8 @@ enum class Instruction(var code: Int) { Activate(0xFE), OpenSession(0xFF), WriteUserData(0xE0), - ReadUserData(0xE1); + ReadUserData(0xE1), + Depersonalize(0xE3); companion object { diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt index 1186dceeb3..58e4043450 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -6,6 +6,7 @@ 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.common.extensions.toHexString import com.tangem.tasks.TaskError import java.util.* @@ -32,11 +33,11 @@ class TlvEncoder { return when (tag.valueType()) { TlvValueType.HexString -> { typeCheck(tag) - return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) { - (value as String).calculateSha256() - } else { - (value as String).hexToBytes() - } + (value as String).hexToBytes() + } + TlvValueType.HexStringToHash -> { + typeCheck(tag) + (value as String).calculateSha256() } TlvValueType.Utf8String -> { typeCheck(tag) @@ -52,8 +53,8 @@ class TlvEncoder { } TlvValueType.BoolValue -> { typeCheck(tag) - Log.e(this::class.simpleName!!, "Unsupported operation: Boolean to ByteArray for tag $tag") - throw TaskError.ConvertError() + val booleanValue = value as Boolean + if (booleanValue) byteArrayOf(1) else byteArrayOf(0) } TlvValueType.ByteArray -> { typeCheck(tag) @@ -61,7 +62,7 @@ class TlvEncoder { } TlvValueType.EllipticCurve -> { typeCheck(tag) - (value as EllipticCurve).curve.plus("\\0").toByteArray() + (value as EllipticCurve).curve.toByteArray() } TlvValueType.DateTime -> { typeCheck(tag) @@ -69,7 +70,7 @@ class TlvEncoder { val year = calendar.get(Calendar.YEAR) val month = calendar.get(Calendar.MONTH) + 1 val day = calendar.get(Calendar.DAY_OF_MONTH) - return year.toByteArray() + month.toByteArray() + day.toByteArray() + return year.toByteArray(2) + month.toByte() + day.toByte() } TlvValueType.ProductMask -> { typeCheck(tag) @@ -79,7 +80,8 @@ class TlvEncoder { } TlvValueType.SettingsMask -> { typeCheck(tag) - (value as SettingsMask).rawValue.toByteArray(2) + val rawValue = (value as SettingsMask).rawValue + rawValue.toByteArray(determineByteArraySize(rawValue)) } TlvValueType.CardStatus -> { typeCheck(tag) @@ -87,7 +89,7 @@ class TlvEncoder { } TlvValueType.SigningMethod -> { typeCheck(tag) - (value as SigningMethod).rawValue.toByteArray() + byteArrayOf((value as SigningMethod).rawValue.toByte()) } TlvValueType.IssuerDataMode -> { typeCheck(tag) @@ -96,6 +98,11 @@ class TlvEncoder { } } + private fun determineByteArraySize(value: Int): Int { + val mask = 0xFFFF0000.toInt() + return if ((value and mask) != 0) 4 else 2 + } + private inline fun typeCheck(tag: TlvTag) { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt index 184f2fb49d..1713fd2e2d 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt @@ -54,7 +54,7 @@ class TlvMapper(val tlvList: List) { } return when (tag.valueType()) { - TlvValueType.HexString -> { + TlvValueType.HexString, TlvValueType.HexStringToHash -> { typeCheck(tag) tlvValue.toHexString() as T } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index c9f3275dfe..44200cd20a 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -5,6 +5,7 @@ package com.tangem.common.tlv */ enum class TlvValueType { HexString, + HexStringToHash, Utf8String, Uint16, Uint32, @@ -36,6 +37,7 @@ enum class TlvTag(val code: Int) { SettingsMask(0x0A), CardData(0x0C), NdefData(0x0D), + CreateWalletAtPersonalize(0x0E), Health(0x0F), Pin(0x10), @@ -51,8 +53,11 @@ enum class TlvTag(val code: Int) { SessionKeyA(0x1A), SessionKeyB(0x1B), - Uid(0x0B), Pause(0x1C), + NewPin3(0x1E), + CrExKey(0x1F), + + Uid(0x0B), ManufactureId(0x20), ManufacturerSignature(0x86), @@ -63,6 +68,8 @@ enum class TlvTag(val code: Int) { IssuerDataSignature(0x33), IssuerTransactionSignature(0x34), IssuerDataCounter(0x35), + AcquirerPublicKey(0x37), + Size(0x25), Mode(0x23), Offset(0x24), @@ -117,15 +124,15 @@ enum class TlvTag(val code: Int) { */ fun valueType(): TlvValueType { return when (this) { - CardId, Pin, Pin2, Batch -> TlvValueType.HexString + CardId, Batch, CrExKey -> TlvValueType.HexString + Pin, Pin2, NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve - MaxSignatures, PauseBeforePin2, RemainingSignatures, - SignedHashes, Health, TokenDecimal, + PauseBeforePin2, RemainingSignatures, SignedHashes, Health, TokenDecimal, Offset, Size -> TlvValueType.Uint16 - UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 - IsActivated, TerminalIsLinked -> TlvValueType.BoolValue + MaxSignatures, UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 + IsActivated, TerminalIsLinked, CreateWalletAtPersonalize -> TlvValueType.BoolValue ManufactureDateTime -> TlvValueType.DateTime ProductMask -> TlvValueType.ProductMask SettingsMask -> TlvValueType.SettingsMask diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index d7643bdaae..af6f5e2b76 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -2,6 +2,7 @@ package com.tangem.crypto import com.tangem.commands.EllipticCurve import net.i2p.crypto.eddsa.EdDSASecurityProvider +import org.spongycastle.jce.provider.BouncyCastleProvider import java.security.PublicKey import java.security.SecureRandom import java.security.Security @@ -13,7 +14,7 @@ import javax.crypto.spec.SecretKeySpec object CryptoUtils { fun initCrypto() { - Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1) + Security.insertProviderAt(BouncyCastleProvider(), 1) Security.addProvider(EdDSASecurityProvider()) } diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt index c8b3bcb083..b22ed95049 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt @@ -1,7 +1,7 @@ package com.tangem.tasks import com.tangem.commands.Card -import com.tangem.commands.SettingsMask +import com.tangem.commands.Settings import com.tangem.commands.WriteIssuerDataCommand import com.tangem.commands.WriteIssuerDataResponse import com.tangem.commands.common.IssuerDataToVerify @@ -55,7 +55,7 @@ class WriteIssuerDataTask( if (isCounterRequired()) issuerDataCounter != null else true private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean { return command.verify( diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt index f56ac1c5d5..887e832548 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt @@ -1,9 +1,6 @@ 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.* import com.tangem.commands.common.IssuerDataMode import com.tangem.commands.common.IssuerDataToVerify import com.tangem.common.CardEnvironment @@ -98,7 +95,7 @@ internal class WriteIssuerExtraDataTask( if (isCounterRequired()) issuerDataCounter != null else true private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean { val publicKey = issuerPublicKey ?: card.issuerPublicKey!! diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt index b1ebcf143e..64cb5ce15d 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt @@ -68,15 +68,15 @@ class TlvMapperTest { .isNotNull() assertThat(settingsMask.rawValue) .isEqualTo(32289) - assertThat(settingsMask.contains(SettingsMask.skipSecurityDelayIfValidatedByLinkedTerminal)) + assertThat(settingsMask.contains(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal)) .isFalse() - assertThat(settingsMask.contains(SettingsMask.isReusable)) + assertThat(settingsMask.contains(Settings.IsReusable)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.allowSwapPIN2)) + assertThat(settingsMask.contains(Settings.AllowSwapPIN2)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.useDynamicNdef)) + assertThat(settingsMask.contains(Settings.UseDynamicNdef)) .isTrue() - assertThat(settingsMask.contains(SettingsMask.forbidPurgeWallet)) + assertThat(settingsMask.contains(Settings.ForbidPurgeWallet)) .isFalse() } diff --git a/tangem-demo/build.gradle b/tangem-demo/build.gradle index b1de1a90fd..614e6b9552 100644 --- a/tangem-demo/build.gradle +++ b/tangem-demo/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt index 01207d703d..1a294a8897 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -4,7 +4,9 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager +import com.tangem.commands.personalization.CardConfig import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tangemtest.extensions.init import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError import com.tangem.tasks.TaskEvent @@ -151,6 +153,30 @@ class MainActivity : AppCompatActivity() { } } btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) } + btn_personalize?.setOnClickListener { _ -> + cardManager.personalize( + CardConfig.init(application), "BB00000000000395" + ) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = it.data.cardId } + } + } + } + btn_depersonalize?.setOnClickListener { _ -> + cardManager.depersonalize(cardId) { + when (it) { + is TaskEvent.Completion -> { + if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + } + is TaskEvent.Event -> runOnUiThread { + tv_card_cid?.text = "Depersonalized: ${it.data.success.toString()}" + } + } + } + } } private fun createSampleHashes(): Array { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt new file mode 100644 index 0000000000..1b409eddd3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt @@ -0,0 +1,129 @@ +package com.tangem.tangemtest.extensions + +import android.app.Application +import android.content.Context +import com.tangem.commands.* +import com.tangem.commands.personalization.CardConfig +import com.tangem.commands.personalization.NdefRecord +import com.tangem.commands.personalization.entities.Issuer +import java.util.* + +fun CardConfig.Companion.init(application: Application): CardConfig { + + val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE) + + val signingMethod = SigningMethod.build( + signHash = preferences.getBoolean("personalization_SigningMethod_0", false), + signRaw = preferences.getBoolean("personalization_SigningMethod_1", false), + signHashValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_2", false), + signRawValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_3", false), + signHashValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_4", false), + signRawValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_5", false), + signPos = preferences.getBoolean("personalization_SigningMethod_6", false) + ) + + val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true) + val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false) + val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false) + + val productMaskBuilder = ProductMaskBuilder() + if (isNote) productMaskBuilder.add(ProductMask.note) + if (isTag) productMaskBuilder.add(ProductMask.tag) + if (isIdCard) productMaskBuilder.add(ProductMask.idCard) + val productMask = productMaskBuilder.build() + + var tokenSymbol: String? = null + var tokenContractAddress: String? = null + var tokenDecimal: Int? = null + if (preferences.getBoolean("personalization_isToken", false)) { + tokenSymbol = preferences.getString("personalization_token_symbol", "") + tokenContractAddress = preferences.getString("personalization_token_contract_address", "") + tokenDecimal = preferences.getString("personalization_token_decimal", "")!!.toInt() + } + + val cardData = CardData( + blockchainName = preferences.getString("personalization_Blockchain", "BTC"), + batchId = preferences.getString("personalization_card_batch", "FFFF"), + productMask = productMask, + tokenSymbol = tokenSymbol, + tokenContractAddress = tokenContractAddress, + tokenDecimal = tokenDecimal, + issuerName = Issuer.id, + manufactureDateTime = Calendar.getInstance().time, + manufacturerSignature = null) + + + val ndefAar = preferences.getString("personalization_NDEF_AAR", "Release APP") + val ndefUri = preferences.getString("personalization_NDEF_URI", "https://tangem.com") + + val ndefs = mutableListOf() + if (!ndefUri.isNullOrEmpty()) { + ndefs.add(NdefRecord(NdefRecord.Type.URI, ndefUri)) + } + if (ndefAar != "None") { + val type = NdefRecord.Type.AAR + val value = when (ndefAar) { + "Debug APP" -> { + "com.tangem.wallet.debug" + } + "Release APP" -> { + "com.tangem.wallet" + } + "--- CUSTOM ---" -> { + preferences.getString("personalization_NDEF_CUSTOM_AAR", "com.tangem.wallet")!! + } + else -> "" + } + ndefs.add(NdefRecord(type, value)) + } + + return CardConfig( + cardData = cardData, + curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!) + ?: EllipticCurve.Secp256k1, + signingMethod = signingMethod, + createWallet = preferences.getBoolean("personalization_CreateWallet", true), + maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(), + isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true), + protocolAllowUnencrypted = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_None", true), + protocolAllowStaticEncryption = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_Fast", true), + useActivation = preferences.getBoolean("personalization_SettingsMask_NeedActivation", false), + + useOneCommandAtTime = preferences.getBoolean("personalization_SettingsMask_OneApduAtOnce", false), + useCvc = preferences.getBoolean("personalization_SettingsMask_UseCVC", false), + useBlock = preferences.getBoolean("personalization_SettingsMask_UseBlock", false), + allowSwapPin = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN", true), + allowSwapPin2 = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN2", true), + useNdef = preferences.getBoolean("personalization_SettingsMask_UseNDEF", true), + useDynamicNdef = preferences.getBoolean("personalization_SettingsMask_UseDynamicNDEF", true), + protectIssuerDataAgainstReplay = preferences.getBoolean("personalization_SettingsMask_ProtectIssuerDataAgainstReplay", true), + forbidDefaultPin = preferences.getBoolean("personalization_SettingsMask_ForbidDefaultPIN", false), + smartSecurityDelay = preferences.getBoolean("personalization_SettingsMask_SmartSecurityDelay", false), + pauseBeforePin2 = preferences.getString("personalization_PauseBeforePIN2", "15")!!.toInt() * 1000, + allowSelectBlockchain = preferences.getBoolean("personalization_SettingsMask_AllowSelectBlockchain", false), + forbidPurgeWallet = preferences.getBoolean("personalization_SettingsMask_ForbidPurgeWallet", false) + ?: false, + disablePrecomputedNdef = preferences.getBoolean("personalization_SettingsMask_DisablePrecomputedNDEF", false) + ?: false, + skipSecurityDelayIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByIssuer", true), + skipCheckPIN2andCVCIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipCheckPIN2andCVCIfValidatedByIssuer", true), + + skipSecurityDelayIfValidatedByLinkedTerminal = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByLinkedTerminal", true), + restrictOverwriteIssuerDataEx = preferences.getBoolean("personalization_SettingsMask_RestrictOverwriteIssuerDataEx", true), + + requireTerminalTxSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalTxSignature", false), + requireTerminalCertSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalCertSignature", false), + checkPin3onCard = preferences.getBoolean("personalization_SettingsMask_CheckPIN3onCard", true), + + cvc = preferences.getString("personalization_cvc", "000") ?: "000", + pin = preferences.getString("personalization_pin", "000000") ?: "000000", + pin2 = preferences.getString("personalization_pin2", "000") ?: "000", + pin3 = preferences.getString("personalization_pin3", "123") ?: "123", + hexCrExKey = preferences.getString("personalization_CrEx_Key", "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"), + + issuerName = Issuer.id, + acquirerName = "TANGEM SDK", + + ndefRecords = ndefs + ) +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml index 2af3ad92de..d3bb8bbabe 100644 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ b/tangem-demo/src/main/res/layout/activity_main.xml @@ -111,6 +111,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn_create_wallet" /> +