Updated on 2026-08-14
This commit is contained in:
parent
ec51c84a78
commit
db432cdf47
9 changed files with 60 additions and 68 deletions
|
|
@ -265,6 +265,10 @@ class CardManager(
|
|||
fun personalize(config: CardConfig,
|
||||
cardId: String,
|
||||
callback: (result: TaskEvent<Card>) -> Unit) {
|
||||
if (this.config.issuer == null) {
|
||||
callback(TaskEvent.Completion(TaskError.IssuerIsRequired()))
|
||||
return
|
||||
}
|
||||
val personalizationCommand = PersonalizeCommand(config, cardId)
|
||||
val task = SingleCommandTask(personalizationCommand)
|
||||
task.performPreflightRead = false
|
||||
|
|
@ -316,7 +320,10 @@ class CardManager(
|
|||
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
|
||||
return CardEnvironment(
|
||||
cardId = cardId,
|
||||
terminalKeys = terminalKeys
|
||||
terminalKeys = terminalKeys,
|
||||
manufacturerKeyPair = config.manufacturerKeyPair,
|
||||
acquirerKeyPair = config.acquirerKeyPair,
|
||||
issuer = config.issuer
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
class Config(
|
||||
val linkedTerminal: Boolean = true,
|
||||
val issuerPublicKey: ByteArray? = null
|
||||
val issuerPublicKey: ByteArray? = null,
|
||||
val manufacturerKeyPair: KeyPair? = null,
|
||||
val acquirerKeyPair: KeyPair? = null,
|
||||
val issuer: Issuer? = null
|
||||
)
|
||||
|
|
@ -3,9 +3,7 @@ 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
|
||||
|
|
@ -32,9 +30,15 @@ import com.tangem.tasks.TaskError
|
|||
class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer<Card>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
if (cardEnvironment.issuer == null || cardEnvironment.manufacturerKeyPair == null) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
return CommandApdu(
|
||||
Instruction.Personalize,
|
||||
serializePersonalizationData(cardId, config),
|
||||
serializePersonalizationData(
|
||||
cardId, config,
|
||||
cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey,
|
||||
cardEnvironment.acquirerKeyPair?.publicKey),
|
||||
encryptionKey = devPersonalizationKey
|
||||
)
|
||||
}
|
||||
|
|
@ -95,7 +99,10 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
)
|
||||
}
|
||||
|
||||
private fun serializePersonalizationData(cardId: String, config: CardConfig): ByteArray {
|
||||
private fun serializePersonalizationData(cardId: String, config: CardConfig,
|
||||
issuer: Issuer, manufacturerPrivateKey: ByteArray,
|
||||
acquirePublicKey: ByteArray?
|
||||
): ByteArray {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.CardId, cardId)
|
||||
|
||||
|
|
@ -113,24 +120,27 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
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.IssuerDataPublicKey, issuer.dataKeyPair.publicKey)
|
||||
tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey)
|
||||
|
||||
tlvBuilder.append(TlvTag.AcquirerPublicKey, Acquirer.publicKey)
|
||||
|
||||
tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData))
|
||||
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirePublicKey)
|
||||
|
||||
tlvBuilder.append(
|
||||
TlvTag.CardData, serializeCardData(cardId, config.cardData, issuer, manufacturerPrivateKey)
|
||||
)
|
||||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
private fun serializeCardData(cardId: String, cardData: CardData): ByteArray {
|
||||
private fun serializeCardData(
|
||||
cardId: String, cardData: CardData,
|
||||
issuer: Issuer, manufacturerPrivateKey: ByteArray): 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.IssuerId, issuer.id)
|
||||
|
||||
tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName)
|
||||
|
||||
|
|
@ -139,9 +149,9 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
tlvBuilder.append(TlvTag.TokenContractAddress, cardData.tokenContractAddress)
|
||||
tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal)
|
||||
}
|
||||
|
||||
tlvBuilder.append(TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(Manufacturer.devPrivateKey))
|
||||
|
||||
tlvBuilder.append(
|
||||
TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(manufacturerPrivateKey)
|
||||
)
|
||||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
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) }
|
||||
}
|
||||
|
|
@ -1,18 +1,10 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
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) }
|
||||
}
|
||||
data class Issuer(
|
||||
val name: String,
|
||||
val id: String,
|
||||
val dataKeyPair: KeyPair,
|
||||
val transactionKeyPair: KeyPair
|
||||
)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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()
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
|
||||
|
||||
/**
|
||||
* Contains data relating to a Tangem card. It is used in constructing all the commands,
|
||||
|
|
@ -12,7 +14,10 @@ data class CardEnvironment(
|
|||
val terminalKeys: KeyPair? = null,
|
||||
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
var encryptionKey: ByteArray? = null,
|
||||
val cvc: ByteArray? = null
|
||||
val cvc: ByteArray? = null,
|
||||
val manufacturerKeyPair: KeyPair? = null,
|
||||
val acquirerKeyPair: KeyPair? = null,
|
||||
val issuer: Issuer? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -135,12 +135,18 @@ sealed class TaskError(val code: Int) : Exception() {
|
|||
|
||||
class UnknownError : TaskError(6000)
|
||||
|
||||
//Issuer Data Errors
|
||||
//Specific Command Errors
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* This error is returned when [com.tangem.commands.personalization.PersonalizeCommand] is attempted
|
||||
* without [com.tangem.commands.personalization.entities.Issuer] being set in the [com.tangem.Config].
|
||||
*/
|
||||
class IssuerIsRequired : TaskError(7002)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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 {
|
||||
|
|
@ -48,7 +47,7 @@ fun CardConfig.Companion.init(application: Application): CardConfig {
|
|||
tokenSymbol = tokenSymbol,
|
||||
tokenContractAddress = tokenContractAddress,
|
||||
tokenDecimal = tokenDecimal,
|
||||
issuerName = Issuer.id,
|
||||
issuerName = null,
|
||||
manufactureDateTime = Calendar.getInstance().time,
|
||||
manufacturerSignature = null)
|
||||
|
||||
|
|
@ -121,9 +120,6 @@ fun CardConfig.Companion.init(application: Application): CardConfig {
|
|||
pin3 = preferences.getString("personalization_pin3", "123") ?: "123",
|
||||
hexCrExKey = preferences.getString("personalization_CrEx_Key", "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"),
|
||||
|
||||
issuerName = Issuer.id,
|
||||
acquirerName = "TANGEM SDK",
|
||||
|
||||
ndefRecords = ndefs
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue