@@ -3,6 +3,7 @@ package org.cryptomator.presentation.presenter
33import android.content.Intent
44import android.net.Uri
55import android.os.Handler
6+ import android.widget.Toast
67import androidx.biometric.BiometricManager
78import com.google.common.base.Optional
89import net.openid.appauth.AuthorizationException
@@ -50,11 +51,13 @@ import org.cryptomator.presentation.model.ProgressStateModel
5051import org.cryptomator.presentation.model.VaultModel
5152import org.cryptomator.presentation.ui.activity.view.UnlockVaultView
5253import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
54+ import org.cryptomator.presentation.ui.dialog.HubCheckHostAuthenticityDialog
5355import org.cryptomator.presentation.workflow.ActivityResult
5456import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
5557import org.cryptomator.util.SharedPreferencesHandler
5658import org.cryptomator.util.crypto.CryptoMode
5759import java.io.Serializable
60+ import java.net.URI
5861import javax.inject.Inject
5962import timber.log.Timber
6063
@@ -75,6 +78,8 @@ class UnlockVaultPresenter @Inject constructor(
7578 exceptionMappings : ExceptionHandlers
7679) : Presenter<UnlockVaultView>(exceptionMappings) {
7780
81+ private val trustedCryptomatorCloudDomain = " .cryptomator.cloud"
82+
7883 private var startedUsingPrepareUnlock = false
7984 private var retryUnlockHandler: Handler ? = null
8085 private var pendingUnlock: PendingUnlock ? = null
@@ -154,22 +159,85 @@ class UnlockVaultPresenter @Inject constructor(
154159 else -> {}
155160 }
156161 } else if (unverifiedVaultConfig.isPresent && unverifiedVaultConfig.get().keyLoadingStrategy() == KeyLoadingStrategy .HUB ) {
157- when (intent.vaultAction()) {
158- UnlockVaultIntent .VaultAction .UNLOCK -> {
159- val unverifiedHubVaultConfig = unverifiedVaultConfig.get() as UnverifiedHubVaultConfig
160- if (hubAuthService == null ) {
161- hubAuthService = AuthorizationService (context())
162- }
163- view?.showProgress(ProgressModel .GENERIC )
164- unlockHubVault(unverifiedHubVaultConfig, vault)
162+ val unverifiedHubVaultConfig = unverifiedVaultConfig.get() as UnverifiedHubVaultConfig
163+ if (! isConsistentHubConfig(unverifiedHubVaultConfig)) {
164+ Timber .tag(" UnlockVaultPresenter" ).e(" Inconsistent hub config detected. Denying access to protect the user." )
165+ Toast .makeText(context(), R .string.error_hub_not_trustworthy, Toast .LENGTH_LONG ).show()
166+ finish()
167+ } else if (configContainsAllowedHosts(unverifiedHubVaultConfig) && ! isHttpHost(unverifiedHubVaultConfig)) {
168+ allowedHubHosts(unverifiedHubVaultConfig, vault)
169+ } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && ! isHttpHost(unverifiedHubVaultConfig)) {
170+ allowedHubHosts(unverifiedHubVaultConfig, vault)
171+ } else if (isCryptomatorCloud(unverifiedHubVaultConfig) && isHttpHost(unverifiedHubVaultConfig)) {
172+ Timber .tag(" UnlockVaultPresenter" ).e(" Cryptomator Cloud with http is not supported." )
173+ Toast .makeText(context(), R .string.error_hub_not_trustworthy, Toast .LENGTH_LONG ).show()
174+ finish()
175+ } else if (! isHttpHost(unverifiedHubVaultConfig)) {
176+ val hostnames = setOf (unverifiedHubVaultConfig.apiBaseUrl.authority, unverifiedHubVaultConfig.authEndpoint.authority).toTypedArray()
177+ view?.showDialog(HubCheckHostAuthenticityDialog .newInstance(hostnames, unverifiedHubVaultConfig, vault))
178+ } else {
179+ Timber .tag(" UnlockVaultPresenter" ).e(" Cryptomator is not allowed to connect to " + unverifiedHubVaultConfig.apiBaseUrl.authority)
180+ Toast .makeText(context(), R .string.error_hub_not_trustworthy, Toast .LENGTH_LONG ).show()
181+ finish()
182+ }
183+ }
184+ }
185+
186+ fun allowedHubHosts (unverifiedHubVaultConfig : UnverifiedHubVaultConfig , vault : Vault ) {
187+ when (intent.vaultAction()) {
188+ UnlockVaultIntent .VaultAction .UNLOCK -> {
189+ if (hubAuthService == null ) {
190+ hubAuthService = AuthorizationService (context())
165191 }
166- UnlockVaultIntent .VaultAction .UNLOCK_FOR_BIOMETRIC_AUTH -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
167- UnlockVaultIntent .VaultAction .CHANGE_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
168- UnlockVaultIntent .VaultAction .ENCRYPT_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
192+ view?.showProgress(ProgressModel .GENERIC )
193+ unlockHubVault(unverifiedHubVaultConfig, vault)
169194 }
195+ UnlockVaultIntent .VaultAction .UNLOCK_FOR_BIOMETRIC_AUTH -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
196+ UnlockVaultIntent .VaultAction .CHANGE_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
197+ UnlockVaultIntent .VaultAction .ENCRYPT_PASSWORD -> showErrorAndFinish(HubVaultOperationNotSupportedException ())
170198 }
171199 }
172200
201+ private fun isConsistentHubConfig (unverifiedVaultConfig : UnverifiedHubVaultConfig ): Boolean {
202+ return getAuthority(unverifiedVaultConfig.tokenEndpoint) == getAuthority(unverifiedVaultConfig.authEndpoint)
203+ }
204+
205+ private fun isCryptomatorCloud (unverifiedHubVaultConfig : UnverifiedHubVaultConfig ): Boolean {
206+ return unverifiedHubVaultConfig.apiBaseUrl.host.endsWith(trustedCryptomatorCloudDomain)
207+ && unverifiedHubVaultConfig.authEndpoint.host.endsWith(trustedCryptomatorCloudDomain)
208+ }
209+
210+ private fun configContainsAllowedHosts (unverifiedVaultConfig : UnverifiedHubVaultConfig ): Boolean {
211+ val allowedHubHosts = sharedPreferencesHandler.getTrustedHubHosts()
212+ return containsAllowedHosts(allowedHubHosts, unverifiedVaultConfig)
213+ }
214+
215+ private fun containsAllowedHosts (allowedHubHosts : Set <String >, unverifiedVaultConfig : UnverifiedHubVaultConfig ): Boolean {
216+ val canonicalHubHost = getAuthority(unverifiedVaultConfig.apiBaseUrl)
217+ val canonicalAuthHost = getAuthority(unverifiedVaultConfig.authEndpoint)
218+ return allowedHubHosts.contains(canonicalHubHost) && allowedHubHosts.contains(canonicalAuthHost);
219+ }
220+
221+ private fun isHttpHost (unverifiedHubVaultConfig : UnverifiedHubVaultConfig ): Boolean {
222+ return " http" .equals(unverifiedHubVaultConfig.apiBaseUrl.scheme, ignoreCase = true )
223+ || " http" .equals(unverifiedHubVaultConfig.authEndpoint.scheme, ignoreCase = true )
224+ }
225+
226+ private fun getAuthority (uri : URI ): String {
227+ return when (uri.port) {
228+ - 1 -> " %s://%s" .format(uri.scheme, uri.host)
229+ 80 -> " http://%s" .format(uri.host)
230+ 443 -> " https://%s" .format(uri.host)
231+ else -> " %s://%s:%s" .format(uri.scheme, uri.host, uri.port)
232+ }
233+ }
234+
235+ fun onHubCheckHostsAllowed (unverifiedHubVaultConfig : UnverifiedHubVaultConfig , vault : Vault ) {
236+ sharedPreferencesHandler.addTrustedHubHosts(getAuthority(unverifiedHubVaultConfig.apiBaseUrl))
237+ sharedPreferencesHandler.addTrustedHubHosts(getAuthority(unverifiedHubVaultConfig.authEndpoint))
238+ onUnverifiedVaultConfigRetrieved(Optional .of(unverifiedHubVaultConfig), vault)
239+ }
240+
173241 private fun showErrorAndFinish (e : Throwable ) {
174242 showError(e)
175243 finishWithResult(null )
@@ -449,7 +517,7 @@ class UnlockVaultPresenter @Inject constructor(
449517 }
450518
451519 override fun onError (e : Throwable ) {
452- Timber .tag(" VaultListPresenter " ).e(e, " Error while removing vault passwords" )
520+ Timber .tag(" UnlockVaultPresenter " ).e(e, " Error while removing vault passwords" )
453521 finishWithResult(null )
454522 }
455523 })
@@ -515,7 +583,7 @@ class UnlockVaultPresenter @Inject constructor(
515583
516584 @Callback(dispatchResultOkOnly = false )
517585 fun changePasswordAfterAuthentication (result : ActivityResult , vault : Vault , unverifiedVaultConfig : UnverifiedVaultConfig , oldPassword : String , newPassword : String ) {
518- if (result.isResultOk) {
586+ if (result.isResultOk) {
519587 val cloud = result.getSingleResult(CloudModel ::class .java).toCloud()
520588 val vaultWithUpdatedCloud = Vault .aCopyOf(vault).withCloud(cloud).build()
521589 onChangePasswordClick(VaultModel (vaultWithUpdatedCloud), unverifiedVaultConfig, oldPassword, newPassword)
0 commit comments