From ec51c84a7863bf1c59a7ac75623d79cf0986dff7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 20 Mar 2020 15:59:50 +0300 Subject: [PATCH] Updated on 2026-08-14 --- tangem-core/build.gradle | 2 +- .../src/main/java/com/tangem/CardManager.kt | 59 +++++-- .../com/tangem/commands/OpenSessionCommand.kt | 5 + .../java/com/tangem/commands/ReadCommand.kt | 116 ++++++++++--- .../tangem/commands/ReadIssuerDataCommand.kt | 4 +- .../commands/ReadIssuerExtraDataCommand.kt | 4 +- .../commands/personalization/CardConfig.kt | 112 +++++++++++++ .../personalization/DepersonalizeCommand.kt | 31 ++++ .../commands/personalization/NdefEncoder.kt | 87 ++++++++++ .../personalization/PersonalizeCommand.kt | 155 ++++++++++++++++++ .../personalization/entities/Acquirer.kt | 9 + .../personalization/entities/Issuer.kt | 18 ++ .../personalization/entities/Manufacturer.kt | 21 +++ .../com/tangem/common/apdu/Instruction.kt | 4 +- .../java/com/tangem/common/tlv/TlvEncoder.kt | 29 ++-- .../java/com/tangem/common/tlv/TlvMapper.kt | 2 +- .../main/java/com/tangem/common/tlv/TlvTag.kt | 19 ++- .../java/com/tangem/crypto/CryptoUtils.kt | 3 +- .../com/tangem/tasks/WriteIssuerDataTask.kt | 4 +- .../tangem/tasks/WriteIssuerExtraDataTask.kt | 7 +- .../com/tangem/common/tlv/TlvMapperTest.kt | 10 +- tangem-demo/build.gradle | 2 +- .../com/tangem/tangemtest/MainActivity.kt | 26 +++ .../tangemtest/extensions/CardConfig.kt | 129 +++++++++++++++ .../src/main/res/layout/activity_main.xml | 20 +++ tangem-sdk/build.gradle | 4 +- .../DefaultCardManagerDelegate.kt | 2 +- 27 files changed, 811 insertions(+), 73 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt create mode 100644 tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index c76707df84..fe6ed6638a 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -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" diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 83fcd22aeb..7a453944fb 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -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) -> Unit + cardId: String, + userData: ByteArray? = null, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + callback: (result: TaskEvent) -> 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) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) + val task = SingleCommandTask(ReadUserDataCommand()) + runTask(task, cardId, callback) } /** @@ -234,6 +238,39 @@ 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) -> 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) -> Unit) { + val personalizationCommand = PersonalizeCommand(config, cardId) + val task = SingleCommandTask(personalizationCommand) + task.performPreflightRead = false + runTask(task, callback = callback) + } + /** */ diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 9b78cb8bea..85d3ddf866 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -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() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index b735e54d8f..1e55cd404b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -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) + } /** diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index fea83e979f..cb95fd0899 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -28,7 +28,7 @@ class ReadIssuerDataResponse( * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). * Issuer’s 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? diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 3a6e8309ff..5672003477 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -33,7 +33,7 @@ class ReadIssuerExtraDataResponse( * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). * Issuer’s 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? diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt new file mode 100644 index 0000000000..15548f9871 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt @@ -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 +) { + + 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 +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt new file mode 100644 index 0000000000..46239ea4ea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -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() { + + 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) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt new file mode 100644 index 0000000000..a034d171cb --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt @@ -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, 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!") + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt new file mode 100644 index 0000000000..25bd22b4ba --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -0,0 +1,155 @@ +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 +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() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + return CommandApdu( + Instruction.Personalize, + serializePersonalizationData(cardId, config), + 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): 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): 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.dataPublicKey) + tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, Issuer.transactionPublicKey) + + tlvBuilder.append(TlvTag.AcquirerPublicKey, Acquirer.publicKey) + + tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData)) + + 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.devPrivateKey)) + + return tlvBuilder.serialize() + } + + private fun serializeNdef(ndefRecords: List): ByteArray { + return NdefEncoder(ndefRecords, config.useDynamicNdef).encode() + } + + companion object { + val devPersonalizationKey = "1234".calculateSha256().copyOf(32) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt new file mode 100644 index 0000000000..60bf33e774 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt @@ -0,0 +1,9 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.CryptoUtils + +object Acquirer { + val privateKey = "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() + val publicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(privateKey) } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt new file mode 100644 index 0000000000..50ada2281c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt @@ -0,0 +1,18 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.CryptoUtils + +object Issuer { + + val name = "TANGEM SDK" + val id = name + "\u0000" + val dataPrivateKey = "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() + + val dataPublicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(dataPrivateKey) } + + val transactionPrivateKey = + "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".hexToBytes() + + val transactionPublicKey: ByteArray by lazy { CryptoUtils.generatePublicKey(transactionPrivateKey) } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt new file mode 100644 index 0000000000..cbe77110ab --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt @@ -0,0 +1,21 @@ +package com.tangem.commands.personalization.entities + +object Manufacturer { + val devPrivateKey = byteArrayOf( + 0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(), + 0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(), + 0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(), + 0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte() + ) + + val devPublicKey = byteArrayOf(0x04, + 0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(), + 0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(), + 0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(), + 0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(), + 0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(), + 0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(), + 0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(), + 0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte() + ) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index a913813eb2..46c8989535 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -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 { diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt index 1186dceeb3..58e4043450 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -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(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(tag) + (value as String).calculateSha256() } TlvValueType.Utf8String -> { typeCheck(tag) @@ -52,8 +53,8 @@ class TlvEncoder { } TlvValueType.BoolValue -> { typeCheck(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(tag) @@ -61,7 +62,7 @@ class TlvEncoder { } TlvValueType.EllipticCurve -> { typeCheck(tag) - (value as EllipticCurve).curve.plus("\\0").toByteArray() + (value as EllipticCurve).curve.toByteArray() } TlvValueType.DateTime -> { typeCheck(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(tag) @@ -79,7 +80,8 @@ class TlvEncoder { } TlvValueType.SettingsMask -> { typeCheck(tag) - (value as SettingsMask).rawValue.toByteArray(2) + val rawValue = (value as SettingsMask).rawValue + rawValue.toByteArray(determineByteArraySize(rawValue)) } TlvValueType.CardStatus -> { typeCheck(tag) @@ -87,7 +89,7 @@ class TlvEncoder { } TlvValueType.SigningMethod -> { typeCheck(tag) - (value as SigningMethod).rawValue.toByteArray() + byteArrayOf((value as SigningMethod).rawValue.toByte()) } TlvValueType.IssuerDataMode -> { typeCheck(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 typeCheck(tag: TlvTag) { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt index 184f2fb49d..1713fd2e2d 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt @@ -54,7 +54,7 @@ class TlvMapper(val tlvList: List) { } return when (tag.valueType()) { - TlvValueType.HexString -> { + TlvValueType.HexString, TlvValueType.HexStringToHash -> { typeCheck(tag) tlvValue.toHexString() as T } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index c9f3275dfe..44200cd20a 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -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 diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index d7643bdaae..af6f5e2b76 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -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()) } diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt index c8b3bcb083..b22ed95049 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt @@ -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( diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt index f56ac1c5d5..887e832548 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt @@ -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!! diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt index b1ebcf143e..64cb5ce15d 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt @@ -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() } diff --git a/tangem-demo/build.gradle b/tangem-demo/build.gradle index b1de1a90fd..614e6b9552 100644 --- a/tangem-demo/build.gradle +++ b/tangem-demo/build.gradle @@ -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' diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt index 01207d703d..1a294a8897 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -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 { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt new file mode 100644 index 0000000000..1b409eddd3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt @@ -0,0 +1,129 @@ +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.Issuer +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 = Issuer.id, + 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() + 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"), + + issuerName = Issuer.id, + acquirerName = "TANGEM SDK", + + ndefRecords = ndefs + ) +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml index 2af3ad92de..d3bb8bbabe 100644 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ b/tangem-demo/src/main/res/layout/activity_main.xml @@ -111,6 +111,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn_create_wallet" /> +