Updated on 2026-08-14
This commit is contained in:
commit
4c64438643
28 changed files with 373 additions and 65 deletions
|
|
@ -1,36 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":tangem-card-old" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":tangem-card-old" />
|
||||
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" />
|
||||
<option name="LAST_KNOWN_AGP_VERSION" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="true" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
|
||||
<output url="file://$MODULE_DIR$/build/classes/java/main" />
|
||||
<output-test url="file://$MODULE_DIR$/build/classes/java/test" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="Gradle: prov-1.56.0.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="Gradle: core-1.56.0.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="Gradle: eddsa-0.3.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem
|
||||
|
||||
|
||||
/**
|
||||
* Contains data relating to a Tangem card. It is used in constructing all the commands,
|
||||
* and commands can return modified [CardEnvironment].
|
||||
*/
|
||||
data class CardEnvironment(
|
||||
val pin1: String = DEFAULT_PIN,
|
||||
val pin2: String = DEFAULT_PIN2,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,15 @@ import com.tangem.crypto.CryptoUtils
|
|||
import com.tangem.tasks.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* The main interface of Tangem SDK that allows your app to communicate with Tangem cards.
|
||||
*
|
||||
* @property reader is an interface that is responsible for NFC connection and
|
||||
* transfer of data to and from the Tangem Card.
|
||||
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
|
||||
* @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI.
|
||||
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
|
||||
*/
|
||||
class CardManager(
|
||||
private val reader: CardReader,
|
||||
private val cardManagerDelegate: CardManagerDelegate? = null) {
|
||||
|
|
@ -20,11 +29,41 @@ class CardManager(
|
|||
CryptoUtils.initCrypto()
|
||||
}
|
||||
|
||||
/**
|
||||
* To start using any card, you first need to read it using the scanCard() method.
|
||||
* This method launches an NFC session, and once it’s connected with the card,
|
||||
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
|
||||
* it proves that the wallet owns a private key that corresponds to a public one.
|
||||
*
|
||||
* It launches on the new thread a [ScanTask] that will send the following events in a callback:
|
||||
* [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand]
|
||||
* [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand]
|
||||
* [TaskEvent.Completion] with an error field null after successful completion of a task or
|
||||
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
|
||||
*/
|
||||
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
val task = ScanTask()
|
||||
runTask(task, callback = callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows you to sign one or multiple hashes.
|
||||
* Simultaneous signing of array of hashes in a single [SignCommand] is required to support
|
||||
* Bitcoin-type multi-input blockchains (UTXO).
|
||||
* The [SignCommand] will return a corresponding array of signatures.
|
||||
*
|
||||
* This method launches on the new thread [SignCommand] that will send the following events in a callback:
|
||||
* [SignResponse] after completing [SignCommand]
|
||||
* [TaskEvent.Completion] with an error field null after successful completion of a task or
|
||||
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
|
||||
* Please note that Tangem cards usually protect the signing with a security delay
|
||||
* that may last up to 90 seconds, depending on a card.
|
||||
* It is for [CardManagerDelegate] to notify users of security delay.
|
||||
* @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length.
|
||||
* @param cardId CID, Unique Tangem card ID number
|
||||
* @param callback
|
||||
*
|
||||
*/
|
||||
fun sign(hashes: Array<ByteArray>, cardId: String,
|
||||
callback: (result: TaskEvent<SignResponse>) -> Unit) {
|
||||
val signCommand: SignCommand
|
||||
|
|
@ -42,6 +81,9 @@ class CardManager(
|
|||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
fun <T> runTask(task: Task<T>, cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
if (isBusy) {
|
||||
|
|
@ -63,6 +105,9 @@ class CardManager(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
fun <T : CommandResponse> runCommand(command: CommandSerializer<T>,
|
||||
cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,43 @@ package com.tangem
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* Allows interaction with users and shows visual elements.
|
||||
*
|
||||
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
|
||||
*/
|
||||
interface CardManagerDelegate {
|
||||
|
||||
/**
|
||||
* It is called when user is expected to scan a Tangem Card with an Android device.
|
||||
*/
|
||||
fun onNfcSessionStarted()
|
||||
|
||||
/**
|
||||
* It is called when security delay is triggered by the card.
|
||||
* A user is expected to hold the card until the security delay is over.
|
||||
*/
|
||||
fun onSecurityDelay(ms: Int)
|
||||
|
||||
/**
|
||||
* It is called when user takes the card away from the Android device during the scanning
|
||||
* (for example when security delay is in progress) and the TagLostException is received.
|
||||
*/
|
||||
fun onTagLost()
|
||||
|
||||
/**
|
||||
* It is called when NFC session was completed and a user can take the card away from the Android device.
|
||||
*/
|
||||
fun onNfcSessionCompleted()
|
||||
|
||||
/**
|
||||
* It is called when some error occur during NFC session.
|
||||
*/
|
||||
fun onError(error: TaskError? = null)
|
||||
|
||||
fun requestPin(callback: (result: CompletionResult<String>) -> Unit)
|
||||
/**
|
||||
* It is called when a user is expected to enter pin code.
|
||||
*/
|
||||
fun onPinRequested(callback: (result: CompletionResult<String>) -> Unit)
|
||||
|
||||
}
|
||||
|
|
@ -4,9 +4,29 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
||||
/**
|
||||
* Allows interaction between the phone or any other terminal and Tangem card.
|
||||
*
|
||||
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
|
||||
*/
|
||||
interface CardReader {
|
||||
var readingActive: Boolean
|
||||
|
||||
/**
|
||||
* Sends data to the card and receives the reply.
|
||||
*
|
||||
* @param apdu Data to be sent. [CommandApdu] serializes it to a [ByteArray]
|
||||
* @param callback Returns response from the card,
|
||||
* [ResponseApdu] Allows to convert raw data to [Tlv]
|
||||
*/
|
||||
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
|
||||
fun startNfcSession()
|
||||
|
||||
/**
|
||||
* Signals to [CardReader] to become ready to transceive data.
|
||||
*/
|
||||
fun openSession()
|
||||
|
||||
/**
|
||||
* Signals to [CardReader] that no further NFC transition is expected.
|
||||
*/
|
||||
fun closeSession()
|
||||
}
|
||||
|
|
@ -22,6 +22,11 @@ object Log {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for logging events within the SDK.
|
||||
*
|
||||
* It allows to use Android logger or to choose another.
|
||||
*/
|
||||
interface LoggerInterface {
|
||||
fun i(logTag: String, message: String)
|
||||
fun e(logTag: String, message: String)
|
||||
|
|
|
|||
|
|
@ -11,13 +11,27 @@ import com.tangem.common.tlv.TlvMapper
|
|||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* Deserialized response from the Tangem card after [CheckWalletCommand].
|
||||
*
|
||||
* @property cardId Unique Tangem card ID number
|
||||
* @property salt Random salt generated by the card
|
||||
* @property walletSignature Challenge and salt signed with the wallet private key.
|
||||
*/
|
||||
class CheckWalletResponse(
|
||||
val cardId: String,
|
||||
val salt: ByteArray,
|
||||
val walletSignature: ByteArray
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
/**
|
||||
* This command proves that the wallet private key from the card corresponds to the wallet public key.
|
||||
* Standard challenge/response scheme is used.
|
||||
*
|
||||
* @property pin1 Hashed user’s pin 1 code to access the card. Default unhashed value: ‘000000’.
|
||||
* @property cardId Unique Tangem card ID number
|
||||
* @property challenge Random challenge generated by application
|
||||
*/
|
||||
class CheckWalletCommand(
|
||||
private val pin1: String,
|
||||
private val cardId: String,
|
||||
|
|
|
|||
|
|
@ -2,22 +2,41 @@ package com.tangem.commands
|
|||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extentions.toInt
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
||||
/**
|
||||
* Simple interface for responses received after sending commands to Tangem cards.
|
||||
*/
|
||||
interface CommandResponse
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for all Tangem card commands.
|
||||
*/
|
||||
abstract class CommandSerializer<T : CommandResponse> {
|
||||
|
||||
abstract val instruction: Instruction
|
||||
abstract val instructionCode: Int
|
||||
|
||||
/**
|
||||
* Serializes data into a [List] of [com.tangem.common.tlv.Tlv],
|
||||
* then creates [CommandApdu] with this data.
|
||||
*
|
||||
* @return Command data that can be converted to raw bytes with a method [CommandApdu.toBytes].
|
||||
*/
|
||||
abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu
|
||||
|
||||
/**
|
||||
* Deserializes data, received from a card and stored in [ResponseApdu],
|
||||
* into a [List] of [com.tangem.common.tlv.Tlv]. Then this method maps it into a [CommandResponse].
|
||||
*
|
||||
* @return Card response, converted to a [CommandResponse] of a type [T].
|
||||
*/
|
||||
abstract fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): T?
|
||||
|
||||
/**
|
||||
* Helper method to parse security delay information received from a card.
|
||||
*
|
||||
* @return Remaining security delay in milliseconds.
|
||||
*/
|
||||
fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? {
|
||||
val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey)
|
||||
return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt()
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ enum class ProductMask(val code: Byte) {
|
|||
/**
|
||||
* Stores and maps Tangem card settings.
|
||||
*
|
||||
* @property rawValue are card settings in a form of flags,
|
||||
* @property rawValue Card settings in a form of flags,
|
||||
* while flags definitions and values are in [SettingsMask.Companion] as constants.
|
||||
*/
|
||||
data class SettingsMask(val rawValue: Int) {
|
||||
|
|
@ -152,7 +152,6 @@ class Card(
|
|||
/**
|
||||
* This command receives from the Tangem Card all the data about the card and the wallet,
|
||||
* including unique card number (CID or cardId) that has to be submitted while calling all other commands.
|
||||
*
|
||||
*/
|
||||
class ReadCommand : CommandSerializer<Card>() {
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ import com.tangem.common.tlv.TlvTag
|
|||
import com.tangem.crypto.sign
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* @param cardId CID, Unique Tangem card ID number
|
||||
* @param signature Signed hashes (array of resulting signatures)
|
||||
* @param remainingSignatures Remaining number of sign operations before the wallet will stop signing transactions.
|
||||
* @param signedHashes Total number of signed single hashes returned by the card in sign command responses.
|
||||
* Sums up array elements within all SIGN commands
|
||||
*/
|
||||
class SignResponse(
|
||||
val cardId: String,
|
||||
val signature: ByteArray,
|
||||
|
|
@ -19,7 +26,12 @@ class SignResponse(
|
|||
val signedHashes: Int
|
||||
) : CommandResponse
|
||||
|
||||
|
||||
/**
|
||||
* Signs transaction hashes using a wallet private key, stored on the card.
|
||||
*
|
||||
* @property hashes Array of transaction hashes.
|
||||
* @property cardId CID, Unique Tangem card ID number
|
||||
*/
|
||||
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
|
||||
: CommandSerializer<SignResponse>() {
|
||||
|
||||
|
|
@ -51,6 +63,14 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
return CommandApdu(Instruction.Sign, tlvData)
|
||||
}
|
||||
|
||||
/**
|
||||
d
|
||||
* Application can optionally submit a public key Terminal_PublicKey in [SignCommand].
|
||||
* Submitted key is stored by the Tangem card if it differs from a previous submitted Terminal_PublicKey.
|
||||
* The Tangem card will not enforce security delay if [SignCommand] will be called with
|
||||
* TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey
|
||||
* (this key should be generated and securily stored by the application).
|
||||
*/
|
||||
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
|
||||
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
|
||||
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
package com.tangem.common
|
||||
|
||||
import com.tangem.common.CompletionResult.Success
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* Response class encapsulating successful and failed results.
|
||||
* @param T Type of data that is returned in [Success].
|
||||
*/
|
||||
sealed class CompletionResult<T> {
|
||||
class Success<T>(val data: T) : CompletionResult<T>()
|
||||
class Failure<T>(val error: TaskError) : CompletionResult<T>()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ import com.tangem.common.tlv.Tlv
|
|||
import com.tangem.common.tlv.toBytes
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* Class that provides conversion of serialized request and Instruction code
|
||||
* to a raw data that can be sent to the card.
|
||||
*
|
||||
* @property ins Instruction code that determines the type of request for the card.
|
||||
* @property tlvList A list of TLVs that are to be sent to the card
|
||||
*/
|
||||
class CommandApdu(
|
||||
|
||||
private val ins: Int,
|
||||
|
|
@ -31,6 +38,10 @@ class CommandApdu(
|
|||
encryptionKey = encryptionKey
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Request converted to a raw data
|
||||
*/
|
||||
val apduData: ByteArray
|
||||
|
||||
init {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
/**
|
||||
* Instruction code that determines the type of the command that is sent to the Tangem card.
|
||||
* It is used in the construction of [com.tangem.common.apdu.CommandApdu].
|
||||
*/
|
||||
enum class Instruction(var code: Int) {
|
||||
Unknown(0x00),
|
||||
Read(0xF2),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@ package com.tangem.common.apdu
|
|||
|
||||
import com.tangem.common.tlv.Tlv
|
||||
|
||||
/**
|
||||
* Stores response data from the card and parses it to [Tlv] and [StatusWord].
|
||||
*
|
||||
* @property data Raw response from the card.
|
||||
* @property sw Status word code, reflecting the status of the response.
|
||||
* @property statusWord Parsed status word.
|
||||
*/
|
||||
class ResponseApdu(val data: ByteArray) {
|
||||
|
||||
private val sw1: Int = 0x00FF and data[data.size - 2].toInt()
|
||||
|
|
@ -11,6 +18,12 @@ class ResponseApdu(val data: ByteArray) {
|
|||
|
||||
val statusWord: StatusWord = StatusWord.byCode(sw)
|
||||
|
||||
/**
|
||||
* Converts raw response data to the list of TLVs.
|
||||
*
|
||||
* @param encryptionKey key to decrypt response.
|
||||
* (Encryption / decryption functionality is not implemented yet.)
|
||||
*/
|
||||
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
|
||||
return when {
|
||||
data.size < 2 -> null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
enum class StatusWord (val code: Int, val description: String){
|
||||
/**
|
||||
* Part of a response from the card, shows the status of the operation
|
||||
*/
|
||||
enum class StatusWord(val code: Int, val description: String) {
|
||||
|
||||
ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"),
|
||||
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.security.MessageDigest
|
|||
import java.util.*
|
||||
import kotlin.experimental.and
|
||||
|
||||
/**
|
||||
* Extension functions for [ByteArray].
|
||||
*/
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.common.extentions
|
|||
import java.nio.charset.Charset
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
||||
/**
|
||||
* Extension functions for [String].
|
||||
*/
|
||||
fun String.calculateSha256(): ByteArray {
|
||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||
val data = this.toByteArray(Charset.forName("UTF-8"))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package com.tangem.common.tlv
|
|||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* The data converted to the Tag Length Value protocol.
|
||||
*/
|
||||
class Tlv {
|
||||
|
||||
val tag: TlvTag
|
||||
|
|
|
|||
|
|
@ -14,8 +14,22 @@ class MissingTagException(message: String? = null) : TlvMapperException(message)
|
|||
class WrongTypeException(message: String? = null) : TlvMapperException(message)
|
||||
class ConversionException(message: String? = null) : TlvMapperException(message)
|
||||
|
||||
/**
|
||||
* Maps value fields in [Tlv] from raw [ByteArray] to concrete classes
|
||||
* according to their [TlvTag] and corresponding [TlvValueType].
|
||||
*
|
||||
* @property tlvList List of TLVs, which values are to be converted to particular classes.
|
||||
*/
|
||||
class TlvMapper(val tlvList: List<Tlv>) {
|
||||
|
||||
/**
|
||||
* Finds [Tlv] by its [TlvTag].
|
||||
* Returns null if [Tlv] is not found, otherwise converts its value to [T].
|
||||
*
|
||||
* @param tag [TlvTag] of a [Tlv] which value is to be returned.
|
||||
*
|
||||
* @return Value converted to a nullable type [T].
|
||||
*/
|
||||
inline fun <reified T> mapOptional(tag: TlvTag): T? =
|
||||
try {
|
||||
map<T>(tag)
|
||||
|
|
@ -23,6 +37,17 @@ class TlvMapper(val tlvList: List<Tlv>) {
|
|||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds [Tlv] by its [TlvTag].
|
||||
* Throws [MissingTagException] if [Tlv] is not found,
|
||||
* otherwise converts [Tlv] value to [T].
|
||||
*
|
||||
* @param tag [TlvTag] of a [Tlv] which value is to be returned.
|
||||
*
|
||||
* @return [Tlv] value converted to a nullable type [T].
|
||||
*
|
||||
* @throws [MissingTagException] if no [Tlv] is found by the Tag.
|
||||
*/
|
||||
inline fun <reified T> map(tag: TlvTag): T {
|
||||
val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value
|
||||
?: if (tag.valueType() == TlvValueType.BoolValue && T::class == Boolean::class) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
/**
|
||||
* Contains all possible value types that value for [TlvTag] can contain.
|
||||
*/
|
||||
enum class TlvValueType {
|
||||
HexString,
|
||||
Utf8String,
|
||||
|
|
@ -14,6 +17,9 @@ enum class TlvValueType {
|
|||
SigningMethod
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all TLV tags, with their code and descriptive name.
|
||||
*/
|
||||
enum class TlvTag(val code: Int) {
|
||||
Unknown(0x00),
|
||||
CardId(0x01),
|
||||
|
|
@ -97,13 +103,9 @@ enum class TlvTag(val code: Int) {
|
|||
TerminalPublicKey(0x5C),
|
||||
TerminalTransactionSignature(0x57);
|
||||
|
||||
fun hasNestedTlv(): Boolean {
|
||||
return when (this) {
|
||||
TlvTag.CardData -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [TlvValueType] associated with a [TlvTag]
|
||||
*/
|
||||
fun valueType(): TlvValueType {
|
||||
return when (this) {
|
||||
CardId, Pin, Batch -> TlvValueType.HexString
|
||||
|
|
|
|||
|
|
@ -13,21 +13,65 @@ object CryptoUtils {
|
|||
Security.addProvider(EdDSASecurityProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ByteArray of random bytes.
|
||||
* It is used, among other things, to generate helper private keys
|
||||
* (not the one for the blockchains, that one is generated on the card and does not leave the card).
|
||||
*
|
||||
* @param length length of the ByteArray that is to be generated.
|
||||
*/
|
||||
fun generateRandomBytes(length: Int): ByteArray {
|
||||
val bytes = ByteArray(length)
|
||||
SecureRandom().nextBytes(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to verify that the data was signed with a private key that corresponds
|
||||
* to the provided public key.
|
||||
*
|
||||
* @param publicKey Corresponding to the private key that was used to sing a message
|
||||
* @param message The data that was signed
|
||||
* @param signature Signed data
|
||||
* @param curve Elliptic curve used
|
||||
*
|
||||
* @return Result of a verification
|
||||
*/
|
||||
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
|
||||
EllipticCurve.Secp256k1 -> Sepc256k1.verify(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> Ed25519.verify(publicKey, message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that generates public key from a private key.
|
||||
*
|
||||
* @param privateKeyArray A private key from which a public key is generated
|
||||
* @param curve Elliptic curve used
|
||||
*
|
||||
* @return Public key [ByteArray]
|
||||
*/
|
||||
fun generatePublicKey(
|
||||
privateKeyArray: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1
|
||||
): ByteArray {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> Sepc256k1.generatePublicKey(privateKeyArray)
|
||||
EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to sign a ByteArray with an elliptic curve cryptography.
|
||||
*
|
||||
* @param privateKeyArray Key to sign data
|
||||
* @param curve Elliptic curve that is used to sign data
|
||||
*
|
||||
* @return Signed data
|
||||
*/
|
||||
fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> signSecp256k1(this, privateKeyArray)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,27 @@ import com.tangem.commands.ReadCommand
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
|
||||
/**
|
||||
* Events that [ScanTask] returns on completion of its commands.
|
||||
*/
|
||||
sealed class ScanEvent {
|
||||
|
||||
/**
|
||||
* Contains data from a Tangem card after successful completion of [ReadCommand].
|
||||
*/
|
||||
data class OnReadEvent(val card: Card) : ScanEvent()
|
||||
|
||||
/**
|
||||
* Shows whether the Tangem card was verified on completion of [CheckWalletCommand].
|
||||
*/
|
||||
data class OnVerifyEvent(val isGenuine: Boolean) : ScanEvent()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Task that allows to read Tangem card and verify its private key.
|
||||
*
|
||||
* It performs two commands, [ReadCommand] and [CheckWalletCommand], subsequently.
|
||||
*/
|
||||
internal class ScanTask : Task<ScanEvent>() {
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ import com.tangem.commands.CommandResponse
|
|||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
/**
|
||||
* Allows to perform a single command.
|
||||
*
|
||||
* @property command A command that will be performed.
|
||||
*/
|
||||
class SingleCommandTask<Event : CommandResponse>(
|
||||
private val command: CommandSerializer<Event>
|
||||
) : Task<Event>() {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
|
||||
/**
|
||||
* An error class that represent typical errors that may occur when performing Tangem SDK tasks.
|
||||
* Errors are propagated back to the caller in callbacks.
|
||||
*/
|
||||
sealed class TaskError(description: String? = null) : Exception(description) {
|
||||
class UnknownStatus(sw: Int) : TaskError("Unknown StatusWord: $sw")
|
||||
class MappingError : TaskError()
|
||||
|
|
@ -36,24 +40,54 @@ sealed class TaskError(description: String? = null) : Exception(description) {
|
|||
class HashSizeMustBeEqual() : TaskError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Events that are are sent in callbacks from [Task].
|
||||
*/
|
||||
sealed class TaskEvent<T> {
|
||||
|
||||
/**
|
||||
* A callback that is triggered by a Task.
|
||||
*/
|
||||
class Event<T>(val data: T) : TaskEvent<T>()
|
||||
|
||||
/**
|
||||
* A callback that is triggered when a [Task] is completed.
|
||||
*
|
||||
* @param error is null if it's a successful completion of a [Task]
|
||||
*/
|
||||
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to perform a group of commands interacting between the card and the application.
|
||||
* A task opens an NFC session, sends commands to the card and receives its responses,
|
||||
* repeats the commands if needed, and closes session after receiving the last answer.
|
||||
*/
|
||||
abstract class Task<T> {
|
||||
|
||||
var delegate: CardManagerDelegate? = null
|
||||
var reader: CardReader? = null
|
||||
|
||||
/**
|
||||
* This method should be called to run the [Task] and perform all its operations.
|
||||
*
|
||||
* @param cardEnvironment Relevant current version of a card environment
|
||||
* @param callback It will be triggered during the performance of the [Task]
|
||||
*/
|
||||
fun run(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
delegate?.onNfcSessionStarted()
|
||||
reader?.startNfcSession()
|
||||
reader?.openSession()
|
||||
Log.i(this::class.simpleName!!, "Nfc task is started")
|
||||
onRun(cardEnvironment, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called on [Task] completion, whether it was successful or with failure.
|
||||
*
|
||||
* @param withError True when there is an error
|
||||
* @param taskError The error to be shown by [CardManagerDelegate]
|
||||
*/
|
||||
protected fun completeNfcSession(withError: Boolean = false, taskError: TaskError? = null) {
|
||||
reader?.closeSession()
|
||||
if (withError) {
|
||||
|
|
@ -63,9 +97,16 @@ abstract class Task<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In this method the individual Tasks' logic should be implemented.
|
||||
*/
|
||||
protected abstract fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit)
|
||||
|
||||
/**
|
||||
* This method should be called by Tasks in their [onRun] method wherever
|
||||
* they need to communicate with the Tangem Card by launching commands.
|
||||
*/
|
||||
protected fun <T : CommandResponse> sendCommand(
|
||||
command: CommandSerializer<T>,
|
||||
cardEnvironment: CardEnvironment,
|
||||
|
|
@ -116,10 +157,10 @@ abstract class Task<T> {
|
|||
}
|
||||
is CompletionResult.Failure ->
|
||||
if (result.error is TaskError.TagLost) {
|
||||
delegate?.hideSecurityDelay()
|
||||
delegate?.onTagLost()
|
||||
} else if (result.error is TaskError.UserCancelledError) {
|
||||
callback(CompletionResult.Failure(TaskError.UserCancelledError()))
|
||||
reader?.readingActive = false
|
||||
reader?.closeSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ import com.tangem.tasks.TaskError
|
|||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.android.synthetic.main.nfc_bottom_sheet.*
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of [CardManagerDelegate].
|
||||
* If no customisation is required, this is the preferred way to use Tangem SDK.
|
||||
*/
|
||||
class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDelegate {
|
||||
|
||||
lateinit var activity: FragmentActivity
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.tangem.tangem_sdk_new.nfc.NfcManager
|
||||
|
||||
|
||||
/**
|
||||
* [LifecycleObserver] for [NfcManager], helps to coordinate NFC modes with Activity lifecycle.
|
||||
*/
|
||||
class NfcLifecycleObserver(private var nfcManager: NfcManager) : LifecycleObserver {
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import com.tangem.Log
|
||||
|
||||
/**
|
||||
* Helps use of NFC, leveraging Android NFC functionality.
|
||||
* Launches [NfcAdapter], manages it with [Activity] lifecycle,
|
||||
* enables and disables Nfc Reading Mode, receives NFC [Tag].
|
||||
*/
|
||||
class NfcManager : NfcAdapter.ReaderCallback {
|
||||
|
||||
val reader = NfcReader()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import com.tangem.common.apdu.CommandApdu
|
|||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* Provides NFC communication between an Android application and Tangem card.
|
||||
*/
|
||||
class NfcReader : CardReader {
|
||||
|
||||
override var readingActive = false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue