Updated on 2026-08-14
This commit is contained in:
commit
c2d1c73aed
30 changed files with 271 additions and 244 deletions
3
Gemfile
3
Gemfile
|
|
@ -1,3 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ apply plugin: 'com.android.application'
|
|||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'io.fabric'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
static def generateVersionName() {
|
||||
def gitCommitsCount = "git rev-list HEAD --count".execute().text.trim()
|
||||
|
|
@ -29,24 +30,17 @@ android {
|
|||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")'
|
||||
ext.enableCrashlytics = false
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
applicationIdSuffix ".dev"
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")'
|
||||
ext.enableCrashlytics = false
|
||||
}
|
||||
debug_beta {
|
||||
initWith debug
|
||||
versionNameSuffix "-beta"
|
||||
applicationIdSuffix ".debug"
|
||||
buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("true")'
|
||||
manifestPlaceholders = [fabric_api_key: properties.hasProperty('fabric_api_key')
|
||||
? project.property('fabric_api_key') : '000']
|
||||
}
|
||||
}
|
||||
flavorDimensions ""
|
||||
|
|
@ -75,7 +69,6 @@ android {
|
|||
|
||||
repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
configurations {
|
||||
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
|
||||
|
|
@ -133,8 +126,8 @@ dependencies {
|
|||
// implementation 'com.github.stellar:java-stellar-sdk:0.4.1'
|
||||
implementation 'net.i2p.crypto:eddsa:0.3.0'
|
||||
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.9.9') { transitive = true }
|
||||
implementation('io.fabric.sdk.android:fabric:1.4.0@aar') { transitive = true }
|
||||
implementation 'com.google.firebase:firebase-analytics:17.2.3'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta02'
|
||||
|
||||
implementation files('libs/ripple-core-0.0.1.jar')
|
||||
//4 dependencies for ripple-core TODO: move to module?
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
<application
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<meta-data
|
||||
android:name="io.fabric.ApiKey"
|
||||
android:value="${fabric_api_key}" />
|
||||
|
||||
<provider
|
||||
android:name="com.tangem.data.LogFileProvider"
|
||||
android:authorities="@string/log_file_provider_authorities"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem
|
|||
|
||||
import android.app.Application
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.tangem.data.dp.PrefsManager
|
||||
|
|
@ -16,7 +15,6 @@ import com.tangem.tangem_card.data.Issuer
|
|||
import com.tangem.tangem_sdk.android.data.Firmwares
|
||||
import com.tangem.tangem_sdk.android.data.PINStorage
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
|
@ -77,7 +75,6 @@ class App : Application() {
|
|||
}
|
||||
}
|
||||
)
|
||||
if (BuildConfig.CRASHLYTICS) Fabric.with(this, Crashlytics())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ buildscript {
|
|||
classpath "com.android.tools.build:gradle:$versions.build_gradle"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
|
||||
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
|
||||
classpath "io.fabric.tools:gradle:1.31.0"
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta03'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.3.70',
|
||||
kotlin : '1.3.71',
|
||||
build_gradle: '3.6.1',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ platform :android do
|
|||
})
|
||||
end
|
||||
|
||||
desc "Submit a new Beta Build to Crashlytics Beta"
|
||||
desc "Submit a new Beta Build to Firebase App Distribution"
|
||||
lane :beta do |options|
|
||||
crashlytics(
|
||||
api_token: options[:api_token],
|
||||
build_secret: options[:build_secret],
|
||||
apk_path: options[:apk_path])
|
||||
firebase_app_distribution(
|
||||
app: options[:app_id],
|
||||
apk_path: options[:apk_path],
|
||||
groups: options[:groups])
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
5
fastlane/Pluginfile
Normal file
5
fastlane/Pluginfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Autogenerated by fastlane
|
||||
#
|
||||
# Ensure this file is checked in to source control!
|
||||
|
||||
gem 'fastlane-plugin-firebase_app_distribution'
|
||||
|
|
@ -35,7 +35,7 @@ Build debug and release APKs
|
|||
```
|
||||
fastlane android beta
|
||||
```
|
||||
Submit a new Beta Build to Crashlytics Beta
|
||||
Submit a new Beta Build to Firebase App Distribution
|
||||
|
||||
----
|
||||
|
||||
|
|
|
|||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Thu Feb 27 13:33:42 AST 2020
|
||||
#Mon Mar 30 09:15:33 MSK 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
|
|
|
|||
22
gradlew
vendored
22
gradlew
vendored
|
|
@ -1,5 +1,21 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
|
|
@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
|
@ -109,8 +125,8 @@ if $darwin; then
|
|||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
|
|
|||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
|
|
@ -1,3 +1,19 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
|
|
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
|||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.CardConfig
|
||||
import com.tangem.commands.personalization.DepersonalizeCommand
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.commands.personalization.PersonalizeCommand
|
||||
import com.tangem.commands.personalization.entities.Acquirer
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.commands.personalization.entities.Manufacturer
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
|
|
@ -260,16 +263,15 @@ class CardManager(
|
|||
* During this procedure all data exchange is encrypted.
|
||||
* @param config is a configuration file with all the card settings that are written on the card
|
||||
* during personalization.
|
||||
* @param cardId this parameter will set up CID, Unique Tangem card ID.
|
||||
* @param issuer Issuer is a third-party team or company wishing to use Tangem cards.
|
||||
* @param manufacturer Tangem Card Manufacturer.
|
||||
* @param acquirer Acquirer is a trusted third-party company that operates proprietary
|
||||
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
|
||||
*/
|
||||
fun personalize(config: CardConfig,
|
||||
cardId: String,
|
||||
issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null,
|
||||
callback: (result: TaskEvent<Card>) -> Unit) {
|
||||
if (this.config.issuer == null) {
|
||||
callback(TaskEvent.Completion(TaskError.IssuerIsRequired()))
|
||||
return
|
||||
}
|
||||
val personalizationCommand = PersonalizeCommand(config, cardId)
|
||||
val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer)
|
||||
val task = SingleCommandTask(personalizationCommand)
|
||||
task.performPreflightRead = false
|
||||
runTask(task, cardId, callback = callback)
|
||||
|
|
@ -320,10 +322,7 @@ class CardManager(
|
|||
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
|
||||
return CardEnvironment(
|
||||
cardId = cardId,
|
||||
terminalKeys = terminalKeys,
|
||||
manufacturerKeyPair = config.manufacturerKeyPair,
|
||||
acquirerKeyPair = config.acquirerKeyPair,
|
||||
issuer = config.issuer
|
||||
terminalKeys = terminalKeys
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
class Config(
|
||||
val linkedTerminal: Boolean = true,
|
||||
val issuerPublicKey: ByteArray? = null,
|
||||
val manufacturerKeyPair: KeyPair? = null,
|
||||
val acquirerKeyPair: KeyPair? = null,
|
||||
val issuer: Issuer? = null
|
||||
val issuerPublicKey: ByteArray? = null
|
||||
)
|
||||
|
|
@ -9,9 +9,6 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ReadUserDataResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
class WriteUserDataResponse(
|
||||
/**
|
||||
* CID, Unique Tangem card ID number.
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
package com.tangem.commands.personalization
|
||||
|
||||
import com.tangem.commands.*
|
||||
|
||||
data class NdefRecord(
|
||||
val type: Type,
|
||||
val value: String
|
||||
) {
|
||||
enum class Type {
|
||||
URI, AAR, TEXT
|
||||
}
|
||||
|
||||
val valueInBytes: ByteArray by lazy { value.toByteArray() }
|
||||
}
|
||||
|
||||
/**
|
||||
* It is a configuration file with all the card settings that are written on the card
|
||||
* during [PersonalizeCommand].
|
||||
*/
|
||||
data class CardConfig(
|
||||
val issuerName: String? = null,
|
||||
val acquirerName: String? = null,
|
||||
val series: String? = null,
|
||||
val startNumber: Long = 0,
|
||||
val count: Int = 0,
|
||||
val pin: String,
|
||||
val pin2: String,
|
||||
val pin3: String,
|
||||
val hexCrExKey: String?,
|
||||
val cvc: String,
|
||||
val pauseBeforePin2: Int,
|
||||
val smartSecurityDelay: Boolean,
|
||||
val curveID: EllipticCurve,
|
||||
val signingMethod: SigningMethod,
|
||||
val maxSignatures: Int,
|
||||
val isReusable: Boolean,
|
||||
val allowSwapPin: Boolean,
|
||||
val allowSwapPin2: Boolean,
|
||||
val useActivation: Boolean,
|
||||
val useCvc: Boolean,
|
||||
val useNdef: Boolean,
|
||||
val useDynamicNdef: Boolean,
|
||||
val useOneCommandAtTime: Boolean,
|
||||
val useBlock: Boolean,
|
||||
val allowSelectBlockchain: Boolean,
|
||||
val forbidPurgeWallet: Boolean,
|
||||
val protocolAllowUnencrypted: Boolean,
|
||||
val protocolAllowStaticEncryption: Boolean,
|
||||
val protectIssuerDataAgainstReplay: Boolean,
|
||||
val forbidDefaultPin: Boolean,
|
||||
val disablePrecomputedNdef: Boolean,
|
||||
val skipSecurityDelayIfValidatedByIssuer: Boolean,
|
||||
val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean,
|
||||
val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean,
|
||||
|
||||
val restrictOverwriteIssuerDataEx: Boolean,
|
||||
|
||||
val requireTerminalTxSignature: Boolean,
|
||||
val requireTerminalCertSignature: Boolean,
|
||||
val checkPin3onCard: Boolean,
|
||||
|
||||
val createWallet: Boolean,
|
||||
|
||||
val cardData: CardData,
|
||||
val ndefRecords: List<NdefRecord>
|
||||
) {
|
||||
|
||||
fun getSettingsMask(): SettingsMask {
|
||||
val builder = SettingsMaskBuilder()
|
||||
|
||||
if (allowSwapPin) builder.add(Settings.AllowSwapPIN)
|
||||
if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2)
|
||||
if (useCvc) builder.add(Settings.UseCVC)
|
||||
if (isReusable) builder.add(Settings.IsReusable)
|
||||
|
||||
if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime)
|
||||
if (useNdef) builder.add(Settings.UseNdef)
|
||||
if (useDynamicNdef) builder.add(Settings.UseDynamicNdef)
|
||||
if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef)
|
||||
|
||||
if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted)
|
||||
if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption)
|
||||
|
||||
if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN)
|
||||
|
||||
if (useActivation) builder.add(Settings.UseActivation)
|
||||
|
||||
if (useBlock) builder.add(Settings.UseBlock)
|
||||
if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay)
|
||||
|
||||
if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay)
|
||||
|
||||
if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet)
|
||||
if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain)
|
||||
|
||||
if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer)
|
||||
if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer)
|
||||
|
||||
if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal)
|
||||
if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx)
|
||||
|
||||
if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature)
|
||||
|
||||
if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature)
|
||||
|
||||
if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -20,8 +20,7 @@ class DepersonalizeCommand : CommandSerializer<DepersonalizeResponse>() {
|
|||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
return CommandApdu(
|
||||
Instruction.Depersonalize, byteArrayOf(),
|
||||
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
|
||||
Instruction.Depersonalize, byteArrayOf()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.commands.personalization
|
||||
|
||||
import com.tangem.commands.personalization.entities.NdefRecord
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.commands.personalization
|
|||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CardData
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.commands.personalization.entities.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
|
|
@ -23,22 +23,23 @@ import com.tangem.tasks.TaskError
|
|||
* Personalization is an initialization procedure, required before starting using a card.
|
||||
* During this procedure a card setting is set up.
|
||||
* During this procedure all data exchange is encrypted.
|
||||
* @param config is a configuration file with all the card settings that are written on the card
|
||||
* @property config is a configuration file with all the card settings that are written on the card
|
||||
* during personalization.
|
||||
* @param cardId this parameter will set up CID, Unique Tangem card ID.
|
||||
* @property issuer Issuer is a third-party team or company wishing to use Tangem cards.
|
||||
* @property manufacturer Tangem Card Manufacturer.
|
||||
* @property acquirer Acquirer is a trusted third-party company that operates proprietary
|
||||
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
|
||||
*/
|
||||
class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer<Card>() {
|
||||
class PersonalizeCommand(
|
||||
private val config: CardConfig,
|
||||
private val issuer: Issuer, private val manufacturer: Manufacturer,
|
||||
private val acquirer: Acquirer? = null
|
||||
) : CommandSerializer<Card>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
if (cardEnvironment.issuer == null || cardEnvironment.manufacturerKeyPair == null) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
return CommandApdu(
|
||||
Instruction.Personalize,
|
||||
serializePersonalizationData(
|
||||
cardId, config,
|
||||
cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey,
|
||||
cardEnvironment.acquirerKeyPair?.publicKey),
|
||||
serializePersonalizationData(config),
|
||||
encryptionKey = devPersonalizationKey
|
||||
)
|
||||
}
|
||||
|
|
@ -99,20 +100,19 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
)
|
||||
}
|
||||
|
||||
private fun serializePersonalizationData(cardId: String, config: CardConfig,
|
||||
issuer: Issuer, manufacturerPrivateKey: ByteArray,
|
||||
acquirePublicKey: ByteArray?
|
||||
): ByteArray {
|
||||
private fun serializePersonalizationData(config: CardConfig): ByteArray {
|
||||
val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError()
|
||||
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.CardId, cardId)
|
||||
|
||||
tlvBuilder.append(TlvTag.CurveId, config.curveID)
|
||||
tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures)
|
||||
tlvBuilder.append(TlvTag.SigningMethod, config.signingMethod)
|
||||
tlvBuilder.append(TlvTag.SettingsMask, config.getSettingsMask())
|
||||
tlvBuilder.append(TlvTag.SettingsMask, config.createSettingsMask())
|
||||
tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10)
|
||||
tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray())
|
||||
if (!config.ndefRecords.isNullOrEmpty()) tlvBuilder.append(TlvTag.NdefData, serializeNdef(config.ndefRecords))
|
||||
if (!config.ndefRecords.isNullOrEmpty())
|
||||
tlvBuilder.append(TlvTag.NdefData, serializeNdef(config))
|
||||
|
||||
tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet)
|
||||
|
||||
|
|
@ -123,25 +123,22 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey)
|
||||
tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey)
|
||||
|
||||
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirePublicKey)
|
||||
tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirer?.keyPair?.publicKey)
|
||||
|
||||
tlvBuilder.append(
|
||||
TlvTag.CardData, serializeCardData(cardId, config.cardData, issuer, manufacturerPrivateKey)
|
||||
)
|
||||
tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData))
|
||||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
private fun serializeCardData(
|
||||
cardId: String, cardData: CardData,
|
||||
issuer: Issuer, manufacturerPrivateKey: ByteArray): ByteArray {
|
||||
private fun serializeNdef(config: CardConfig): ByteArray {
|
||||
return NdefEncoder(config.ndefRecords, config.useDynamicNdef).encode()
|
||||
}
|
||||
|
||||
private fun serializeCardData(cardId: String, cardData: CardData): ByteArray {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Batch, cardData.batchId)
|
||||
tlvBuilder.append(TlvTag.ProductMask, cardData.productMask)
|
||||
|
||||
tlvBuilder.append(TlvTag.ManufactureDateTime, cardData.manufactureDateTime)
|
||||
|
||||
tlvBuilder.append(TlvTag.IssuerId, issuer.id)
|
||||
|
||||
tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName)
|
||||
|
||||
if (cardData.tokenSymbol != null) {
|
||||
|
|
@ -150,15 +147,12 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str
|
|||
tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal)
|
||||
}
|
||||
tlvBuilder.append(
|
||||
TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(manufacturerPrivateKey)
|
||||
TlvTag.CardIdManufacturerSignature,
|
||||
cardId.hexToBytes().sign(manufacturer.keyPair.privateKey)
|
||||
)
|
||||
return tlvBuilder.serialize()
|
||||
}
|
||||
|
||||
private fun serializeNdef(ndefRecords: List<NdefRecord>): ByteArray {
|
||||
return NdefEncoder(ndefRecords, config.useDynamicNdef).encode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val devPersonalizationKey = "1234".calculateSha256().copyOf(32)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
data class Acquirer(
|
||||
val keyPair: KeyPair,
|
||||
val name: String? = null,
|
||||
val id: String? = null
|
||||
)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.commands.CardData
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.SigningMethod
|
||||
|
||||
data class NdefRecord(
|
||||
val type: Type,
|
||||
val value: String
|
||||
) {
|
||||
enum class Type {
|
||||
URI, AAR, TEXT
|
||||
}
|
||||
|
||||
val valueInBytes: ByteArray by lazy { value.toByteArray() }
|
||||
}
|
||||
|
||||
/**
|
||||
* It is a configuration file with all the card settings that are written on the card
|
||||
* during [PersonalizeCommand].
|
||||
*/
|
||||
data class CardConfig(
|
||||
val issuerName: String? = null,
|
||||
val acquirerName: String? = null,
|
||||
val series: String? = null,
|
||||
val startNumber: Long = 0,
|
||||
val count: Int = 0,
|
||||
val pin: String,
|
||||
val pin2: String,
|
||||
val pin3: String,
|
||||
val hexCrExKey: String?,
|
||||
val cvc: String,
|
||||
val pauseBeforePin2: Int,
|
||||
val smartSecurityDelay: Boolean,
|
||||
val curveID: EllipticCurve,
|
||||
val signingMethod: SigningMethod,
|
||||
val maxSignatures: Int,
|
||||
val isReusable: Boolean,
|
||||
val allowSwapPin: Boolean,
|
||||
val allowSwapPin2: Boolean,
|
||||
val useActivation: Boolean,
|
||||
val useCvc: Boolean,
|
||||
val useNdef: Boolean,
|
||||
val useDynamicNdef: Boolean,
|
||||
val useOneCommandAtTime: Boolean,
|
||||
val useBlock: Boolean,
|
||||
val allowSelectBlockchain: Boolean,
|
||||
val forbidPurgeWallet: Boolean,
|
||||
val protocolAllowUnencrypted: Boolean,
|
||||
val protocolAllowStaticEncryption: Boolean,
|
||||
val protectIssuerDataAgainstReplay: Boolean,
|
||||
val forbidDefaultPin: Boolean,
|
||||
val disablePrecomputedNdef: Boolean,
|
||||
val skipSecurityDelayIfValidatedByIssuer: Boolean,
|
||||
val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean,
|
||||
val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean,
|
||||
|
||||
val restrictOverwriteIssuerDataEx: Boolean,
|
||||
|
||||
val requireTerminalTxSignature: Boolean,
|
||||
val requireTerminalCertSignature: Boolean,
|
||||
val checkPin3onCard: Boolean,
|
||||
|
||||
val createWallet: Boolean,
|
||||
|
||||
val cardData: CardData,
|
||||
val ndefRecords: List<NdefRecord>
|
||||
) {
|
||||
companion object
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.commands.Settings
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.SettingsMaskBuilder
|
||||
|
||||
internal fun CardConfig.createSettingsMask(): SettingsMask {
|
||||
val builder = SettingsMaskBuilder()
|
||||
|
||||
if (allowSwapPin) builder.add(Settings.AllowSwapPIN)
|
||||
if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2)
|
||||
if (useCvc) builder.add(Settings.UseCVC)
|
||||
if (isReusable) builder.add(Settings.IsReusable)
|
||||
|
||||
if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime)
|
||||
if (useNdef) builder.add(Settings.UseNdef)
|
||||
if (useDynamicNdef) builder.add(Settings.UseDynamicNdef)
|
||||
if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef)
|
||||
|
||||
if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted)
|
||||
if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption)
|
||||
|
||||
if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN)
|
||||
|
||||
if (useActivation) builder.add(Settings.UseActivation)
|
||||
|
||||
if (useBlock) builder.add(Settings.UseBlock)
|
||||
if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay)
|
||||
|
||||
if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay)
|
||||
|
||||
if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet)
|
||||
if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain)
|
||||
|
||||
if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer)
|
||||
if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer)
|
||||
|
||||
if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal)
|
||||
if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx)
|
||||
|
||||
if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature)
|
||||
|
||||
if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature)
|
||||
|
||||
if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
internal fun CardConfig.createCardId(): String? {
|
||||
if (series == null) return null
|
||||
if (startNumber <= 0 || (series.length != 2 && series.length != 4)) return null
|
||||
|
||||
val Alf = "ABCDEF0123456789"
|
||||
fun checkSeries(series: String): Boolean {
|
||||
val containsList = series.filter { Alf.contains(it) }
|
||||
return containsList.length == series.length
|
||||
}
|
||||
if (!checkSeries(series)) return null
|
||||
|
||||
val tail = if (series.length == 2) String.format("%013d", startNumber) else String.format("%011d", startNumber)
|
||||
var cardId = (series + tail).replace(" ", "")
|
||||
if (cardId.length != 15 || Alf.indexOf(cardId[0]) == -1 || Alf.indexOf(cardId[1]) == -1)
|
||||
return null
|
||||
|
||||
cardId += "0"
|
||||
val length = cardId.length
|
||||
var sum = 0
|
||||
for (i in 0 until length) {
|
||||
// get digits in reverse order
|
||||
var digit: Int
|
||||
val cDigit = cardId[length - i - 1]
|
||||
digit = if (cDigit in '0'..'9') cDigit - '0' else cDigit - 'A'
|
||||
|
||||
// every 2nd number multiply with 2
|
||||
if (i % 2 == 1) digit *= 2
|
||||
sum += if (digit > 9) digit - 9 else digit
|
||||
}
|
||||
val lunh = (10 - sum % 10) % 10
|
||||
return cardId.substring(0, 15) + String.format("%d", lunh)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.commands.personalization.entities
|
||||
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
data class Manufacturer(
|
||||
val keyPair: KeyPair,
|
||||
val name: String? = null
|
||||
)
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
|
||||
|
||||
/**
|
||||
* Contains data relating to a Tangem card. It is used in constructing all the commands,
|
||||
|
|
@ -14,10 +12,7 @@ data class CardEnvironment(
|
|||
val terminalKeys: KeyPair? = null,
|
||||
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
var encryptionKey: ByteArray? = null,
|
||||
val cvc: ByteArray? = null,
|
||||
val manufacturerKeyPair: KeyPair? = null,
|
||||
val acquirerKeyPair: KeyPair? = null,
|
||||
val issuer: Issuer? = null
|
||||
val cvc: ByteArray? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -141,12 +141,6 @@ sealed class TaskError(val code: Int) : Exception() {
|
|||
* (when the card's requires it), but the counter is missing.
|
||||
*/
|
||||
class MissingCounter : TaskError(7001)
|
||||
|
||||
/**
|
||||
* This error is returned when [com.tangem.commands.personalization.PersonalizeCommand] is attempted
|
||||
* without [com.tangem.commands.personalization.entities.Issuer] being set in the [com.tangem.Config].
|
||||
*/
|
||||
class IssuerIsRequired : TaskError(7002)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ 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.create
|
||||
import com.tangem.tasks.ScanEvent
|
||||
|
|
@ -153,30 +152,6 @@ class Old_MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) }
|
||||
btn_personalize?.setOnClickListener { _ ->
|
||||
cardManager.personalize(
|
||||
CardConfig.create(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> {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tangemtest.extensions
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.CardConfig
|
||||
import com.tangem.commands.personalization.NdefRecord
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.NdefRecord
|
||||
import java.util.*
|
||||
|
||||
fun CardConfig.Companion.create(application: Application): CardConfig {
|
||||
|
|
|
|||
0
tangem-demo/src/main/res/layout/activity_main.xml
Normal file
0
tangem-demo/src/main/res/layout/activity_main.xml
Normal file
Loading…
Add table
Add a link
Reference in a new issue