Ask user password to initialize CrossSigning

And migrate some logic to the ViewModel
This commit is contained in:
Benoit Marty 2020-06-16 11:14:05 +02:00 committed by Valere
parent 8df7797f6d
commit e4ac28877c
12 changed files with 140 additions and 93 deletions

View file

@ -41,7 +41,7 @@ interface CrossSigningService {
* Users needs to enter credentials
*/
fun initializeCrossSigning(authParams: UserPasswordAuth?,
callback: MatrixCallback<Unit>? = null)
callback: MatrixCallback<Unit>)
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,

View file

@ -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<Unit>?) {
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>) {
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<InitializeCrossSigningTask.Result> {
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)

View file

@ -36,6 +36,7 @@ internal data class SignatureUploadResponse(
)
// TODO Not used. Remove?
@JsonClass(generateAdapter = true)
data class UploadResponseFailure(
@Json(name = "status")

View file

@ -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")
}
}

View file

@ -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)
}
}

View file

@ -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<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
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)

View file

@ -25,6 +25,4 @@ sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction()
object OpenGroup : HomeActivitySharedAction()
// TODO Remove?
object PromptForSecurityBootstrap : HomeActivitySharedAction()
}

View file

@ -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()
}

View file

@ -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<HomeActivityViewState, EmptyAction, EmptyViewEvents>(initialState) {
) : VectorViewModel<HomeActivityViewState, EmptyAction, HomeActivityViewEvents>(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<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
// 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
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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)