diff --git a/dependencies.gradle b/dependencies.gradle index 3c7aaf12d8..f61f047e5a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ kotlin : '1.3.72', - build_gradle: '3.6.3', + build_gradle: '4.0.0', ] diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/tangem-core/src/main/java/com/tangem/CardReader.kt b/tangem-core/src/main/java/com/tangem/CardReader.kt index 356b14fd34..deb1f33f14 100644 --- a/tangem-core/src/main/java/com/tangem/CardReader.kt +++ b/tangem-core/src/main/java/com/tangem/CardReader.kt @@ -16,6 +16,15 @@ interface CardReader { val tag: BroadcastChannel var scope: CoroutineScope? + /** + * Sends data to the card and receives the reply in an asynchronous way using coroutines. + * + * @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] + */ + suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult + /** * Sends data to the card and receives the reply. * diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index f3a77df81d..e46be34484 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -12,14 +12,8 @@ import com.tangem.crypto.EncryptionHelper import com.tangem.crypto.FastEncryptionHelper import com.tangem.crypto.StrongEncryptionHelper import com.tangem.crypto.pbkdf2Hash -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.launch +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* /** * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] @@ -61,11 +55,11 @@ enum class TagType { * If null, a default header and text body will be used. */ class CardSession( - val environment: SessionEnvironment, - private val reader: CardReader, - val viewDelegate: SessionViewDelegate, - private var cardId: String? = null, - private val initialMessage: Message? = null + val environment: SessionEnvironment, + private val reader: CardReader, + val viewDelegate: SessionViewDelegate, + private var cardId: String? = null, + private val initialMessage: Message? = null ) { var connectedTag: TagType? = null @@ -75,7 +69,9 @@ class CardSession( */ private var state = CardSessionState.Inactive - private val scope = CoroutineScope(Dispatchers.IO) + val scope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { _, ex -> + throw ex + } private val tag = this.javaClass.simpleName @@ -86,17 +82,14 @@ class CardSession( * @param callback will be triggered with a [CompletionResult] of a session. */ fun , R : CommandResponse> startWithRunnable( - runnable: T, callback: (result: CompletionResult) -> Unit) { + runnable: T, callback: (result: CompletionResult) -> Unit + ) { start(runnable.performPreflightRead) { session, error -> if (error != null) { callback(CompletionResult.Failure(error)) return@start } - if (runnable is ReadCommand) { - callback(CompletionResult.Success(environment.card as R)) - return@start - } runnable.run(this) { result -> when (result) { @@ -121,8 +114,10 @@ class CardSession( * Starts a card session and performs preflight [ReadCommand]. * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. */ - fun start(performPreflightRead: Boolean = true, - callback: (session: CardSession, error: TangemSdkError?) -> Unit) { + fun start( + performPreflightRead: Boolean = true, + callback: (session: CardSession, error: TangemSdkError?) -> Unit + ) { if (state != CardSessionState.Inactive) { callback(this, TangemSdkError.Busy()) @@ -133,21 +128,21 @@ class CardSession( scope.launch { reader.tag - .asFlow() - .collect { tagType -> - if (tagType == null && connectedTag != null) { - handleTagLost() - } else if (tagType != null) { - connectedTag = tagType - viewDelegate.onTagConnected() + .asFlow() + .collect { tagType -> + if (tagType == null && connectedTag != null) { + handleTagLost() + } else if (tagType != null) { + connectedTag = tagType + viewDelegate.onTagConnected() - if (tagType == TagType.Nfc && performPreflightRead) { - preflightCheck(callback) - } else { - callback(this@CardSession, null) - } + if (tagType == TagType.Nfc && performPreflightRead) { + preflightCheck(callback) + } else { + callback(this@CardSession, null) } } + } } reader.scope = scope reader.startSession() @@ -164,15 +159,8 @@ class CardSession( readCommand.run(this) { result -> when (result) { is CompletionResult.Failure -> { - tryHandleError(result.error) { handleErrorResult -> - when (handleErrorResult) { - is CompletionResult.Success -> preflightCheck(callback) - is CompletionResult.Failure -> { - stopWithError(result.error) - callback(this, result.error) - } - } - } + stopWithError(result.error) + callback(this, result.error) } is CompletionResult.Success -> { val receivedCardId = result.data.cardId @@ -230,65 +218,53 @@ class CardSession( fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { val subscription = reader.tag.openSubscription() - scope.launch { subscription.consumeAsFlow() - .filterNotNull() - .collect { - reader.transceiveApdu(apdu) { result -> - subscription.cancel() - callback(result) - } - } - } - } - - private fun tryHandleError( - error: TangemSdkError, callback: (result: CompletionResult) -> Unit) { - - when (error) { - is TangemSdkError.NeedEncryption -> { - Log.i(tag, "Establishing encryption") - when (environment.encryptionMode) { - EncryptionMode.NONE -> { - environment.encryptionKey = null - environment.encryptionMode = EncryptionMode.FAST - } - EncryptionMode.FAST -> { - environment.encryptionKey = null - environment.encryptionMode = EncryptionMode.STRONG - } - EncryptionMode.STRONG -> { - Log.e(tag, "Encryption doesn't work") - callback(CompletionResult.Failure(TangemSdkError.NeedEncryption())) - } + .filterNotNull() + .map { establishEncryption() } + .map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) } + .map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) } + .catch { error -> + if (error is TangemSdkError) callback( + CompletionResult.Failure( + error + ) + ) + } + .collect { result -> + subscription.cancel() + callback(result) } - return establishEncryption(callback) - } - else -> callback(CompletionResult.Failure(TangemSdkError.UnknownError())) } } - private fun establishEncryption(callback: (result: CompletionResult) -> Unit) { + private suspend fun establishEncryption(): CompletionResult { + if (environment.encryptionKey != null) return CompletionResult.Success(true) val encryptionHelper: EncryptionHelper = - if (environment.encryptionMode == EncryptionMode.STRONG) { - StrongEncryptionHelper() - } else { - FastEncryptionHelper() - } - val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) - openSesssionCommand.run(this) { result -> - when (result) { - is CompletionResult.Success -> { - val uid = result.data.uid - val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) - val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) - val sessionKey = (secret + protocolKey).calculateSha256() - environment.encryptionKey = sessionKey - callback(CompletionResult.Success(true)) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + when (environment.encryptionMode) { + EncryptionMode.NONE -> return CompletionResult.Success(true) + EncryptionMode.FAST -> FastEncryptionHelper() + EncryptionMode.STRONG -> StrongEncryptionHelper() } + val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) + val apdu = openSesssionCommand.serialize(environment) + + val response = reader.transceiveApdu(apdu) + when (response) { + is CompletionResult.Success -> { + val result = try { + openSesssionCommand.deserialize(environment, response.data) + } catch (error: TangemSdkError) { + return CompletionResult.Failure(error) + } + val uid = result.uid + val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + environment.encryptionKey = sessionKey + return CompletionResult.Success(true) + } + is CompletionResult.Failure -> return CompletionResult.Failure(response.error) } } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 5f8734f685..78b6d28715 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -42,7 +42,7 @@ data class SessionEnvironment( /** * All possible encryption modes. */ -enum class EncryptionMode(val code: Byte) { +enum class EncryptionMode(val code: Int) { NONE(0x0), FAST(0x1), STRONG(0x2) diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index c5ac313d34..a4d035d669 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -8,6 +8,8 @@ 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.commands.verifycard.VerifyCardCommand +import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -294,6 +296,11 @@ class TangemSdk( startSessionWithRunnable(PurgeWalletCommand(), cardId, initialMessage, callback) } + fun verify(cardId: String? = null, online: Boolean = true, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSessionWithRunnable(VerifyCardCommand(online), cardId, initialMessage, callback) + } + /** * Command available on SDK cards only * diff --git a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt index 4aabb3da6b..9e35b75edd 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt @@ -29,6 +29,7 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) { class DecodingFailedMissingTag : TangemSdkError(20005) class DecodingFailedTypeMismatch : TangemSdkError(20006) class DecodingFailed : TangemSdkError(20007) + class InvalidResponse : TangemSdkError(20008) /** * This error is returned when unknown [StatusWord] is received from a card. diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e3cbdf970a..09b6f08b94 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -90,15 +90,11 @@ class CheckWalletCommand( tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu( - Instruction.CheckWallet, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return CheckWalletResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 0b17642735..96343045a1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -2,13 +2,31 @@ package com.tangem.commands import com.tangem.* import com.tangem.common.CompletionResult -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.apdu.StatusWord -import com.tangem.common.apdu.toTangemSdkError +import com.tangem.common.apdu.* import com.tangem.common.extensions.toInt import com.tangem.common.tlv.TlvTag + +interface ApduSerializable { + /** + * Serializes data into an array of [com.tangem.common.tlv.Tlv], + * then creates [CommandApdu] with this data. + * @param environment [SessionEnvironment] of the current card + * @return command data converted to [CommandApdu] that allows to convert it to [ByteArray] + * that can be sent to a Tangem card + */ + fun serialize(environment: SessionEnvironment): CommandApdu + + /** + * Deserializes data received from a card and stored in [ResponseApdu] + * into an array of [com.tangem.common.tlv.Tlv]. Then maps it into a [CommandResponse]. + * @param environment [SessionEnvironment] of the current card. + * @param apdu received data. + * @return Card response converted to a [CommandResponse] of a type [T] + */ + fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T +} + /** * Basic interface for a parsed response from [Command]. */ @@ -17,28 +35,10 @@ interface CommandResponse /** * Basic class for Tangem card commands */ -abstract class Command : CardSessionRunnable { +abstract class Command : ApduSerializable, CardSessionRunnable { override val performPreflightRead: Boolean = true - /** - * Serializes data into an array of [com.tangem.common.tlv.Tlv], - * then creates [CommandApdu] with this data. - * @param environment [SessionEnvironment] of the current card - * @return command data converted to [CommandApdu] that allows to convert it to [ByteArray] - * that can be sent to a Tangem card - */ - abstract fun serialize(environment: SessionEnvironment): CommandApdu - - /** - * Deserializes data received from a card and stored in [ResponseApdu] - * into an array of [com.tangem.common.tlv.Tlv]. Then maps it into a [CommandResponse]. - * @param environment [SessionEnvironment] of the current card. - * @param apdu received data. - * @return Card response converted to a [CommandResponse] of a type [T] - */ - abstract fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") if (session.environment.handleErrors) { @@ -52,14 +52,18 @@ abstract class Command : CardSessionRunnable { } } - open fun performPreCheck(session: CardSession, - callback: (result: CompletionResult) -> Unit): Boolean { + open fun performPreCheck( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ): Boolean { return false } - open fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit): Boolean { + open fun performAfterCheck( + session: CardSession, + result: CompletionResult, + callback: (result: CompletionResult) -> Unit + ): Boolean { return false } @@ -80,25 +84,63 @@ abstract class Command : CardSessionRunnable { } } - private fun transceiveApdu(apdu: CommandApdu, session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.send(apdu) { result -> + private fun transceiveApdu( + apdu: CommandApdu, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}") + session.send(apdu) { result -> when (result) { is CompletionResult.Success -> { val responseApdu = result.data + when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged - -> callback(CompletionResult.Success(responseApdu)) + StatusWord.ProcessCompleted, StatusWord.Pin1Changed, + StatusWord.Pin2Changed, StatusWord.PinsChanged -> { + try { + val decryptedResponseApdu = + responseApdu.decrypt(session.environment.encryptionKey) + callback(CompletionResult.Success(decryptedResponseApdu)) + } catch (error: TangemSdkError) { + callback(CompletionResult.Failure(error)) + } + } StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. - val remainingTime = deserializeSecurityDelay(responseApdu, session.environment) + val remainingTime = + deserializeSecurityDelay(responseApdu) if (remainingTime != null) { session.viewDelegate.onSecurityDelay( - remainingTime, - session.environment.card?.pauseBeforePin2 ?: 0) + remainingTime, + session.environment.card?.pauseBeforePin2 ?: 0 + ) + } + Log.i( + this::class.simpleName!!, + "Nfc command ${this::class.simpleName!!} " + + "triggered security delay of $remainingTime milliseconds" + ) + transceiveApdu(apdu, session, callback) + } + StatusWord.NeedEncryption -> { + Log.i(this::class.simpleName!!, "Establishing encryption") + when (session.environment.encryptionMode) { + EncryptionMode.NONE -> { + session.environment.encryptionKey = null + session.environment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + session.environment.encryptionKey = null + session.environment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TangemSdkError.NeedEncryption())) + return@send + } } - Log.i(this::class.simpleName!!, "Nfc command ${this::class.simpleName!!} " + - "triggered security delay of $remainingTime milliseconds") transceiveApdu(apdu, session, callback) } else -> { @@ -126,7 +168,9 @@ abstract class Command : CardSessionRunnable { * * @return Remaining security delay in milliseconds. */ - private fun deserializeSecurityDelay(responseApdu: ResponseApdu, environment: SessionEnvironment): Int? { + private fun deserializeSecurityDelay( + responseApdu: ResponseApdu + ): Int? { val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index e0bf433c99..dba1b085d9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -80,14 +80,11 @@ class CreateWalletCommand : Command() { tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Pin2, environment.pin2) tlvBuilder.append(TlvTag.Cvc, environment.cvc) - return CommandApdu( - Instruction.CreateWallet, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) 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 891e28f3c1..1301703b9f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -10,8 +10,8 @@ import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag class OpenSessionResponse( - val sessionKeyB: ByteArray, - val uid: ByteArray + val sessionKeyB: ByteArray, + val uid: ByteArray ) : CommandResponse /** @@ -25,19 +25,21 @@ class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Pin2, environment.pin2) - return CommandApdu( - Instruction.PurgeWallet, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return PurgeWalletResponse( 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 49bf955851..3c6aa19bff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -390,13 +390,10 @@ class ReadCommand : Command() { */ tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) - return CommandApdu( - Instruction.Read, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.Read, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu, environment) + return CardDeserializer.deserialize(apdu) } } \ No newline at end of file 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 d01a3adbe8..b3015b0be3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -103,15 +103,11 @@ class ReadIssuerDataCommand( tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu( - Instruction.ReadIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadIssuerDataResponse( 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 82e148a05d..1f5c77b1da 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -152,16 +152,11 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu( - Instruction.ReadIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() - + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadIssuerExtraDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index b8ee2022d6..3f79960e93 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -68,15 +68,11 @@ class ReadUserDataCommand : Command() { builder.append(TlvTag.CardId, environment.card?.cardId) builder.append(TlvTag.Pin, environment.pin1) - return CommandApdu( - Instruction.ReadUserData, builder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadUserData, builder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadUserDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index fc926be116..816a0cd373 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -99,10 +99,7 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, environment.cvc) addTerminalSignature(environment, dataToSign, tlvBuilder) - return CommandApdu( - Instruction.Sign, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) } private fun flattenHashes(): ByteArray { @@ -133,8 +130,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2574a2f4a6..e9a23afac7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -114,15 +114,11 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu( - Instruction.WriteIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return WriteIssuerDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 84e0b50851..5fd85d01c3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -184,10 +184,7 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu( - Instruction.WriteIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } private fun getDataToWrite(): ByteArray = @@ -199,8 +196,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) ) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index fc0d0467ae..1cdac8447a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -77,15 +77,12 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, environment.pin2) - return CommandApdu( - Instruction.WriteUserData, builder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteUserData, builder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() + return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId)) } diff --git a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt index ff29df7075..ccf809cf65 100644 --- a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt @@ -1,6 +1,5 @@ package com.tangem.commands.common -import com.tangem.SessionEnvironment import com.tangem.TangemSdkError import com.tangem.commands.Card import com.tangem.commands.CardData @@ -11,9 +10,8 @@ import com.tangem.common.tlv.TlvTag class CardDeserializer() { companion object { - fun deserialize(apdu: ResponseApdu, environment: SessionEnvironment): Card { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + fun deserialize(apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) 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 index 425d33180d..5648b69def 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -47,15 +47,11 @@ class PersonalizeCommand( } override fun serialize(environment: SessionEnvironment): CommandApdu { - return CommandApdu( - Instruction.Personalize, - serializePersonalizationData(config), - encryptionKey = devPersonalizationKey - ) + return CommandApdu(Instruction.Personalize, serializePersonalizationData(config)) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu, environment) + return CardDeserializer.deserialize(apdu) } private fun serializePersonalizationData(config: CardConfig): ByteArray { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index e2d37df4bb..a804704598 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -15,42 +15,27 @@ import java.io.ByteArrayOutputStream */ class CommandApdu( - private val ins: Int, - private val tlvs: ByteArray, + val ins: Int, + private val tlvs: ByteArray, - private val le: Int = 0x00, + private val p1: Int, + private val p2: Int, - private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null, + private val le: Int = 0x00, - private val cla: Int = ISO_CLA) { + private val cla: Int = ISO_CLA +) { constructor( - instruction: Instruction, - tlvs: ByteArray, - encryptionMode: EncryptionMode = EncryptionMode.NONE, - encryptionKey: ByteArray? = null + instruction: Instruction, + tlvs: ByteArray ) : this( - instruction.code, - tlvs, - encryptionMode = encryptionMode, - encryptionKey = encryptionKey + instruction.code, + tlvs, + 0, + 0 ) - private val p1: Int - private val p2: Int - - init { - if (ins == Instruction.OpenSession.code) { - p1 = 0x00 - p2 = encryptionMode.code.toInt() - } else { - p1 = encryptionMode.code.toInt() - p2 = 0x00 - } - } - - /** * Request converted to a raw data */ @@ -62,7 +47,7 @@ class CommandApdu( private fun toBytes(): ByteArray { - val data = if (encryptionKey != null) tlvs.encrypt() else tlvs + val data = tlvs val byteStream = ByteArrayOutputStream() byteStream.write(cla) @@ -83,14 +68,21 @@ class CommandApdu( } - private fun ByteArray.encrypt(): ByteArray { - val crc: ByteArray = tlvs.calculateCrc16() - val stream = ByteArrayOutputStream() - stream.write(this.size.toByteArray(2)) - stream.write(crc) - stream.write(this) - return stream.toByteArray().encrypt(encryptionKey!!) + fun encrypt( + encryptionMode: EncryptionMode, + encryptionKey: ByteArray? + ): CommandApdu { + if (encryptionMode == EncryptionMode.NONE + || encryptionKey == null || p1 != EncryptionMode.NONE.code + ) { + return this } + val crc: ByteArray = tlvs.calculateCrc16() + val dataToEncrypt = tlvs.size.toByteArray(2) + crc + tlvs + val encryptedData = dataToEncrypt.encrypt(encryptionKey) + + return CommandApdu(ins, encryptedData, encryptionMode.code, p2, le, cla) + } companion object { const val ISO_CLA = 0x00 diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e7e8625384..bb947d074a 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,5 +1,6 @@ package com.tangem.common.apdu +import com.tangem.TangemSdkError import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv import com.tangem.crypto.decrypt @@ -27,40 +28,36 @@ class ResponseApdu(private val data: ByteArray) { * @param encryptionKey key to decrypt response. * (Encryption / decryption functionality is not implemented yet.) */ - fun getTlvData(encryptionKey: ByteArray? = null): List? { + fun getTlvData(): List? { return if (data.size <= 2) { null } else { - val responseData = data.copyOf(data.size - 2) - return if (encryptionKey != null) { - if (data.size >= 18) { - val decryptedData = decrypt(responseData, encryptionKey) - Tlv.deserialize(decryptedData) - } else { - null - } - } else { - Tlv.deserialize(responseData) - } + Tlv.deserialize(data.copyOf(data.size - 2)) } } - private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + fun decrypt(encryptionKey: ByteArray?): ResponseApdu { + if (encryptionKey == null) return this + + if (data.size < 18) throw TangemSdkError.InvalidResponse() + + val responseData = data.copyOf(data.size - 2) + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) val inputStream = ByteArrayInputStream(decryptedData) val baLength = ByteArray(2) inputStream.read(baLength) val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) - if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + if (length > decryptedData.size - 4) throw TangemSdkError.InvalidResponse() val baCRC = ByteArray(2) inputStream.read(baCRC) val answerData = ByteArray(length) inputStream.read(answerData) val crc: ByteArray = answerData.calculateCrc16() - if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + if (!baCRC.contentEquals(crc)) throw TangemSdkError.InvalidResponse() - return answerData + return ResponseApdu(answerData + data[data.size - 2] + data[data.size - 1]) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 1b203b28fe..8b9d8f2452 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -59,7 +59,7 @@ internal class ScanTask : CardSessionRunnable { when (result) { is CompletionResult.Success -> { try { - val card = CardDeserializer.deserialize(result.data, session.environment) + val card = CardDeserializer.deserialize(result.data) callback(CompletionResult.Success(card)) } catch (error: TangemSdkError) { callback(CompletionResult.Failure(error)) diff --git a/tangem-sdk-android-config/.circleci/config.yml b/tangem-sdk-android-config/.circleci/config.yml old mode 100755 new mode 100644 diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt index 09c84716e4..7ef49ed0e0 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt @@ -52,5 +52,6 @@ fun TangemSdkError.localizedDescription(): Int { is TangemSdkError.WrongCardNumber -> R.string.error_wrong_card_number is TangemSdkError.WrongCardType -> R.string.error_wrong_card_type is TangemSdkError.CardError -> R.string.error_card_error + is TangemSdkError.InvalidResponse -> R.string.error_invalid_response } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt index 860c8d5f55..6c73836773 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt @@ -84,11 +84,11 @@ class NfcManager : NfcAdapter.ReaderCallback, ReadingActiveListener { nfcAdapter = null } - fun enableReaderMode() { + private fun enableReaderMode() { nfcAdapter?.enableReaderMode(activity, this, READER_FLAGS, Bundle()) } - fun disableReaderMode() { + private fun disableReaderMode() { nfcAdapter?.disableReaderMode(activity) } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 26ec450462..4c8834f807 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -12,6 +12,8 @@ import com.tangem.common.extensions.toHexString import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume data class NfcTag(val type: TagType, val isoDep: IsoDep?, val nfcV: NfcV? = null) @@ -61,11 +63,16 @@ class NfcReader : CardReader { //TODO: send user cancelled if (cancelled) } - override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { - val data = apdu.apduData + override suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult = + suspendCancellableCoroutine { continuation -> + transceiveApdu(apdu) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { val rawResponse: ByteArray? = try { - transcieveAndLog(data, callback) + transcieveAndLog(apdu.apduData) } catch (exception: TagLostException) { callback.invoke(CompletionResult.Failure(TangemSdkError.TagLost())) nfcTag = null @@ -82,7 +89,7 @@ class NfcReader : CardReader { rawResponse?.let { callback.invoke(CompletionResult.Success(ResponseApdu(it))) } } - private fun transcieveAndLog(data: ByteArray, callback: (response: CompletionResult) -> Unit): ByteArray? { + private fun transcieveAndLog(data: ByteArray): ByteArray? { Log.i(this::class.simpleName!!, "Sending data to the card, size is ${data.size}") Log.v(this::class.simpleName!!, "Raw data that is to be sent to the card: ${data.toHexString()}") val rawResponse = nfcTag?.isoDep?.transceive(data) diff --git a/tangem-sdk/src/main/res/values/strings_errors.xml b/tangem-sdk/src/main/res/values/strings_errors.xml index b41a0e5d1f..393a84e554 100644 --- a/tangem-sdk/src/main/res/values/strings_errors.xml +++ b/tangem-sdk/src/main/res/values/strings_errors.xml @@ -29,4 +29,5 @@ You tapped a different card. Please match the in-app Card ID to the your physical Card ID to continue this process. This card is configured for a different app. Please see the information on your card and download the related app. Your card is missing essential data + Invalid Response \ No newline at end of file