Updated on 2026-08-14
This commit is contained in:
commit
ba194f6556
12 changed files with 180 additions and 280 deletions
|
|
@ -69,6 +69,11 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
updateViews()
|
||||
|
||||
val card = ctx.card
|
||||
if (!card.allowSwapPIN() && !card.allowSwapPIN2() && card.forbidPurgeWallet()) {
|
||||
fabMenu.hide()
|
||||
}
|
||||
|
||||
// set listeners
|
||||
fabMenu.setOnClickListener { showMenu(fabMenu) }
|
||||
|
||||
|
|
@ -278,11 +283,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.
|
|||
|
||||
ivBlockchain.setImageResource(Blockchain.getLogoImageResource(ctx.card!!.blockchainID, ctx.card!!.tokenSymbol))
|
||||
|
||||
if (ctx.card!!.isReusable!!)
|
||||
tvReusable.setText(R.string.details_reusable)
|
||||
else
|
||||
tvReusable.setText(R.string.details_one_off_card)
|
||||
|
||||
var s = ""
|
||||
for (signingM in ctx.card!!.allowedSigningMethod) {
|
||||
s += if (signingM == ctx.card!!.signingMethod) {
|
||||
|
|
@ -328,44 +328,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.
|
|||
|
||||
tvFirmware.text = ctx.card!!.firmwareVersion
|
||||
|
||||
var features = ""
|
||||
|
||||
features += if (ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!!) {
|
||||
getString(R.string.details_both_pins_can_be_changed)
|
||||
} else if (ctx.card!!.allowSwapPIN()!!) {
|
||||
getString(R.string.details_pin1_can_be_changed)
|
||||
} else if (ctx.card!!.allowSwapPIN2()!!) {
|
||||
getString(R.string.details_pin2_can_be_changed)
|
||||
} else {
|
||||
getString(R.string.details_both_pins_fixed)
|
||||
}
|
||||
|
||||
if (ctx.card!!.needCVC()!!)
|
||||
features += getString(R.string.details_required_cvc)
|
||||
|
||||
|
||||
if (ctx.card!!.supportDynamicNDEF()!!) {
|
||||
features += getString(R.string.details_dynamic_ndef)
|
||||
} else if (ctx.card!!.supportNDEF()!!)
|
||||
features += getString(R.string.details_ndef)
|
||||
|
||||
if (ctx.card!!.supportBlock()!!)
|
||||
features += getString(R.string.details_blockable)
|
||||
|
||||
if (ctx.card!!.supportLinkingTerminal()) {
|
||||
features += getString(R.string.details_linking_card_supported)
|
||||
llLinkedCard.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
|
||||
if (ctx.card!!.supportOnlyOneCommandAtTime())
|
||||
features += getString(R.string.details_atomic_commmands)
|
||||
|
||||
if (features.endsWith("\n"))
|
||||
features = features.substring(0, features.length - 1)
|
||||
|
||||
tvFeatures.text = features
|
||||
|
||||
val textIsLinked = if (ctx.card.terminalIsLinked) {
|
||||
getString(R.string.details_linked_card_to_phone)
|
||||
} else {
|
||||
|
|
@ -373,25 +335,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter.
|
|||
}
|
||||
tvIsLinked.text = textIsLinked
|
||||
|
||||
if (ctx.card!!.useDefaultPIN1()) {
|
||||
imgPIN.setImageResource(R.drawable.unlock_pin1)
|
||||
imgPIN.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_default_pin_1, Toast.LENGTH_LONG).show() }
|
||||
} else {
|
||||
imgPIN.setImageResource(R.drawable.lock_pin1)
|
||||
imgPIN.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_user_pin_1, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
if (ctx.card!!.pauseBeforePIN2 > 0 && (ctx.card!!.useDefaultPIN2()!! || !ctx.card!!.useSmartSecurityDelay())) {
|
||||
imgPIN2orSecurityDelay.setImageResource(R.drawable.timer)
|
||||
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, String.format(getString(R.string.details_security_delay), ctx.card!!.pauseBeforePIN2 / 1000.0), Toast.LENGTH_LONG).show() }
|
||||
} else if (ctx.card!!.useDefaultPIN2()!!) {
|
||||
imgPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2)
|
||||
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_default_pin_2, Toast.LENGTH_LONG).show() }
|
||||
} else {
|
||||
imgPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2)
|
||||
imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_user_pin_2, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
|
||||
if (ctx.card!!.useDevelopersFirmware()!!) {
|
||||
imgDeveloperVersion.setImageResource(R.drawable.ic_developer_version)
|
||||
imgDeveloperVersion.visibility = View.VISIBLE
|
||||
|
|
|
|||
|
|
@ -159,32 +159,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="@dimen/verify_card_left_col_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/details_is_card_reusable"
|
||||
android:textSize="@dimen/text_size_1_small" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvReusable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="@dimen/text_size_1_small"
|
||||
android:textStyle="normal|bold"
|
||||
tools:text="Reusable" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llLinkedCard"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -288,32 +262,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="@dimen/verify_card_left_col_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/details_features"
|
||||
android:textSize="@dimen/text_size_1_small" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFeatures"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="@dimen/text_size_2_small"
|
||||
android:textStyle="normal|bold"
|
||||
tools:text="Allows change PIN1 and PIN2 \n Dynamic NDEF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/view2"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -584,34 +532,13 @@
|
|||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgPIN"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="false"
|
||||
android:layout_alignParentEnd="false"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toStartOf="@+id/imgPIN2orSecurityDelay"
|
||||
android:src="@drawable/unlock_pin1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgPIN2orSecurityDelay"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="false"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:src="@drawable/unlock_pin2" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgDeveloperVersion"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@drawable/ic_developer_version" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.tangem_card.reader.TLVList;
|
|||
import com.tangem.tangem_card.util.Util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
|
||||
|
|
@ -359,6 +358,11 @@ public class TangemCard {
|
|||
return (settingsMask & SettingsMask.AllowSwapPIN2) != 0;
|
||||
}
|
||||
|
||||
public Boolean forbidPurgeWallet() {
|
||||
if (settingsMask == null) return null;
|
||||
return (settingsMask & SettingsMask.ForbidPurgeWallet) != 0;
|
||||
}
|
||||
|
||||
public Boolean needCVC() {
|
||||
if (settingsMask == null) return null;
|
||||
return (settingsMask & SettingsMask.UseCVC) != 0;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -263,17 +263,15 @@ class CardManager(
|
|||
* 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
|
||||
* 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) {
|
||||
val personalizationCommand = PersonalizeCommand(config, cardId, issuer, manufacturer, acquirer)
|
||||
val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer)
|
||||
val task = SingleCommandTask(personalizationCommand)
|
||||
task.performPreflightRead = false
|
||||
runTask(task, callback = callback)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ReadUserDataResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
class WriteUserDataResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.commands.personalization.entities.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
|
|
@ -27,14 +25,13 @@ import com.tangem.tasks.TaskError
|
|||
* During this procedure all data exchange is encrypted.
|
||||
* @property config is a configuration file with all the card settings that are written on the card
|
||||
* during personalization.
|
||||
* @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,
|
||||
private val config: CardConfig,
|
||||
private val issuer: Issuer, private val manufacturer: Manufacturer,
|
||||
private val acquirer: Acquirer? = null
|
||||
) : CommandSerializer<Card>() {
|
||||
|
|
@ -42,8 +39,7 @@ class PersonalizeCommand(
|
|||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
return CommandApdu(
|
||||
Instruction.Personalize,
|
||||
serializePersonalizationData(
|
||||
cardId, config),
|
||||
serializePersonalizationData(config),
|
||||
encryptionKey = devPersonalizationKey
|
||||
)
|
||||
}
|
||||
|
|
@ -104,17 +100,19 @@ class PersonalizeCommand(
|
|||
)
|
||||
}
|
||||
|
||||
private fun serializePersonalizationData(cardId: String, config: CardConfig): ByteArray {
|
||||
private fun serializePersonalizationData(config: CardConfig): ByteArray {
|
||||
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, serializeNdef(config))
|
||||
|
||||
tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet)
|
||||
|
||||
|
|
@ -131,15 +129,16 @@ class PersonalizeCommand(
|
|||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
private fun serializeNdef(config: CardConfig): ByteArray {
|
||||
return NdefEncoder(config.ndefRecords, config.useDynamicNdef).encode()
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -154,10 +153,6 @@ class PersonalizeCommand(
|
|||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
private fun serializeNdef(ndefRecords: List<NdefRecord>): ByteArray {
|
||||
return NdefEncoder(ndefRecords, config.useDynamicNdef).encode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val devPersonalizationKey = "1234".calculateSha256().copyOf(32)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.commands.Settings
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.SettingsMaskBuilder
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue