Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: bulletproofing crypto box to cc migration (WPB-14250) (🍒4.6) #3136

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.wire.crypto.CoreCrypto
import com.wire.crypto.CoreCryptoException
import com.wire.crypto.client.toByteArray
import com.wire.kalium.cryptography.exceptions.ProteusException
import com.wire.kalium.cryptography.exceptions.ProteusStorageMigrationException
import io.ktor.util.decodeBase64Bytes
import io.ktor.util.encodeBase64
import java.io.File
Expand All @@ -46,6 +47,7 @@ class ProteusClientCoreCryptoImpl private constructor(
override suspend fun remoteFingerPrint(sessionId: CryptoSessionId): ByteArray = wrapException {
coreCrypto.proteusFingerprintRemote(sessionId.value).toByteArray()
}

override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray = wrapException {
coreCrypto.proteusFingerprintPrekeybundle(preKey.encodedData.decodeBase64Bytes()).toByteArray()
}
Expand Down Expand Up @@ -157,31 +159,43 @@ class ProteusClientCoreCryptoImpl private constructor(
acc && File(rootDir).resolve(file).deleteRecursively()
}

private suspend fun migrateFromCryptoBoxIfNecessary(coreCrypto: CoreCrypto, rootDir: String) {
if (cryptoBoxFilesExists(File(rootDir))) {
kaliumLogger.i("migrating from crypto box at: $rootDir")
coreCrypto.proteusCryptoboxMigrate(rootDir)
kaliumLogger.i("migration successful")

if (deleteCryptoBoxFiles(rootDir)) {
kaliumLogger.i("successfully deleted old crypto box files")
} else {
kaliumLogger.e("Failed to deleted old crypto box files at $rootDir")
}
}
}

@Suppress("TooGenericExceptionCaught")
@Suppress("TooGenericExceptionCaught", "ThrowsCount")
suspend operator fun invoke(coreCrypto: CoreCrypto, rootDir: String): ProteusClientCoreCryptoImpl {
try {
migrateFromCryptoBoxIfNecessary(coreCrypto, rootDir)
coreCrypto.proteusInit()
return ProteusClientCoreCryptoImpl(coreCrypto)
} catch (exception: ProteusStorageMigrationException) {
throw exception
} catch (e: CoreCryptoException) {
throw ProteusException(e.message, ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), e.cause)
throw ProteusException(
message = e.message,
code = ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()),
cause = e.cause
)
} catch (e: Exception) {
throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause)
}
}

@Suppress("TooGenericExceptionCaught")
private suspend fun migrateFromCryptoBoxIfNecessary(coreCrypto: CoreCrypto, rootDir: String) {
try {
if (cryptoBoxFilesExists(File(rootDir))) {
kaliumLogger.i("migrating from crypto box at: $rootDir")
coreCrypto.proteusCryptoboxMigrate(rootDir)
kaliumLogger.i("migration successful")

if (deleteCryptoBoxFiles(rootDir)) {
kaliumLogger.i("successfully deleted old crypto box files")
} else {
kaliumLogger.e("Failed to deleted old crypto box files at $rootDir")
}
}
} catch (exception: Exception) {
kaliumLogger.e("Failed to migrate from crypto box to core crypto, exception: $exception")
throw ProteusStorageMigrationException("Failed to migrate from crypto box at $rootDir", exception)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

package com.wire.kalium.cryptography.exceptions

class ProteusException(message: String?, val code: Code, cause: Throwable? = null) : Exception(message, cause) {
open class ProteusException(message: String?, val code: Code, cause: Throwable? = null) : Exception(message, cause) {

constructor(message: String?, code: Int, cause: Throwable? = null) : this(message, fromNativeCode(code), cause)

Expand Down Expand Up @@ -191,3 +191,6 @@ class ProteusException(message: String?, val code: Code, cause: Throwable? = nul
}
}
}

class ProteusStorageMigrationException(override val message: String, val rootCause: Throwable? = null) :
ProteusException(message, Int.MIN_VALUE, null)
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

package com.wire.kalium.logic.data.client

import com.wire.kalium.cryptography.CoreCryptoCentral
import com.wire.kalium.cryptography.ProteusClient
import com.wire.kalium.cryptography.coreCryptoCentral
import com.wire.kalium.cryptography.cryptoboxProteusClient
import com.wire.kalium.cryptography.exceptions.ProteusStorageMigrationException
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.obfuscateId
import com.wire.kalium.logic.CoreFailure
Expand Down Expand Up @@ -58,20 +60,15 @@ class ProteusClientProviderImpl(
private val userId: UserId,
private val passphraseStorage: PassphraseStorage,
private val kaliumConfigs: KaliumConfigs,
private val dispatcher: KaliumDispatcher = KaliumDispatcherImpl
private val dispatcher: KaliumDispatcher = KaliumDispatcherImpl,
private val proteusMigrationRecoveryHandler: ProteusMigrationRecoveryHandler
) : ProteusClientProvider {

private var _proteusClient: ProteusClient? = null
private val mutex = Mutex()

override suspend fun clearLocalFiles() {
mutex.withLock {
withContext(dispatcher.io) {
_proteusClient?.close()
_proteusClient = null
FileUtil.deleteDirectory(rootProteusPath)
}
}
mutex.withLock { removeLocalFiles() }
}

override suspend fun getOrCreate(): ProteusClient {
Expand Down Expand Up @@ -119,7 +116,7 @@ class ProteusClientProviderImpl(
kaliumLogger.logStructuredJson(KaliumLogLevel.ERROR, TAG, logMap)
throw e
}
central.proteusClient()
getCentralProteusClientOrError(central)
} else {
cryptoboxProteusClient(
rootDir = rootProteusPath,
Expand All @@ -129,6 +126,34 @@ class ProteusClientProviderImpl(
}
}

private suspend fun getCentralProteusClientOrError(central: CoreCryptoCentral): ProteusClient {
return try {
central.proteusClient()
} catch (exception: ProteusStorageMigrationException) {
proteusMigrationRecoveryHandler.clearClientData { removeLocalFiles() }
val logMap = mapOf(
"userId" to userId.value.obfuscateId(),
"exception" to exception,
"message" to exception.message,
"stackTrace" to exception.stackTraceToString()
)
kaliumLogger.withTextTag(TAG).logStructuredJson(KaliumLogLevel.ERROR, "Proteus storage migration failed", logMap)
throw exception
}
}

/**
* Actually deletes the proteus local files.
* Important! It is the caller responsibility to use the mutex, DON'T add a mutex here or it will be dead lock it.
*/
private suspend fun removeLocalFiles() {
withContext(dispatcher.io) {
_proteusClient?.close()
_proteusClient = null
FileUtil.deleteDirectory(rootProteusPath)
}
}

private companion object {
const val TAG = "ProteusClientProvider"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.data.client

import com.wire.kalium.logic.data.logout.LogoutReason

/**
* Handles the migration error of a proteus client storage from CryptoBox to CoreCrypto.
* It will perform a logout, using [LogoutReason.MIGRATION_TO_CC_FAILED] as the reason.
*
* This achieves that the client data is cleared and the user is logged out without losing content.
*/
interface ProteusMigrationRecoveryHandler {
suspend fun clearClientData(clearLocalFiles: suspend () -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ enum class LogoutReason {
/**
* Session Expired.
*/
SESSION_EXPIRED;
SESSION_EXPIRED,

/**
* The migration to CC failed.
* This will trigger a cleanup of the local client data and prepare for a fresh start without losing data.
*/
MIGRATION_TO_CC_FAILED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ internal class SessionMapperImpl : SessionMapper {
LogoutReason.REMOVED_CLIENT -> LogoutReasonEntity.REMOVED_CLIENT
LogoutReason.DELETED_ACCOUNT -> LogoutReasonEntity.DELETED_ACCOUNT
LogoutReason.SESSION_EXPIRED -> LogoutReasonEntity.SESSION_EXPIRED
LogoutReason.MIGRATION_TO_CC_FAILED -> LogoutReasonEntity.MIGRATION_TO_CC_FAILED
}

override fun toSsoIdEntity(ssoId: SsoId?): SsoIdEntity? =
Expand All @@ -131,6 +132,7 @@ internal class SessionMapperImpl : SessionMapper {
LogoutReasonEntity.REMOVED_CLIENT -> LogoutReason.REMOVED_CLIENT
LogoutReasonEntity.DELETED_ACCOUNT -> LogoutReason.DELETED_ACCOUNT
LogoutReasonEntity.SESSION_EXPIRED -> LogoutReason.SESSION_EXPIRED
LogoutReasonEntity.MIGRATION_TO_CC_FAILED -> LogoutReason.MIGRATION_TO_CC_FAILED
}

override fun fromEntityToProxyCredentialsDTO(proxyCredentialsEntity: ProxyCredentialsEntity): ProxyCredentialsDTO =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.wire.kalium.logic.data.client.MLSClientProvider
import com.wire.kalium.logic.data.client.MLSClientProviderImpl
import com.wire.kalium.logic.data.client.ProteusClientProvider
import com.wire.kalium.logic.data.client.ProteusClientProviderImpl
import com.wire.kalium.logic.data.client.ProteusMigrationRecoveryHandler
import com.wire.kalium.logic.data.client.remote.ClientRemoteDataSource
import com.wire.kalium.logic.data.client.remote.ClientRemoteRepository
import com.wire.kalium.logic.data.connection.ConnectionDataSource
Expand Down Expand Up @@ -181,6 +182,7 @@ import com.wire.kalium.logic.feature.client.IsAllowedToRegisterMLSClientUseCase
import com.wire.kalium.logic.feature.client.IsAllowedToRegisterMLSClientUseCaseImpl
import com.wire.kalium.logic.feature.client.MLSClientManager
import com.wire.kalium.logic.feature.client.MLSClientManagerImpl
import com.wire.kalium.logic.feature.client.ProteusMigrationRecoveryHandlerImpl
import com.wire.kalium.logic.feature.client.RegisterMLSClientUseCase
import com.wire.kalium.logic.feature.client.RegisterMLSClientUseCaseImpl
import com.wire.kalium.logic.feature.connection.ConnectionScope
Expand Down Expand Up @@ -617,12 +619,17 @@ class UserSessionScope internal constructor(
private val updateKeyingMaterialThresholdProvider: UpdateKeyingMaterialThresholdProvider
get() = UpdateKeyingMaterialThresholdProviderImpl(kaliumConfigs)

private val proteusMigrationRecoveryHandler: ProteusMigrationRecoveryHandler by lazy {
ProteusMigrationRecoveryHandlerImpl(lazy { logout })
}

val proteusClientProvider: ProteusClientProvider by lazy {
ProteusClientProviderImpl(
rootProteusPath = rootPathsProvider.rootProteusPath(userId),
userId = userId,
passphraseStorage = globalPreferences.passphraseStorage,
kaliumConfigs = kaliumConfigs
kaliumConfigs = kaliumConfigs,
proteusMigrationRecoveryHandler = proteusMigrationRecoveryHandler
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
import com.wire.kalium.logic.feature.client.ClearClientDataUseCase
import com.wire.kalium.logic.feature.session.DeregisterTokenUseCase
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.logic.kaliumLogger
import com.wire.kalium.logic.sync.UserSessionWorkScheduler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
Expand Down Expand Up @@ -106,6 +107,9 @@ internal class LogoutUseCaseImpl @Suppress("LongParameterList") constructor(
}

LogoutReason.SELF_SOFT_LOGOUT -> clearCurrentClientIdAndFirebaseTokenFlag()
LogoutReason.MIGRATION_TO_CC_FAILED -> prepareForCoreCryptoMigrationRecovery()
}.also {
kaliumLogger.withTextTag(TAG).d("Logout reason: $reason")
}

userConfigRepository.clearE2EISettings()
Expand All @@ -115,6 +119,13 @@ internal class LogoutUseCaseImpl @Suppress("LongParameterList") constructor(
}.let { if (waitUntilCompletes) it.join() else it }
}

private suspend fun prepareForCoreCryptoMigrationRecovery() {
clearClientDataUseCase()
logoutRepository.clearClientRelatedLocalMetadata()
clientRepository.clearRetainedClientId()
pushTokenRepository.setUpdateFirebaseTokenFlag(true)
}

private suspend fun clearCurrentClientIdAndFirebaseTokenFlag() {
clientRepository.clearCurrentClientId()
clientRepository.clearNewClients()
Expand Down Expand Up @@ -146,5 +157,6 @@ internal class LogoutUseCaseImpl @Suppress("LongParameterList") constructor(

companion object {
const val CLEAR_DATA_DELAY = 1000L
const val TAG = "LogoutUseCase"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.client

import com.wire.kalium.logic.data.client.ProteusMigrationRecoveryHandler
import com.wire.kalium.logic.data.logout.LogoutReason
import com.wire.kalium.logic.feature.auth.LogoutUseCase
import com.wire.kalium.logic.kaliumLogger

internal class ProteusMigrationRecoveryHandlerImpl(
private val logoutUseCase: Lazy<LogoutUseCase>
) : ProteusMigrationRecoveryHandler {

/**
* Handles the migration error of a proteus client storage from CryptoBox to CoreCrypto.
* It will perform a logout, using [LogoutReason.MIGRATION_TO_CC_FAILED] as the reason.
*
* This achieves that the client data is cleared and the user is logged out without losing content.
*/
@Suppress("TooGenericExceptionCaught")
override suspend fun clearClientData(clearLocalFiles: suspend () -> Unit) {
try {
kaliumLogger.withTextTag(TAG).i("Starting the recovery from failed Proteus storage migration")
clearLocalFiles()
logoutUseCase.value(LogoutReason.MIGRATION_TO_CC_FAILED, true)
} catch (e: Exception) {
kaliumLogger.withTextTag(TAG).e("Fatal, error while clearing client data: $e")
throw e
} finally {
kaliumLogger.withTextTag(TAG).i("Finished the recovery from failed Proteus storage migration")
}
}

private companion object {
const val TAG = "ProteusMigrationRecoveryHandler"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.kaliumLogger
import com.wire.kalium.network.exceptions.AuthenticationCodeFailure
import com.wire.kalium.network.exceptions.KaliumException
import com.wire.kalium.network.exceptions.authenticationCodeFailure
Expand Down Expand Up @@ -144,8 +145,9 @@ class RegisterClientUseCaseImpl @OptIn(DelicateKaliumApi::class) internal constr
cookieLabel,
verificationCode
)
}.fold({
RegisterClientResult.Failure.Generic(it)
}.fold({ error ->
kaliumLogger.withTextTag(TAG).e("There was an error while registering the client $error")
RegisterClientResult.Failure.Generic(error)
}, { registerClientParam ->
clientRepository.registerClient(registerClientParam)
// todo? separate this in mls client usesCase register! separate everything
Expand Down Expand Up @@ -237,4 +239,8 @@ class RegisterClientUseCaseImpl @OptIn(DelicateKaliumApi::class) internal constr
}
}
}

private companion object {
const val TAG = "RegisterClientUseCase"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ internal class SlowSlowSyncCriteriaProviderImpl(
LogoutReason.SESSION_EXPIRED -> "Logout: SESSION_EXPIRED"
LogoutReason.REMOVED_CLIENT -> "Logout: REMOVED_CLIENT"
LogoutReason.DELETED_ACCOUNT -> "Logout: DELETED_ACCOUNT"
LogoutReason.MIGRATION_TO_CC_FAILED -> "Logout: MIGRATION_TO_CC_FAILED"
null -> null
}?.let { MissingRequirement(it) }

Expand Down
Loading
Loading