diff --git a/app/src/androidTest/java/com/etesync/syncadapter/SSLSocketFactoryCompatTest.java b/app/src/androidTest/java/com/etesync/syncadapter/SSLSocketFactoryCompatTest.java deleted file mode 100644 index 5795da06..00000000 --- a/app/src/androidTest/java/com/etesync/syncadapter/SSLSocketFactoryCompatTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package com.etesync.syncadapter; - -import android.os.Build; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.net.Socket; - -import javax.net.ssl.SSLSocket; - -import at.bitfire.cert4android.CustomCertManager; -import okhttp3.mockwebserver.MockWebServer; - -import static androidx.test.InstrumentationRegistry.getTargetContext; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertTrue; - -public class SSLSocketFactoryCompatTest { - - SSLSocketFactoryCompat factory; - MockWebServer server = new MockWebServer(); - - @Before - public void startServer() throws Exception { - factory = new SSLSocketFactoryCompat(new CustomCertManager(getTargetContext().getApplicationContext(), true)); - server.start(); - } - - @After - public void stopServer() throws Exception { - server.shutdown(); - } - - - @Test - public void testUpgradeTLS() throws IOException { - Socket s = factory.createSocket(server.getHostName(), server.getPort()); - assertTrue(s instanceof SSLSocket); - - SSLSocket ssl = (SSLSocket)s; - assertFalse(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "SSLv3")); - assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1")); - - if (Build.VERSION.SDK_INT >= 16) { - assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1.1")); - assertTrue(org.apache.commons.lang3.ArrayUtils.contains(ssl.getEnabledProtocols(), "TLSv1.2")); - } - } - -} diff --git a/app/src/main/java/com/etesync/syncadapter/App.kt b/app/src/main/java/com/etesync/syncadapter/App.kt index b5138dbd..1ca66da9 100644 --- a/app/src/main/java/com/etesync/syncadapter/App.kt +++ b/app/src/main/java/com/etesync/syncadapter/App.kt @@ -22,7 +22,6 @@ import android.os.StrictMode import android.provider.CalendarContract import android.provider.ContactsContract import androidx.core.content.ContextCompat -import at.bitfire.cert4android.CustomCertManager import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.CalendarStorageException import at.bitfire.vcard4android.ContactsStorageException @@ -38,18 +37,12 @@ import io.requery.Persistable import io.requery.android.sqlite.DatabaseSource import io.requery.meta.EntityModel import io.requery.sql.EntityDataStore -import okhttp3.internal.tls.OkHostnameVerifier import org.acra.ACRA import org.jetbrains.anko.doAsync import java.util.* -import javax.net.ssl.HostnameVerifier class App : Application() { - - var certManager: CustomCertManager? = null - private set - /** * @return [EntityDataStore] single instance for the application. * @@ -70,7 +63,6 @@ class App : Application() { @SuppressLint("HardwareIds") override fun onCreate() { super.onCreate() - reinitCertManager() reinitLogger() StrictMode.enableDefaults() initPrefVersion() @@ -120,22 +112,6 @@ class App : Application() { serviceDB.close() } - fun reinitCertManager() { - if (BuildConfig.customCerts) { - if (certManager != null) - certManager!!.close() - - val dbHelper = ServiceDB.OpenHelper(this) - val settings = Settings(dbHelper.readableDatabase) - - certManager = CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false)) - sslSocketFactoryCompat = SSLSocketFactoryCompat(certManager!!) - hostnameVerifier = certManager!!.hostnameVerifier(OkHostnameVerifier.INSTANCE) - - dbHelper.close() - } - } - fun reinitLogger() { Logger.initialize(this) } @@ -307,16 +283,6 @@ class App : Application() { var appName: String = "EteSync" - var sslSocketFactoryCompat: SSLSocketFactoryCompat? = null - private set - - var hostnameVerifier: HostnameVerifier? = null - private set - - init { - at.bitfire.cert4android.Constants.log = java.util.logging.Logger.getLogger("etesync.cert4android") - } - lateinit var accountType: String private set lateinit var addressBookAccountType: String diff --git a/app/src/main/java/com/etesync/syncadapter/HttpClient.kt b/app/src/main/java/com/etesync/syncadapter/HttpClient.kt index c04659ed..b37c2d68 100644 --- a/app/src/main/java/com/etesync/syncadapter/HttpClient.kt +++ b/app/src/main/java/com/etesync/syncadapter/HttpClient.kt @@ -10,141 +10,227 @@ package com.etesync.syncadapter import android.content.Context import android.os.Build +import android.security.KeyChain +import at.bitfire.cert4android.CertTlsSocketFactory +import at.bitfire.cert4android.CustomCertManager +import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.model.ServiceDB import com.etesync.syncadapter.model.Settings +import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response +import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.logging.HttpLoggingInterceptor +import java.io.File import java.io.IOException import java.net.InetSocketAddress import java.net.Proxy +import java.net.Socket import java.net.URI -import java.text.SimpleDateFormat +import java.security.KeyStore +import java.security.Principal import java.util.* import java.util.concurrent.TimeUnit import java.util.logging.Level +import javax.net.ssl.KeyManager +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509ExtendedKeyManager +import javax.net.ssl.X509TrustManager import java.util.logging.Logger as LoggerType -import com.etesync.syncadapter.log.Logger - -object HttpClient { - private val client = OkHttpClient() - private val userAgentInterceptor = UserAgentInterceptor() - private val userAgent: String +class HttpClient private constructor( + val okHttpClient: OkHttpClient, + private val certManager: CustomCertManager? +): AutoCloseable { - init { - userAgent = "${App.appName}/${BuildConfig.VERSION_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}" - } - - fun create(context: Context?, logger: LoggerType, host: String?, token: String): OkHttpClient { - var builder = defaultBuilder(context, logger) + companion object { + /** [OkHttpClient] singleton to build all clients from */ + val sharedClient = OkHttpClient.Builder() + // set timeouts + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .readTimeout(120, TimeUnit.SECONDS) - // use account settings for authentication - builder = addAuthentication(builder, host, token) + // don't allow redirects by default, because it would break PROPFIND handling + .followRedirects(false) - return builder.build() - } + // add User-Agent to every request + .addNetworkInterceptor(UserAgentInterceptor) - @JvmOverloads - fun create(context: Context?, settings: AccountSettings, logger: LoggerType = Logger.log): OkHttpClient { - return create(context, logger, settings.uri!!.host, settings.authToken) + .build() } - @JvmOverloads - fun create(context: Context?, logger: LoggerType = Logger.log): OkHttpClient { - return defaultBuilder(context, logger).build() + override fun close() { + certManager?.close() } - fun create(context: Context?, uri: URI, authToken: String): OkHttpClient { - return create(context, Logger.log, uri.host, authToken) - } + class Builder( + val context: Context? = null, + accountSettings: AccountSettings? = null, + val logger: java.util.logging.Logger = Logger.log + ) { + private var certManager: CustomCertManager? = null + private var certificateAlias: String? = null + + private val orig = sharedClient.newBuilder() + + init { + // add network logging, if requested + if (logger.isLoggable(Level.FINEST)) { + val loggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { + message -> logger.finest(message) + }) + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + orig.addInterceptor(loggingInterceptor) + } + context?.let { + val dbHelper = ServiceDB.OpenHelper(context) + val settings = Settings(dbHelper.readableDatabase) + val distrustSystemCerts = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false) + + try { + if (settings.getBoolean(App.OVERRIDE_PROXY, false)) { + val address = InetSocketAddress( + settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT), + settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT) + ) + + val proxy = Proxy(Proxy.Type.HTTP, address) + orig.proxy(proxy) + Logger.log.log(Level.INFO, "Using proxy", proxy) + } + } catch (e: Exception) { + Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e) + } finally { + dbHelper.close() + } - private fun defaultBuilder(context: Context?, logger: LoggerType): OkHttpClient.Builder { - val builder = client.newBuilder() + //if (BuildConfig.customCerts) + customCertManager(CustomCertManager(context, !distrustSystemCerts)) + } - // use MemorizingTrustManager to manage self-signed certificates - if (context != null) { - val app = context.applicationContext as App - if (App.sslSocketFactoryCompat != null && app.certManager != null) - builder.sslSocketFactory(App.sslSocketFactoryCompat!!, app.certManager!!) - if (App.hostnameVerifier != null) - builder.hostnameVerifier(App.hostnameVerifier!!) + // use account settings for authentication + accountSettings?.let { + addAuthentication(accountSettings.uri!!.host, accountSettings.authToken) + } } - // set timeouts - builder.connectTimeout(30, TimeUnit.SECONDS) - builder.writeTimeout(30, TimeUnit.SECONDS) - builder.readTimeout(120, TimeUnit.SECONDS) + constructor(context: Context, host: String?, authToken: String): this(context) { + addAuthentication(host, authToken) + } - // custom proxy support - if (context != null) { - val dbHelper = ServiceDB.OpenHelper(context) - try { - val settings = Settings(dbHelper.readableDatabase) - if (settings.getBoolean(App.OVERRIDE_PROXY, false)) { - val address = InetSocketAddress( - settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT), - settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT) - ) - - val proxy = Proxy(Proxy.Type.HTTP, address) - builder.proxy(proxy) - Logger.log.log(Level.INFO, "Using proxy", proxy) + fun withDiskCache(): Builder { + val context = context ?: throw IllegalArgumentException("Context is required to find the cache directory") + for (dir in arrayOf(context.externalCacheDir, context.cacheDir).filterNotNull()) { + if (dir.exists() && dir.canWrite()) { + val cacheDir = File(dir, "HttpClient") + cacheDir.mkdir() + Logger.log.fine("Using disk cache: $cacheDir") + orig.cache(Cache(cacheDir, 10*1024*1024)) + break } - } catch (e: IllegalArgumentException) { - Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e) - } catch (e: NullPointerException) { - Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e) - } finally { - dbHelper.close() } + return this } - // add User-Agent to every request - builder.addNetworkInterceptor(userAgentInterceptor) - - // add network logging, if requested - if (logger.isLoggable(Level.FINEST)) { - val loggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> logger.finest(message) }) - loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY - builder.addInterceptor(loggingInterceptor) + fun customCertManager(manager: CustomCertManager) { + certManager = manager + } + fun setForeground(foreground: Boolean): Builder { + certManager?.appInForeground = foreground + return this } - return builder - } + private fun addAuthentication(host: String?, token: String): Builder { + val authHandler = TokenAuthenticator(host, token) - private fun addAuthentication(builder: OkHttpClient.Builder, host: String?, token: String): OkHttpClient.Builder { - val authHandler = TokenAuthenticator(host, token) + orig.addNetworkInterceptor(authHandler) - return builder.addNetworkInterceptor(authHandler) - } + return this + } - private class TokenAuthenticator internal constructor(internal val host: String?, internal val token: String?) : Interceptor { + private class TokenAuthenticator internal constructor(internal val host: String?, internal val token: String?) : Interceptor { - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - var request = chain.request() - - /* Only add to the host we want. */ - if (host == null || request.url().host() == host) { - if (token != null && request.header(HEADER_AUTHORIZATION) == null) { - request = request.newBuilder() - .header(HEADER_AUTHORIZATION, "Token $token") - .build() + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + + /* Only add to the host we want. */ + if (host == null || request.url().host() == host) { + if (token != null && request.header(HEADER_AUTHORIZATION) == null) { + request = request.newBuilder() + .header(HEADER_AUTHORIZATION, "Token $token") + .build() + } } + + return chain.proceed(request) } - return chain.proceed(request) + companion object { + protected val HEADER_AUTHORIZATION = "Authorization" + } } - companion object { - protected val HEADER_AUTHORIZATION = "Authorization" + fun build(): HttpClient { + val trustManager = certManager ?: { + val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + factory.init(null as KeyStore?) + factory.trustManagers.first() as X509TrustManager + }() + + val hostnameVerifier = certManager?.hostnameVerifier(OkHostnameVerifier.INSTANCE) + ?: OkHostnameVerifier.INSTANCE + + var keyManager: KeyManager? = null + try { + certificateAlias?.let { alias -> + val context = requireNotNull(context) + + // get client certificate and private key + val certs = KeyChain.getCertificateChain(context, alias) ?: return@let + val key = KeyChain.getPrivateKey(context, alias) ?: return@let + logger.fine("Using client certificate $alias for authentication (chain length: ${certs.size})") + + // create Android KeyStore (performs key operations without revealing secret data to DAVx5) + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + + // create KeyManager + keyManager = object: X509ExtendedKeyManager() { + override fun getServerAliases(p0: String?, p1: Array?): Array? = null + override fun chooseServerAlias(p0: String?, p1: Array?, p2: Socket?) = null + + override fun getClientAliases(p0: String?, p1: Array?) = + arrayOf(alias) + + override fun chooseClientAlias(p0: Array?, p1: Array?, p2: Socket?) = + alias + + override fun getCertificateChain(forAlias: String?) = + certs.takeIf { forAlias == alias } + + override fun getPrivateKey(forAlias: String?) = + key.takeIf { forAlias == alias } + } + } + } catch (e: Exception) { + logger.log(Level.SEVERE, "Couldn't set up client certificate authentication", e) + } + + orig.sslSocketFactory(CertTlsSocketFactory(keyManager, trustManager), trustManager) + orig.hostnameVerifier(hostnameVerifier) + + return HttpClient(orig.build(), certManager) } + } - internal class UserAgentInterceptor : Interceptor { + private object UserAgentInterceptor : Interceptor { + private val userAgent = "${App.appName}/${BuildConfig.VERSION_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}" + @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val locale = Locale.getDefault() diff --git a/app/src/main/java/com/etesync/syncadapter/SSLSocketFactoryCompat.kt b/app/src/main/java/com/etesync/syncadapter/SSLSocketFactoryCompat.kt deleted file mode 100644 index e9229044..00000000 --- a/app/src/main/java/com/etesync/syncadapter/SSLSocketFactoryCompat.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package com.etesync.syncadapter - -import android.os.Build -import com.etesync.syncadapter.log.Logger -import java.io.IOException -import java.net.InetAddress -import java.net.Socket -import java.security.GeneralSecurityException -import java.util.* -import javax.net.ssl.SSLContext -import javax.net.ssl.SSLSocket -import javax.net.ssl.SSLSocketFactory -import javax.net.ssl.X509TrustManager - -class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory() { - - private var delegate: SSLSocketFactory? = null - - init { - try { - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, arrayOf(trustManager), null) - delegate = sslContext.socketFactory - } catch (e: GeneralSecurityException) { - throw AssertionError() // The system has no TLS. Just give up. - } - - } - - private fun upgradeTLS(ssl: SSLSocket) { - if (protocols != null) - ssl.enabledProtocols = protocols - - if (cipherSuites != null) - ssl.enabledCipherSuites = cipherSuites - } - - - override fun getDefaultCipherSuites(): Array? { - return cipherSuites - } - - override fun getSupportedCipherSuites(): Array? { - return cipherSuites - } - - @Throws(IOException::class) - override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket { - val ssl = delegate!!.createSocket(s, host, port, autoClose) - if (ssl is SSLSocket) - upgradeTLS(ssl) - return ssl - } - - @Throws(IOException::class) - override fun createSocket(host: String, port: Int): Socket { - val ssl = delegate!!.createSocket(host, port) - if (ssl is SSLSocket) - upgradeTLS(ssl) - return ssl - } - - @Throws(IOException::class) - override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket { - val ssl = delegate!!.createSocket(host, port, localHost, localPort) - if (ssl is SSLSocket) - upgradeTLS(ssl) - return ssl - } - - @Throws(IOException::class) - override fun createSocket(host: InetAddress, port: Int): Socket { - val ssl = delegate!!.createSocket(host, port) - if (ssl is SSLSocket) - upgradeTLS(ssl) - return ssl - } - - @Throws(IOException::class) - override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket { - val ssl = delegate!!.createSocket(address, port, localAddress, localPort) - if (ssl is SSLSocket) - upgradeTLS(ssl) - return ssl - } - - companion object { - // Android 5.0+ (API level 21) provides reasonable default settings - // but it still allows SSLv3 - // https://developer.android.com/reference/javax/net/ssl/SSLSocket.html - var protocols: Array? = null - var cipherSuites: Array? = null - init { - if (Build.VERSION.SDK_INT >= 23) { - // Since Android 6.0 (API level 23), - // - TLSv1.1 and TLSv1.2 is enabled by default - // - SSLv3 is disabled by default - // - all modern ciphers are activated by default - protocols = null - cipherSuites = null - Logger.log.fine("Using device default TLS protocols/ciphers") - } else { - (SSLSocketFactory.getDefault().createSocket() as? SSLSocket)?.use { socket -> - try { - /* set reasonable protocol versions */ - // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0) - // - remove all SSL versions (especially SSLv3) because they're insecure now - val whichProtocols = LinkedList() - for (protocol in socket.supportedProtocols.filterNot { it.contains("SSL", true) }) - whichProtocols += protocol - Logger.log.info("Enabling (only) these TLS protocols: ${whichProtocols.joinToString(", ")}") - protocols = whichProtocols.toTypedArray() - - /* set up reasonable cipher suites */ - val knownCiphers = arrayOf( - // TLS 1.2 - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - // maximum interoperability - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "SSL_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - // additionally - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" - ) - val availableCiphers = socket.supportedCipherSuites - Logger.log.info("Available cipher suites: ${availableCiphers.joinToString(", ")}") - - /* For maximum security, preferredCiphers should *replace* enabled ciphers (thus - * disabling ciphers which are enabled by default, but have become unsecure), but for - * the security level of DAVx5 and maximum compatibility, disabling of insecure - * ciphers should be a server-side task */ - - // for the final set of enabled ciphers, take the ciphers enabled by default, ... - val whichCiphers = LinkedList() - whichCiphers.addAll(socket.enabledCipherSuites) - Logger.log.fine("Cipher suites enabled by default: ${whichCiphers.joinToString(", ")}") - // ... add explicitly allowed ciphers ... - whichCiphers.addAll(knownCiphers) - // ... and keep only those which are actually available - whichCiphers.retainAll(availableCiphers) - - Logger.log.info("Enabling (only) these TLS ciphers: " + whichCiphers.joinToString(", ")) - cipherSuites = whichCiphers.toTypedArray() - } catch (e: IOException) { - Logger.log.severe("Couldn't determine default TLS settings") - } - } - } - } - } -} diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt index 8737d227..914e5527 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt @@ -236,15 +236,7 @@ constructor(context: Context, account: Account, settings: AccountSettings, extra return null } - var resourceClient = HttpClient.create(context) - - // authenticate only against a certain host, and only upon request - // resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password()); - - // allow redirects - resourceClient = resourceClient.newBuilder() - .followRedirects(true) - .build() + val resourceClient = HttpClient.Builder(context).build().okHttpClient try { val response = resourceClient.newCall(Request.Builder() diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt index 29289d73..cf946f94 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt @@ -111,7 +111,7 @@ abstract class SyncAdapterService : Service() { Logger.log.info("Refreshing " + serviceType + " collections of service #" + serviceType.toString()) val settings = AccountSettings(context, account) - val httpClient = HttpClient.create(context, settings) + val httpClient = HttpClient.Builder(context, settings).build().okHttpClient val journalsManager = JournalManager(httpClient, HttpUrl.get(settings.uri!!)!!) diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt index 0a3f9345..fcfe4582 100644 --- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt +++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt @@ -87,7 +87,7 @@ constructor(protected val context: Context, protected val account: Account, prot init { // create HttpClient with given logger - httpClient = HttpClient.create(context, settings) + httpClient = HttpClient.Builder(context, settings).build().okHttpClient data = (context.applicationContext as App).data val serviceEntity = JournalModel.Service.fetch(data, accountName, serviceType) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.kt index f3c895df..45e4e1be 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/AddMemberFragment.kt @@ -60,7 +60,7 @@ class AddMemberFragment : DialogFragment() { private inner class MemberAdd : AsyncTask() { override fun doInBackground(vararg voids: Void): AddResult { try { - val httpClient = HttpClient.create(ctx!!, settings!!) + val httpClient = HttpClient.Builder(ctx, settings).build().okHttpClient val userInfoManager = UserInfoManager(httpClient, remote!!) val userInfo = userInfoManager.fetch(memberEmail) @@ -102,7 +102,7 @@ class AddMemberFragment : DialogFragment() { override fun doInBackground(vararg voids: Void): AddResultSecond { try { val settings = settings!! - val httpClient = HttpClient.create(ctx!!, settings) + val httpClient = HttpClient.Builder(ctx!!, settings).build().okHttpClient val journalsManager = JournalManager(httpClient, remote!!) val data = (ctx!!.applicationContext as App).data diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.kt index 46a60c58..35775402 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/AppSettingsActivity.kt @@ -14,6 +14,7 @@ import android.os.Build import android.os.Bundle import com.google.android.material.snackbar.Snackbar import androidx.preference.* +import at.bitfire.cert4android.CustomCertManager import com.etesync.syncadapter.App import com.etesync.syncadapter.BuildConfig import com.etesync.syncadapter.R @@ -43,7 +44,6 @@ class AppSettingsActivity : BaseActivity() { internal lateinit var settings: Settings internal lateinit var prefResetHints: Preference - internal lateinit var prefResetCertificates: Preference internal lateinit var prefOverrideProxy: SwitchPreferenceCompat internal lateinit var prefDistrustSystemCerts: SwitchPreferenceCompat @@ -125,7 +125,15 @@ class AppSettingsActivity : BaseActivity() { prefDistrustSystemCerts = findPreference("distrust_system_certs") as SwitchPreferenceCompat prefDistrustSystemCerts.isChecked = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false) - prefResetCertificates = findPreference("reset_certificates") + findPreference("reset_certificates").apply { + isVisible = BuildConfig.customCerts + isEnabled = true + onPreferenceClickListener = Preference.OnPreferenceClickListener { + resetCertificates() + false + } + } + val prefChangeNotification = findPreference("show_change_notification") as SwitchPreferenceCompat prefChangeNotification.isChecked = context!!.defaultSharedPreferences.getBoolean(App.CHANGE_NOTIFICATION, true) @@ -143,8 +151,6 @@ class AppSettingsActivity : BaseActivity() { resetHints() else if (preference === prefDistrustSystemCerts) setDistrustSystemCerts(preference.isChecked) - else if (preference === prefResetCertificates) - resetCertificates() else return false return true @@ -157,15 +163,11 @@ class AppSettingsActivity : BaseActivity() { private fun setDistrustSystemCerts(distrust: Boolean) { settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust) - - // re-initialize certificate manager - val app = context!!.applicationContext as App - app.reinitCertManager() } private fun resetCertificates() { - (context!!.applicationContext as App).certManager?.resetCertificates() - Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() + if (CustomCertManager.resetCertificates(activity!!)) + Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() } private inner class LanguageTask internal constructor(private val mListPreference: ListPreference) : AsyncTask() { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt index e93215b1..0bccc10e 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt @@ -15,22 +15,4 @@ open class BaseActivity : AppCompatActivity() { } return false } - - override fun onResume() { - super.onResume() - - val app = applicationContext as App - val certManager = app.certManager - if (certManager != null) - certManager.appInForeground = true - } - - override fun onPause() { - super.onPause() - - val app = applicationContext as App - val certManager = app.certManager - if (certManager != null) - certManager.appInForeground = false - } } \ No newline at end of file diff --git a/app/src/main/java/com/etesync/syncadapter/ui/ChangeEncryptionPasswordActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/ChangeEncryptionPasswordActivity.kt index e18e06a9..794c5bab 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/ChangeEncryptionPasswordActivity.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/ChangeEncryptionPasswordActivity.kt @@ -61,7 +61,7 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() { fun changePasswordDo(old_password: String, new_password: String) { val settings = AccountSettings(this, account) - val httpClient = HttpClient.create(this, settings) + val httpClient = HttpClient.Builder(this, settings).build().okHttpClient doAsync { Logger.log.info("Started deriving old key") diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.kt index db2e5461..5ac00d85 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersListFragment.kt @@ -53,7 +53,7 @@ class CollectionMembersListFragment : ListFragment(), AdapterView.OnItemClickLis asyncTask = doAsync { try { val settings = AccountSettings(context!!, account) - val httpClient = HttpClient.create(context!!, settings) + val httpClient = HttpClient.Builder(context, settings).build().okHttpClient val journalsManager = JournalManager(httpClient, HttpUrl.get(settings.uri!!)!!) val journal = JournalManager.Journal.fakeWithUid(journalEntity.uid) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt index 4976927a..f6fd4b76 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt @@ -101,7 +101,8 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks val settings = AccountSettings(context, account) val principal = HttpUrl.get(settings.uri!!) - val journalManager = JournalManager(HttpClient.create(context, settings), principal!!) + val httpClient = HttpClient.Builder(context, settings).build().okHttpClient + val journalManager = JournalManager(httpClient, principal!!) var uid = info.uid if (uid == null) { diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.kt index 335da8e3..28da708c 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/DeleteCollectionFragment.kt @@ -88,7 +88,8 @@ class DeleteCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks val settings = AccountSettings(context, account) val principal = HttpUrl.get(settings.uri!!) - val journalManager = JournalManager(HttpClient.create(context, settings), principal!!) + val httpClient = HttpClient.Builder(context, settings).build().okHttpClient + val journalManager = JournalManager(httpClient, principal!!) val crypto = Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid!!) journalManager.delete(JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid!!)) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.kt index e3e82e06..1070f144 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/RemoveMemberFragment.kt @@ -27,7 +27,7 @@ class RemoveMemberFragment : DialogFragment() { memberEmail = arguments!!.getString(KEY_MEMBER) try { settings = AccountSettings(context!!, account!!) - httpClient = HttpClient.create(context!!, settings!!) + httpClient = HttpClient.Builder(context, settings).build().okHttpClient } catch (e: InvalidAccountException) { e.printStackTrace() } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/BaseConfigurationFinder.kt b/app/src/main/java/com/etesync/syncadapter/ui/setup/BaseConfigurationFinder.kt index 9797889e..92c13323 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/BaseConfigurationFinder.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/BaseConfigurationFinder.kt @@ -12,6 +12,7 @@ import com.etesync.syncadapter.HttpClient import com.etesync.syncadapter.journalmanager.Crypto import com.etesync.syncadapter.journalmanager.Exceptions import com.etesync.syncadapter.journalmanager.JournalAuthenticator +import com.etesync.syncadapter.log.Logger import com.etesync.syncadapter.log.StringHandler import com.etesync.syncadapter.model.CollectionInfo import okhttp3.HttpUrl @@ -20,22 +21,14 @@ import java.io.IOException import java.io.Serializable import java.net.URI import java.util.* -import java.util.logging.Level -import java.util.logging.Logger class BaseConfigurationFinder(protected val context: Context, protected val credentials: LoginCredentials) { - protected val log: Logger protected val logBuffer = StringHandler() protected var httpClient: OkHttpClient init { - - log = Logger.getLogger("syncadapter.BaseConfigurationFinder") - log.level = Level.FINEST - log.addHandler(logBuffer) - - httpClient = HttpClient.create(context, log) + httpClient = HttpClient.Builder(context).build().okHttpClient } @@ -50,11 +43,11 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred try { authtoken = authenticator.getAuthToken(credentials.userName, credentials.password) } catch (e: Exceptions.HttpException) { - log.warning(e.message) + Logger.log.warning(e.message) failed = true } catch (e: IOException) { - log.warning(e.message) + Logger.log.warning(e.message) failed = true } @@ -69,7 +62,7 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred protected fun findInitialConfiguration(service: CollectionInfo.Type): Configuration.ServiceInfo { // put discovered information here val config = Configuration.ServiceInfo() - log.info("Finding initial " + service.toString() + " service configuration") + Logger.log.info("Finding initial " + service.toString() + " service configuration") return config } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.kt index 75671316..34bcda2f 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupEncryptionFragment.kt @@ -95,7 +95,7 @@ class SetupEncryptionFragment : DialogFragment() { try { val cryptoManager: Crypto.CryptoManager - val httpClient = HttpClient.create(getContext(), config.url, config.authtoken!!) + val httpClient = HttpClient.Builder(context, config.url.host, config.authtoken!!).build().okHttpClient val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(config.url)!!) val userInfo = userInfoManager.fetch(config.userName) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.kt index b4bf4943..80f42a93 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.kt +++ b/app/src/main/java/com/etesync/syncadapter/ui/setup/SetupUserInfoFragment.kt @@ -51,7 +51,7 @@ class SetupUserInfoFragment : DialogFragment() { override fun doInBackground(vararg accounts: Account): SetupUserInfo.SetupUserInfoResult { try { val cryptoManager: Crypto.CryptoManager - val httpClient = HttpClient.create(context, settings) + val httpClient = HttpClient.Builder(context, settings).build().okHttpClient val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(settings.uri!!)!!) var userInfo: UserInfoManager.UserInfo? = userInfoManager.fetch(account.name) diff --git a/cert4android b/cert4android index af1ae810..f57a8ee9 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit af1ae810e8aceddd79fed17e6af8a88cb726bd55 +Subproject commit f57a8ee9b11a74ca48c067b42dc0411259c567c8