From e4903a2548d204c0ec1e9c46dcf519afce64c3ad Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 12 Mar 2026 10:26:17 +0100 Subject: [PATCH 1/5] Implement Hub Host trust validation --- .../presenter/UnlockVaultPresenter.kt | 92 ++++++++++++++++--- .../ui/activity/UnlockVaultActivity.kt | 13 ++- .../dialog/HubCheckHostAuthenticityDialog.kt | 58 ++++++++++++ .../ui/fragment/SettingsFragment.kt | 8 ++ .../dialog_hub_check_host_authenticity.xml | 18 ++++ presentation/src/main/res/values/strings.xml | 12 +++ presentation/src/main/res/xml/preferences.xml | 13 +++ .../util/SharedPreferencesHandler.kt | 32 +++++++ 8 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt create mode 100644 presentation/src/main/res/layout/dialog_hub_check_host_authenticity.xml diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index 6b729c46c..9039b65ed 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -3,6 +3,7 @@ package org.cryptomator.presentation.presenter import android.content.Intent import android.net.Uri import android.os.Handler +import android.widget.Toast import androidx.biometric.BiometricManager import com.google.common.base.Optional import net.openid.appauth.AuthorizationException @@ -50,11 +51,13 @@ import org.cryptomator.presentation.model.ProgressStateModel import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.ui.activity.view.UnlockVaultView import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog +import org.cryptomator.presentation.ui.dialog.HubCheckHostAuthenticityDialog import org.cryptomator.presentation.workflow.ActivityResult import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CryptoMode import java.io.Serializable +import java.net.URI import javax.inject.Inject import timber.log.Timber @@ -75,6 +78,8 @@ class UnlockVaultPresenter @Inject constructor( exceptionMappings: ExceptionHandlers ) : Presenter(exceptionMappings) { + private val trustedCryptomatorCloudDomain = ".cryptomator.cloud" + private var startedUsingPrepareUnlock = false private var retryUnlockHandler: Handler? = null private var pendingUnlock: PendingUnlock? = null @@ -154,22 +159,85 @@ class UnlockVaultPresenter @Inject constructor( else -> {} } } else if (unverifiedVaultConfig.isPresent && unverifiedVaultConfig.get().keyLoadingStrategy() == KeyLoadingStrategy.HUB) { - when (intent.vaultAction()) { - UnlockVaultIntent.VaultAction.UNLOCK -> { - val unverifiedHubVaultConfig = unverifiedVaultConfig.get() as UnverifiedHubVaultConfig - if (hubAuthService == null) { - hubAuthService = AuthorizationService(context()) - } - view?.showProgress(ProgressModel.GENERIC) - unlockHubVault(unverifiedHubVaultConfig, vault) + val unverifiedHubVaultConfig = unverifiedVaultConfig.get() as UnverifiedHubVaultConfig + if (!isConsistentHubConfig(unverifiedHubVaultConfig)) { + Timber.tag("VaultListPresenter").e("Inconsistent hub config detected. Denying access to protect the user.") + Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() + } else if (configContainsAllowedHosts(unverifiedHubVaultConfig)) { + allowedHubHosts(unverifiedHubVaultConfig, vault) + } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { + allowedHubHosts(unverifiedHubVaultConfig, vault) + } else if (isHttpHost(unverifiedHubVaultConfig) && !isLocalhost(unverifiedHubVaultConfig)) { + Timber.tag("VaultListPresenter").e("Denying attempt to connect to hub instance via unencrypted HTTP.") + Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() + } else if (sharedPreferencesHandler.allowUnknownHubHosts()) { + val hostnames = arrayOf(unverifiedHubVaultConfig.apiBaseUrl.authority, unverifiedHubVaultConfig.authEndpoint.authority) + view?.showDialog(HubCheckHostAuthenticityDialog.newInstance(hostnames, unverifiedHubVaultConfig, vault)) + } else { + Timber.tag("VaultListPresenter").e("Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority + ". Check your cryptomator.allowedHubHosts config.") + Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() + } + } + } + + fun allowedHubHosts(unverifiedHubVaultConfig: UnverifiedHubVaultConfig, vault: Vault) { + when (intent.vaultAction()) { + UnlockVaultIntent.VaultAction.UNLOCK -> { + if (hubAuthService == null) { + hubAuthService = AuthorizationService(context()) } - UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> showErrorAndFinish(HubVaultOperationNotSupportedException()) - UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException()) - UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException()) + view?.showProgress(ProgressModel.GENERIC) + unlockHubVault(unverifiedHubVaultConfig, vault) } + UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> showErrorAndFinish(HubVaultOperationNotSupportedException()) + UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException()) + UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException()) } } + private fun isConsistentHubConfig(unverifiedVaultConfig: UnverifiedHubVaultConfig): Boolean { + return getAuthority(unverifiedVaultConfig.tokenEndpoint) == getAuthority(unverifiedVaultConfig.authEndpoint) + } + + private fun isCryptomatorCloud(unverifiedHubVaultConfig: UnverifiedHubVaultConfig): Boolean { + return unverifiedHubVaultConfig.apiBaseUrl.host.endsWith(trustedCryptomatorCloudDomain) + && unverifiedHubVaultConfig.authEndpoint.host.endsWith(trustedCryptomatorCloudDomain) + } + + private fun configContainsAllowedHosts(unverifiedVaultConfig: UnverifiedHubVaultConfig): Boolean { + val allowedHubHosts = sharedPreferencesHandler.getTrustedHubHosts() + return containsAllowedHosts(allowedHubHosts, unverifiedVaultConfig) + } + + private fun containsAllowedHosts(allowedHubHosts: Set, unverifiedVaultConfig: UnverifiedHubVaultConfig): Boolean { + val canonicalHubHost = getAuthority(unverifiedVaultConfig.apiBaseUrl) + val canonicalAuthHost = getAuthority(unverifiedVaultConfig.authEndpoint) + return allowedHubHosts.contains(canonicalHubHost) && allowedHubHosts.contains(canonicalAuthHost); + } + + private fun isHttpHost(unverifiedHubVaultConfig: UnverifiedHubVaultConfig): Boolean { + return "http".equals(unverifiedHubVaultConfig.apiBaseUrl.scheme, ignoreCase = true) || "http".equals(unverifiedHubVaultConfig.authEndpoint.scheme, ignoreCase = true) + } + + private fun isLocalhost(unverifiedHubVaultConfig: UnverifiedHubVaultConfig): Boolean { + return "localhost".equals(unverifiedHubVaultConfig.apiBaseUrl.host, ignoreCase = true) || "localhost".equals(unverifiedHubVaultConfig.authEndpoint.host, ignoreCase = true) + } + + private fun getAuthority(uri: URI): String { + return when(uri.port) { + -1 -> "%s://%s".format(uri.scheme, uri.host) + 80 -> "http://%s".format(uri.host) + 443 -> "https://%s".format(uri.host) + else -> "%s://%s:%s".format(uri.scheme, uri.host, uri.port) + } + } + + fun onHubCheckHostsAllowed(unverifiedHubVaultConfig: UnverifiedHubVaultConfig, vault: Vault) { + sharedPreferencesHandler.addTrustedHubHosts(getAuthority(unverifiedHubVaultConfig.apiBaseUrl)) + sharedPreferencesHandler.addTrustedHubHosts(getAuthority(unverifiedHubVaultConfig.authEndpoint)) + onUnverifiedVaultConfigRetrieved(Optional.of(unverifiedHubVaultConfig), vault) + } + private fun showErrorAndFinish(e: Throwable) { showError(e) finishWithResult(null) @@ -515,7 +583,7 @@ class UnlockVaultPresenter @Inject constructor( @Callback(dispatchResultOkOnly = false) fun changePasswordAfterAuthentication(result: ActivityResult, vault: Vault, unverifiedVaultConfig: UnverifiedVaultConfig, oldPassword: String, newPassword: String) { - if(result.isResultOk) { + if (result.isResultOk) { val cloud = result.getSingleResult(CloudModel::class.java).toCloud() val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build() onChangePasswordClick(VaultModel(vaultWithUpdatedCloud), unverifiedVaultConfig, oldPassword, newPassword) diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt index ed72ddc53..f4fd60797 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt @@ -17,6 +17,7 @@ import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog import org.cryptomator.presentation.ui.dialog.ChangePasswordDialog import org.cryptomator.presentation.ui.dialog.CreateHubDeviceDialog import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog +import org.cryptomator.presentation.ui.dialog.HubCheckHostAuthenticityDialog import org.cryptomator.presentation.ui.dialog.HubLicenseUpgradeRequiredDialog import org.cryptomator.presentation.ui.dialog.HubUserSetupRequiredDialog import org.cryptomator.presentation.ui.dialog.HubVaultAccessForbiddenDialog @@ -38,7 +39,8 @@ class UnlockVaultActivity : BaseActivity(ActivityUnl HubUserSetupRequiredDialog.Callback, // HubVaultArchivedDialog.Callback, // HubLicenseUpgradeRequiredDialog.Callback, // - HubVaultAccessForbiddenDialog.Callback { + HubVaultAccessForbiddenDialog.Callback, // + HubCheckHostAuthenticityDialog.Callback { @Inject lateinit var presenter: UnlockVaultPresenter @@ -188,4 +190,13 @@ class UnlockVaultActivity : BaseActivity(ActivityUnl finish() } + override fun onHubCheckHostsAllowed(unverifiedHubVaultConfig: UnverifiedHubVaultConfig, vault: Vault) { + presenter.onHubCheckHostsAllowed(unverifiedHubVaultConfig, vault) + } + + override fun onHubCheckHostsDenied(unverifiedHubVaultConfig: UnverifiedHubVaultConfig) { + // todo toast + finish() + } + } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt new file mode 100644 index 000000000..0f72c75fb --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt @@ -0,0 +1,58 @@ +package org.cryptomator.presentation.ui.dialog + +import android.content.DialogInterface +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import org.cryptomator.domain.UnverifiedHubVaultConfig +import org.cryptomator.domain.UnverifiedVaultConfig +import org.cryptomator.domain.Vault +import org.cryptomator.generator.Dialog +import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.DialogHubCheckHostAuthenticityBinding + +@Dialog +class HubCheckHostAuthenticityDialog : BaseDialog(DialogHubCheckHostAuthenticityBinding::inflate) { + + interface Callback { + + fun onHubCheckHostsAllowed(unverifiedHubVaultConfig: UnverifiedHubVaultConfig, vault: Vault) + fun onHubCheckHostsDenied(unverifiedHubVaultConfig: UnverifiedHubVaultConfig) + + } + + public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { + val unverifiedHubVaultConfig = requireArguments().getSerializable(UNVERIFIED_VAULT_CONFIG_ARG) as UnverifiedHubVaultConfig + val vault = requireArguments().getSerializable(VAULT_ARG) as Vault + return builder // + .setTitle(R.string.dialog_hub_check_host_authenticity_title) // + .setPositiveButton(requireActivity().getString(R.string.dialog_hub_check_host_authenticity_neutral_button)) { _: DialogInterface, _: Int -> callback?.onHubCheckHostsAllowed(unverifiedHubVaultConfig, vault) } + .setNegativeButton(requireActivity().getString(R.string.dialog_button_cancel)) { _: DialogInterface, _: Int -> callback?.onHubCheckHostsDenied(unverifiedHubVaultConfig) } + .create() + } + + override fun onStart() { + super.onStart() + val dialog = dialog as AlertDialog? + dialog?.setCanceledOnTouchOutside(false) + } + + public override fun setupView() { + val hostnames = requireArguments().getSerializable(HOSTNAMES_ARG) as Array + binding.tvHostnames.text = hostnames.sorted().joinToString(separator = "\n") + } + + companion object { + private const val HOSTNAMES_ARG = "hostnames" + private const val UNVERIFIED_VAULT_CONFIG_ARG = "unverifiedVaultConfig" + private const val VAULT_ARG = "vault" + fun newInstance(hostnames: Array, unverifiedVaultConfig: UnverifiedVaultConfig, vault: Vault): HubCheckHostAuthenticityDialog { + val dialog = HubCheckHostAuthenticityDialog() + val args = Bundle() + args.putSerializable(HOSTNAMES_ARG, hostnames) + args.putSerializable(UNVERIFIED_VAULT_CONFIG_ARG, unverifiedVaultConfig) + args.putSerializable(VAULT_ARG, vault) + dialog.arguments = args + return dialog + } + } +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt index 02a4a9ab4..d049d3277 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/fragment/SettingsFragment.kt @@ -71,6 +71,12 @@ class SettingsFragment : PreferenceFragmentCompatLayout() { true } + private val clearTrustedHubHostsClickListener = Preference.OnPreferenceClickListener { + sharedPreferencesHandler.clearTrustedHubHosts() + Toast.makeText(requireContext(), R.string.notification_cleared_trusted_hosts, Toast.LENGTH_LONG).show() + true + } + private val useAutoPhotoUploadChangedListener = Preference.OnPreferenceChangeListener { _, newValue -> onUseAutoPhotoUploadChanged(TRUE == newValue) true @@ -226,6 +232,7 @@ class SettingsFragment : PreferenceFragmentCompatLayout() { super.onResume() (findPreference(SEND_ERROR_REPORT_ITEM_KEY) as Preference?)?.onPreferenceClickListener = sendErrorReportClickListener (findPreference(LRU_CACHE_CLEAR_ITEM_KEY) as Preference?)?.onPreferenceClickListener = clearCacheClickListener + (findPreference(CLEAR_TRUSTED_HUB_HOSTS) as Preference?)?.onPreferenceClickListener = clearTrustedHubHostsClickListener (findPreference(SharedPreferencesHandler.DEBUG_MODE) as Preference?)?.onPreferenceChangeListener = debugModeChangedListener (findPreference(SharedPreferencesHandler.DISABLE_APP_WHEN_OBSCURED) as Preference?)?.onPreferenceChangeListener = disableAppWhenObscuredChangedListener (findPreference(SharedPreferencesHandler.SECURE_SCREEN) as Preference?)?.onPreferenceChangeListener = disableSecureScreenChangedListener @@ -327,6 +334,7 @@ class SettingsFragment : PreferenceFragmentCompatLayout() { private const val UPDATE_INTERVAL_ITEM_KEY = "updateInterval" private const val DISPLAY_LRU_CACHE_SIZE_ITEM_KEY = "displayLruCacheSize" private const val LRU_CACHE_CLEAR_ITEM_KEY = "lruCacheClear" + private const val CLEAR_TRUSTED_HUB_HOSTS = "clearTrustedHubHosts" } } diff --git a/presentation/src/main/res/layout/dialog_hub_check_host_authenticity.xml b/presentation/src/main/res/layout/dialog_hub_check_host_authenticity.xml new file mode 100644 index 000000000..b3a07349e --- /dev/null +++ b/presentation/src/main/res/layout/dialog_hub_check_host_authenticity.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index b26d4ca70..8ad41fde1 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -51,6 +51,8 @@ Unsupported Hub version. Hub is only supported on Android 12 and above. + Hub is not trustworthy. + @@ -332,6 +334,11 @@ @string/screen_cryptomator_variants_lite_install Switching from or to the main F-Droid repository variant requires a fresh setup of the app. + + Hub + Allow Unknown Hub Hosts + Clear Unknown Hub Hosts + Cancel @@ -558,6 +565,9 @@ Go to Profile @string/dialog_button_cancel + Trust this hosts? + @string/dialog_unable_to_share_positive_button + Cryptomator needs storage access to use local vaults Cryptomator needs storage access to use auto photo upload Cryptomator needs notification permissions to display vault status for example @@ -633,6 +643,8 @@ Authenticating… + Cleared trusted hosts + Cache @string/screen_settings_section_auto_photo_upload_toggle Cache recently accessed files encrypted locally on the device for later reuse when reopened diff --git a/presentation/src/main/res/xml/preferences.xml b/presentation/src/main/res/xml/preferences.xml index ac100058b..da0e0e9b9 100644 --- a/presentation/src/main/res/xml/preferences.xml +++ b/presentation/src/main/res/xml/preferences.xml @@ -52,6 +52,19 @@ + + + + + + + + { + return defaultSharedPreferences + .getStringSet(TRUSTED_HUB_HOSTS, emptySet()) + ?.toSet() ?: emptySet() + } + + fun clearTrustedHubHosts() { + defaultSharedPreferences.edit().putStringSet(TRUSTED_HUB_HOSTS, mutableSetOf()).apply() + } + companion object { private const val SCREEN_LOCK_DIALOG_SHOWN = "askForScreenLockDialogShown" @@ -318,6 +348,8 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen const val BIOMETRIC_AUTHENTICATION = "biometricAuthentication" const val CRYPTOMATOR_VARIANTS = "cryptomatorVariants" const val LICENSES_ACTIVITY = "licensesActivity" + const val TRUSTED_HUB_HOSTS = "trustedHubHosts" + const val ALLOW_UNKNOWN_HUB_HOSTS = "allowUnknownHubHosts" } private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) { From a4888a3b617db49c77d8539dbf3e8ed93bf6f991 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 12 Mar 2026 12:02:17 +0100 Subject: [PATCH 2/5] Always allow to add untrusted Hubs and force https --- .../presenter/UnlockVaultPresenter.kt | 21 +++++++++---------- presentation/src/main/res/xml/preferences.xml | 5 ----- .../util/SharedPreferencesHandler.kt | 9 -------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index 9039b65ed..f8beeabfe 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -163,19 +163,22 @@ class UnlockVaultPresenter @Inject constructor( if (!isConsistentHubConfig(unverifiedHubVaultConfig)) { Timber.tag("VaultListPresenter").e("Inconsistent hub config detected. Denying access to protect the user.") Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() - } else if (configContainsAllowedHosts(unverifiedHubVaultConfig)) { + finish() + } else if (configContainsAllowedHosts(unverifiedHubVaultConfig) && isHttpHost(unverifiedHubVaultConfig)) { allowedHubHosts(unverifiedHubVaultConfig, vault) } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { allowedHubHosts(unverifiedHubVaultConfig, vault) - } else if (isHttpHost(unverifiedHubVaultConfig) && !isLocalhost(unverifiedHubVaultConfig)) { - Timber.tag("VaultListPresenter").e("Denying attempt to connect to hub instance via unencrypted HTTP.") + } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && isHttpHost(unverifiedHubVaultConfig)) { + Timber.tag("VaultListPresenter").e("Cryptomator Cloud with http is not supported.") Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() - } else if (sharedPreferencesHandler.allowUnknownHubHosts()) { - val hostnames = arrayOf(unverifiedHubVaultConfig.apiBaseUrl.authority, unverifiedHubVaultConfig.authEndpoint.authority) + finish() + } else if (!isHttpHost(unverifiedHubVaultConfig)) { + val hostnames = setOf(unverifiedHubVaultConfig.apiBaseUrl.authority, unverifiedHubVaultConfig.authEndpoint.authority).toTypedArray() view?.showDialog(HubCheckHostAuthenticityDialog.newInstance(hostnames, unverifiedHubVaultConfig, vault)) } else { - Timber.tag("VaultListPresenter").e("Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority + ". Check your cryptomator.allowedHubHosts config.") + Timber.tag("VaultListPresenter").e("Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority) Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() + finish() } } } @@ -219,12 +222,8 @@ class UnlockVaultPresenter @Inject constructor( return "http".equals(unverifiedHubVaultConfig.apiBaseUrl.scheme, ignoreCase = true) || "http".equals(unverifiedHubVaultConfig.authEndpoint.scheme, ignoreCase = true) } - private fun isLocalhost(unverifiedHubVaultConfig: UnverifiedHubVaultConfig): Boolean { - return "localhost".equals(unverifiedHubVaultConfig.apiBaseUrl.host, ignoreCase = true) || "localhost".equals(unverifiedHubVaultConfig.authEndpoint.host, ignoreCase = true) - } - private fun getAuthority(uri: URI): String { - return when(uri.port) { + return when (uri.port) { -1 -> "%s://%s".format(uri.scheme, uri.host) 80 -> "http://%s".format(uri.host) 443 -> "https://%s".format(uri.host) diff --git a/presentation/src/main/res/xml/preferences.xml b/presentation/src/main/res/xml/preferences.xml index da0e0e9b9..5ad540813 100644 --- a/presentation/src/main/res/xml/preferences.xml +++ b/presentation/src/main/res/xml/preferences.xml @@ -54,11 +54,6 @@ - - diff --git a/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt b/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt index da283cea5..6b9957666 100644 --- a/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt +++ b/util/src/main/java/org/cryptomator/util/SharedPreferencesHandler.kt @@ -283,14 +283,6 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen return defaultSharedPreferences.getBoolean(MICROSOFT_WORKAROUND, false) } - fun setAllowUnknownHubHosts(enabled: Boolean) { - defaultSharedPreferences.setValue(ALLOW_UNKNOWN_HUB_HOSTS, enabled) - } - - fun allowUnknownHubHosts(): Boolean { - return defaultSharedPreferences.getBoolean(ALLOW_UNKNOWN_HUB_HOSTS, true) - } - fun addTrustedHubHosts(host: String) { val hosts = defaultSharedPreferences .getStringSet(TRUSTED_HUB_HOSTS, emptySet()) @@ -349,7 +341,6 @@ constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListen const val CRYPTOMATOR_VARIANTS = "cryptomatorVariants" const val LICENSES_ACTIVITY = "licensesActivity" const val TRUSTED_HUB_HOSTS = "trustedHubHosts" - const val ALLOW_UNKNOWN_HUB_HOSTS = "allowUnknownHubHosts" } private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) { From 5a7fa9ef2afe025abb8540dfba38adf341f93c29 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 12 Mar 2026 12:26:34 +0100 Subject: [PATCH 3/5] Cleanup --- .../cryptomator/presentation/presenter/UnlockVaultPresenter.kt | 3 ++- .../presentation/ui/activity/UnlockVaultActivity.kt | 1 - .../presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt | 2 +- presentation/src/main/res/values/strings.xml | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index f8beeabfe..cad2a81fd 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -219,7 +219,8 @@ class UnlockVaultPresenter @Inject constructor( } private fun isHttpHost(unverifiedHubVaultConfig: UnverifiedHubVaultConfig): Boolean { - return "http".equals(unverifiedHubVaultConfig.apiBaseUrl.scheme, ignoreCase = true) || "http".equals(unverifiedHubVaultConfig.authEndpoint.scheme, ignoreCase = true) + return "http".equals(unverifiedHubVaultConfig.apiBaseUrl.scheme, ignoreCase = true) + || "http".equals(unverifiedHubVaultConfig.authEndpoint.scheme, ignoreCase = true) } private fun getAuthority(uri: URI): String { diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt index f4fd60797..9d18010b8 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/UnlockVaultActivity.kt @@ -195,7 +195,6 @@ class UnlockVaultActivity : BaseActivity(ActivityUnl } override fun onHubCheckHostsDenied(unverifiedHubVaultConfig: UnverifiedHubVaultConfig) { - // todo toast finish() } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt index 0f72c75fb..62c2615d2 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/HubCheckHostAuthenticityDialog.kt @@ -38,7 +38,7 @@ class HubCheckHostAuthenticityDialog : BaseDialog - binding.tvHostnames.text = hostnames.sorted().joinToString(separator = "\n") + binding.tvHostnames.text = hostnames.sorted().joinToString(separator = "\n") { "• $it" } } companion object { diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 8ad41fde1..1926e1673 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -336,7 +336,6 @@ Hub - Allow Unknown Hub Hosts Clear Unknown Hub Hosts From c3df11a346eff4345daa7c709d28fdc4c615c316 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 12 Mar 2026 12:48:50 +0100 Subject: [PATCH 4/5] Fix HTTP check in AllowedHosts of Hub --- .../cryptomator/presentation/presenter/UnlockVaultPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index cad2a81fd..03973edcc 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -164,7 +164,7 @@ class UnlockVaultPresenter @Inject constructor( Timber.tag("VaultListPresenter").e("Inconsistent hub config detected. Denying access to protect the user.") Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() finish() - } else if (configContainsAllowedHosts(unverifiedHubVaultConfig) && isHttpHost(unverifiedHubVaultConfig)) { + } else if (configContainsAllowedHosts(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { allowedHubHosts(unverifiedHubVaultConfig, vault) } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { allowedHubHosts(unverifiedHubVaultConfig, vault) From b8f841f4881be31bc5994035876904150e5d69e3 Mon Sep 17 00:00:00 2001 From: Julian Raufelder Date: Thu, 12 Mar 2026 12:59:22 +0100 Subject: [PATCH 5/5] Cleanup --- .../presentation/presenter/UnlockVaultPresenter.kt | 8 ++++---- presentation/src/main/res/values/strings.xml | 5 +---- presentation/src/main/res/xml/preferences.xml | 6 +----- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt index 03973edcc..7d3a19210 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/UnlockVaultPresenter.kt @@ -161,7 +161,7 @@ class UnlockVaultPresenter @Inject constructor( } else if (unverifiedVaultConfig.isPresent && unverifiedVaultConfig.get().keyLoadingStrategy() == KeyLoadingStrategy.HUB) { val unverifiedHubVaultConfig = unverifiedVaultConfig.get() as UnverifiedHubVaultConfig if (!isConsistentHubConfig(unverifiedHubVaultConfig)) { - Timber.tag("VaultListPresenter").e("Inconsistent hub config detected. Denying access to protect the user.") + Timber.tag("UnlockVaultPresenter").e("Inconsistent hub config detected. Denying access to protect the user.") Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() finish() } else if (configContainsAllowedHosts(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { @@ -169,14 +169,14 @@ class UnlockVaultPresenter @Inject constructor( } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && !isHttpHost(unverifiedHubVaultConfig)) { allowedHubHosts(unverifiedHubVaultConfig, vault) } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && isHttpHost(unverifiedHubVaultConfig)) { - Timber.tag("VaultListPresenter").e("Cryptomator Cloud with http is not supported.") + Timber.tag("UnlockVaultPresenter").e("Cryptomator Cloud with http is not supported.") Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() finish() } else if (!isHttpHost(unverifiedHubVaultConfig)) { val hostnames = setOf(unverifiedHubVaultConfig.apiBaseUrl.authority, unverifiedHubVaultConfig.authEndpoint.authority).toTypedArray() view?.showDialog(HubCheckHostAuthenticityDialog.newInstance(hostnames, unverifiedHubVaultConfig, vault)) } else { - Timber.tag("VaultListPresenter").e("Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority) + Timber.tag("UnlockVaultPresenter").e("Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority) Toast.makeText(context(), R.string.error_hub_not_trustworthy, Toast.LENGTH_LONG).show() finish() } @@ -517,7 +517,7 @@ class UnlockVaultPresenter @Inject constructor( } override fun onError(e: Throwable) { - Timber.tag("VaultListPresenter").e(e, "Error while removing vault passwords") + Timber.tag("UnlockVaultPresenter").e(e, "Error while removing vault passwords") finishWithResult(null) } }) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 1926e1673..7fdc301d9 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -251,6 +251,7 @@ Update search results while entering the query Search using glob pattern Use glob pattern matching like alice.*.jpg + Reset Unknown Hub Hosts Automatic locking Lock after @@ -334,10 +335,6 @@ @string/screen_cryptomator_variants_lite_install Switching from or to the main F-Droid repository variant requires a fresh setup of the app. - - Hub - Clear Unknown Hub Hosts - Cancel diff --git a/presentation/src/main/res/xml/preferences.xml b/presentation/src/main/res/xml/preferences.xml index 5ad540813..0ac6ba89f 100644 --- a/presentation/src/main/res/xml/preferences.xml +++ b/presentation/src/main/res/xml/preferences.xml @@ -50,13 +50,9 @@ android:summary="@string/screen_settings_cryptomator_variants_summary" android:title="@string/screen_settings_cryptomator_variants_label" /> - - - - + android:title="@string/screen_hub_reset_unknown_hub_hosts_title" />