Merge pull request #1662 from vector-im/feature/manage_4s_setting

4S settings screen
This commit is contained in:
Valere 2020-07-11 18:39:33 +02:00 committed by GitHub
commit a08a1d4f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 313 additions and 44 deletions

View File

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

View File

@ -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<SecretsSynchronisationInfo> {
return Observable.combineLatest<List<UserAccountData>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, 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 {

View File

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

View File

@ -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<String>, keyId: String?): IntegrityResult
fun requestSecret(name: String, myOtherDeviceId: String)

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
/* ==========================================================================================

View File

@ -185,7 +185,6 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
AlertDialog.Builder(it)
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
}
},
{

View File

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

View File

@ -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<Boolean> {
session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it)
}
if (isValid) {
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
awaitCallback<Unit> {
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")

View File

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

View File

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

View File

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

View File

@ -146,7 +146,7 @@ class VerificationRequestController @Inject constructor(
}
}
if (state.isMe && state.currentDeviceCanCrossSign) {
if (state.isMe && state.currentDeviceCanCrossSign && !state.selfVerificationMode) {
dividerItem {
id("sep_notMe")
}

View File

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

View File

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

View File

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

View File

@ -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<Disposable>()
// 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<VectorPreferenceCategory>("SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY")!!
}
private val secureBackupPreference by lazy {
findPreference<VectorPreference>("SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY")!!
}
// private val secureBackupResetPreference by lazy {
// findPreference<VectorPreference>(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

View File

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

View File

@ -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 = {

View File

@ -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" />
<TextView
android:id="@+id/bootstrapSetupWarningTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:text="@string/reset_secure_backup_warning"
android:textColor="@color/riotx_destructive_accent"
android:drawableStart="@drawable/ic_warning_small"
android:drawablePadding="4dp"
android:textSize="14sp" />
</LinearLayout>

View File

@ -843,6 +843,15 @@
<string name="settings_send_message_with_enter">Send message with enter</string>
<string name="settings_send_message_with_enter_summary">Enter button of the soft keyboard will send message instead of adding a line break</string>
<string name="settings_secure_backup_section_title">Secure Backup</string>
<string name="settings_secure_backup_manage">Manage</string>
<string name="settings_secure_backup_setup">Set up Secure Backup</string>
<string name="settings_secure_backup_reset">Reset Secure Backup</string>
<string name="settings_secure_backup_enter_to_setup">Set up on this device</string>
<string name="settings_secure_backup_section_info">Safeguard against losing access to encrypted messages &amp; data by backing up encryption keys on your server.</string>
<string name="reset_secure_backup_title">Generate a new Security Key or set a new Security Phrase for your existing backup.</string>
<string name="reset_secure_backup_warning">This will replace your current Key or Phrase.</string>
<string name="settings_deactivate_account_section">Deactivate account</string>
<string name="settings_deactivate_my_account">Deactivate my account</string>
<string name="settings_discovery_category">Discovery</string>

View File

@ -48,6 +48,23 @@
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY"
android:title="@string/settings_secure_backup_section_title">
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY"
android:persistent="false"
android:icon="@drawable/ic_secure_backup"
android:title="@string/settings_secure_backup_setup" />
<im.vector.riotx.core.preference.VectorPreference
android:focusable="false"
android:persistent="false"
android:summary="@string/settings_secure_backup_section_info" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
android:title="@string/settings_cryptography_manage_keys">