From e4ac28877c3473f1212ebd98642e895529ed8790 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 11:14:05 +0200 Subject: [PATCH] Ask user password to initialize CrossSigning And migrate some logic to the ViewModel --- .../crosssigning/CrossSigningService.kt | 2 +- .../DefaultCrossSigningService.kt | 6 +- .../model/rest/SignatureUploadResponse.kt | 1 + .../crypto/recover/BootstrapBottomSheet.kt | 7 +- .../recover/BootstrapSharedViewModel.kt | 2 +- .../riotx/features/home/HomeActivity.kt | 106 ++++++------------ .../features/home/HomeActivitySharedAction.kt | 2 - .../features/home/HomeActivityViewEvents.kt | 25 +++++ .../features/home/HomeActivityViewModel.kt | 74 +++++++++++- .../features/navigation/DefaultNavigator.kt | 4 +- .../riotx/features/navigation/Navigator.kt | 2 +- .../DeviceVerificationInfoEpoxyController.kt | 2 +- 12 files changed, 140 insertions(+), 93 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 6e4960b821..2f23646460 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -41,7 +41,7 @@ interface CrossSigningService { * Users needs to enter credentials */ fun initializeCrossSigning(authParams: UserPasswordAuth?, - callback: MatrixCallback? = null) + callback: MatrixCallback) fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, 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 445c44de0e..06355e6d72 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 @@ -140,7 +140,7 @@ internal class DefaultCrossSigningService @Inject constructor( * - Sign the keys and upload them * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures */ - override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback?) { + override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback) { Timber.d("## CrossSigning initializeCrossSigning") val params = InitializeCrossSigningTask.Params( @@ -150,7 +150,7 @@ internal class DefaultCrossSigningService @Inject constructor( this.callbackThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) + callback.onFailure(failure) } override fun onSuccess(data: InitializeCrossSigningTask.Result) { @@ -162,7 +162,7 @@ internal class DefaultCrossSigningService @Inject constructor( userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } - callback?.onSuccess(Unit) + callback.onSuccess(Unit) } } }.executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt index ef459fbc59..7bc5ecc6fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt @@ -36,6 +36,7 @@ internal data class SignatureUploadResponse( ) +// TODO Not used. Remove? @JsonClass(generateAdapter = true) data class UploadResponseFailure( @Json(name = "status") 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 15edf53a0d..a5bab9be5a 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 @@ -44,7 +44,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( - val isNewAccount: Boolean + val isNewAccount: Boolean, + val initCrossSigningOnly: Boolean ) : Parcelable override val showExpanded = true @@ -168,10 +169,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { const val EXTRA_ARGS = "EXTRA_ARGS" - fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean) { + fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean, initCrossSigningOnly: Boolean) { BootstrapBottomSheet().apply { isCancelable = false - arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation)) } + arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation, initCrossSigningOnly)) } }.show(fragmentManager, "BootstrapBottomSheet") } } 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 b887df90ca..52ccd8a10d 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 @@ -408,7 +408,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(true) + ?: BootstrapBottomSheet.Args(isNewAccount = true, initCrossSigningOnly = true) return fragment.bootstrapViewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 526ae3a70d..8d5fc5f564 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -23,12 +23,14 @@ import android.os.Parcelable import android.view.MenuItem import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel import im.vector.matrix.android.api.session.InitialSyncProgressService +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent @@ -38,10 +40,10 @@ import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.pushers.PushersManager -import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.workers.signout.SignOutViewModel @@ -104,15 +106,12 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) - is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java) } - is HomeActivitySharedAction.PromptForSecurityBootstrap -> { - BootstrapBottomSheet.show(supportFragmentManager, true) - } }.exhaustive } .disposeOnDestroy() @@ -123,16 +122,13 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet notificationDrawerManager.clearAllEvents() } - homeActivityViewModel.subscribe(this) { renderState(it) } - - /* - // TODO Remove - // Ask again if the app is relaunched - if (!homeActivityViewModel.hasDisplayedCompleteSecurityPrompt - && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { - promptCompleteSecurityIfNeeded() + homeActivityViewModel.observeViewEvents { + when (it) { + is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + }.exhaustive } - */ + homeActivityViewModel.subscribe(this) { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() @@ -144,7 +140,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = false Timber.v("${getString(status.statusText)} ${status.percentProgress}") waiting_view.setOnClickListener { // block interactions @@ -164,63 +159,29 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet }.exhaustive } - /* - // TODO Remove - private fun promptCompleteSecurityIfNeeded() { - val session = activeSessionHolder.getSafeActiveSession() ?: return - if (!session.hasAlreadySynced()) return - if (homeActivityViewModel.hasDisplayedCompleteSecurityPrompt) return - - // ensure keys are downloaded - session.cryptoService().downloadKeys(listOf(session.myUserId), true, object : MatrixCallback> { - override fun onSuccess(data: MXUsersDevicesMap) { - runOnUiThread { - alertCompleteSecurity(session) - } - } - }) - } - */ - - // TODO Remove - /* - private fun alertCompleteSecurity(session: Session) { - val myCrossSigningKeys = session.cryptoService().crossSigningService() - .getMyCrossSigningKeys() - val crossSigningEnabledOnAccount = myCrossSigningKeys != null - - if (!crossSigningEnabledOnAccount && !homeActivityViewModel.isAccountCreation) { - // Do not propose for SSO accounts, because we do not support yet confirming account credentials using SSO - if (session.getHomeServerCapabilities().canChangePassword) { - // We need to ask - promptSecurityEvent( - session, - R.string.upgrade_security, - R.string.security_prompt_text - ) { - it.navigator.upgradeSessionSecurity(it) - } - } else { - // Do not do it again - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true - } - } else if (myCrossSigningKeys?.isTrusted() == false) { - // We need to ask - promptSecurityEvent( - session, - R.string.crosssigning_verify_this_session, - R.string.confirm_your_identity - ) { - it.navigator.waitSessionVerification(it) - } + private fun handleAskPasswordToInitCrossSigning(events: HomeActivityViewEvents.AskPasswordToInitCrossSigning) { + // We need to ask + promptSecurityEvent( + events.userItem, + R.string.upgrade_security, + R.string.security_prompt_text + ) { + it.navigator.upgradeSessionSecurity(it, true) } } - */ - /* - // TODO Remove - private fun promptSecurityEvent(session: Session, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true + private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) { + // We need to ask + promptSecurityEvent( + event.userItem, + R.string.crosssigning_verify_this_session, + R.string.confirm_your_identity + ) { + it.navigator.waitSessionVerification(it) + } + } + + private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { popupAlertManager.postVectorAlert( VerificationVectorAlert( uid = "upgradeSecurity", @@ -228,7 +189,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet description = getString(descRes), iconId = R.drawable.ic_shield_warning ).apply { - matrixItem = session.getUser(session.myUserId)?.toMatrixItem() + matrixItem = userItem colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { @@ -239,7 +200,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } ) } - */ override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt index f4fa8f08a4..49c8de4d8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt @@ -25,6 +25,4 @@ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() object OpenGroup : HomeActivitySharedAction() - // TODO Remove? - object PromptForSecurityBootstrap : HomeActivitySharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt new file mode 100644 index 0000000000..2f1d8b2705 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt @@ -0,0 +1,25 @@ +/* + * 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.riotx.features.home + +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.riotx.core.platform.VectorViewEvents + +sealed class HomeActivityViewEvents : VectorViewEvents { + data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() + data class OnNewSession(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index 9849a17b48..ab90cd45ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -21,11 +21,16 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.session.InitialSyncProgressService +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.rx.asObservable import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.platform.EmptyAction -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.login.ReAuthHelper import timber.log.Timber @@ -35,7 +40,7 @@ class HomeActivityViewModel @AssistedInject constructor( @Assisted private val args: HomeActivityArgs, private val activeSessionHolder: ActiveSessionHolder, private val reAuthHelper: ReAuthHelper -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -52,8 +57,7 @@ class HomeActivityViewModel @AssistedInject constructor( } } - // TODO Remove? - var hasDisplayedCompleteSecurityPrompt: Boolean = false + private var checkBootstrap = false init { observeInitialSync() @@ -66,6 +70,19 @@ class HomeActivityViewModel @AssistedInject constructor( session.getInitialSyncProgressStatus() .asObservable() .subscribe { status -> + when (status) { + is InitialSyncProgressService.Status.Progressing -> { + // Schedule a check of the bootstrap when the init sync will be finished + checkBootstrap = true + } + is InitialSyncProgressService.Status.Idle -> { + if (checkBootstrap) { + checkBootstrap = false + maybeBootstrapCrossSigning() + } + } + } + setState { copy( initialSyncProgressServiceStatus = status @@ -92,12 +109,57 @@ class HomeActivityViewModel @AssistedInject constructor( session = null, user = session.myUserId, password = password - ) + ), + callback = NoOpMatrixCallback() ) - // TODO Download keys? } } + private fun maybeBootstrapCrossSigning() { + // In case of account creation, it is already done before + if (args.accountCreation) return + + val session = activeSessionHolder.getSafeActiveSession() ?: return + + // Ensure keys of the user are downloaded + session.cryptoService().downloadKeys(listOf(session.myUserId), true, object : MatrixCallback> { + override fun onSuccess(data: MXUsersDevicesMap) { + // Is there already cross signing keys here? + val mxCrossSigningInfo = session.cryptoService().crossSigningService().getMyCrossSigningKeys() + if (mxCrossSigningInfo != null) { + // Cross-signing is already set up for this user, is it trusted? + if(!mxCrossSigningInfo.isTrusted()) { + // New session + _viewEvents.post(HomeActivityViewEvents.OnNewSession(session.getUser(session.myUserId)?.toMatrixItem())) + } + } else { + // Initialize cross-signing + val password = reAuthHelper.data + + if (password == null) { + // Check this is not an SSO account + if (session.getHomeServerCapabilities().canChangePassword) { + // Ask password to the user: Upgrade security + _viewEvents.post(HomeActivityViewEvents.AskPasswordToInitCrossSigning(session.getUser(session.myUserId)?.toMatrixItem())) + } + // Else (SSO) just ignore for the moment + } else { + // We do not use the viewModel context because we do not want to cancel this action + Timber.d("Initialize cross signing") + session.cryptoService().crossSigningService().initializeCrossSigning( + authParams = UserPasswordAuth( + session = null, + user = session.myUserId, + password = password + ), + callback = NoOpMatrixCallback() + ) + } + } + } + }) + } + override fun handle(action: EmptyAction) { // NA } 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 a909e5becf..5b70d65523 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 @@ -124,9 +124,9 @@ class DefaultNavigator @Inject constructor( } } - override fun upgradeSessionSecurity(context: Context) { + override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, false) + BootstrapBottomSheet.show(context.supportFragmentManager, false, initCrossSigningOnly) } } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 916a46c041..ce4d5ef3ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -42,7 +42,7 @@ interface Navigator { fun waitSessionVerification(context: Context) - fun upgradeSessionSecurity(context: Context) + fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt index 4123e260e2..ddec06b9a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt @@ -89,7 +89,7 @@ class DeviceVerificationInfoEpoxyController @Inject constructor(private val stri description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc)) } } else { - // You need tomcomplete security + // You need to complete security genericItem { id("trust${cryptoDeviceInfo.deviceId}") style(GenericItem.STYLE.BIG_TEXT)