Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-30 17:12:27 +00:00
commit 4596adfe7e
9 changed files with 49 additions and 95 deletions

View file

@ -5,6 +5,9 @@ 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.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.TerminalKeysService
import com.tangem.crypto.CryptoUtils
@ -261,15 +264,16 @@ class CardManager(
* @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.
* @param issuer Issuer is a third-party team or company wishing to use Tangem cards.
* @param manufacturer Tangem Card Manufacturer.
* @param acquirer Acquirer is a trusted third-party company that operates proprietary
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
*/
fun personalize(config: CardConfig,
cardId: String,
issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null,
callback: (result: TaskEvent<Card>) -> Unit) {
if (this.config.issuer == null) {
callback(TaskEvent.Completion(TaskError.IssuerIsRequired()))
return
}
val personalizationCommand = PersonalizeCommand(config, cardId)
val personalizationCommand = PersonalizeCommand(config, cardId, issuer, manufacturer, acquirer)
val task = SingleCommandTask(personalizationCommand)
task.performPreflightRead = false
runTask(task, callback = callback)
@ -320,10 +324,7 @@ class CardManager(
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
return CardEnvironment(
cardId = cardId,
terminalKeys = terminalKeys,
manufacturerKeyPair = config.manufacturerKeyPair,
acquirerKeyPair = config.acquirerKeyPair,
issuer = config.issuer
terminalKeys = terminalKeys
)
}

View file

@ -1,12 +1,6 @@
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 manufacturerKeyPair: KeyPair? = null,
val acquirerKeyPair: KeyPair? = null,
val issuer: Issuer? = null
val issuerPublicKey: ByteArray? = null
)

View file

@ -3,7 +3,9 @@ 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
@ -23,22 +25,25 @@ import com.tangem.tasks.TaskError
* 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
* @property 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.
* @property cardId this parameter will set up CID, Unique Tangem card ID.
* @property issuer Issuer is a third-party team or company wishing to use Tangem cards.
* @property manufacturer Tangem Card Manufacturer.
* @property acquirer Acquirer is a trusted third-party company that operates proprietary
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
*/
class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer<Card>() {
class PersonalizeCommand(
private val config: CardConfig, private val cardId: String,
private val issuer: Issuer, private val manufacturer: Manufacturer,
private val acquirer: Acquirer? = null
) : 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,
cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey,
cardEnvironment.acquirerKeyPair?.publicKey),
cardId, config),
encryptionKey = devPersonalizationKey
)
}
@ -99,10 +104,7 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
)
}
private fun serializePersonalizationData(cardId: String, config: CardConfig,
issuer: Issuer, manufacturerPrivateKey: ByteArray,
acquirePublicKey: ByteArray?
): ByteArray {
private fun serializePersonalizationData(cardId: String, config: CardConfig): ByteArray {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.CardId, cardId)
@ -123,17 +125,13 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey)
tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey)
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirePublicKey)
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirer?.keyPair?.publicKey)
tlvBuilder.append(
TlvTag.CardData, serializeCardData(cardId, config.cardData, issuer, manufacturerPrivateKey)
)
tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData))
return tlvBuilder.serialize()
}
private fun serializeCardData(
cardId: String, cardData: CardData,
issuer: Issuer, manufacturerPrivateKey: ByteArray): ByteArray {
private fun serializeCardData(cardId: String, cardData: CardData): ByteArray {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Batch, cardData.batchId)
tlvBuilder.append(TlvTag.ProductMask, cardData.productMask)
@ -150,7 +148,8 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal)
}
tlvBuilder.append(
TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(manufacturerPrivateKey)
TlvTag.CardIdManufacturerSignature,
cardId.hexToBytes().sign(manufacturer.keyPair.privateKey)
)
return tlvBuilder.serialize()
}

View file

@ -0,0 +1,9 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
data class Acquirer(
val keyPair: KeyPair,
val name: String? = null,
val id: String? = null
)

View file

@ -0,0 +1,8 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
data class Manufacturer(
val keyPair: KeyPair,
val name: String? = null
)

View file

@ -1,7 +1,5 @@
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,
@ -14,10 +12,7 @@ data class CardEnvironment(
val terminalKeys: KeyPair? = null,
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
var encryptionKey: ByteArray? = null,
val cvc: ByteArray? = null,
val manufacturerKeyPair: KeyPair? = null,
val acquirerKeyPair: KeyPair? = null,
val issuer: Issuer? = null
val cvc: ByteArray? = null
) {
companion object {

View file

@ -141,12 +141,6 @@ sealed class TaskError(val code: Int) : Exception() {
* (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

@ -4,9 +4,7 @@ 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
@ -153,30 +151,6 @@ 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<ByteArray> {

View file

@ -111,26 +111,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_create_wallet" />
<Button
android:id="@+id/btn_personalize"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Personalize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_read_write_user_data" />
<Button
android:id="@+id/btn_depersonalize"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Depersonalize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_personalize" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>