Updated on 2026-08-14

This commit is contained in:
Tangem 2020-03-23 16:58:14 +00:00
commit 4538d65393
28 changed files with 807 additions and 77 deletions

View file

@ -12,9 +12,9 @@ dependencies {
implementation "com.madgag.spongycastle:core:1.58.0.0"
implementation "com.madgag.spongycastle:prov:1.58.0.0"
implementation 'net.i2p.crypto:eddsa:0.3.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testImplementation "com.google.truth:truth:1.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin"
}
sourceCompatibility = "8"

View file

@ -1,6 +1,10 @@
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.common.CardEnvironment
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils
@ -176,16 +180,16 @@ class CardManager(
* User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation.
*/
fun writeUserData(
cardId: String,
userData: ByteArray? = null,
userProtectedData: ByteArray? = null,
userCounter: Int? = null,
userProtectedCounter: Int? = null,
callback: (result: TaskEvent<WriteUserDataResponse>) -> Unit
cardId: String,
userData: ByteArray? = null,
userProtectedData: ByteArray? = null,
userCounter: Int? = null,
userProtectedCounter: Int? = null,
callback: (result: TaskEvent<WriteUserDataResponse>) -> Unit
) {
val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter)
val task = SingleCommandTask(writeUserDataCommand)
runTask(task, cardId, callback)
val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter)
val task = SingleCommandTask(writeUserDataCommand)
runTask(task, cardId, callback)
}
/**
@ -199,8 +203,8 @@ class CardManager(
* For example, this fields may contain blockchain nonce value.
*/
fun readUserData(cardId: String, callback: (result: TaskEvent<ReadUserDataResponse>) -> Unit) {
val task = SingleCommandTask(ReadUserDataCommand())
runTask(task, cardId, callback)
val task = SingleCommandTask(ReadUserDataCommand())
runTask(task, cardId, callback)
}
/**
@ -234,6 +238,43 @@ class CardManager(
runTask(task, cardId, callback)
}
/**
* Command available on SDK cards only
*
* This command resets card to initial state,
* erasing all data written during personalization and usage.
* @param cardId CID, Unique Tangem card ID number.
*/
fun depersonalize(cardId: String,
callback: (result: TaskEvent<DepersonalizeResponse>) -> Unit) {
val depersonalizeCommand = DepersonalizeCommand()
val task = SingleCommandTask(depersonalizeCommand)
runTask(task, cardId, callback)
}
/**
* Command available on SDK cards only
*
* 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
* during personalization.
* @param cardId this parameter will set up CID, Unique Tangem card ID.
*/
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
runTask(task, callback = callback)
}
/**
*/
@ -279,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

@ -14,7 +14,12 @@ class OpenSessionResponse(
val uid: ByteArray
) : CommandResponse
/**
* In case of encrypted communication, App should setup a session before calling any further command.
* [OpenSessionCommand] generates secret session_key that is used by both host and card
* to encrypt and decrypt commands payload.
*/
class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer<OpenSessionResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()

View file

@ -32,6 +32,49 @@ data class SigningMethod(val rawValue: Int) {
const val signHashValidatedByIssuerAndWriteIssuerData = 4
const val signRawValidatedByIssuerAndWriteIssuerData = 5
const val signPos = 6
fun build(
signHash: Boolean = false,
signRaw: Boolean = false,
signHashValidatedByIssuer: Boolean = false,
signRawValidatedByIssuer: Boolean = false,
signHashValidatedByIssuerAndWriteIssuerData: Boolean = false,
signRawValidatedByIssuerAndWriteIssuerData: Boolean = false,
signPos: Boolean = false
): SigningMethod {
fun Boolean.toInt() = if (this) 1 else 0
val signingMethodsCount = 0 +
signHash.toInt() +
signRaw.toInt() +
signHashValidatedByIssuer.toInt() +
signRawValidatedByIssuer.toInt() +
signHashValidatedByIssuerAndWriteIssuerData.toInt() +
signRawValidatedByIssuerAndWriteIssuerData.toInt() +
signPos.toInt()
var signingMethod: Int = 0
if (signingMethodsCount == 1) {
if (signHash) signingMethod += SigningMethod.signHash
if (signRaw) signingMethod += SigningMethod.signRaw
if (signHashValidatedByIssuer) signingMethod += SigningMethod.signHashValidatedByIssuer
if (signRawValidatedByIssuer) signingMethod += SigningMethod.signRawValidatedByIssuer
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
if (signPos) signingMethod += SigningMethod.signPos
} else if (signingMethodsCount > 1) {
signingMethod = 0x80
if (signHash) signingMethod += 0x01
if (signRaw) signingMethod += 0x01 shl SigningMethod.signRaw
if (signHashValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuer
if (signRawValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuer
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
if (signPos) signingMethod += 0x01 shl SigningMethod.signPos
}
return SigningMethod(signingMethod)
}
}
}
@ -79,43 +122,74 @@ data class ProductMask(val rawValue: Int) {
}
}
class ProductMaskBuilder() {
private var productMaskValue = 0
fun add(productCode: Int) {
productMaskValue = productMaskValue or productCode
}
fun build() = ProductMask(productMaskValue)
}
/**
* Stores and maps Tangem card settings.
*
* @property rawValue Card settings in a form of flags,
* while flags definitions and values are in [SettingsMask.Companion] as constants.
* while flags definitions and possible values are in [Settings].
*/
data class SettingsMask(val rawValue: Int) {
fun contains(settings: Settings): Boolean = (rawValue and settings.code) != 0
}
fun contains(value: Int): Boolean = (rawValue and value) != 0
enum class Settings(val code: Int) {
IsReusable(0x0001),
UseActivation(0x0002),
ForbidPurgeWallet(0x0004),
UseBlock(0x0008),
companion object {
const val isReusable = 0x0001
const val useActivation = 0x0002
const val forbidPurgeWallet = 0x0004
const val useBlock = 0x0008
AllowSwapPIN(0x0010),
AllowSwapPIN2(0x0020),
UseCVC(0x0040),
ForbidDefaultPIN(0x0080),
const val allowSwapPIN = 0x0010
const val allowSwapPIN2 = 0x0020
const val useCVC = 0x0040
const val forbidDefaultPIN = 0x0080
UseOneCommandAtTime(0x0100),
UseNdef(0x0200),
UseDynamicNdef(0x0400),
SmartSecurityDelay(0x0800),
const val useOneCommandAtTime = 0x0100
const val useNdef = 0x0200
const val useDynamicNdef = 0x0400
const val smartSecurityDelay = 0x0800
ProtocolAllowUnencrypted(0x1000),
ProtocolAllowStaticEncryption(0x2000),
const val protocolAllowUnencrypted = 0x1000
const val protocolAllowStaticEncryption = 0x2000
ProtectIssuerDataAgainstReplay(0x4000),
RestrictOverwriteIssuerDataEx(0x00100000),
const val protectIssuerDataAgainstReplay = 0x4000
AllowSelectBlockchain(0x8000),
const val allowSelectBlockchain = 0x8000
DisablePrecomputedNdef(0x00010000),
const val disablePrecomputedNdef = 0x00010000
SkipSecurityDelayIfValidatedByLinkedTerminal(0x00080000),
SkipCheckPin2andCvcIfValidatedByIssuer(0x00040000),
SkipSecurityDelayIfValidatedByIssuer(0x00020000),
const val skipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000
RequireTermTxSignature(0x01000000),
RequireTermCertSignature(0x02000000),
CheckPIN3onCard(0x04000000)
}
class SettingsMaskBuilder() {
private var settingsMaskValue = 0
fun add(settings: Settings) {
settingsMaskValue = settingsMaskValue or settings.code
}
fun build() = SettingsMask(settingsMaskValue)
}
/**

View file

@ -28,7 +28,7 @@ class ReadIssuerDataResponse(
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers signature of SHA256-hashed [cardId] concatenated with [issuerData]:
* SHA256([cardId] | [issuerData]).
* When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
*/
@ -36,7 +36,7 @@ class ReadIssuerDataResponse(
/**
* An optional counter that protect issuer data against replay attack.
* When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask]
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
*/
val issuerDataCounter: Int?

View file

@ -33,7 +33,7 @@ class ReadIssuerExtraDataResponse(
* Issuers signature of [issuerData] with Issuer Data Private Key (which is kept on card).
* Issuers signature of SHA256-hashed [cardId] concatenated with [issuerData]:
* SHA256([cardId] | [issuerData]).
* When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of
* SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]:
* SHA256([cardId] | [issuerData] | [issuerDataCounter]).
*/
@ -41,7 +41,7 @@ class ReadIssuerExtraDataResponse(
/**
* An optional counter that protect issuer data against replay attack.
* When flag [SettingsMask.protectIssuerDataAgainstReplay] set in [SettingsMask]
* When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask]
* then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand].
*/
val issuerDataCounter: Int?

View file

@ -0,0 +1,112 @@
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

@ -0,0 +1,31 @@
package com.tangem.commands.personalization
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
data class DepersonalizeResponse(val success: Boolean) : CommandResponse
/**
* Command available on SDK cards only
*
* This command resets card to initial state,
* erasing all data written during personalization and usage.
* @param cardId CID, Unique Tangem card ID number.
*/
class DepersonalizeCommand : CommandSerializer<DepersonalizeResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
return CommandApdu(
Instruction.Depersonalize, byteArrayOf(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? {
return DepersonalizeResponse(true)
}
}

View file

@ -0,0 +1,87 @@
package com.tangem.commands.personalization
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
/**
* Encodes information that is to be written on the card as an Ndef Tag.
*/
class NdefEncoder(private val ndefRecords: List<NdefRecord>, private val useDinamicNdef: Boolean) {
fun encode(): ByteArray {
val bs = ByteArrayOutputStream()
// space for size
bs.write(0)
bs.write(0)
for (i in ndefRecords.indices) {
val headerValue = (if (i == 0) 0x80 else 0x00) or (if (!useDinamicNdef && i == ndefRecords.size - 1) 0x40 else 0x00)
var value: ByteArray = ndefRecords[i].value.toByteArray(StandardCharsets.UTF_8)
encodeValue(ndefRecords[i], headerValue, bs)
}
val result = bs.toByteArray()
result[0] = (result.size - 2 shr 8).toByte()
result[1] = (result.size - 2 and 0xFF).toByte()
return result
}
private fun encodeValue(ndefRecord: NdefRecord, headerValue: Int, bs: ByteArrayOutputStream) {
when (ndefRecord.type) {
NdefRecord.Type.AAR -> {
bs.write((headerValue or 0x14)) // NDEF Header
bs.write(0x0F) // Length of the record type
bs.write(ndefRecord.valueInBytes.size) // Length of the payload data
bs.write(byteArrayOf(0x61.toByte(), 0x6E.toByte(), 0x64.toByte(), 0x72.toByte(), 0x6F.toByte(), 0x69.toByte(), 0x64.toByte(), 0x2E.toByte(), 0x63.toByte(), 0x6F.toByte(), 0x6D.toByte(), 0x3A.toByte(),
0x70.toByte(), 0x6B.toByte(), 0x67.toByte())) // type name
bs.write(ndefRecord.valueInBytes)
}
NdefRecord.Type.URI -> {
bs.write((headerValue or 0x11)) // NDEF Header
bs.write(0x01) // Length of the record type
val uriIdentifierCode: Byte
val prefix: String
when {
ndefRecord.value.startsWith("http://www.") -> {
uriIdentifierCode = 0x01.toByte()
prefix = "http://www."
}
ndefRecord.value.startsWith("https://www.") -> {
uriIdentifierCode = 0x02.toByte()
prefix = "https://www."
}
ndefRecord.value.startsWith("http://") -> {
uriIdentifierCode = 0x03.toByte()
prefix = "http://"
}
ndefRecord.value.startsWith("https://") -> {
uriIdentifierCode = 0x04.toByte()
prefix = "https://"
}
else -> {
throw Exception()
}
}
val value = ndefRecord.value.substring(prefix.length).toByteArray()
bs.write(value.size + 1) // Length of the payload data
bs.write(0x55) // URI
bs.write(uriIdentifierCode.toInt()) // ?
bs.write(value)
}
NdefRecord.Type.TEXT -> {
bs.write((headerValue or 0x11)) // NDEF Header
bs.write(0x01) // Length of the record type
bs.write(ndefRecord.valueInBytes.size.toByte() + 1 + "en".length) // Length of the payload data
bs.write(0x54) // Text
bs.write(0x02) // UTF8(MSB=0)|"en".length
bs.write("en".toByteArray(StandardCharsets.US_ASCII))
bs.write(ndefRecord.valueInBytes)
}
else -> throw Exception("Invalid NDEF record in config!")
}
}
}

View file

@ -0,0 +1,165 @@
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.Issuer
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
/**
* Command available on SDK cards only
*
* 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
* during personalization.
* @param cardId this parameter will set up CID, Unique Tangem card ID.
*/
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,
cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey,
cardEnvironment.acquirerKeyPair?.publicKey),
encryptionKey = devPersonalizationKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null
return try {
val tlvMapper = TlvMapper(tlvData)
Card(
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
status = tlvMapper.mapOptional(TlvTag.Status),
firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware),
cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey),
settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask),
issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey),
curve = tlvMapper.mapOptional(TlvTag.CurveId),
maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures),
signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod),
pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2),
walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures),
walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes),
health = tlvMapper.mapOptional(TlvTag.Health),
isActivated = tlvMapper.map(TlvTag.IsActivated),
activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed),
paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion),
userCounter = tlvMapper.mapOptional(TlvTag.UserCounter),
terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked),
cardData = deserializeCardData(tlvData)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
}
private fun deserializeCardData(tlvData: List<Tlv>): CardData? {
val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let {
Tlv.deserialize(it.value)
}
if (cardDataTlvs.isNullOrEmpty()) return null
val tlvMapper = TlvMapper(cardDataTlvs)
return CardData(
batchId = tlvMapper.mapOptional(TlvTag.Batch),
manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime),
issuerName = tlvMapper.mapOptional(TlvTag.IssuerId),
blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId),
manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature),
productMask = tlvMapper.mapOptional(TlvTag.ProductMask),
tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol),
tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress),
tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal)
)
}
private fun serializePersonalizationData(cardId: String, config: CardConfig,
issuer: Issuer, manufacturerPrivateKey: ByteArray,
acquirePublicKey: ByteArray?
): ByteArray {
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.PauseBeforePin2, config.pauseBeforePin2 / 10)
tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray())
if (!config.ndefRecords.isNullOrEmpty()) tlvBuilder.append(TlvTag.NdefData, serializeNdef(config.ndefRecords))
tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet)
tlvBuilder.append(TlvTag.NewPin, config.pin)
tlvBuilder.append(TlvTag.NewPin2, config.pin2)
tlvBuilder.append(TlvTag.NewPin3, config.pin3)
tlvBuilder.append(TlvTag.CrExKey, config.hexCrExKey)
tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey)
tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey)
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,
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.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(manufacturerPrivateKey)
)
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,10 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
data class Issuer(
val name: String,
val id: String,
val dataKeyPair: KeyPair,
val transactionKeyPair: KeyPair
)

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

@ -6,6 +6,7 @@ package com.tangem.common.apdu
*/
enum class Instruction(var code: Int) {
Unknown(0x00),
Personalize(0xF1),
Read(0xF2),
VerifyCard(0xF3),
ValidateCard(0xF4),
@ -20,7 +21,8 @@ enum class Instruction(var code: Int) {
Activate(0xFE),
OpenSession(0xFF),
WriteUserData(0xE0),
ReadUserData(0xE1);
ReadUserData(0xE1),
Depersonalize(0xE3);
companion object {

View file

@ -6,6 +6,7 @@ import com.tangem.commands.common.IssuerDataMode
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toHexString
import com.tangem.tasks.TaskError
import java.util.*
@ -32,11 +33,11 @@ class TlvEncoder {
return when (tag.valueType()) {
TlvValueType.HexString -> {
typeCheck<T, String>(tag)
return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) {
(value as String).calculateSha256()
} else {
(value as String).hexToBytes()
}
(value as String).hexToBytes()
}
TlvValueType.HexStringToHash -> {
typeCheck<T, String>(tag)
(value as String).calculateSha256()
}
TlvValueType.Utf8String -> {
typeCheck<T, String>(tag)
@ -52,8 +53,8 @@ class TlvEncoder {
}
TlvValueType.BoolValue -> {
typeCheck<T, Boolean>(tag)
Log.e(this::class.simpleName!!, "Unsupported operation: Boolean to ByteArray for tag $tag")
throw TaskError.ConvertError()
val booleanValue = value as Boolean
if (booleanValue) byteArrayOf(1) else byteArrayOf(0)
}
TlvValueType.ByteArray -> {
typeCheck<T, ByteArray>(tag)
@ -61,7 +62,7 @@ class TlvEncoder {
}
TlvValueType.EllipticCurve -> {
typeCheck<T, EllipticCurve>(tag)
(value as EllipticCurve).curve.plus("\\0").toByteArray()
(value as EllipticCurve).curve.toByteArray()
}
TlvValueType.DateTime -> {
typeCheck<T, Date>(tag)
@ -69,7 +70,7 @@ class TlvEncoder {
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH) + 1
val day = calendar.get(Calendar.DAY_OF_MONTH)
return year.toByteArray() + month.toByteArray() + day.toByteArray()
return year.toByteArray(2) + month.toByte() + day.toByte()
}
TlvValueType.ProductMask -> {
typeCheck<T, ProductMask>(tag)
@ -79,7 +80,8 @@ class TlvEncoder {
}
TlvValueType.SettingsMask -> {
typeCheck<T, SettingsMask>(tag)
(value as SettingsMask).rawValue.toByteArray(2)
val rawValue = (value as SettingsMask).rawValue
rawValue.toByteArray(determineByteArraySize(rawValue))
}
TlvValueType.CardStatus -> {
typeCheck<T, CardStatus>(tag)
@ -87,7 +89,7 @@ class TlvEncoder {
}
TlvValueType.SigningMethod -> {
typeCheck<T, SigningMethod>(tag)
(value as SigningMethod).rawValue.toByteArray()
byteArrayOf((value as SigningMethod).rawValue.toByte())
}
TlvValueType.IssuerDataMode -> {
typeCheck<T, IssuerDataMode>(tag)
@ -96,6 +98,11 @@ class TlvEncoder {
}
}
private fun determineByteArraySize(value: Int): Int {
val mask = 0xFFFF0000.toInt()
return if ((value and mask) != 0) 4 else 2
}
private inline fun <reified T, reified ExpectedT> typeCheck(tag: TlvTag) {
if (T::class != ExpectedT::class) {
Log.e(this::class.simpleName!!,

View file

@ -54,7 +54,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
}
return when (tag.valueType()) {
TlvValueType.HexString -> {
TlvValueType.HexString, TlvValueType.HexStringToHash -> {
typeCheck<T, String>(tag)
tlvValue.toHexString() as T
}

View file

@ -5,6 +5,7 @@ package com.tangem.common.tlv
*/
enum class TlvValueType {
HexString,
HexStringToHash,
Utf8String,
Uint16,
Uint32,
@ -36,6 +37,7 @@ enum class TlvTag(val code: Int) {
SettingsMask(0x0A),
CardData(0x0C),
NdefData(0x0D),
CreateWalletAtPersonalize(0x0E),
Health(0x0F),
Pin(0x10),
@ -51,8 +53,11 @@ enum class TlvTag(val code: Int) {
SessionKeyA(0x1A),
SessionKeyB(0x1B),
Uid(0x0B),
Pause(0x1C),
NewPin3(0x1E),
CrExKey(0x1F),
Uid(0x0B),
ManufactureId(0x20),
ManufacturerSignature(0x86),
@ -63,6 +68,8 @@ enum class TlvTag(val code: Int) {
IssuerDataSignature(0x33),
IssuerTransactionSignature(0x34),
IssuerDataCounter(0x35),
AcquirerPublicKey(0x37),
Size(0x25),
Mode(0x23),
Offset(0x24),
@ -117,15 +124,15 @@ enum class TlvTag(val code: Int) {
*/
fun valueType(): TlvValueType {
return when (this) {
CardId, Pin, Pin2, Batch -> TlvValueType.HexString
CardId, Batch, CrExKey -> TlvValueType.HexString
Pin, Pin2, NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash
ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress ->
TlvValueType.Utf8String
CurveId -> TlvValueType.EllipticCurve
MaxSignatures, PauseBeforePin2, RemainingSignatures,
SignedHashes, Health, TokenDecimal,
PauseBeforePin2, RemainingSignatures, SignedHashes, Health, TokenDecimal,
Offset, Size -> TlvValueType.Uint16
UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32
IsActivated, TerminalIsLinked -> TlvValueType.BoolValue
MaxSignatures, UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32
IsActivated, TerminalIsLinked, CreateWalletAtPersonalize -> TlvValueType.BoolValue
ManufactureDateTime -> TlvValueType.DateTime
ProductMask -> TlvValueType.ProductMask
SettingsMask -> TlvValueType.SettingsMask

View file

@ -2,6 +2,7 @@ package com.tangem.crypto
import com.tangem.commands.EllipticCurve
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.PublicKey
import java.security.SecureRandom
import java.security.Security
@ -13,7 +14,7 @@ import javax.crypto.spec.SecretKeySpec
object CryptoUtils {
fun initCrypto() {
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
Security.insertProviderAt(BouncyCastleProvider(), 1)
Security.addProvider(EdDSASecurityProvider())
}

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

@ -1,7 +1,7 @@
package com.tangem.tasks
import com.tangem.commands.Card
import com.tangem.commands.SettingsMask
import com.tangem.commands.Settings
import com.tangem.commands.WriteIssuerDataCommand
import com.tangem.commands.WriteIssuerDataResponse
import com.tangem.commands.common.IssuerDataToVerify
@ -55,7 +55,7 @@ class WriteIssuerDataTask(
if (isCounterRequired()) issuerDataCounter != null else true
private fun isCounterRequired(): Boolean =
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean {
return command.verify(

View file

@ -1,9 +1,6 @@
package com.tangem.tasks
import com.tangem.commands.Card
import com.tangem.commands.SettingsMask
import com.tangem.commands.WriteIssuerDataResponse
import com.tangem.commands.WriteIssuerExtraDataCommand
import com.tangem.commands.*
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.common.CardEnvironment
@ -98,7 +95,7 @@ internal class WriteIssuerExtraDataTask(
if (isCounterRequired()) issuerDataCounter != null else true
private fun isCounterRequired(): Boolean =
card.settingsMask?.contains(SettingsMask.protectIssuerDataAgainstReplay) != false
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean {
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!

View file

@ -68,15 +68,15 @@ class TlvMapperTest {
.isNotNull()
assertThat(settingsMask.rawValue)
.isEqualTo(32289)
assertThat(settingsMask.contains(SettingsMask.skipSecurityDelayIfValidatedByLinkedTerminal))
assertThat(settingsMask.contains(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal))
.isFalse()
assertThat(settingsMask.contains(SettingsMask.isReusable))
assertThat(settingsMask.contains(Settings.IsReusable))
.isTrue()
assertThat(settingsMask.contains(SettingsMask.allowSwapPIN2))
assertThat(settingsMask.contains(Settings.AllowSwapPIN2))
.isTrue()
assertThat(settingsMask.contains(SettingsMask.useDynamicNdef))
assertThat(settingsMask.contains(Settings.UseDynamicNdef))
.isTrue()
assertThat(settingsMask.contains(SettingsMask.forbidPurgeWallet))
assertThat(settingsMask.contains(Settings.ForbidPurgeWallet))
.isFalse()
}

View file

@ -38,7 +38,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'

View file

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

@ -0,0 +1,125 @@
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 java.util.*
fun CardConfig.Companion.init(application: Application): CardConfig {
val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE)
val signingMethod = SigningMethod.build(
signHash = preferences.getBoolean("personalization_SigningMethod_0", false),
signRaw = preferences.getBoolean("personalization_SigningMethod_1", false),
signHashValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_2", false),
signRawValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_3", false),
signHashValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_4", false),
signRawValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_5", false),
signPos = preferences.getBoolean("personalization_SigningMethod_6", false)
)
val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true)
val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false)
val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false)
val productMaskBuilder = ProductMaskBuilder()
if (isNote) productMaskBuilder.add(ProductMask.note)
if (isTag) productMaskBuilder.add(ProductMask.tag)
if (isIdCard) productMaskBuilder.add(ProductMask.idCard)
val productMask = productMaskBuilder.build()
var tokenSymbol: String? = null
var tokenContractAddress: String? = null
var tokenDecimal: Int? = null
if (preferences.getBoolean("personalization_isToken", false)) {
tokenSymbol = preferences.getString("personalization_token_symbol", "")
tokenContractAddress = preferences.getString("personalization_token_contract_address", "")
tokenDecimal = preferences.getString("personalization_token_decimal", "")!!.toInt()
}
val cardData = CardData(
blockchainName = preferences.getString("personalization_Blockchain", "BTC"),
batchId = preferences.getString("personalization_card_batch", "FFFF"),
productMask = productMask,
tokenSymbol = tokenSymbol,
tokenContractAddress = tokenContractAddress,
tokenDecimal = tokenDecimal,
issuerName = null,
manufactureDateTime = Calendar.getInstance().time,
manufacturerSignature = null)
val ndefAar = preferences.getString("personalization_NDEF_AAR", "Release APP")
val ndefUri = preferences.getString("personalization_NDEF_URI", "https://tangem.com")
val ndefs = mutableListOf<NdefRecord>()
if (!ndefUri.isNullOrEmpty()) {
ndefs.add(NdefRecord(NdefRecord.Type.URI, ndefUri))
}
if (ndefAar != "None") {
val type = NdefRecord.Type.AAR
val value = when (ndefAar) {
"Debug APP" -> {
"com.tangem.wallet.debug"
}
"Release APP" -> {
"com.tangem.wallet"
}
"--- CUSTOM ---" -> {
preferences.getString("personalization_NDEF_CUSTOM_AAR", "com.tangem.wallet")!!
}
else -> ""
}
ndefs.add(NdefRecord(type, value))
}
return CardConfig(
cardData = cardData,
curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!)
?: EllipticCurve.Secp256k1,
signingMethod = signingMethod,
createWallet = preferences.getBoolean("personalization_CreateWallet", true),
maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(),
isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true),
protocolAllowUnencrypted = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_None", true),
protocolAllowStaticEncryption = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_Fast", true),
useActivation = preferences.getBoolean("personalization_SettingsMask_NeedActivation", false),
useOneCommandAtTime = preferences.getBoolean("personalization_SettingsMask_OneApduAtOnce", false),
useCvc = preferences.getBoolean("personalization_SettingsMask_UseCVC", false),
useBlock = preferences.getBoolean("personalization_SettingsMask_UseBlock", false),
allowSwapPin = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN", true),
allowSwapPin2 = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN2", true),
useNdef = preferences.getBoolean("personalization_SettingsMask_UseNDEF", true),
useDynamicNdef = preferences.getBoolean("personalization_SettingsMask_UseDynamicNDEF", true),
protectIssuerDataAgainstReplay = preferences.getBoolean("personalization_SettingsMask_ProtectIssuerDataAgainstReplay", true),
forbidDefaultPin = preferences.getBoolean("personalization_SettingsMask_ForbidDefaultPIN", false),
smartSecurityDelay = preferences.getBoolean("personalization_SettingsMask_SmartSecurityDelay", false),
pauseBeforePin2 = preferences.getString("personalization_PauseBeforePIN2", "15")!!.toInt() * 1000,
allowSelectBlockchain = preferences.getBoolean("personalization_SettingsMask_AllowSelectBlockchain", false),
forbidPurgeWallet = preferences.getBoolean("personalization_SettingsMask_ForbidPurgeWallet", false)
?: false,
disablePrecomputedNdef = preferences.getBoolean("personalization_SettingsMask_DisablePrecomputedNDEF", false)
?: false,
skipSecurityDelayIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByIssuer", true),
skipCheckPIN2andCVCIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipCheckPIN2andCVCIfValidatedByIssuer", true),
skipSecurityDelayIfValidatedByLinkedTerminal = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByLinkedTerminal", true),
restrictOverwriteIssuerDataEx = preferences.getBoolean("personalization_SettingsMask_RestrictOverwriteIssuerDataEx", true),
requireTerminalTxSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalTxSignature", false),
requireTerminalCertSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalCertSignature", false),
checkPin3onCard = preferences.getBoolean("personalization_SettingsMask_CheckPIN3onCard", true),
cvc = preferences.getString("personalization_cvc", "000") ?: "000",
pin = preferences.getString("personalization_pin", "000000") ?: "000000",
pin2 = preferences.getString("personalization_pin2", "000") ?: "000",
pin3 = preferences.getString("personalization_pin3", "123") ?: "123",
hexCrExKey = preferences.getString("personalization_CrEx_Key", "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"),
ndefRecords = ndefs
)
}

View file

@ -111,6 +111,26 @@
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>

View file

@ -47,11 +47,11 @@ dependencies {
implementation 'com.skyfishjy.ripplebackground:library:1.0.1'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin"
implementation 'at.favre.lib:armadillo:0.9.0'

View file

@ -40,7 +40,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
}
private fun showReadingDialog(activity: FragmentActivity, cardId: String?) {
val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null)
val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null)
readingDialog = BottomSheetDialog(activity)
readingDialog?.setContentView(dialogView)
readingDialog?.dismissWithAnimation = true