diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupCertificateProvider.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupCertificateProvider.kt new file mode 100644 index 00000000..52e1ffad --- /dev/null +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupCertificateProvider.kt @@ -0,0 +1,60 @@ +package com.tangem.operations.backup + +import com.tangem.common.CompletionResult +import com.tangem.common.core.CompletionCallback +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.services.Result +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.sign +import com.tangem.operations.attestation.OnlineCardVerifier + +class BackupCertificateProvider { + + private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier() + + suspend fun getCertificate( + cardId: String, + cardPublicKey: ByteArray, + developmentMode: Boolean, + callback: CompletionCallback, + ) { + if (developmentMode) { + val issuerPrivateKey = + "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() + val issuerSignature = cardPublicKey.sign(issuerPrivateKey) + callback(CompletionResult.Success(generateCertificate(cardPublicKey, issuerSignature))) + return + } + + when ( + val result = + onlineCardVerifier.getCardData(cardId, cardPublicKey) + ) { + is Result.Success -> { + val signature = result.data.issuerSignature.guard { + callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed())) + return + } + callback( + CompletionResult.Success( + generateCertificate(cardPublicKey, signature.hexToBytes()), + ), + ) + } + + is Result.Failure -> + callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed())) + } + } + + private fun generateCertificate(cardPublicKey: ByteArray, issuerSignature: ByteArray): ByteArray { + return TlvBuilder().run { + append(TlvTag.CardPublicKey, cardPublicKey) + append(TlvTag.IssuerDataSignature, issuerSignature) + serialize() + } + } +} diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupService.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupService.kt index 2ebcaaff..345ffe44 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupService.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/BackupService.kt @@ -1,6 +1,7 @@ package com.tangem.operations.backup import com.squareup.moshi.JsonClass +import com.tangem.Log import com.tangem.Message import com.tangem.TangemSdk import com.tangem.common.CardIdFormatter @@ -16,11 +17,17 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.guard import com.tangem.common.json.MoshiJsonConverter import com.tangem.common.services.secure.SecureStorage -import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvTag import com.tangem.operations.CommandResponse +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import java.io.PrintWriter +import java.io.StringWriter @Suppress("LargeClass") class BackupService( @@ -65,7 +72,16 @@ class BackupService( */ var skipCompatibilityChecks: Boolean = false + private val backupScope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { _, throwable -> + val sw = StringWriter() + throwable.printStackTrace(PrintWriter(sw)) + val exceptionAsString: String = sw.toString() + Log.error { exceptionAsString } + throw throwable + } + private val handleErrors = sdk.config.handleErrors + private val backupCertificateProvider = BackupCertificateProvider() init { updateState() @@ -85,7 +101,22 @@ class BackupService( callback(CompletionResult.Failure(TangemSdkError.TooMuchBackupCards())) return } - readBackupCard(primaryCard, callback) + + if (primaryCard.certificate != null) { + readBackupCard(primaryCard, callback) + return + } + backupScope.launch { + fetchCertificate( + cardId = primaryCard.cardId, + cardPublicKey = primaryCard.cardPublicKey, + onFailed = { callback(CompletionResult.Failure(it)) }, + ) { + val updatedPrimaryCard = primaryCard.copy(certificate = it) + repo.data = repo.data.copy(primaryCard = updatedPrimaryCard) + readBackupCard(updatedPrimaryCard, callback) + } + } } fun setAccessCode(code: String): CompletionResult { @@ -159,7 +190,7 @@ class BackupService( } ?: Message(header = stringsLocator.getString(StringsLocator.ID.BACKUP_PREPARE_PRIMARY_CARD_MESSAGE)) sdk.startSessionWithRunnable( - runnable = StartPrimaryCardLinkingTask(), + runnable = StartPrimaryCardLinkingCommand(), cardId = cardId, initialMessage = message, iconScanRes = iconScanRes, @@ -175,6 +206,28 @@ class BackupService( } } + private suspend fun fetchCertificate( + cardId: String, + cardPublicKey: ByteArray, + onFailed: (TangemSdkError) -> Unit, + onLoaded: (ByteArray) -> Unit, + ) { + backupCertificateProvider.getCertificate( + cardId = cardId, + cardPublicKey = cardPublicKey, + developmentMode = false, + ) { + val certificate = when (it) { + is CompletionResult.Success -> it.data + is CompletionResult.Failure -> { + onFailed(TangemSdkError.IssuerSignatureLoadingFailed()) + return@getCertificate + } + } + onLoaded(certificate) + } + } + private fun handleCompletion(result: CompletionResult, callback: CompletionCallback) { when (result) { is CompletionResult.Success -> { @@ -205,13 +258,21 @@ class BackupService( return currentState } - private fun addBackupCard(backupCard: BackupCard) { - val updatedList = repo.data.backupCards - .filter { it.cardId != backupCard.cardId } - .plus(backupCard) - - repo.data = repo.data.copy(backupCards = updatedList) - updateState() + private fun addBackupCard(backupCard: BackupCard, callback: CompletionCallback) { + backupScope.launch { + fetchCertificate( + backupCard.cardId, + backupCard.cardPublicKey, + onFailed = { callback(CompletionResult.Failure(it)) }, + ) { cert -> + val updatedBackupCard = backupCard.copy(certificate = cert) + val updatedList = repo.data.backupCards + .filter { it.cardId != updatedBackupCard.cardId } + .plus(updatedBackupCard) + repo.data = repo.data.copy(backupCards = updatedList) + updateState() + } + } } private fun readBackupCard(primaryCard: PrimaryCard, callback: CompletionCallback) { @@ -227,7 +288,7 @@ class BackupService( ) { result -> when (result) { is CompletionResult.Success -> { - addBackupCard(result.data) + addBackupCard(result.data, callback) callback(CompletionResult.Success(Unit)) } @@ -370,6 +431,7 @@ class BackupService( private fun onBackupCompleted() { repo.reset() + backupScope.cancel() } sealed class State { @@ -402,11 +464,10 @@ class RawPrimaryCard( @Suppress("LongParameterList") @JsonClass(generateAdapter = true) -class PrimaryCard( +data class PrimaryCard( val cardId: String, - override val cardPublicKey: ByteArray, + val cardPublicKey: ByteArray, val linkingKey: ByteArray, - override val issuerSignature: ByteArray, // For compatibility check with backup card val existingWalletsCount: Int, val isHDWalletAllowed: Boolean, @@ -415,12 +476,12 @@ class PrimaryCard( val batchId: String?, // for compatibility with interrupted backups val firmwareVersion: FirmwareVersion?, // for compatibility with interrupted backups val isKeysImportAllowed: Boolean?, // for compatibility with interrupted backups -) : CertificateProvider { - constructor(rawPrimaryCard: RawPrimaryCard, issuerSignature: ByteArray) : this( + val certificate: ByteArray?, +) : CommandResponse { + constructor(rawPrimaryCard: RawPrimaryCard, certificate: ByteArray) : this( cardId = rawPrimaryCard.cardId, cardPublicKey = rawPrimaryCard.cardPublicKey, linkingKey = rawPrimaryCard.linkingKey, - issuerSignature = issuerSignature, existingWalletsCount = rawPrimaryCard.existingWalletsCount, isHDWalletAllowed = rawPrimaryCard.isHDWalletAllowed, issuer = rawPrimaryCard.issuer, @@ -428,6 +489,7 @@ class PrimaryCard( batchId = rawPrimaryCard.batchId, firmwareVersion = rawPrimaryCard.firmwareVersion, isKeysImportAllowed = rawPrimaryCard.isKeysImportAllowed, + certificate = certificate, ) } @@ -440,19 +502,19 @@ class RawBackupCard( ) : CommandResponse @JsonClass(generateAdapter = true) -class BackupCard( +data class BackupCard( val cardId: String, - override val cardPublicKey: ByteArray, + val cardPublicKey: ByteArray, val linkingKey: ByteArray, val attestSignature: ByteArray, - override val issuerSignature: ByteArray, -) : CertificateProvider { - constructor(rawBackupCard: RawBackupCard, issuerSignature: ByteArray) : this( + val certificate: ByteArray?, +) { + constructor(rawBackupCard: RawBackupCard, certificate: ByteArray) : this( cardId = rawBackupCard.cardId, cardPublicKey = rawBackupCard.cardPublicKey, linkingKey = rawBackupCard.linkingKey, attestSignature = rawBackupCard.attestSignature, - issuerSignature = issuerSignature, + certificate = certificate, ) } @@ -516,17 +578,3 @@ class BackupRepo( enum class StorageKey { BackupData } } - -interface CertificateProvider { - val cardPublicKey: ByteArray - val issuerSignature: ByteArray -} - -@Throws(TangemSdkError::class) -fun CertificateProvider.generateCertificate(): ByteArray { - return TlvBuilder().run { - append(TlvTag.CardPublicKey, cardPublicKey) - append(TlvTag.IssuerDataSignature, issuerSignature) - serialize() - } -} diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkBackupCardsCommand.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkBackupCardsCommand.kt index 55cb4986..3817106c 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkBackupCardsCommand.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkBackupCardsCommand.kt @@ -103,7 +103,7 @@ class LinkBackupCardsCommand( TlvBuilder().apply { append(TlvTag.FileIndex, index) append(TlvTag.BackupCardLinkingKey, card.linkingKey) - append(TlvTag.Certificate, card.generateCertificate()) + append(TlvTag.Certificate, card.certificate) append(TlvTag.CardSignature, card.attestSignature) }.serialize() } diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkPrimaryCardCommand.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkPrimaryCardCommand.kt index 3caaa39c..6fc4fb36 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkPrimaryCardCommand.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/LinkPrimaryCardCommand.kt @@ -108,7 +108,7 @@ class LinkPrimaryCardCommand( tlvBuilder.append(TlvTag.Pin, environment.accessCode.value) tlvBuilder.append(TlvTag.Pin2, environment.passcode.value) tlvBuilder.append(TlvTag.PrimaryCardLinkingKey, primaryCard.linkingKey) - tlvBuilder.append(TlvTag.Certificate, primaryCard.generateCertificate()) + tlvBuilder.append(TlvTag.Certificate, primaryCard.certificate) tlvBuilder.append(TlvTag.BackupAttestSignature, attestSignature) tlvBuilder.append(TlvTag.NewPin, accessCode) tlvBuilder.append(TlvTag.NewPin2, passcode) diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingCommand.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingCommand.kt index 428c5a3a..3c94c77e 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingCommand.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingCommand.kt @@ -14,7 +14,7 @@ import com.tangem.operations.Command /** */ -class StartPrimaryCardLinkingCommand : Command() { +class StartPrimaryCardLinkingCommand : Command() { override val allowsRequestAccessCodeFromRepository: Boolean get() = false @@ -45,7 +45,7 @@ class StartPrimaryCardLinkingCommand : Command() { return CommandApdu(Instruction.StartPrimaryCardLinking, tlvBuilder.serialize()) } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): RawPrimaryCard { + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PrimaryCard { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() @@ -55,7 +55,7 @@ class StartPrimaryCardLinkingCommand : Command() { val card = environment.card ?: throw TangemSdkError.MissingPreflightRead() - return RawPrimaryCard( + return PrimaryCard( cardId = decoder.decode(TlvTag.CardId), cardPublicKey = cardPublicKey, linkingKey = decoder.decode(TlvTag.PrimaryCardLinkingKey), @@ -66,6 +66,7 @@ class StartPrimaryCardLinkingCommand : Command() { batchId = card.batchId, firmwareVersion = card.firmwareVersion, isKeysImportAllowed = card.settings.isKeysImportAllowed, + certificate = null, ) } } diff --git a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingTask.kt b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingTask.kt index 98ff9587..dff8f461 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingTask.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/operations/backup/StartPrimaryCardLinkingTask.kt @@ -1,69 +1,26 @@ package com.tangem.operations.backup import com.tangem.common.CompletionResult -import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.services.Result -import com.tangem.crypto.sign -import com.tangem.operations.attestation.OnlineCardVerifier -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class StartPrimaryCardLinkingTask : CardSessionRunnable { override val allowsRequestAccessCodeFromRepository: Boolean get() = false - private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier() - override fun run(session: CardSession, callback: CompletionCallback) { StartPrimaryCardLinkingCommand() .run(session) { result -> when (result) { is CompletionResult.Success -> { - loadIssuerSignature(result.data, session, callback) + callback(CompletionResult.Success(result.data)) + // loadIssuerSignature(result.data, session, callback) } is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } - - private fun loadIssuerSignature( - rawCard: RawPrimaryCard, - session: CardSession, - callback: CompletionCallback, - ) { - if (session.environment.card?.firmwareVersion?.type == FirmwareVersion.FirmwareType.Sdk) { - val issuerPrivateKey = - "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() - val issuerSignature = rawCard.cardPublicKey.sign(issuerPrivateKey) - callback(CompletionResult.Success(PrimaryCard(rawCard, issuerSignature))) - return - } - - session.scope.launch(Dispatchers.IO) { - when ( - val result = - onlineCardVerifier.getCardData(rawCard.cardId, rawCard.cardPublicKey) - ) { - is Result.Success -> { - val signature = result.data.issuerSignature.guard { - callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed())) - return@launch - } - val primaryCard = PrimaryCard(rawCard, issuerSignature = signature.hexToBytes()) - callback(CompletionResult.Success(primaryCard)) - } - - is Result.Failure -> - callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed())) - } - } - } }