Updated on 2026-08-14

This commit is contained in:
Tangem 2019-10-01 11:51:31 +03:00
parent 3d3d561519
commit 383f963155
12 changed files with 418 additions and 0 deletions

1
tangem-card-new/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,32 @@
apply plugin: "kotlin"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
sourceCompatibility = "8"
targetCompatibility = "8"
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

View file

@ -0,0 +1,23 @@
package com.tangem
data class Card(
val cid: String
) {
// private var CID: ByteArray? = null
// private var status: Status? = null
// private var UID: String? = null
// private var blockchainID: String? = null
// private var manufacturer = Manufacturer.Unknown
// private var manufacturerConfirmed = false
// private var maxSignatures: Int = 0
// private var remainingSignatures: Int = 0
// private var PIN: String? = null
// private var pbWalletKey: ByteArray? = null
// private var pbCardKey: ByteArray? = null
// private var pbWalletKeyRar: ByteArray? = null
// private var dtPersonalization: Date? = null
// private var terminalPrivateKey: ByteArray? = null
// private var terminalPublicKey: ByteArray? = null
// private var terminalIsLinked = false
}

View file

@ -0,0 +1,16 @@
package com.tangem
data class CardEnvironment(
val pin1: String = DEFAULT_PIN,
val pin2: String = DEFAULT_PIN2,
val terminalPublicKey: ByteArray? = null,
val terminalPrivateKey: ByteArray? = null
) {
companion object {
const val DEFAULT_PIN = "000000"
const val DEFAULT_PIN2 = "000"
}
}

View file

@ -0,0 +1,22 @@
package com.tangem
class CardEnvironmentRepository(val dataStorage: DataStorage?) {
var cardEnvironment: CardEnvironment = initCardEnvironment()
set(value) {
if (cardEnvironment != value) {
saveCardEnvirnoment(value)
}
}
private fun initCardEnvironment(): CardEnvironment {
return CardEnvironment(
dataStorage?.getPin1() ?: CardEnvironment.DEFAULT_PIN,
dataStorage?.getPin2() ?: CardEnvironment.DEFAULT_PIN2
)
}
private fun saveCardEnvirnoment(cardEnvironment: CardEnvironment) {
//TODO: logic for saving fields in persistence storage
}
}

View file

@ -0,0 +1,53 @@
package com.tangem
open class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null,
dataStorage: DataStorage? = null) {
private val cardEnvironmentRepository = CardEnvironmentRepository(dataStorage)
var card: Card? = null
private set
fun scanCard(callback: (result: TaskResult) -> Unit) {
val task = ScanTask(cardManagerDelegate, reader)
runTask(task, cardEnvironmentRepository.cardEnvironment) {
if (it is TaskResult.CommandCompleted && it.resultData is Card) {
this.card = it.resultData
}
callback.invoke(it)
}
}
fun signTransaction(signCommandData: SignCommandData,
callback: (result: TaskResult) -> Unit) {
val task = SignTask(signCommandData, cardManagerDelegate, reader)
runTask(task, cardEnvironmentRepository.cardEnvironment, callback)
}
fun runTask(task: Task, environment: CardEnvironment? = null,
callback: (result: TaskResult) -> Unit) {
task.run(environment ?: cardEnvironmentRepository.cardEnvironment) {result, returnedEnvironment ->
//TODO: environment is kept in Repository
cardEnvironmentRepository.cardEnvironment = returnedEnvironment
callback.invoke(result)
}
}
fun runCommand(command: Command,
environment: CardEnvironment? = null,
callback: (result: TaskResult) -> Unit) {
val task = BasicTask(command, cardManagerDelegate, reader)
runTask(task, environment, callback)
}
}
sealed class TaskResult {
data class Success(val resultData: Any) : TaskResult()
data class CommandCompleted(val resultData: Any) : TaskResult()
data class Error(val error: CardError) : TaskResult()
}

View file

@ -0,0 +1,12 @@
package com.tangem
interface CardManagerDelegate {
fun showSecurityDelay(seconds: Int)
fun hideSecurityDelay()
fun requestPin(success: () -> String, error: (cardError: CardError) -> CardError)
fun openNfcPopup()
fun closeNfcPopup()
}

View file

@ -0,0 +1,7 @@
package com.tangem
import com.tangem.data.ResponseApdu
interface CardReader {
fun sendApdu(apduSerialized: ByteArray, callback: (responseApdu: ResponseApdu) -> Unit)
}

View file

@ -0,0 +1,112 @@
package com.tangem
import com.tangem.data.CommandApdu
import com.tangem.data.ResponseApduParsed
import com.tangem.data.Tlv
import com.tangem.enums.Instruction
import com.tangem.enums.TlvTag
abstract class Command {
abstract val instruction: Instruction
abstract val instructionCode: Int
abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu
abstract fun deserialize(responseApduParsed: ResponseApduParsed): TaskResult
}
class ReadCardCommand(val pin1: String) : Command() {
override val instruction = Instruction.Read
override val instructionCode = instruction.code
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
return CommandApdu(
listOf(Tlv(TlvTag.Pin, TlvTag.Pin.code, pin1.toByteArray())), instruction)
}
override fun deserialize(responseApduParsed: ResponseApduParsed): TaskResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
class CheckWalletCommand(
val pin1: String, val cid: String,
val challenge: ByteArray, val publicKeyChallenge: ByteArray) : Command() {
override val instruction = Instruction.CheckWallet
override val instructionCode = instruction.code
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deserialize(responseApduParsed: ResponseApduParsed): TaskResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
class SignCommand(val signCommandData: SignCommandData) : Command() {
override val instruction = Instruction.Sign
override val instructionCode = instruction.code
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deserialize(responseApduParsed: ResponseApduParsed): TaskResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
data class SignCommandData(
val cid: String,
val pin1: String,
val pin2: String,
val cvc: String,
val transactionHash: TransactionHash,
val issuerData: IssuerData,
val terminalPublicKey: ByteArray? = null,
val terminalTransactionSignature: ByteArray? = null
)
data class TransactionHash(
val transactionHash: ByteArray,
val transactionRaw: ByteArray,
val hashName: String
)
data class IssuerData(
val issuerData: ByteArray,
val issuerDataCounter: Int,
val issuerTransactionSignature: ByteArray,
val issuerDataSignature: ByteArray
)
sealed class CommandData {
data class CheckWalletResponse(
val cid: String,
val salt: ByteArray,
val walletSignature: ByteArray
)
}
sealed class CommandResult{
data class Success(val data: CommandData) : CommandResult()
data class Error(val error: CardError) : CommandResult()
}

View file

@ -0,0 +1,10 @@
package com.tangem
interface DataStorage {
fun getTerminalPublicKey(): ByteArray?
fun getTerminalPrivateKey(): ByteArray?
fun getPin1(): String?
fun getPin2(): String?
}

View file

@ -0,0 +1,16 @@
package com.tangem
data class SignResponse(
val cid: String,
val signature: ByteArray,
val remainingSignatures: Int,
val signedHashes: Int
)
data class CardError(
val code: Int = 0
)

View file

@ -0,0 +1,114 @@
package com.tangem
abstract class Task(protected val delegate: CardManagerDelegate? = null, private val reader: CardReader) {
abstract fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit)
protected fun executeCommand(command: Command, cardEnvironment: CardEnvironment,
callback: (result: TaskResult) -> Unit) {
reader.sendApdu(command.serialize(cardEnvironment).serialize()) {
val parsedApdu = it.deserialize()
if (parsedApdu == null) callback.invoke(TaskResult.Error(CardError()))
else if (!parsedApdu.statusCompleted()) {
callback.invoke(TaskResult.Error(CardError(parsedApdu.status.code)))
} else {
val response = command.deserialize(parsedApdu)
callback.invoke(response)
}
}
}
}
class BasicTask(
private val command: Command,
delegate: CardManagerDelegate? = null,
reader: CardReader) : Task(delegate, reader) {
override fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit) {
executeCommand(command, cardEnvironment) {
callback.invoke(it, updateEnvironment())
}
}
private fun updateEnvironment() : CardEnvironment{
//TODO: set logic of changing CardEnvironment when needed
return CardEnvironment()
}
}
class SignTask(private val signCommandData: SignCommandData,
delegate: CardManagerDelegate? = null,
reader: CardReader) : Task(delegate, reader) {
private lateinit var callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit
override fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit) {
this.callback = callback
delegate?.openNfcPopup()
val signCommand = SignCommand(signCommandData)
executeCommand(signCommand, cardEnvironment) {
delegate?.closeNfcPopup()
run { callback(it, updateEnvironment()) }
}
}
private fun updateEnvironment() : CardEnvironment{
//TODO: set logic of changing CardEnvironment when needed
return CardEnvironment()
}
}
class ScanTask(delegate: CardManagerDelegate? = null, reader: CardReader) : Task(delegate, reader) {
private lateinit var card: Card
override fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit) {
delegate?.openNfcPopup()
val readCommand = ReadCardCommand(cardEnvironment.pin1)
executeCommand(readCommand, cardEnvironment) {
if (it is TaskResult.Success) {
card = it.resultData as Card
val checkWalletCommand = prepareCheckWalletCommand(cardEnvironment)
executeCommand(checkWalletCommand, cardEnvironment) {
delegate?.closeNfcPopup()
//TODO: pass check wallet response data
run { callback(it, updateEnvironment()) }
}
} else {
//TODO: Handle error
}
}
}
private fun updateEnvironment() : CardEnvironment{
//TODO: set logic of changing CardEnvironment when needed
return CardEnvironment()
}
private fun prepareCheckWalletCommand(cardEnvironment: CardEnvironment): CheckWalletCommand {
val challenge = generateChallenge()
return CheckWalletCommand(
cardEnvironment.pin1,
card.cid,
challenge,
byteArrayOf())
}
private fun generateChallenge(): ByteArray {
return byteArrayOf()
}
}