diff --git a/app/src/main/assets/features_dev.json b/app/src/main/assets/features_dev.json index d6feffa030..e416a046a7 100644 --- a/app/src/main/assets/features_dev.json +++ b/app/src/main/assets/features_dev.json @@ -13,6 +13,6 @@ }, { "name": "isCreatingTwinCardsAllowed", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/main/assets/features_prod.json b/app/src/main/assets/features_prod.json index d6feffa030..e416a046a7 100644 --- a/app/src/main/assets/features_prod.json +++ b/app/src/main/assets/features_prod.json @@ -13,6 +13,6 @@ }, { "name": "isCreatingTwinCardsAllowed", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 9af980f7d8..f106f2a930 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -7,20 +7,16 @@ import com.tangem.TangemSdkError import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.commands.CommandResponse +import com.tangem.commands.ReadIssuerDataCommand import com.tangem.commands.common.card.Card import com.tangem.commands.common.card.CardStatus import com.tangem.commands.common.card.masks.Product -import com.tangem.commands.file.ReadFileDataCommand import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult -import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.TlvDecoder -import com.tangem.common.tlv.TlvTag import com.tangem.tap.domain.TapSdkError -import com.tangem.tap.domain.twins.TwinsHelper +import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tasks.ScanTask data class ScanNoteResponse( @@ -75,7 +71,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { callback(CompletionResult.Success(ScanNoteResponse( - walletManager, card, verifyResult.data + walletManager, card, verifyResult.data, publicKey ))) } is CompletionResult.Failure -> { @@ -89,20 +85,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable) -> Unit ) { - ReadFileDataCommand().run(session) { filesResult -> - when (filesResult) { + ReadIssuerDataCommand().run(session) { readDataResult -> + when (readDataResult) { is CompletionResult.Success -> { - val decoder = Tlv.deserialize(filesResult.data.fileData)?.let { TlvDecoder(it) } - val name = decoder?.decodeOptional(TlvTag.FileName) - if (name != null && name == TwinsHelper.TWIN_FILE_NAME) { - val publicKey = decoder.decodeOptional(TlvTag.FileData)?.toHexString() + val verified = TwinCardsManager.verifyTwinPublicKey( + readDataResult.data.issuerData, card.walletPublicKey + ) + if (verified) { + val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) val walletManager = try { - WalletManagerFactory.makeMultisigWalletManager(card, publicKey!!.hexToBytes()) + WalletManagerFactory.makeMultisigWalletManager(card, twinPublicKey) } catch (exception: Exception) { callback(CompletionResult.Success(ScanNoteResponse(null, card))) return@run } - verifyCard(walletManager, card, publicKey, session, callback) + verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback) return@run } else { callback(CompletionResult.Success(ScanNoteResponse(null, card))) @@ -111,7 +108,6 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable callback(CompletionResult.Success(ScanNoteResponse(null, card))) } - } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 4b008408e6..7296c72a11 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -7,7 +7,6 @@ import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus import com.tangem.common.CompletionResult import com.tangem.tasks.CreateWalletTask -import com.tangem.tasks.file.DeleteFilesTask class CreateFirstTwinWalletTask : CardSessionRunnable { override val requiresPin2 = false @@ -18,24 +17,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { when (response) { is CompletionResult.Success -> { session.environment.card = session.environment.card?.copy(status = CardStatus.Empty) - finishTask(session, callback) + CreateWalletTask().run(session) { callback(it) } } is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error)) } } } else { - finishTask(session, callback) - } - } - - private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { - DeleteFilesTask().run(session) { deleteResponse -> - when (deleteResponse) { - is CompletionResult.Success -> - CreateWalletTask().run(session) { callback(it) } - is CompletionResult.Failure -> - callback(CompletionResult.Failure(deleteResponse.error)) - } + CreateWalletTask().run(session) { callback(it) } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 4885e78f38..c3fde9061d 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -5,10 +5,9 @@ import com.tangem.CardSessionRunnable import com.tangem.commands.CreateWalletResponse import com.tangem.commands.PurgeWalletCommand import com.tangem.commands.common.card.CardStatus -import com.tangem.commands.file.WriteFileDataCommand import com.tangem.common.CompletionResult +import com.tangem.common.extensions.hexToBytes import com.tangem.tasks.CreateWalletTask -import com.tangem.tasks.file.DeleteFilesTask class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessionRunnable { override val requiresPin2 = true @@ -31,20 +30,23 @@ class CreateSecondTwinWalletTask(private val firstPublicKey: String) : CardSessi } private fun finishTask(session: CardSession, callback: (result: CompletionResult) -> Unit) { - DeleteFilesTask().run(session) { deleteResponse -> - when (deleteResponse) { + CreateWalletTask().run(session) { result -> + when (result) { is CompletionResult.Success -> { - val file = TwinCardsManager.createFileWithPublicKey(firstPublicKey) - WriteFileDataCommand(file).run(session) { result -> - when (result) { - is CompletionResult.Success -> CreateWalletTask().run(session, callback) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + session.environment.card = + session.environment.card?.copy(status = CardStatus.Loaded) + WriteProtectedIssuerDataTask( + firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys + ).run(session) { writeResult -> + when (writeResult) { + is CompletionResult.Success -> callback(result) + is CompletionResult.Failure -> + callback(CompletionResult.Failure(writeResult.error)) } } } - is CompletionResult.Failure -> - callback(CompletionResult.Failure(deleteResponse.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index f33030c138..f63f2b1ea3 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -1,17 +1,14 @@ package com.tangem.tap.domain.twins +import com.tangem.KeyPair import com.tangem.Message import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.commands.file.FileData -import com.tangem.commands.file.WriteFileDataCommand import com.tangem.common.CompletionResult import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.TlvEncoder -import com.tangem.common.tlv.TlvTag -import com.tangem.common.tlv.serialize +import com.tangem.crypto.CryptoUtils import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.tangemSdkManager @@ -54,7 +51,7 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) { suspend fun complete(message: Message): Result { val response = tangemSdkManager.runTaskAsync( - WriteFileDataCommand(createFileWithPublicKey(secondCardPublicKey!!)), + WriteProtectedIssuerDataTask(secondCardPublicKey!!.hexToBytes(), issuerKeys), currentCardId, message ) return when (response) { @@ -72,11 +69,19 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) { } companion object { - fun createFileWithPublicKey(publicKey: String): FileData { - val nameTlv = TlvEncoder().encode(TlvTag.FileName, "TwinPublicKey") - val pubKey = TlvEncoder().encode(TlvTag.FileData, publicKey.hexToBytes()) - val tlvs = listOf(nameTlv, pubKey).serialize() - return FileData.DataProtectedByPasscode(tlvs) + val issuerKeys = KeyPair( + privateKey = "F9F4C50636C9E6FC65F92655BD5C21C85A5F6A34DCD0F1E75FCEA1980FE242F5".hexToBytes(), + publicKey = ("048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540F" + + "AC49AF25129F6A538A28AD6341358E3C4F9963064F" + + "7E365372A651D374E5C23CDD37FD099BF2").hexToBytes() + ) + + fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean { + if (issuerData.size < 65) return false + val publicKey = issuerData.sliceArray(0 until 65) + val signedKey = issuerData.sliceArray(65 until issuerData.size) + return (cardWalletPublicKey != null && + CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt new file mode 100644 index 0000000000..30d3b688dd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.domain.twins + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.KeyPair +import com.tangem.TangemSdkError +import com.tangem.commands.* +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.guard +import com.tangem.common.files.FileHashHelper + +class WriteProtectedIssuerDataTask( + private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair +) : CardSessionRunnable { + override val requiresPin2 = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val hashes = arrayOf(twinPublicKey.calculateSha256()) + SignCommand(hashes).run(session) { signResult -> + when (signResult) { + is CompletionResult.Success -> { + ReadIssuerDataCommand().run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> { + writeIssuerData( + twinPublicKey, issuerKeys, signResult.data.signature, + readResult.data, session, callback + ) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(signResult.error)) + } + } + } + + private fun writeIssuerData( + twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray, + readResponse: ReadIssuerDataResponse, + session: CardSession, callback: (result: CompletionResult + ) -> Unit) { + val cardId = session.environment.card?.cardId.guard { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + val counter = (readResponse.issuerDataCounter ?: 0) + 1 + val data = twinPublicKey + cardSignature + val signedByIssuer = FileHashHelper.prepareHashes( + cardId, data, counter, issuerKeys.privateKey + ) + WriteIssuerDataCommand( + data, signedByIssuer.finalizingSignature!!, + counter, issuerKeys.publicKey + ).run(session, callback) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_twin_cards.xml b/app/src/main/res/layout/fragment_details_twin_cards.xml index 02b5a83991..7549f32998 100644 --- a/app/src/main/res/layout/fragment_details_twin_cards.xml +++ b/app/src/main/res/layout/fragment_details_twin_cards.xml @@ -38,12 +38,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:gravity="center" + android:gravity="start" android:text="@string/details_twins_recreate_step_format" android:textColor="@color/colorSecondary" android:textSize="20sp" + android:textStyle="bold" app:layout_constraintEnd_toEndOf="@+id/v_step_1" - app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@id/v_step_1" app:layout_constraintTop_toTopOf="parent" />