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

Secure backup is required #989

Merged
merged 5 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class DebugVectorFeatures(

override fun tchapIsLabsVisible(domain: String) = vectorFeatures.tchapIsLabsVisible(domain)

override fun tchapIsSecureBackupRequired() = vectorFeatures.tchapIsSecureBackupRequired()

override fun onboardingVariant(): OnboardingVariant {
return readPreferences().getEnum<OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
}
Expand Down
1 change: 1 addition & 0 deletions vector-config/src/btchap/res/values/config-features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<bool name="tchap_is_cross_signing_enabled">true</bool>
<bool name="tchap_is_key_backup_enabled">true</bool>
<bool name="tchap_is_thread_enabled">false</bool>
<bool name="tchap_is_secure_backup_required">false</bool>

<string-array name="tchap_is_voip_supported_homeservers" translatable="false" />
</resources>
1 change: 1 addition & 0 deletions vector-config/src/devTchap/res/values/config-features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<bool name="tchap_is_cross_signing_enabled">true</bool>
<bool name="tchap_is_key_backup_enabled">true</bool>
<bool name="tchap_is_thread_enabled">false</bool>
<bool name="tchap_is_secure_backup_required">true</bool>

<string-array name="tchap_is_voip_supported_homeservers" translatable="false" />
</resources>
1 change: 1 addition & 0 deletions vector-config/src/tchap/res/values/config-features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<bool name="tchap_is_cross_signing_enabled">true</bool>
<bool name="tchap_is_key_backup_enabled">true</bool>
<bool name="tchap_is_thread_enabled">false</bool>
<bool name="tchap_is_secure_backup_required">false</bool>

<string-array name="tchap_is_voip_supported_homeservers" translatable="false" />
</resources>
2 changes: 2 additions & 0 deletions vector/src/main/java/im/vector/app/features/VectorFeatures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface VectorFeatures {
fun tchapIsKeyBackupEnabled(): Boolean
fun tchapIsThreadEnabled(): Boolean
fun tchapIsLabsVisible(domain: String): Boolean
fun tchapIsSecureBackupRequired(): Boolean
fun onboardingVariant(): OnboardingVariant
fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean
fun isOnboardingSplashCarouselEnabled(): Boolean
Expand Down Expand Up @@ -71,6 +72,7 @@ class DefaultVectorFeatures @Inject constructor(
override fun tchapIsThreadEnabled() = booleanProvider.getBoolean(R.bool.tchap_is_thread_enabled)
override fun tchapIsLabsVisible(domain: String) = booleanProvider.getBoolean(R.bool.settings_root_labs_visible) ||
domain == appNameProvider.getAppName()
override fun tchapIsSecureBackupRequired() = booleanProvider.getBoolean(R.bool.tchap_is_secure_backup_required)
override fun onboardingVariant() = Config.ONBOARDING_VARIANT
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = false // TCHAP no carousel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.VectorFeatures
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isSecureBackupRequired
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
import org.matrix.android.sdk.api.session.securestorage.KeyInfo
Expand Down Expand Up @@ -88,7 +92,9 @@ data class SharedSecureStorageViewState(
class SharedSecureStorageViewModel @AssistedInject constructor(
@Assisted private val initialState: SharedSecureStorageViewState,
private val stringProvider: StringProvider,
private val vectorFeatures: VectorFeatures,
private val session: Session,
private val rawService: RawService,
private val matrix: Matrix,
) :
VectorViewModel<SharedSecureStorageViewState, SharedSecureStorageAction, SharedSecureStorageViewEvent>(initialState) {
Expand All @@ -102,16 +108,25 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
setState {
copy(userId = session.myUserId)
}
if (initialState.requestType is RequestType.ReadSecrets) {
val integrityResult =
session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId)
if (integrityResult !is IntegrityResult.Success) {
_viewEvents.post(
SharedSecureStorageViewEvent.Error(
stringProvider.getString(R.string.enter_secret_storage_invalid),
true
)
)

// TCHAP force to configure secure backup even if well-known is null
// Do not check integrity if the session is not verified
viewModelScope.launch(Dispatchers.IO) {
val elementWellKnown = rawService.getElementWellknown(session.sessionParams)
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: vectorFeatures.tchapIsSecureBackupRequired()
val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()

if ((isThisSessionVerified || !isSecureBackupRequired) && initialState.requestType is RequestType.ReadSecrets) {
val integrityResult =
session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId)
if (integrityResult !is IntegrityResult.Success) {
_viewEvents.post(
SharedSecureStorageViewEvent.Error(
stringProvider.getString(R.string.enter_secret_storage_invalid),
true
)
)
}
}
}

Expand Down
yostyle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ class BootstrapSetupRecoveryKeyFragment :

// TCHAP we directly send user to Security Key
// Actions when a key backup exist
// views.bootstrapSetupSecureSubmit.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration)
// }

// Actions when there is no key backup
// views.bootstrapSetupSecureUseSecurityKey.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false))
// }
// views.bootstrapSetupSecureUseSecurityPassphrase.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true))
// }

// views.bootstrapSetupSecureSubmit.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration)
// }
//
// // Actions when there is no key backup
// views.bootstrapSetupSecureUseSecurityKey.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false))
// }
// views.bootstrapSetupSecureUseSecurityPassphrase.views.bottomSheetActionClickableZone.debouncedClicks {
// sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true))
// }
sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false))
}

Expand Down Expand Up @@ -95,7 +94,7 @@ class BootstrapSetupRecoveryKeyFragment :
private fun renderBackupMethodActions(method: SecureBackupMethod) = with(views) {
bootstrapSetupSecureUseSecurityKey.isVisible = method.isKeyAvailable
// TCHAP Hide Security Passphrase
// views.bootstrapSetupSecureUseSecurityPassphrase.isVisible = method.isPassphraseAvailable
// views.bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = method.isPassphraseAvailable
// bootstrapSetupSecureUseSecurityPassphrase.isVisible = method.isPassphraseAvailable
// bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = method.isPassphraseAvailable
}
}
yostyle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.VectorFeatures
import im.vector.app.features.auth.PendingAuthHandler
import im.vector.app.features.raw.wellknown.SecureBackupMethod
import im.vector.app.features.raw.wellknown.getElementWellknown
Expand All @@ -45,7 +46,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.raw.RawService
Expand All @@ -63,6 +63,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState,
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val vectorFeatures: VectorFeatures,
private val session: Session,
private val rawService: RawService,
private val bootstrapTask: BootstrapCrossSigningTask,
Expand Down Expand Up @@ -92,8 +93,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
val wellKnown = rawService.getElementWellknown(session.sessionParams)
setState {
copy(
isSecureBackupRequired = wellKnown?.isSecureBackupRequired().orFalse(),
secureBackupMethod = wellKnown?.secureBackupMethod() ?: SecureBackupMethod.KEY_OR_PASSPHRASE,
// TCHAP force to configure secure backup key even if well-known is null
isSecureBackupRequired = wellKnown?.isSecureBackupRequired() ?: vectorFeatures.tchapIsSecureBackupRequired(),
secureBackupMethod = wellKnown?.secureBackupMethod() ?: SecureBackupMethod.KEY,
yostyle marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
Expand Down Expand Up @@ -212,7 +214,10 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
is BootstrapActions.DoInitialize -> {
if (state.passphrase == state.passphraseRepeat) {
startInitializeFlow(state)
// TCHAP do not ask user password multiple times
if (state.step !is BootstrapStep.AccountReAuth) {
startInitializeFlow(state)
}
} else {
setState {
copy(
Expand All @@ -222,7 +227,10 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
}
is BootstrapActions.DoInitializeGeneratedKey -> {
startInitializeFlow(state)
// TCHAP do not ask user password multiple times
if (state.step !is BootstrapStep.AccountReAuth) {
startInitializeFlow(state)
}
}
BootstrapActions.RecoveryKeySaved -> {
_viewEvents.post(BootstrapViewEvents.RecoveryKeySaved)
Expand Down Expand Up @@ -603,4 +611,4 @@ class BootstrapSharedViewModel @AssistedInject constructor(
}
}

private val BootstrapViewState.canLeave: Boolean get() = !isSecureBackupRequired || isRecoverySetup
private val BootstrapViewState.canLeave: Boolean get() = isRecoverySetup // TCHAP can leave even if secure backup is required
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,19 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.VectorFeatures
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewEvents
import im.vector.app.features.crypto.verification.user.VerificationTransactionData
import im.vector.app.features.crypto.verification.user.toDataClass
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isSecureBackupRequired
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
Expand Down Expand Up @@ -98,6 +96,7 @@ data class SelfVerificationViewState(
class SelfVerificationViewModel @AssistedInject constructor(
@Assisted private val initialState: SelfVerificationViewState,
private val session: Session,
private val vectorFeatures: VectorFeatures,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val rawService: RawService,
private val stringProvider: StringProvider,
Expand Down Expand Up @@ -140,9 +139,9 @@ class SelfVerificationViewModel @AssistedInject constructor(
// This is async, but at this point should be in cache
// so it's ok to not wait until result
viewModelScope.launch(Dispatchers.IO) {
val wellKnown = rawService.getElementWellknown(session.sessionParams)
// Tchap: force verification when recovery is setup
setState {
copy(isVerificationRequired = wellKnown?.isSecureBackupRequired().orFalse())
copy(isVerificationRequired = session.sharedSecretStorageService().isRecoverySetup())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import im.vector.app.features.raw.wellknown.isSecureBackupRequired
import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import im.vector.app.features.voicebroadcast.recording.usecase.StopOngoingVoiceBroadcastUseCase
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -97,6 +98,7 @@ class HomeActivityViewModel @AssistedInject constructor(
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase,
private val ensureSessionSyncingUseCase: EnsureSessionSyncingUseCase,
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
private val coroutineDispatchers: CoroutineDispatchers,
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {

Expand Down Expand Up @@ -391,7 +393,8 @@ class HomeActivityViewModel @AssistedInject constructor(

private fun sessionHasBeenUnverified(elementWellKnown: ElementWellKnown?) {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired()
?: vectorFeatures.tchapIsSecureBackupRequired() // TCHAP force to configure secure backup even if well-known is null
if (isSecureBackupRequired) {
// If 4S is forced, force verification
// for stability cancel all pending verifications?
Expand Down Expand Up @@ -425,7 +428,8 @@ class HomeActivityViewModel @AssistedInject constructor(
}

val elementWellKnown = rawService.getElementWellknown(session.sessionParams)
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired()
?: vectorFeatures.tchapIsSecureBackupRequired() // TCHAP force to configure secure backup even if well-known is null

// In case of account creation, it is already done before
if (initialState.authenticationDescription is AuthenticationDescription.Register) {
Expand Down Expand Up @@ -464,15 +468,24 @@ class HomeActivityViewModel @AssistedInject constructor(
// Is there already cross signing keys here?
val mxCrossSigningInfo = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
if (mxCrossSigningInfo != null) {
if (isSecureBackupRequired && !session.sharedSecretStorageService().isRecoverySetup()) {
// TCHAP Setup 4S and cross-signing if needed
if (isSecureBackupRequired && !session.sharedSecretStorageService().isRecoverySetup() && mxCrossSigningInfo.isTrusted()) {
// If 4S is forced, start the full interactive setup flow
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
} else {
// Cross-signing is already set up for this user, is it trusted?
if (!mxCrossSigningInfo.isTrusted()) {
if (isSecureBackupRequired) {
// If 4S is forced, force verification
_viewEvents.post(HomeActivityViewEvents.ForceVerification(true))
// TCHAP Setup 4S and cross-signing if needed
viewModelScope.launch {
val currentSessionCanBeVerified = checkIfCurrentSessionCanBeVerifiedUseCase.execute()
if (currentSessionCanBeVerified) {
// If 4S is forced, force verification
_viewEvents.post(HomeActivityViewEvents.ForceVerification(true))
} else {
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
}
}
} else {
// we wan't to check if there is a way to actually verify this session,
// that means that there is another session to verify against, or
Expand Down Expand Up @@ -592,11 +605,11 @@ class HomeActivityViewModel @AssistedInject constructor(
private suspend fun CrossSigningService.awaitCrossSigninInitialization(
block: Continuation<UIABaseAuth>.(response: RegistrationFlowResponse, errCode: String?) -> Unit
) {
initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.block(flowResponse, errCode)
}
initializeCrossSigning(
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.block(flowResponse, errCode)
}
)
}
)
}
Loading