Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-31 16:00:40 +03:00
parent 3c80122b6e
commit 0b9d306c9e
8 changed files with 193 additions and 202 deletions

View file

@ -1,11 +1,11 @@
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.commands.personalization.entities.Acquirer
import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.common.CardEnvironment

View file

@ -1,112 +0,0 @@
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<NdefRecord>
) {
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
}

View file

@ -1,5 +1,6 @@
package com.tangem.commands.personalization
import com.tangem.commands.personalization.entities.NdefRecord
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets

View file

@ -3,21 +3,16 @@ 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.commands.personalization.util.CardIdCreator
import com.tangem.commands.personalization.entities.*
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
/**
@ -104,18 +99,18 @@ class PersonalizeCommand(
}
private fun serializePersonalizationData(config: CardConfig): ByteArray {
val cardId = CardIdCreator.create(config.series, config.startNumber)
?: throw TaskError.SerializeCommandError()
val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError()
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.SettingsMask, config.createSettingsMask())
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))
if (!config.ndefRecords.isNullOrEmpty())
tlvBuilder.append(TlvTag.NdefData, config.serializeNdef(config.ndefRecords))
tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet)
@ -128,37 +123,10 @@ class PersonalizeCommand(
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirer?.keyPair?.publicKey)
tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData))
tlvBuilder.append(TlvTag.CardData, config.serializeCardData(cardId, issuer, manufacturer))
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.keyPair.privateKey)
)
return tlvBuilder.serialize()
}
private fun serializeNdef(ndefRecords: List<NdefRecord>): ByteArray {
return NdefEncoder(ndefRecords, config.useDynamicNdef).encode()
}
companion object {
val devPersonalizationKey = "1234".calculateSha256().copyOf(32)
}

View file

@ -0,0 +1,70 @@
package com.tangem.commands.personalization.entities
import com.tangem.commands.CardData
import com.tangem.commands.EllipticCurve
import com.tangem.commands.SigningMethod
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<NdefRecord>
) {
companion object
}

View file

@ -0,0 +1,113 @@
package com.tangem.commands.personalization.entities
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.commands.SettingsMaskBuilder
import com.tangem.commands.personalization.NdefEncoder
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvTag
import com.tangem.crypto.sign
/**
[REDACTED_AUTHOR]
*/
internal fun CardConfig.createSettingsMask(): 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()
}
internal fun CardConfig.createCardId(): String? {
if (series == null) return null
if (startNumber <= 0 || (series.length != 2 && series.length != 4)) return null
val Alf = "ABCDEF0123456789"
fun checkSeries(series: String): Boolean {
val containsList = series.filter { Alf.contains(it) }
return containsList.length == series.length
}
if (!checkSeries(series)) return null
val tail = if (series.length == 2) String.format("%013d", startNumber) else String.format("%011d", startNumber)
var cardId = (series + tail).replace(" ", "")
if (cardId.length != 15 || Alf.indexOf(cardId[0]) == -1 || Alf.indexOf(cardId[1]) == -1)
return null
cardId += "0"
val length = cardId.length
var sum = 0
for (i in 0 until length) {
// get digits in reverse order
var digit: Int
val cDigit = cardId[length - i - 1]
digit = if (cDigit in '0'..'9') cDigit - '0' else cDigit - 'A'
// every 2nd number multiply with 2
if (i % 2 == 1) digit *= 2
sum += if (digit > 9) digit - 9 else digit
}
val lunh = (10 - sum % 10) % 10
return cardId.substring(0, 15) + String.format("%d", lunh)
}
internal fun CardConfig.serializeNdef(ndefRecords: List<NdefRecord>): ByteArray {
return NdefEncoder(ndefRecords, useDynamicNdef).encode()
}
internal fun CardConfig.serializeCardData(cardId: String, issuer: Issuer, manufacturer: Manufacturer): 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.keyPair.privateKey)
)
return tlvBuilder.serialize()
}

View file

@ -1,49 +0,0 @@
package com.tangem.commands.personalization.util
/**
[REDACTED_AUTHOR]
*/
internal class CardIdCreator {
companion object {
private val Alf = "ABCDEF0123456789"
fun create(nsSeries: String?, number: Long): String? {
val series = nsSeries ?: return null
if (number <= 0 || (series.length != 2 && series.length != 4)) return null
if (!checkSeries(series)) return null
val tail = if (series.length == 2) String.format("%013d", number) else String.format("%011d", number)
val cardId = series + tail
return completeCardID(cardId)
}
private fun checkSeries(series: String): Boolean {
val containsList = series.filter { Alf.contains(it) }
return containsList.length == series.length
}
private fun completeCardID(imCardId: String): String? {
var cardId = imCardId
cardId = cardId.replace(" ", "")
if (cardId.length != 15 || Alf.indexOf(cardId[0]) == -1 || Alf.indexOf(cardId[1]) == -1)
return null
cardId += "0"
val length = cardId.length
var sum = 0
for (i in 0 until length) {
// get digits in reverse order
var digit: Int
val cDigit = cardId[length - i - 1]
digit = if (cDigit in '0'..'9') cDigit - '0' else cDigit - 'A'
// every 2nd number multiply with 2
if (i % 2 == 1) digit *= 2
sum += if (digit > 9) digit - 9 else digit
}
val lunh = (10 - sum % 10) % 10
return cardId.substring(0, 15) + String.format("%d", lunh)
}
}
}

View file

@ -3,8 +3,8 @@ 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.CardConfig
import com.tangem.commands.personalization.entities.NdefRecord
import java.util.*
fun CardConfig.Companion.init(application: Application): CardConfig {