diff --git a/CHANGES.md b/CHANGES.md index 9d124dff66..8a20924d42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ Improvements 🙌: - Creating and listening to EventInsertEntity. (#1634) - Handling (almost) properly the groups fetching (#1634) - Improve fullscreen media display (#327) + - Setup server recovery banner (#1648) + - Set up SSSS from security settings (#1567) Bugfix 🐛: - Regression | Share action menu do not work (#1647) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index e8fef1361d..ad2245eef3 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -17,9 +17,14 @@ package im.vector.matrix.rx import androidx.paging.PagedList +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.identity.ThreePid @@ -36,9 +41,11 @@ import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import io.reactivex.Observable import io.reactivex.Single +import io.reactivex.functions.Function3 class RxSession(private val session: Session) { @@ -165,6 +172,38 @@ class RxSession(private val session: Session) { session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) } } + + fun liveSecretSynchronisationInfo(): Observable { + return Observable.combineLatest, Optional, Optional, SecretsSynchronisationInfo>( + liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), + liveCrossSigningInfo(session.myUserId), + liveCrossSigningPrivateKeys(), + Function3 { _, crossSigningInfo, pInfo -> + // first check if 4S is already setup + val is4SSetup = session.sharedSecretStorageService.isRecoverySetup() + val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null + val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true + val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse() + + val keysBackupService = session.cryptoService().keysBackupService() + val currentBackupVersion = keysBackupService.currentBackupVersion + val megolmBackupAvailable = currentBackupVersion != null + val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo() + + val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion + SecretsSynchronisationInfo( + isBackupSetup = is4SSetup, + isCrossSigningEnabled = isCrossSigningEnabled, + isCrossSigningTrusted = isCrossSigningTrusted, + allPrivateKeysKnown = allPrivateKeysKnown, + megolmBackupAvailable = megolmBackupAvailable, + megolmSecretKnown = megolmKeyKnown, + isMegolmKeyIn4S = session.sharedSecretStorageService.isMegolmKeyInBackup() + ) + } + ) + .distinctUntilChanged() + } } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/SecretsSynchronisationInfo.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/SecretsSynchronisationInfo.kt new file mode 100644 index 0000000000..616783706b --- /dev/null +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/SecretsSynchronisationInfo.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.rx + +data class SecretsSynchronisationInfo( + val isBackupSetup: Boolean, + val isCrossSigningEnabled: Boolean, + val isCrossSigningTrusted: Boolean, + val allPrivateKeysKnown: Boolean, + val megolmBackupAvailable: Boolean, + val megolmSecretKnown: Boolean, + val isMegolmKeyIn4S: Boolean +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt index 6644972aca..22fbcf2d26 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.securestorage import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -124,6 +125,13 @@ interface SharedSecretStorageService { ) is IntegrityResult.Success } + fun isMegolmKeyInBackup(): Boolean { + return checkShouldBeAbleToAccessSecrets( + secretNames = listOf(KEYBACKUP_SECRET_SSSS_NAME), + keyId = null + ) is IntegrityResult.Success + } + fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult fun requestSecret(name: String, myOtherDeviceId: String) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt index eb1c07cb92..5ad1013f49 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -71,8 +71,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( delay(1500) cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let { // TODO check if there is already one that is being sent? - if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { - Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we already request for that session: $it") + if (it.state == OutgoingGossipingRequestState.SENDING /**|| it.state == OutgoingGossipingRequestState.SENT*/) { + Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it") return@launch } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index fdecfe202e..5a7c07fb53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.crosssigning import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional @@ -509,9 +510,7 @@ internal class DefaultCrossSigningService @Inject constructor( override fun allPrivateKeysKnown(): Boolean { return checkSelfTrust().isVerified() - && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null - && cryptoStore.getCrossSigningPrivateKeys()?.user != null - && cryptoStore.getCrossSigningPrivateKeys()?.master != null + && cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() } override fun trustUser(otherUserId: String, callback: MatrixCallback) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt index a10b6d2645..d1591e35d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt @@ -20,4 +20,6 @@ data class PrivateKeysInfo( val master: String? = null, val selfSigned: String? = null, val user: String? = null -) +) { + fun allKnown() = master != null && selfSigned != null && user != null +} diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index bdd873d0cd..59bf7a8aeb 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -162,9 +162,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { return this } - protected fun Disposable.disposeOnDestroy(): Disposable { + protected fun Disposable.disposeOnDestroy() { uiDisposables.add(this) - return this } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index c0b1b54c09..f4343a3e58 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -234,9 +234,8 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { private val uiDisposables = CompositeDisposable() - protected fun Disposable.disposeOnDestroyView(): Disposable { + protected fun Disposable.disposeOnDestroyView() { uiDisposables.add(this) - return this } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index 124bef2e28..28711115c3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -185,7 +185,6 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() AlertDialog.Builder(it) .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(throwable)) - } }, { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index f14d27b3d9..945a8c2866 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -45,7 +45,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( - val initCrossSigningOnly: Boolean + val initCrossSigningOnly: Boolean, + val forceReset4S: Boolean ) : Parcelable override val showExpanded = true @@ -180,10 +181,15 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { const val EXTRA_ARGS = "EXTRA_ARGS" - fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean) { + fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean, forceReset4S: Boolean) { BootstrapBottomSheet().apply { isCancelable = false - arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(initCrossSigningOnly)) } + arguments = Bundle().apply { + this.putParcelable(EXTRA_ARGS, Args( + initCrossSigningOnly, + forceReset4S + )) + } }.show(fragmentManager, "BootstrapBottomSheet") } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index 9f68e09444..8781cbe570 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -258,6 +258,30 @@ class BootstrapCrossSigningTask @Inject constructor( ) } } + } else { + Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found") + // ensure we store existing backup secret if we have it! + val knownSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() + if (knownSecret != null && knownSecret.version == serverVersion.version) { + // check it matches + val isValid = awaitCallback { + session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it) + } + if (isValid) { + Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") + awaitCallback { + extractCurveKeyFromRecoveryKey(knownSecret.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it + ) + } + } + } else { + Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key is unknown by this session") + } + } } } catch (failure: Throwable) { Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup") diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 156acf845f..ea558145c0 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -58,6 +58,13 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme bootstrapSetupSecureUseSecurityPassphrase.isVisible = false bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false } else { + if (state.step.reset) { + bootstrapSetupSecureText.text = getString(R.string.reset_secure_backup_title) + bootstrapSetupWarningTextView.isVisible = true + } else { + bootstrapSetupSecureText.text = getString(R.string.bottom_sheet_setup_secure_backup_subtitle) + bootstrapSetupWarningTextView.isVisible = false + } // Choose between create a passphrase or use a recovery key bootstrapSetupSecureSubmit.isVisible = false bootstrapSetupSecureUseSecurityKey.isVisible = true diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 22dcab217e..8b247bd975 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -69,7 +69,11 @@ class BootstrapSharedViewModel @AssistedInject constructor( init { - if (args.initCrossSigningOnly) { + if (args.forceReset4S) { + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true)) + } + } else if (args.initCrossSigningOnly) { // Go straight to account password setState { copy(step = BootstrapStep.AccountPassword(false)) @@ -554,7 +558,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? { val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS) - ?: BootstrapBottomSheet.Args(initCrossSigningOnly = true) + ?: BootstrapBottomSheet.Args(initCrossSigningOnly = true, forceReset4S = false) return fragment.bootstrapViewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index c7639068d1..71b00016ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -89,7 +89,7 @@ sealed class BootstrapStep { object CheckingMigration : BootstrapStep() // Use will be asked to choose between passphrase or recovery key, or to start process if a key backup exists - data class FirstForm(val keyBackUpExist: Boolean) : BootstrapStep() + data class FirstForm(val keyBackUpExist: Boolean, val reset: Boolean = false) : BootstrapStep() data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt index 88f6607a41..8ac2ce72cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt @@ -146,7 +146,7 @@ class VerificationRequestController @Inject constructor( } } - if (state.isMe && state.currentDeviceCanCrossSign) { + if (state.isMe && state.currentDeviceCanCrossSign && !state.selfVerificationMode) { dividerItem { id("sep_notMe") } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index f0fdc207f9..770b03d345 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -193,14 +193,15 @@ class HomeDetailFragment @Inject constructor( } private fun setupKeysBackupBanner() { - serverBackupStatusViewModel.subscribe(this) { - when (val banState = it.bannerState.invoke()) { - is BannerState.Setup -> homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) - BannerState.BackingUp -> homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) - null, - BannerState.Hidden -> homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false) - } - }.disposeOnDestroyView() + serverBackupStatusViewModel + .subscribe(this) { + when (val banState = it.bannerState.invoke()) { + is BannerState.Setup -> homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) + BannerState.BackingUp -> homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) + null, + BannerState.Hidden -> homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false) + } + } homeKeysBackupBanner.delegate = this } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 3c3e85d3f6..26908182b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -145,7 +145,7 @@ class DefaultNavigator @Inject constructor( override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly) + BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly, false) } } @@ -221,7 +221,7 @@ class DefaultNavigator @Inject constructor( // if cross signing is enabled we should propose full 4S sessionHolder.getSafeActiveSession()?.let { session -> if (session.cryptoService().crossSigningService().canCrossSign() && context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, false) + BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly = false, forceReset4S = false) } else { context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport)) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index e4a0eb3eb6..9d08a9f62f 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -72,6 +72,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY" const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" +// const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY" // user const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY" diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 73c167fa74..9d71c1712e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -23,6 +23,7 @@ import android.content.Intent import android.widget.Button import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.preference.Preference import androidx.preference.PreferenceCategory @@ -33,6 +34,8 @@ import im.vector.matrix.android.internal.crypto.crosssigning.isVerified import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse +import im.vector.matrix.rx.SecretsSynchronisationInfo +import im.vector.matrix.rx.rx import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.dialogs.ExportKeysDialog @@ -42,11 +45,16 @@ import im.vector.riotx.core.intent.analyseIntent import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.SimpleTextWatcher import im.vector.riotx.core.preference.VectorPreference +import im.vector.riotx.core.preference.VectorPreferenceCategory import im.vector.riotx.core.utils.openFileSelection import im.vector.riotx.core.utils.toast import im.vector.riotx.features.crypto.keys.KeysExporter import im.vector.riotx.features.crypto.keys.KeysImporter import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity +import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet +import im.vector.riotx.features.themes.ThemeUtils +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( @@ -56,6 +64,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( override var titleRes = R.string.settings_security_and_privacy override val preferenceXmlRes = R.xml.vector_settings_security_privacy + private var disposables = mutableListOf() // cryptography private val mCryptographyCategory by lazy { @@ -92,6 +101,97 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( // My device name may have been updated refreshMyDevice() refreshXSigningStatus() + session.rx().liveSecretSynchronisationInfo() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + refresh4SSection(it) + refreshXSigningStatus() + }.also { + disposables.add(it) + } + } + + private val secureBackupCategory by lazy { + findPreference("SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY")!! + } + private val secureBackupPreference by lazy { + findPreference("SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY")!! + } +// private val secureBackupResetPreference by lazy { +// findPreference(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY) +// } + + override fun onPause() { + super.onPause() + disposables.forEach { + it.dispose() + } + disposables.clear() + } + + private fun refresh4SSection(info: SecretsSynchronisationInfo) { + // it's a lot of if / else if / else + // But it's not yet clear how to manage all cases + if (!info.isCrossSigningEnabled) { + // There is not cross signing, so we can remove the section + secureBackupCategory.isVisible = false + } else { + if (!info.isBackupSetup) { + if (info.isCrossSigningEnabled && info.allPrivateKeysKnown) { + // You can setup recovery! + secureBackupCategory.isVisible = true + secureBackupPreference.title = getString(R.string.settings_secure_backup_setup) + secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false) + true + } + } else { + // just hide all, you can't setup from here + // you should synchronize to get gossips + secureBackupCategory.isVisible = false + } + } else { + // so here we know that 4S is setup + if (info.isCrossSigningTrusted && info.allPrivateKeysKnown) { + // Looks like we have all cross signing secrets and session is trusted + // Let's see if there is a megolm backup + if (!info.megolmBackupAvailable || info.megolmSecretKnown) { + // Only option here is to create a new backup if you want? + // aka reset + secureBackupCategory.isVisible = true + secureBackupPreference.title = getString(R.string.settings_secure_backup_reset) + secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = true) + true + } + } else if (!info.megolmSecretKnown) { + // megolm backup is available but we don't have key + // you could try to synchronize to get missing megolm key ? + secureBackupCategory.isVisible = true + secureBackupPreference.title = getString(R.string.settings_secure_backup_enter_to_setup) + secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + vectorActivity.let { + it.navigator.requestSelfSessionVerification(it) + } + true + } + } else { + secureBackupCategory.isVisible = false + } + } else { + // there is a backup, but this session is not trusted, or is missing some secrets + // you should enter passphrase to get them or verify against another session + secureBackupCategory.isVisible = true + secureBackupPreference.title = getString(R.string.settings_secure_backup_enter_to_setup) + secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + vectorActivity.let { + it.navigator.requestSelfSessionVerification(it) + } + true + } + } + } + } } override fun bindPref() { @@ -115,26 +215,37 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( } refreshXSigningStatus() + + secureBackupPreference.icon = activity?.let { + ThemeUtils.tintDrawable(it, + ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_settings_icon_tint_color) + } } + // Todo this should be refactored and use same state as 4S section private fun refreshXSigningStatus() { val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys() val xSigningIsEnableInAccount = crossSigningKeys != null val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() - if (xSigningKeyCanSign) { - mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted) - mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete) - } else if (xSigningKeysAreTrusted) { - mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom) - mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted) - } else if (xSigningIsEnableInAccount) { - mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black) - mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted) - } else { - mCrossSigningStatePreference.setIcon(android.R.color.transparent) - mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled) + when { + xSigningKeyCanSign -> { + mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted) + mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete) + } + xSigningKeysAreTrusted -> { + mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom) + mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted) + } + xSigningIsEnableInAccount -> { + mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black) + mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted) + } + else -> { + mCrossSigningStatePreference.setIcon(android.R.color.transparent) + mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled) + } } mCrossSigningStatePreference.isVisible = true diff --git a/vector/src/main/java/im/vector/riotx/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/riotx/features/workers/signout/ServerBackupStatusViewModel.kt index dca98c16b2..bfeb959534 100644 --- a/vector/src/main/java/im/vector/riotx/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/workers/signout/ServerBackupStatusViewModel.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo @@ -114,9 +115,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS // So recovery is not setup // Check if cross signing is enabled and local secrets known if (crossSigningInfo.getOrNull()?.isTrusted() == true - && pInfo.getOrNull()?.master != null - && pInfo.getOrNull()?.selfSigned != null - && pInfo.getOrNull()?.user != null + && pInfo.getOrNull()?.allKnown().orFalse() ) { // So 4S is not setup and we have local secrets, return@Function4 BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) diff --git a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt index 16be661f06..2ebf086796 100644 --- a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -121,7 +121,7 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), super.onActivityCreated(savedInstanceState) setupRecoveryButton.action = { - BootstrapBottomSheet.show(parentFragmentManager, false) + BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false) } exitAnywayButton.action = { diff --git a/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml index 82344824d0..ffe1e4680c 100644 --- a/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml +++ b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml @@ -43,6 +43,7 @@ app:actionDescription="@string/bottom_sheet_setup_secure_backup_security_key_subtitle" app:actionTitle="@string/bottom_sheet_setup_secure_backup_security_key_title" app:leftIcon="@drawable/ic_security_key_24dp" + app:tint="?attr/riotx_text_primary" app:rightIcon="@drawable/ic_arrow_right" tools:visibility="visible" /> @@ -63,6 +64,7 @@ app:actionDescription="@string/bottom_sheet_setup_secure_backup_security_phrase_subtitle" app:actionTitle="@string/bottom_sheet_setup_secure_backup_security_phrase_title" app:leftIcon="@drawable/ic_security_phrase_24dp" + app:tint="?attr/riotx_text_primary" app:rightIcon="@drawable/ic_arrow_right" tools:visibility="visible" /> @@ -71,4 +73,18 @@ android:layout_height="1dp" android:background="?attr/vctr_list_divider_color" /> + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1bfff06005..37a849b3fd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -843,6 +843,15 @@ Send message with enter Enter button of the soft keyboard will send message instead of adding a line break + Secure Backup + Manage + Set up Secure Backup + Reset Secure Backup + Set up on this device + Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. + Generate a new Security Key or set a new Security Phrase for your existing backup. + This will replace your current Key or Phrase. + Deactivate account Deactivate my account Discovery diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index 8b4823eac9..9bfe5e944b 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -48,6 +48,23 @@ + + + + + + + +