Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-11 06:52:26 +00:00
commit a8d3a54da4
29 changed files with 437 additions and 208 deletions

View file

@ -1,6 +1,8 @@
package com.tangem
import com.tangem.commands.*
import com.tangem.common.CardEnvironment
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.*
import java.util.concurrent.Executors
@ -16,10 +18,12 @@ import java.util.concurrent.Executors
*/
class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null) {
private val cardManagerDelegate: CardManagerDelegate? = null,
private val config: Config = Config()
) {
private var terminalKeysService: TerminalKeysService? = null
private var isBusy = false
private val cardEnvironmentRepository = mutableMapOf<String, CardEnvironment>()
private val cardManagerExecutor = Executors.newSingleThreadExecutor()
init {
@ -67,7 +71,7 @@ class CardManager(
callback: (result: TaskEvent<SignResponse>) -> Unit) {
val signCommand: SignCommand
try {
signCommand = SignCommand(hashes, cardId)
signCommand = SignCommand(hashes)
} catch (error: Exception) {
if (error is TaskError) {
callback(TaskEvent.Completion(error))
@ -91,7 +95,7 @@ class CardManager(
*/
fun readIssuerData(cardId: String,
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
val getIssuerDataCommand = ReadIssuerDataCommand(cardId)
val getIssuerDataCommand = ReadIssuerDataCommand()
val task = SingleCommandTask(getIssuerDataCommand)
runTask(task, cardId, callback)
}
@ -114,10 +118,10 @@ class CardManager(
issuerDataCounter: Int? = null,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
val writeIssuerDataCommand = WriteIssuerDataCommand(
cardId,
issuerData,
issuerDataSignature,
issuerDataCounter)
issuerData,
issuerDataSignature,
issuerDataCounter
)
val task = SingleCommandTask(writeIssuerDataCommand)
runTask(task, cardId, callback)
}
@ -134,7 +138,7 @@ class CardManager(
*/
fun createWallet(cardId: String,
callback: (result: TaskEvent<CreateWalletResponse>) -> Unit) {
val createWalletCommand = CreateWalletCommand(cardId)
val createWalletCommand = CreateWalletCommand()
val task = SingleCommandTask(createWalletCommand)
runTask(task, cardId, callback)
}
@ -148,7 +152,7 @@ class CardManager(
*/
fun purgeWallet(cardId: String,
callback: (result: TaskEvent<PurgeWalletResponse>) -> Unit) {
val purgeWalletCommand = PurgeWalletCommand(cardId)
val purgeWalletCommand = PurgeWalletCommand()
val task = SingleCommandTask(purgeWalletCommand)
runTask(task, cardId, callback)
}
@ -163,7 +167,7 @@ class CardManager(
return
}
val environment = fetchCardEnvironment(cardId)
val environment = prepareCardEnvironment(cardId)
isBusy = true
task.reader = reader
@ -187,7 +191,19 @@ class CardManager(
runTask(task, cardId, callback)
}
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
return cardEnvironmentRepository[cardId] ?: CardEnvironment(cardId = cardId)
/**
* Allows to set a particular [TerminalKeysService] to retrieve terminal keys.
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
*/
fun setTerminalKeysService(terminalKeysService: TerminalKeysService) {
this.terminalKeysService = terminalKeysService
}
private fun prepareCardEnvironment(cardId: String?): CardEnvironment {
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
return CardEnvironment(
cardId = cardId,
terminalKeys = terminalKeys
)
}
}

View file

@ -0,0 +1,5 @@
package com.tangem
data class Config(
val linkedTerminal: Boolean = true
)

View file

@ -1,14 +1,16 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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.CryptoUtils
import com.tangem.tasks.TaskError
/**
@ -22,7 +24,16 @@ class CheckWalletResponse(
val cardId: String,
val salt: ByteArray,
val walletSignature: ByteArray
) : CommandResponse
) : CommandResponse {
fun verify(curve: EllipticCurve, publicKey: ByteArray, challenge: ByteArray): Boolean {
return CryptoUtils.verify(
publicKey,
challenge + salt,
walletSignature,
curve)
}
}
/**
* This command proves that the wallet private key from the card corresponds to the wallet public key.
@ -32,18 +43,16 @@ class CheckWalletResponse(
* @property cardId Unique Tangem card ID number
* @property challenge Random challenge generated by application
*/
class CheckWalletCommand(
private val cardId: String,
private val challenge: ByteArray
) : CommandSerializer<CheckWalletResponse>() {
class CheckWalletCommand : CommandSerializer<CheckWalletResponse>() {
val challenge = CryptoUtils.generateRandomBytes(16)
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = listOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.Challenge, challenge)
)
return CommandApdu(Instruction.CheckWallet, tlvData)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Challenge, challenge)
return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {

View file

@ -1,6 +1,6 @@
package com.tangem.commands
import com.tangem.CardEnvironment
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extensions.toInt

View file

@ -1,12 +1,13 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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.tasks.TaskError
@ -37,21 +38,15 @@ class CreateWalletResponse(
*
* @property cardId CID, Unique Tangem card ID number.
*/
class CreateWalletCommand(
private val cardId: String
) : CommandSerializer<CreateWalletResponse>() {
class CreateWalletCommand : CommandSerializer<CreateWalletResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
)
if (cardEnvironment.cvc != null) {
tlvData.add(Tlv(TlvTag.Cvc, cardEnvironment.cvc))
}
return CommandApdu(Instruction.CreateWallet, tlvData)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc)
return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? {

View file

@ -1,12 +1,13 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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.tasks.TaskError
@ -23,23 +24,20 @@ class PurgeWalletResponse(
) : CommandResponse
/**
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
* If Is_Reusable flag is disabled, the card switches to Purged state.
* Purged state is final, it makes the card useless.
* @property cardId CID, Unique Tangem card ID number.
*/
class PurgeWalletCommand(
private val cardId: String
) : CommandSerializer<PurgeWalletResponse>() {
class PurgeWalletCommand : CommandSerializer<PurgeWalletResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
)
return CommandApdu(Instruction.PurgeWallet, tlvData)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? {

View file

@ -1,11 +1,12 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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.tlv.Tlv
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
@ -296,19 +297,16 @@ class Card(
class ReadCommand : CommandSerializer<Card>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
/**
* [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain
* default value of 000000.
* In order to obtain cards data, [ReadCommand] should use the correct pin 1 value.
* The card will not respond if wrong pin 1 has been submitted.
*/
val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()))
cardEnvironment.terminalKeys?.let { terminalKeys ->
Tlv(TlvTag.TerminalPublicKey, terminalKeys.publicKey)
}
return CommandApdu(Instruction.Read, tlvData)
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey)
return CommandApdu(Instruction.Read, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
@ -349,7 +347,7 @@ class ReadCommand : CommandSerializer<Card>() {
private fun deserializeCardData(tlvData: List<Tlv>): CardData? {
val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let {
Tlv.tlvListFromBytes(it.value)
Tlv.deserialize(it.value)
}
if (cardDataTlvs.isNullOrEmpty()) return null

View file

@ -1,12 +1,13 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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.tasks.TaskError
@ -49,16 +50,13 @@ class ReadIssuerDataResponse(
* wallet balance signed by the issuer or additional issuers attestation data.
* @property cardId CID, Unique Tangem card ID number.
*/
class ReadIssuerDataCommand(
private val cardId: String
) : CommandSerializer<ReadIssuerDataResponse>() {
class ReadIssuerDataCommand : CommandSerializer<ReadIssuerDataResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = listOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes())
)
return CommandApdu(Instruction.ReadIssuerData, tlvData)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? {

View file

@ -1,12 +1,13 @@
package com.tangem.commands
import com.tangem.CardEnvironment
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
@ -32,7 +33,7 @@ class SignResponse(
* @property hashes Array of transaction hashes.
* @property cardId CID, Unique Tangem card ID number
*/
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
class SignCommand(private val hashes: Array<ByteArray>)
: CommandSerializer<SignResponse>() {
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
@ -50,17 +51,16 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
}
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())),
Tlv(TlvTag.TransactionOutHash, dataToSign)
)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte()))
tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign)
tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc)
addTerminalSignature(cardEnvironment, tlvData)
return CommandApdu(Instruction.Sign, tlvData)
addTerminalSignature(cardEnvironment, tlvBuilder)
return CommandApdu(Instruction.Sign, tlvBuilder.serialize())
}
/**
@ -70,11 +70,11 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
* TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey
* (this key should be generated and securily stored by the application).
*/
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvBuilder: TlvBuilder) {
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData))
tlvData.add(Tlv(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey))
tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData)
tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey)
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.commands
import com.tangem.CardEnvironment
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
@ -8,6 +8,7 @@ import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toByteArray
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
@ -30,24 +31,20 @@ class WriteIssuerDataResponse(
* @property issuerDataCounter An optional counter that protect issuer data against replay attack.
*/
class WriteIssuerDataCommand(
private val cardId: String,
private val issuerData: ByteArray,
private val issuerDataSignature: ByteArray,
private val issuerDataCounter: Int? = null
) : CommandSerializer<WriteIssuerDataResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = mutableListOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.IssuerData, issuerData),
Tlv(TlvTag.IssuerDataSignature, issuerDataSignature)
)
if (issuerDataCounter != null) {
tlvData.add(Tlv(TlvTag.IssuerDataCounter, issuerDataCounter.toByteArray()))
}
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.IssuerData, issuerData)
tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature)
tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter)
return CommandApdu(Instruction.WriteIssuerData, tlvData)
return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize())
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? {

View file

@ -0,0 +1,12 @@
package com.tangem.common
/**
* Interface for a service for managing Terminal keypair, used for Linked Terminal feature.
* Its implementation Needs to be provided to [com.tangem.CardManager]
* by calling [com.tangem.CardManager.setTerminalKeysService].
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
*/
interface TerminalKeysService {
fun getKeys(): KeyPair
}

View file

@ -1,8 +1,6 @@
package com.tangem.common.apdu
import com.tangem.EncryptionMode
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.toBytes
import com.tangem.common.EncryptionMode
import java.io.ByteArrayOutputStream
/**
@ -15,7 +13,7 @@ import java.io.ByteArrayOutputStream
class CommandApdu(
private val ins: Int,
private val tlvList: List<Tlv>,
private val tlvs: ByteArray,
private val cla: Byte = ISO_CLA,
private val p1: Byte = 0x00,
@ -28,12 +26,12 @@ class CommandApdu(
constructor(
instruction: Instruction,
tlvList: List<Tlv>,
tlvs: ByteArray,
encryptionMode: EncryptionMode = EncryptionMode.NONE,
encryptionKey: ByteArray? = null
) : this(
instruction.code,
tlvList,
tlvs,
encryptionMode = encryptionMode,
encryptionKey = encryptionKey
)
@ -51,13 +49,7 @@ class CommandApdu(
private fun toBytes(): ByteArray {
val data = if (tlvList.isNotEmpty()) {
tlvList.toBytes()
} else {
byteArrayOf()
}
val lc = data.size
val lc = tlvs.size
val byteStream = ByteArrayOutputStream()
byteStream.write(cla.toInt())
@ -66,7 +58,7 @@ class CommandApdu(
byteStream.write(p2.toInt())
if (lc != 0) {
writeLength(byteStream, lc)
byteStream.write(data)
byteStream.write(tlvs)
}
return byteStream.toByteArray()
}

View file

@ -27,7 +27,7 @@ class ResponseApdu(private val data: ByteArray) {
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
return when {
data.size <= 2 -> null
else -> Tlv.tlvListFromBytes(data.copyOf(data.size - 2))
else -> Tlv.deserialize(data.copyOf(data.size - 2))
}
}

View file

@ -53,13 +53,13 @@ class Tlv {
}
fun tlvListFromBytes(mData: ByteArray): List<Tlv>? {
fun deserialize(mData: ByteArray): List<Tlv>? {
val tlvList = mutableListOf<Tlv>()
val stream = ByteArrayInputStream(mData)
var tlv: Tlv? = null
var tlv: Tlv?
do {
try {
tlv = Tlv.tlvFromBytes(stream)
tlv = tlvFromBytes(stream)
if (tlv != null) tlvList.add(tlv)
} catch (e: IOException) {
Log.e(this::class.java.simpleName,"TLVError: " + e.message)
@ -73,10 +73,10 @@ class Tlv {
}
fun List<Tlv>.toBytes(): ByteArray =
this.map { it.toBytes() }.reduce { arr1, arr2 -> arr1 + arr2 }
fun List<Tlv>.serialize(): ByteArray =
this.map { it.serialize() }.reduce { arr1, arr2 -> arr1 + arr2 }
fun Tlv.toBytes(): ByteArray {
fun Tlv.serialize(): ByteArray {
val tag = byteArrayOf(this.tag.code.toByte())
val length = getLengthInBytes(this.value.size)
val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00)

View file

@ -0,0 +1,15 @@
package com.tangem.common.tlv
class TlvBuilder {
private val tlvs = mutableListOf<Tlv>()
private val encoder = TlvEncoder()
internal inline fun <reified T> append(tag: TlvTag, value: T?) {
if (value == null) return
tlvs.add(encoder.encode(tag, value))
}
fun serialize(): ByteArray = tlvs.serialize()
}

View file

@ -0,0 +1,81 @@
package com.tangem.common.tlv
import com.tangem.commands.*
import com.tangem.common.extensions.*
import com.tangem.tasks.TaskError
import java.time.Year
import java.util.*
class TlvEncoder {
internal inline fun <reified T> encode(tag: TlvTag, value: T?): Tlv {
if (value != null) {
return Tlv(tag, encodeValue(value, tag))
} else {
throw TaskError.SerializeCommandError("Encoding error. Value for tag $tag is null")
}
}
private inline fun <reified T> encodeValue(value: T, tag: TlvTag): ByteArray {
return when (tag.valueType()) {
TlvValueType.HexString -> {
typeCheck<T, String>(tag)
return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) {
(value as String).calculateSha256()
} else {
(value as String).hexToBytes()
}
}
TlvValueType.Utf8String -> {
typeCheck<T, String>(tag)
(value as String).toByteArray()
}
TlvValueType.IntValue -> {
typeCheck<T, Int>(tag)
(value as Int).toByteArray()
}
TlvValueType.BoolValue -> {
typeCheck<T, Boolean>(tag)
throw ConversionException("Usopported operation: Boolean to ByteArray for tag $tag")
}
TlvValueType.ByteArray -> {
typeCheck<T, ByteArray>(tag)
value as ByteArray
}
TlvValueType.EllipticCurve -> {
typeCheck<T, EllipticCurve>(tag)
(value as EllipticCurve).curve.plus("\\0").toByteArray()
}
TlvValueType.DateTime -> {
typeCheck<T, Date>(tag)
val calendar = Calendar.getInstance().apply { time = (value as Date) }
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()
}
TlvValueType.ProductMask -> {
typeCheck<T, ProductMask>(tag)
byteArrayOf(
(value as ProductMask).code
)
}
TlvValueType.SettingsMask -> {
typeCheck<T, SettingsMask>(tag)
(value as SettingsMask).rawValue.toByteArray()
}
TlvValueType.CardStatus -> {
typeCheck<T, CardStatus>(tag)
(value as CardStatus).code.toByteArray()
}
TlvValueType.SigningMethod -> {
typeCheck<T, SigningMethod>(tag)
(value as SigningMethod).rawValue.toByteArray()
}
}
}
private inline fun <reified T, reified ExpectedT> typeCheck(tag: TlvTag) {
if (T::class != ExpectedT::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
}
}

View file

@ -108,7 +108,7 @@ enum class TlvTag(val code: Int) {
*/
fun valueType(): TlvValueType {
return when (this) {
CardId, Pin, Batch -> TlvValueType.HexString
CardId, Pin, Pin2, Batch -> TlvValueType.HexString
ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress ->
TlvValueType.Utf8String
CurveId -> TlvValueType.EllipticCurve

View file

@ -1,7 +1,8 @@
package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.common.CardEnvironment
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
@ -31,64 +32,47 @@ sealed class ScanEvent {
internal class ScanTask : Task<ScanEvent>() {
override fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
val readCommand = ReadCommand()
sendCommand(readCommand, cardEnvironment) { readResult ->
if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard)))
when (readResult) {
if (currentCard == null) {
completeNfcSession(true, TaskError.MissingPreflightRead())
callback(TaskEvent.Completion(TaskError.MissingPreflightRead()))
is CompletionResult.Failure -> {
if (readResult.error !is TaskError.UserCancelledError) {
completeNfcSession(true, readResult.error)
}
callback(TaskEvent.Completion(readResult.error))
}
} else if (currentCard.status != CardStatus.Loaded) {
completeNfcSession()
callback(TaskEvent.Completion())
is CompletionResult.Success -> {
val card = readResult.data
} else if (currentCard.curve == null || currentCard.walletPublicKey == null) {
completeNfcSession(true, TaskError.CardError())
callback(TaskEvent.Completion(TaskError.CardError()))
callback(TaskEvent.Event(ScanEvent.OnReadEvent(card)))
} else {
if (card.curve == null || card.walletPublicKey == null) {
completeNfcSession(true)
callback(TaskEvent.Completion(TaskError.CardError()))
return@sendCommand
}
val checkWalletCommand = CheckWalletCommand()
val challenge = CryptoUtils.generateRandomBytes(16)
val checkWalletCommand = CheckWalletCommand(
card.cardId,
challenge)
sendCommand(checkWalletCommand, cardEnvironment) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelledError) {
completeNfcSession(true, result.error)
}
callback(TaskEvent.Completion(result.error))
}
is CompletionResult.Success -> {
completeNfcSession()
val checkWalletResponse = result.data
val verified = CryptoUtils.verify(
card.walletPublicKey,
challenge + checkWalletResponse.salt,
checkWalletResponse.walletSignature,
card.curve)
if (verified) {
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
callback(TaskEvent.Completion())
} else {
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
}
}
sendCommand(checkWalletCommand, cardEnvironment) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelledError) {
completeNfcSession(true, result.error)
}
callback(TaskEvent.Completion(result.error))
}
is CompletionResult.Success -> {
completeNfcSession()
val verified = result.data.verify(
currentCard.curve,
currentCard.walletPublicKey,
checkWalletCommand.challenge
)
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified)))
callback(TaskEvent.Completion())
}
}
}
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.commands.Card
import com.tangem.common.CardEnvironment
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
@ -14,8 +15,11 @@ class SingleCommandTask<Event : CommandResponse>(
private val command: CommandSerializer<Event>
) : Task<Event>() {
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<Event>) -> Unit
) {
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {

View file

@ -1,11 +1,13 @@
package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.common.CardEnvironment
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.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.StatusWord
@ -32,12 +34,15 @@ sealed class TaskError(description: String? = null) : Exception(description) {
class VefificationFailed : TaskError()
class CardError : TaskError()
class ReaderError() : TaskError()
class SerializeCommandError() : TaskError()
class SerializeCommandError(description: String? = null) : TaskError(description)
class CardIsMissing() : TaskError()
class EmptyHashes() : TaskError()
class TooMuchHashes() : TaskError()
class HashSizeMustBeEqual() : TaskError()
class WrongCard() : TaskError()
class MissingPreflightRead() : TaskError()
}
/**
@ -67,6 +72,7 @@ abstract class Task<T> {
var delegate: CardManagerDelegate? = null
var reader: CardReader? = null
var performPreflightRead: Boolean = true
/**
* This method should be called to run the [Task] and perform all its operations.
@ -79,7 +85,12 @@ abstract class Task<T> {
delegate?.onNfcSessionStarted()
reader?.openSession()
Log.i(this::class.simpleName!!, "Nfc task is started")
onRun(cardEnvironment, callback)
if (performPreflightRead) {
runWithPreflightRead(cardEnvironment, callback)
} else {
onRun(cardEnvironment, null, callback)
}
}
/**
@ -101,6 +112,7 @@ abstract class Task<T> {
* In this method the individual Tasks' logic should be implemented.
*/
protected abstract fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<T>) -> Unit)
/**
@ -165,6 +177,34 @@ abstract class Task<T> {
}
}
}
private fun runWithPreflightRead(
environment: CardEnvironment, callback: (result: TaskEvent<T>) -> Unit) {
sendCommand(ReadCommand(), environment) { readResult ->
when (readResult) {
is CompletionResult.Failure -> {
completeNfcSession(true, readResult.error)
callback(TaskEvent.Completion(readResult.error))
}
is CompletionResult.Success -> {
val receivedCardId = readResult.data.cardId
if (environment.cardId != null && environment.cardId != receivedCardId) {
completeNfcSession(true, TaskError.WrongCard())
callback(TaskEvent.Completion(TaskError.WrongCard()))
return@sendCommand
}
val newEnvironment = environment.copy(cardId = receivedCardId)
onRun(newEnvironment, readResult.data, callback)
}
}
}
}
}

View file

@ -2,7 +2,9 @@ package com.tangem.common.apdu
import com.google.common.truth.Truth.assertThat
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvTag
import org.junit.Test
@ -11,12 +13,13 @@ class CommandApduTest {
@Test
fun `simple READ command to bytes`() {
val pinInBytes = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111,
34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3)
val pin = "000000"
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, pin)
val commandApdu = CommandApdu(
Instruction.Read,
mutableListOf(Tlv(TlvTag.Pin, pinInBytes)))
tlvBuilder.serialize()
)
val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 34, 16, 32, -111, -76, -47, 66, -126, 63, 125,
32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10,
-45, 19, -124, -123, -55, -94, 3)
@ -27,17 +30,18 @@ class CommandApduTest {
@Test
fun `READ with terminal key to bytes`() {
val pinInBytes = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111,
34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3)
val pin = "000000"
val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60,
26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126,
91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58,
-68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90)
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, pin)
tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey)
val commandApdu = CommandApdu(
Instruction.Read,
mutableListOf(
Tlv(TlvTag.Pin, pinInBytes),
Tlv(TlvTag.TerminalPublicKey, terminalPublicKey)))
tlvBuilder.serialize()
)
val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 101, 16, 32, -111, -76, -47, 66, -126, 63,
125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25,

View file

@ -11,12 +11,12 @@ class TlvMapperTest {
private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0)
private val tlvData = Tlv.tlvListFromBytes(rawData)
private val tlvData = Tlv.deserialize(rawData)
private val tlvMapper = TlvMapper(tlvData!!)
private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData)
private val cardDataMapper = TlvMapper(Tlv.tlvListFromBytes(cardDataRaw)!!)
private val cardDataMapper = TlvMapper(Tlv.deserialize(cardDataRaw)!!)
@Test
fun `map optional when value is present`() {
@ -88,7 +88,7 @@ class TlvMapperTest {
@Test
fun `map SigningMethods set of methods returns correct value`() {
val localMapper = TlvMapper(Tlv.tlvListFromBytes("070195".hexToBytes())!!)
val localMapper = TlvMapper(Tlv.deserialize("070195".hexToBytes())!!)
val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod)
assertThat(signingMethod.contains(SigningMethod.signHash))

View file

@ -17,7 +17,7 @@ class TlvTest {
-10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124,
-123, -55, -94, 3)
assertThat(tlvs.toBytes())
assertThat(tlvs.serialize())
.isEqualTo(expected)
}
@ -34,7 +34,7 @@ class TlvTest {
-55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19,
-86, -1, 26, 8, 100, -126, -74, 20, -28, 83)
assertThat(tlvs.toBytes())
assertThat(tlvs.serialize())
.isEqualTo(expected)
}
@ -44,7 +44,7 @@ class TlvTest {
-10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124,
-123, -55, -94, 3)
val tlvs = Tlv.tlvListFromBytes(bytes)
val tlvs = Tlv.deserialize(bytes)
assertThat(tlvs)
.isNotNull()
@ -65,7 +65,7 @@ class TlvTest {
-55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19,
-86, -1, 26, 8, 100, -126, -74, 20, -28, 83)
val tlvs = Tlv.tlvListFromBytes(bytes)
val tlvs = Tlv.deserialize(bytes)
assertThat(tlvs)
.isNotNull()
@ -91,12 +91,12 @@ class TlvTest {
@Test
fun `Bytes to TLVs, wrong values`() {
val bytes = byteArrayOf(0)
val tlvs = Tlv.tlvListFromBytes(bytes)
val tlvs = Tlv.deserialize(bytes)
assertThat(tlvs)
.isNull()
val bytes1 = byteArrayOf(0, 0, 0, 0, 0, 0, 0)
val tlvs1 = Tlv.tlvListFromBytes(bytes1)
val tlvs1 = Tlv.deserialize(bytes1)
assertThat(tlvs1)
.isNull()
}

View file

@ -3,9 +3,7 @@ package com.tangem.tangemtest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate
import com.tangem.tangem_sdk_new.NfcLifecycleObserver
import com.tangem.tangem_sdk_new.nfc.NfcManager
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
@ -13,10 +11,7 @@ import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val nfcManager = NfcManager()
private val cardManagerDelegate: DefaultCardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader)
private val cardManager = CardManager(nfcManager.reader, cardManagerDelegate)
private lateinit var cardManager: CardManager
private lateinit var cardId: String
private lateinit var issuerData: ByteArray
private lateinit var issuerDataSignature: ByteArray
@ -25,10 +20,7 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
nfcManager.setCurrentActivity(this)
cardManagerDelegate.activity = this
lifecycle.addObserver(NfcLifecycleObserver(nfcManager))
cardManager = CardManager.init(this)
btn_scan?.setOnClickListener { _ ->
cardManager.scanCard { taskEvent ->
@ -38,6 +30,10 @@ class MainActivity : AppCompatActivity() {
is ScanEvent.OnReadEvent -> {
// Handle returned card data
cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
runOnUiThread {
tv_card_cid?.text = cardId
btn_create_wallet.isEnabled = true
}
}
is ScanEvent.OnVerifyEvent -> {
//Handle card verification
@ -127,6 +123,11 @@ class MainActivity : AppCompatActivity() {
}
is TaskEvent.Event -> runOnUiThread {
tv_card_cid?.text = it.data.status.name
btn_sign.isEnabled = true
btn_read_issuer_data.isEnabled = true
btn_purge_wallet.isEnabled = true
btn_create_wallet.isEnabled = false
}
}
}

View file

@ -40,16 +40,19 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
implementation 'at.favre.lib:armadillo:0.9.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
}

View file

@ -0,0 +1,56 @@
package com.tangem.tangem_sdk_new
import android.app.Application
import android.content.SharedPreferences
import at.favre.lib.armadillo.Armadillo
import com.tangem.common.KeyPair
import com.tangem.common.TerminalKeysService
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
/**
* Service for managing Terminal keypair, used for Linked Terminal feature.
* Needs to be provided to [com.tangem.CardManager] by calling [com.tangem.CardManager.setTerminalKeysService]
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
* @param applicationContext is required to retrieve an instance of [SharedPreferences]
*/
class TerminalKeysStorage(applicationContext: Application): TerminalKeysService {
private val preferences: SharedPreferences by lazy {
Armadillo.create(applicationContext, PREFERENCES_KEY)
.encryptionFingerprint(applicationContext)
.build()
}
/**
* Retrieves generated keys from encrypted shared preferences if the keys exist.
* Generates new and stores them in encrypted shared preferences otherwise
*/
override fun getKeys(): KeyPair {
val privateKey = preferences.getString(TERMINAL_PRIVATE_KEY, null)?.hexToBytes()
val publicKey = preferences.getString(TERMINAL_PUBLIC_KEY, null)?.hexToBytes()
return if (privateKey == null || publicKey == null) {
generateAndSaveKeys()
} else {
KeyPair(publicKey, privateKey)
}
}
private fun generateAndSaveKeys(): KeyPair {
val privateKey = CryptoUtils.generateRandomBytes(32)
val publicKey = CryptoUtils.generatePublicKey(privateKey)
preferences.edit().putString(TERMINAL_PRIVATE_KEY, privateKey.toHexString()).apply()
preferences.edit().putString(TERMINAL_PUBLIC_KEY, publicKey.toHexString()).apply()
return KeyPair(publicKey, privateKey)
}
companion object {
const val PREFERENCES_KEY = "myPrefs"
const val TERMINAL_PRIVATE_KEY = "terminalPrivateKey"
const val TERMINAL_PUBLIC_KEY = "terminalPublicKey"
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.tangem_sdk_new.extensions
import androidx.fragment.app.FragmentActivity
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate
import com.tangem.tangem_sdk_new.NfcLifecycleObserver
import com.tangem.tangem_sdk_new.TerminalKeysStorage
import com.tangem.tangem_sdk_new.nfc.NfcManager
fun CardManager.Companion.init(activity: FragmentActivity): CardManager {
val nfcManager = NfcManager().apply {
this.setCurrentActivity(activity)
activity.lifecycle.addObserver(NfcLifecycleObserver(this))
}
val cardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader).apply {
this.activity = activity
}
return CardManager(nfcManager.reader, cardManagerDelegate).apply {
this.setTerminalKeysService(TerminalKeysStorage(activity.application))
}
}

View file

@ -13,7 +13,7 @@ import android.os.Bundle
import com.tangem.Log
/**
* Helps use of NFC, leveraging Android NFC functionality.
* Helps use NFC, leveraging Android NFC functionality.
* Launches [NfcAdapter], manages it with [Activity] lifecycle,
* enables and disables Nfc Reading Mode, receives NFC [Tag].
*/