diff --git a/CHANGES.md b/CHANGES.md index 74aaa8cee3..afe6fa8461 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ Bugfix 🐛: - Invalid popup when pressing back (#1635) - Simplifies draft management and should fix bunch of draft issues (#952, #683) - Very long topic cannot be fully visible (#1957) + - Properly detect cross signing keys reset Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 8e0d7407c6..ab30d3052d 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -359,7 +359,6 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM cryptoStore.storeUserDevices(userId, workingCopy) } - // Handle cross signing keys update val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 394b93306c..9065da47b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -302,22 +302,42 @@ internal class RealmCryptoStore @Inject constructor( userEntity.crossSigningInfoEntity?.deleteFromRealm() userEntity.crossSigningInfoEntity = null } else { + var shouldResetMyDevicesLocalTrust = false CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? val existingMaster = signingInfo.getMasterKey() if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { crossSigningKeysMapper.update(existingMaster, masterKey) } else { + Timber.d("## CrossSigning MSK change for $userId") val keyEntity = crossSigningKeysMapper.map(masterKey) signingInfo.setMasterKey(keyEntity) + if (userId == credentials.userId) { + shouldResetMyDevicesLocalTrust = true + // my msk has changed! clear my private key + // Could we have some race here? e.g I am the one that did change the keys + // could i get this update to early and clear the private keys? + // -> initializeCrossSigning is guarding for that by storing all at once + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = null + } + } } val existingSelfSigned = signingInfo.getSelfSignedKey() if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) } else { + Timber.d("## CrossSigning SSK change for $userId") val keyEntity = crossSigningKeysMapper.map(selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) + if (userId == credentials.userId) { + shouldResetMyDevicesLocalTrust = true + // my ssk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = null + } + } } // Only for me @@ -326,10 +346,29 @@ internal class RealmCryptoStore @Inject constructor( if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { crossSigningKeysMapper.update(existingUSK, userSigningKey) } else { + Timber.d("## CrossSigning USK change for $userId") val keyEntity = crossSigningKeysMapper.map(userSigningKey) signingInfo.setUserSignedKey(keyEntity) + if (userId == credentials.userId) { + shouldResetMyDevicesLocalTrust = true + // my usk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignUserPrivateKey = null + } + } } } + + // When my cross signing keys are reset, we consider clearing all existing device trust + if (shouldResetMyDevicesLocalTrust) { + realm.where() + .equalTo(UserEntityFields.USER_ID, credentials.userId) + .findFirst() + ?.devices?.forEach { + it?.trustLevelEntity?.crossSignedVerified = false + it?.trustLevelEntity?.locallyVerified = it.deviceId == credentials.deviceId + } + } userEntity.crossSigningInfoEntity = signingInfo } } @@ -1197,7 +1236,7 @@ internal class RealmCryptoStore @Inject constructor( .findAll() .mapNotNull { entity -> when (entity.type) { - GossipRequestType.KEY -> { + GossipRequestType.KEY -> { IncomingRoomKeyRequest( userId = entity.otherUserId, deviceId = entity.otherDeviceId, diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 829ab1624b..32a4af1b1b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -138,6 +138,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } @@ -182,6 +183,17 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } + private fun handleCrossSigningInvalidated(event: HomeActivityViewEvents.OnCrossSignedInvalidated) { + // We need to ask + promptSecurityEvent( + event.userItem, + R.string.crosssigning_verify_this_session, + R.string.confirm_your_identity + ) { + it.navigator.waitSessionVerification(it) + } + } + private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) { // We need to ask promptSecurityEvent( diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index be62997e0c..2a29e13572 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -22,5 +22,6 @@ import org.matrix.android.sdk.api.util.MatrixItem sealed class HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents() + data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() object PromptToEnableSessionPush : HomeActivityViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 46ae65ec02..48a71db35c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -27,6 +27,9 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.pushrules.RuleIds @@ -38,9 +41,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth import org.matrix.android.sdk.rx.asObservable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import org.matrix.android.sdk.rx.rx import timber.log.Timber class HomeActivityViewModel @AssistedInject constructor( @@ -67,11 +68,39 @@ class HomeActivityViewModel @AssistedInject constructor( } private var checkBootstrap = false + private var onceTrusted = false init { observeInitialSync() mayBeInitializeCrossSigning() checkSessionPushIsOn() + observeCrossSigningReset() + } + + private fun observeCrossSigningReset() { + val safeActiveSession = activeSessionHolder.getSafeActiveSession() + val crossSigningService = safeActiveSession + ?.cryptoService() + ?.crossSigningService() + onceTrusted = crossSigningService + ?.allPrivateKeysKnown() ?: false + + safeActiveSession + ?.rx() + ?.liveCrossSigningInfo(safeActiveSession.myUserId) + ?.subscribe { + val isVerified = it.getOrNull()?.isTrusted() ?: false + if (!isVerified && onceTrusted) { + // cross signing keys have been reset + // Tigger a popup to re-verify + _viewEvents.post( + HomeActivityViewEvents.OnCrossSignedInvalidated( + safeActiveSession.getUser(safeActiveSession.myUserId)?.toMatrixItem() + ) + ) + } + onceTrusted = isVerified + }?.disposeOnClear() } private fun observeInitialSync() { diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index eeb1639045..3bdcfc4018 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -30,6 +30,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.settings.VectorPreferences +import io.reactivex.Observable +import io.reactivex.functions.Function3 import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session @@ -39,8 +41,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import io.reactivex.Observable -import io.reactivex.functions.Function3 import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.util.concurrent.TimeUnit