Merge pull request #2671 from nextcloud/fix-trust-manager

Split different exception scopes / Use own OkHttp client for coil
This commit is contained in:
Marcel Hibbe 2023-03-14 19:59:09 +01:00 committed by GitHub
commit 4c64503cc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 62 deletions

View File

@ -36,7 +36,7 @@ import com.nextcloud.talk.events.CertificateEvent
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.SecurityUtils import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.ssl.MagicTrustManager import com.nextcloud.talk.utils.ssl.TrustManager
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
@ -81,7 +81,7 @@ open class BaseActivity : AppCompatActivity() {
fun showCertificateDialog( fun showCertificateDialog(
cert: X509Certificate, cert: X509Certificate,
magicTrustManager: MagicTrustManager, trustManager: TrustManager,
sslErrorHandler: SslErrorHandler? sslErrorHandler: SslErrorHandler?
) { ) {
val formatter = DateFormat.getDateInstance(DateFormat.LONG) val formatter = DateFormat.getDateInstance(DateFormat.LONG)
@ -121,7 +121,7 @@ open class BaseActivity : AppCompatActivity() {
.setTitle(R.string.nc_certificate_dialog_title) .setTitle(R.string.nc_certificate_dialog_title)
.setMessage(dialogText) .setMessage(dialogText)
.setPositiveButton(R.string.nc_yes) { _, _ -> .setPositiveButton(R.string.nc_yes) { _, _ ->
magicTrustManager.addCertInTrustStore(cert) trustManager.addCertInTrustStore(cert)
sslErrorHandler?.proceed() sslErrorHandler?.proceed()
} }
.setNegativeButton(R.string.nc_no) { _, _ -> .setNegativeButton(R.string.nc_no) { _, _ ->

View File

@ -255,6 +255,8 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
imageLoaderBuilder.logger(DebugLogger()) imageLoaderBuilder.logger(DebugLogger())
} }
imageLoaderBuilder.okHttpClient(okHttpClient)
return imageLoaderBuilder.build() return imageLoaderBuilder.build()
} }

View File

@ -52,7 +52,7 @@ public class ReadFilesystemOperation {
okHttpClientBuilder.followRedirects(false); okHttpClientBuilder.followRedirects(false);
okHttpClientBuilder.followSslRedirects(false); okHttpClientBuilder.followSslRedirects(false);
okHttpClientBuilder.authenticator( okHttpClientBuilder.authenticator(
new RestModule.MagicAuthenticator( new RestModule.HttpAuthenticator(
ApiUtils.getCredentials( ApiUtils.getCredentials(
currentUser.getUsername(), currentUser.getUsername(),
currentUser.getToken() currentUser.getToken()

View File

@ -40,7 +40,7 @@ import com.nextcloud.talk.components.filebrowser.models.properties.NCPreview
import com.nextcloud.talk.components.filebrowser.models.properties.OCFavorite import com.nextcloud.talk.components.filebrowser.models.properties.OCFavorite
import com.nextcloud.talk.components.filebrowser.models.properties.OCId import com.nextcloud.talk.components.filebrowser.models.properties.OCId
import com.nextcloud.talk.components.filebrowser.models.properties.OCSize import com.nextcloud.talk.components.filebrowser.models.properties.OCSize
import com.nextcloud.talk.dagger.modules.RestModule.MagicAuthenticator import com.nextcloud.talk.dagger.modules.RestModule.HttpAuthenticator
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem import com.nextcloud.talk.remotefilebrowser.model.RemoteFileBrowserItem
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -61,7 +61,7 @@ class ReadFolderListingOperation(okHttpClient: OkHttpClient, currentUser: User,
okHttpClientBuilder.followRedirects(false) okHttpClientBuilder.followRedirects(false)
okHttpClientBuilder.followSslRedirects(false) okHttpClientBuilder.followSslRedirects(false)
okHttpClientBuilder.authenticator( okHttpClientBuilder.authenticator(
MagicAuthenticator( HttpAuthenticator(
ApiUtils.getCredentials( ApiUtils.getCredentials(
currentUser.username, currentUser.username,
currentUser.token currentUser.token

View File

@ -64,7 +64,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.nextcloud.talk.utils.ssl.MagicTrustManager import com.nextcloud.talk.utils.ssl.TrustManager
import de.cotech.hw.fido.WebViewFidoBridge import de.cotech.hw.fido.WebViewFidoBridge
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -88,7 +88,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
lateinit var userManager: UserManager lateinit var userManager: UserManager
@Inject @Inject
lateinit var magicTrustManager: MagicTrustManager lateinit var trustManager: TrustManager
@Inject @Inject
lateinit var eventBus: EventBus lateinit var eventBus: EventBus
@ -288,10 +288,10 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
handler.cancel() handler.cancel()
} else { } else {
try { try {
magicTrustManager.checkServerTrusted(arrayOf(cert), "generic") trustManager.checkServerTrusted(arrayOf(cert), "generic")
handler.proceed() handler.proceed()
} catch (exception: CertificateException) { } catch (exception: CertificateException) {
eventBus.post(CertificateEvent(cert, magicTrustManager, handler)) eventBus.post(CertificateEvent(cert, trustManager, handler))
} }
} }
} catch (exception: Exception) { } catch (exception: Exception) {

View File

@ -32,9 +32,9 @@ import com.nextcloud.talk.users.UserManager;
import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.LoggingUtils; import com.nextcloud.talk.utils.LoggingUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences; import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.ssl.MagicKeyManager; import com.nextcloud.talk.utils.ssl.KeyManager;
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat; import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat;
import com.nextcloud.talk.utils.ssl.TrustManager;
import java.io.IOException; import java.io.IOException;
import java.net.CookieManager; import java.net.CookieManager;
@ -121,13 +121,13 @@ public class RestModule {
@Singleton @Singleton
@Provides @Provides
MagicTrustManager provideMagicTrustManager() { TrustManager provideTrustManager() {
return new MagicTrustManager(); return new TrustManager();
} }
@Singleton @Singleton
@Provides @Provides
MagicKeyManager provideKeyManager(AppPreferences appPreferences, UserManager userManager) { KeyManager provideKeyManager(AppPreferences appPreferences, UserManager userManager) {
KeyStore keyStore = null; KeyStore keyStore = null;
try { try {
keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore = KeyStore.getInstance("AndroidKeyStore");
@ -135,7 +135,7 @@ public class RestModule {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null); kmf.init(keyStore, null);
X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0]; X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
return new MagicKeyManager(origKm, userManager, appPreferences); return new KeyManager(origKm, userManager, appPreferences);
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage()); Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage());
} catch (CertificateException e) { } catch (CertificateException e) {
@ -153,9 +153,9 @@ public class RestModule {
@Singleton @Singleton
@Provides @Provides
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicKeyManager keyManager, MagicTrustManager SSLSocketFactoryCompat provideSslSocketFactoryCompat(KeyManager keyManager, TrustManager
magicTrustManager) { trustManager) {
return new SSLSocketFactoryCompat(keyManager, magicTrustManager); return new SSLSocketFactoryCompat(keyManager, trustManager);
} }
@Singleton @Singleton
@ -184,7 +184,7 @@ public class RestModule {
@Singleton @Singleton
@Provides @Provides
OkHttpClient provideHttpClient(Proxy proxy, AppPreferences appPreferences, OkHttpClient provideHttpClient(Proxy proxy, AppPreferences appPreferences,
MagicTrustManager magicTrustManager, TrustManager trustManager,
SSLSocketFactoryCompat sslSocketFactoryCompat, Cache cache, SSLSocketFactoryCompat sslSocketFactoryCompat, Cache cache,
CookieManager cookieManager, Dispatcher dispatcher) { CookieManager cookieManager, Dispatcher dispatcher) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
@ -198,9 +198,9 @@ public class RestModule {
httpClient.cache(cache); httpClient.cache(cache);
// Trust own CA and all self-signed certs // Trust own CA and all self-signed certs
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager); httpClient.sslSocketFactory(sslSocketFactoryCompat, trustManager);
httpClient.retryOnConnectionFailure(true); httpClient.retryOnConnectionFailure(true);
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE)); httpClient.hostnameVerifier(trustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE));
httpClient.dispatcher(dispatcher); httpClient.dispatcher(dispatcher);
if (!Proxy.NO_PROXY.equals(proxy)) { if (!Proxy.NO_PROXY.equals(proxy)) {
@ -209,7 +209,7 @@ public class RestModule {
if (appPreferences.getProxyCredentials() && if (appPreferences.getProxyCredentials() &&
!TextUtils.isEmpty(appPreferences.getProxyUsername()) && !TextUtils.isEmpty(appPreferences.getProxyUsername()) &&
!TextUtils.isEmpty(appPreferences.getProxyPassword())) { !TextUtils.isEmpty(appPreferences.getProxyPassword())) {
httpClient.proxyAuthenticator(new MagicAuthenticator(Credentials.basic( httpClient.proxyAuthenticator(new HttpAuthenticator(Credentials.basic(
appPreferences.getProxyUsername(), appPreferences.getProxyUsername(),
appPreferences.getProxyPassword()), "Proxy-Authorization")); appPreferences.getProxyPassword()), "Proxy-Authorization"));
} }
@ -253,12 +253,12 @@ public class RestModule {
} }
} }
public static class MagicAuthenticator implements Authenticator { public static class HttpAuthenticator implements Authenticator {
private String credentials; private String credentials;
private String authenticatorType; private String authenticatorType;
public MagicAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) { public HttpAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) {
this.credentials = credentials; this.credentials = credentials;
this.authenticatorType = authenticatorType; this.authenticatorType = authenticatorType;
} }

View File

@ -21,21 +21,23 @@
package com.nextcloud.talk.events; package com.nextcloud.talk.events;
import android.webkit.SslErrorHandler; import android.webkit.SslErrorHandler;
import androidx.annotation.Nullable;
import com.nextcloud.talk.utils.ssl.MagicTrustManager; import com.nextcloud.talk.utils.ssl.TrustManager;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import androidx.annotation.Nullable;
public class CertificateEvent { public class CertificateEvent {
private final X509Certificate x509Certificate; private final X509Certificate x509Certificate;
private final MagicTrustManager magicTrustManager; private final TrustManager trustManager;
@Nullable @Nullable
private final SslErrorHandler sslErrorHandler; private final SslErrorHandler sslErrorHandler;
public CertificateEvent(X509Certificate x509Certificate, MagicTrustManager magicTrustManager, public CertificateEvent(X509Certificate x509Certificate, TrustManager trustManager,
@Nullable SslErrorHandler sslErrorHandler) { @Nullable SslErrorHandler sslErrorHandler) {
this.x509Certificate = x509Certificate; this.x509Certificate = x509Certificate;
this.magicTrustManager = magicTrustManager; this.trustManager = trustManager;
this.sslErrorHandler = sslErrorHandler; this.sslErrorHandler = sslErrorHandler;
} }
@ -48,7 +50,7 @@ public class CertificateEvent {
return x509Certificate; return x509Certificate;
} }
public MagicTrustManager getMagicTrustManager() { public TrustManager getMagicTrustManager() {
return magicTrustManager; return trustManager;
} }
} }

View File

@ -295,7 +295,7 @@ class ChunkedFileUploader(
// okHttpClientBuilder.readTimeout(Duration.ofMinutes(30)) // TODO set timeout // okHttpClientBuilder.readTimeout(Duration.ofMinutes(30)) // TODO set timeout
okHttpClientBuilder.protocols(listOf(Protocol.HTTP_1_1)) okHttpClientBuilder.protocols(listOf(Protocol.HTTP_1_1))
okHttpClientBuilder.authenticator( okHttpClientBuilder.authenticator(
RestModule.MagicAuthenticator( RestModule.HttpAuthenticator(
ApiUtils.getCredentials( ApiUtils.getCredentials(
currentUser.username, currentUser.username,
currentUser.token currentUser.token

View File

@ -45,15 +45,15 @@ import javax.net.ssl.X509KeyManager;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
public class MagicKeyManager implements X509KeyManager { public class KeyManager implements X509KeyManager {
private static final String TAG = "MagicKeyManager"; private static final String TAG = "KeyManager";
private final X509KeyManager keyManager; private final X509KeyManager keyManager;
private UserManager userManager; private UserManager userManager;
private AppPreferences appPreferences; private AppPreferences appPreferences;
private Context context; private Context context;
public MagicKeyManager(X509KeyManager keyManager, UserManager userManager, AppPreferences appPreferences) { public KeyManager(X509KeyManager keyManager, UserManager userManager, AppPreferences appPreferences) {
this.keyManager = keyManager; this.keyManager = keyManager;
this.userManager = userManager; this.userManager = userManager;
this.appPreferences = appPreferences; this.appPreferences = appPreferences;

View File

@ -38,29 +38,36 @@ import java.security.KeyStoreException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
public class MagicTrustManager implements X509TrustManager { public class TrustManager implements X509TrustManager {
private static final String TAG = "MagicTrustManager"; private static final String TAG = "TrustManager";
private File keystoreFile; private File keystoreFile;
private X509TrustManager systemTrustManager = null; private X509TrustManager systemTrustManager = null;
private KeyStore trustedKeyStore = null; private KeyStore trustedKeyStore = null;
public MagicTrustManager() { public TrustManager() {
keystoreFile = new File(NextcloudTalkApplication.Companion.getSharedApplication().getDir("CertsKeystore", keystoreFile = new File(NextcloudTalkApplication.Companion.getSharedApplication()
Context.MODE_PRIVATE), "keystore.bks"); .getDir("CertsKeystore", Context.MODE_PRIVATE),
"keystore.bks");
try (FileInputStream fileInputStream = new FileInputStream(keystoreFile)) { try {
trustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustedKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedKeyStore.load(fileInputStream, null); } catch (KeyStoreException e) {
} catch (Exception exception) { Log.e(TAG, "Trusted key store can't be created.", e);
}
if (keystoreFile.exists()) {
try (FileInputStream fileInputStream = new FileInputStream(keystoreFile)) {
trustedKeyStore.load(fileInputStream, null);
} catch (Exception exception) {
Log.e(TAG, "Error during opening the trusted key store.", exception);
}
} else {
try { try {
trustedKeyStore.load(null, null); trustedKeyStore.load(null, null);
} catch (Exception e) { } catch (Exception e) {
@ -71,11 +78,11 @@ public class MagicTrustManager implements X509TrustManager {
TrustManagerFactory trustManagerFactory = null; TrustManagerFactory trustManagerFactory = null;
try { try {
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory. trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.
getDefaultAlgorithm()); getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null); trustManagerFactory.init((KeyStore) null);
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { for (javax.net.ssl.TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) { if (trustManager instanceof X509TrustManager) {
systemTrustManager = (X509TrustManager) trustManager; systemTrustManager = (X509TrustManager) trustManager;
break; break;
@ -88,8 +95,8 @@ public class MagicTrustManager implements X509TrustManager {
} }
public HostnameVerifier getHostnameVerifier(HostnameVerifier defaultHostNameVerifier) { public javax.net.ssl.HostnameVerifier getHostnameVerifier(javax.net.ssl.HostnameVerifier defaultHostNameVerifier) {
return new MagicHostnameVerifier(defaultHostNameVerifier); return new HostnameVerifier(defaultHostNameVerifier);
} }
private boolean isCertInTrustStore(X509Certificate[] x509Certificates, String s) { private boolean isCertInTrustStore(X509Certificate[] x509Certificates, String s) {
@ -99,15 +106,15 @@ public class MagicTrustManager implements X509TrustManager {
systemTrustManager.checkServerTrusted(x509Certificates, s); systemTrustManager.checkServerTrusted(x509Certificates, s);
return true; return true;
} catch (CertificateException e) { } catch (CertificateException e) {
if (!isCertInMagicTrustStore(x509Certificate)) { if (!isCertInTrustStore(x509Certificate)) {
EventBus.getDefault().post(new CertificateEvent(x509Certificate, this, EventBus.getDefault().post(new CertificateEvent(x509Certificate, this,
null)); null));
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
while (!isCertInMagicTrustStore(x509Certificate) && System.currentTimeMillis() <= while (!isCertInTrustStore(x509Certificate) && System.currentTimeMillis() <=
startTime + 15000) { startTime + 15000) {
//do nothing //do nothing
} }
return isCertInMagicTrustStore(x509Certificate); return isCertInTrustStore(x509Certificate);
} else { } else {
return true; return true;
} }
@ -117,7 +124,7 @@ public class MagicTrustManager implements X509TrustManager {
return false; return false;
} }
private boolean isCertInMagicTrustStore(X509Certificate x509Certificate) { private boolean isCertInTrustStore(X509Certificate x509Certificate) {
if (trustedKeyStore != null) { if (trustedKeyStore != null) {
try { try {
if (trustedKeyStore.getCertificateAlias(x509Certificate) != null) { if (trustedKeyStore.getCertificateAlias(x509Certificate) != null) {
@ -159,11 +166,11 @@ public class MagicTrustManager implements X509TrustManager {
return systemTrustManager.getAcceptedIssuers(); return systemTrustManager.getAcceptedIssuers();
} }
private class MagicHostnameVerifier implements HostnameVerifier { private class HostnameVerifier implements javax.net.ssl.HostnameVerifier {
private static final String TAG = "MagicHostnameVerifier"; private static final String TAG = "HostnameVerifier";
private HostnameVerifier defaultHostNameVerifier; private javax.net.ssl.HostnameVerifier defaultHostNameVerifier;
private MagicHostnameVerifier(HostnameVerifier defaultHostNameVerifier) { private HostnameVerifier(javax.net.ssl.HostnameVerifier defaultHostNameVerifier) {
this.defaultHostNameVerifier = defaultHostNameVerifier; this.defaultHostNameVerifier = defaultHostNameVerifier;
} }

View File

@ -452,7 +452,7 @@ class WebSocketInstance internal constructor(
} }
companion object { companion object {
private const val TAG = "MagicWebSocketInstance" private const val TAG = "WebSocketInstance"
private const val NORMAL_CLOSURE = 1000 private const val NORMAL_CLOSURE = 1000
} }
} }