Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-23 18:17:43 +03:00
parent ec51c84a78
commit db432cdf47
9 changed files with 60 additions and 68 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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