1
0
mirror of https://github.com/etesync/android synced 2024-12-23 23:18:46 +00:00

Upgrade cert4android and refactor httpclient based on upstream

This commit is contained in:
Tom Hacohen 2019-03-15 09:50:04 +00:00
parent 75020c1841
commit 4134f78da4
19 changed files with 214 additions and 421 deletions

View File

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

View File

@ -22,7 +22,6 @@ import android.os.StrictMode
import android.provider.CalendarContract import android.provider.CalendarContract
import android.provider.ContactsContract import android.provider.ContactsContract
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.CalendarStorageException import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.vcard4android.ContactsStorageException import at.bitfire.vcard4android.ContactsStorageException
@ -38,18 +37,12 @@ import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource import io.requery.android.sqlite.DatabaseSource
import io.requery.meta.EntityModel import io.requery.meta.EntityModel
import io.requery.sql.EntityDataStore import io.requery.sql.EntityDataStore
import okhttp3.internal.tls.OkHostnameVerifier
import org.acra.ACRA import org.acra.ACRA
import org.jetbrains.anko.doAsync import org.jetbrains.anko.doAsync
import java.util.* import java.util.*
import javax.net.ssl.HostnameVerifier
class App : Application() { class App : Application() {
var certManager: CustomCertManager? = null
private set
/** /**
* @return [EntityDataStore] single instance for the application. * @return [EntityDataStore] single instance for the application.
* *
@ -70,7 +63,6 @@ class App : Application() {
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
reinitCertManager()
reinitLogger() reinitLogger()
StrictMode.enableDefaults() StrictMode.enableDefaults()
initPrefVersion() initPrefVersion()
@ -120,22 +112,6 @@ class App : Application() {
serviceDB.close() 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() { fun reinitLogger() {
Logger.initialize(this) Logger.initialize(this)
} }
@ -307,16 +283,6 @@ class App : Application() {
var appName: String = "EteSync" 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 lateinit var accountType: String
private set private set
lateinit var addressBookAccountType: String lateinit var addressBookAccountType: String

View File

@ -10,79 +10,87 @@ package com.etesync.syncadapter
import android.content.Context import android.content.Context
import android.os.Build 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.ServiceDB
import com.etesync.syncadapter.model.Settings import com.etesync.syncadapter.model.Settings
import okhttp3.Cache
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.net.Socket
import java.net.URI import java.net.URI
import java.text.SimpleDateFormat import java.security.KeyStore
import java.security.Principal
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.logging.Level 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 java.util.logging.Logger as LoggerType
import com.etesync.syncadapter.log.Logger
object HttpClient { class HttpClient private constructor(
private val client = OkHttpClient() val okHttpClient: OkHttpClient,
private val userAgentInterceptor = UserAgentInterceptor() private val certManager: CustomCertManager?
): AutoCloseable {
private val userAgent: String 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)
// don't allow redirects by default, because it would break PROPFIND handling
.followRedirects(false)
// add User-Agent to every request
.addNetworkInterceptor(UserAgentInterceptor)
.build()
}
override fun close() {
certManager?.close()
}
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 { init {
userAgent = "${App.appName}/${BuildConfig.VERSION_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}" // 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)
} }
fun create(context: Context?, logger: LoggerType, host: String?, token: String): OkHttpClient { context?.let {
var builder = defaultBuilder(context, logger)
// use account settings for authentication
builder = addAuthentication(builder, host, token)
return builder.build()
}
@JvmOverloads
fun create(context: Context?, settings: AccountSettings, logger: LoggerType = Logger.log): OkHttpClient {
return create(context, logger, settings.uri!!.host, settings.authToken)
}
@JvmOverloads
fun create(context: Context?, logger: LoggerType = Logger.log): OkHttpClient {
return defaultBuilder(context, logger).build()
}
fun create(context: Context?, uri: URI, authToken: String): OkHttpClient {
return create(context, Logger.log, uri.host, authToken)
}
private fun defaultBuilder(context: Context?, logger: LoggerType): OkHttpClient.Builder {
val builder = client.newBuilder()
// 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!!)
}
// set timeouts
builder.connectTimeout(30, TimeUnit.SECONDS)
builder.writeTimeout(30, TimeUnit.SECONDS)
builder.readTimeout(120, TimeUnit.SECONDS)
// custom proxy support
if (context != null) {
val dbHelper = ServiceDB.OpenHelper(context) val dbHelper = ServiceDB.OpenHelper(context)
try {
val settings = Settings(dbHelper.readableDatabase) val settings = Settings(dbHelper.readableDatabase)
val distrustSystemCerts = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)
try {
if (settings.getBoolean(App.OVERRIDE_PROXY, false)) { if (settings.getBoolean(App.OVERRIDE_PROXY, false)) {
val address = InetSocketAddress( val address = InetSocketAddress(
settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT), settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT),
@ -90,35 +98,57 @@ object HttpClient {
) )
val proxy = Proxy(Proxy.Type.HTTP, address) val proxy = Proxy(Proxy.Type.HTTP, address)
builder.proxy(proxy) orig.proxy(proxy)
Logger.log.log(Level.INFO, "Using proxy", proxy) Logger.log.log(Level.INFO, "Using proxy", proxy)
} }
} catch (e: IllegalArgumentException) { } catch (e: Exception) {
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) Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e)
} finally { } finally {
dbHelper.close() dbHelper.close()
} }
//if (BuildConfig.customCerts)
customCertManager(CustomCertManager(context, !distrustSystemCerts))
} }
// add User-Agent to every request // use account settings for authentication
builder.addNetworkInterceptor(userAgentInterceptor) accountSettings?.let {
addAuthentication(accountSettings.uri!!.host, accountSettings.authToken)
// 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)
} }
return builder constructor(context: Context, host: String?, authToken: String): this(context) {
addAuthentication(host, authToken)
} }
private fun addAuthentication(builder: OkHttpClient.Builder, host: String?, token: String): OkHttpClient.Builder { 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
}
}
return this
}
fun customCertManager(manager: CustomCertManager) {
certManager = manager
}
fun setForeground(foreground: Boolean): Builder {
certManager?.appInForeground = foreground
return this
}
private fun addAuthentication(host: String?, token: String): Builder {
val authHandler = TokenAuthenticator(host, token) val authHandler = TokenAuthenticator(host, token)
return builder.addNetworkInterceptor(authHandler) orig.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 {
@ -144,7 +174,63 @@ object HttpClient {
} }
} }
internal class UserAgentInterceptor : Interceptor { 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<out Principal>?): Array<String>? = null
override fun chooseServerAlias(p0: String?, p1: Array<out Principal>?, p2: Socket?) = null
override fun getClientAliases(p0: String?, p1: Array<out Principal>?) =
arrayOf(alias)
override fun chooseClientAlias(p0: Array<out String>?, p1: Array<out Principal>?, 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)
}
}
private object UserAgentInterceptor : Interceptor {
private val userAgent = "${App.appName}/${BuildConfig.VERSION_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}"
@Throws(IOException::class) @Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val locale = Locale.getDefault() val locale = Locale.getDefault()

View File

@ -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<String>? {
return cipherSuites
}
override fun getSupportedCipherSuites(): Array<String>? {
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<String>? = null
var cipherSuites: Array<String>? = 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<String>()
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<String>()
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")
}
}
}
}
}
}

View File

@ -236,15 +236,7 @@ constructor(context: Context, account: Account, settings: AccountSettings, extra
return null return null
} }
var resourceClient = HttpClient.create(context) val resourceClient = HttpClient.Builder(context).build().okHttpClient
// 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()
try { try {
val response = resourceClient.newCall(Request.Builder() val response = resourceClient.newCall(Request.Builder()

View File

@ -111,7 +111,7 @@ abstract class SyncAdapterService : Service() {
Logger.log.info("Refreshing " + serviceType + " collections of service #" + serviceType.toString()) Logger.log.info("Refreshing " + serviceType + " collections of service #" + serviceType.toString())
val settings = AccountSettings(context, account) 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 journalsManager = JournalManager(httpClient, HttpUrl.get(settings.uri!!)!!)

View File

@ -87,7 +87,7 @@ constructor(protected val context: Context, protected val account: Account, prot
init { init {
// create HttpClient with given logger // create HttpClient with given logger
httpClient = HttpClient.create(context, settings) httpClient = HttpClient.Builder(context, settings).build().okHttpClient
data = (context.applicationContext as App).data data = (context.applicationContext as App).data
val serviceEntity = JournalModel.Service.fetch(data, accountName, serviceType) val serviceEntity = JournalModel.Service.fetch(data, accountName, serviceType)

View File

@ -60,7 +60,7 @@ class AddMemberFragment : DialogFragment() {
private inner class MemberAdd : AsyncTask<Void, Void, MemberAdd.AddResult>() { private inner class MemberAdd : AsyncTask<Void, Void, MemberAdd.AddResult>() {
override fun doInBackground(vararg voids: Void): AddResult { override fun doInBackground(vararg voids: Void): AddResult {
try { try {
val httpClient = HttpClient.create(ctx!!, settings!!) val httpClient = HttpClient.Builder(ctx, settings).build().okHttpClient
val userInfoManager = UserInfoManager(httpClient, remote!!) val userInfoManager = UserInfoManager(httpClient, remote!!)
val userInfo = userInfoManager.fetch(memberEmail) val userInfo = userInfoManager.fetch(memberEmail)
@ -102,7 +102,7 @@ class AddMemberFragment : DialogFragment() {
override fun doInBackground(vararg voids: Void): AddResultSecond { override fun doInBackground(vararg voids: Void): AddResultSecond {
try { try {
val settings = settings!! val settings = settings!!
val httpClient = HttpClient.create(ctx!!, settings) val httpClient = HttpClient.Builder(ctx!!, settings).build().okHttpClient
val journalsManager = JournalManager(httpClient, remote!!) val journalsManager = JournalManager(httpClient, remote!!)
val data = (ctx!!.applicationContext as App).data val data = (ctx!!.applicationContext as App).data

View File

@ -14,6 +14,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import androidx.preference.* import androidx.preference.*
import at.bitfire.cert4android.CustomCertManager
import com.etesync.syncadapter.App import com.etesync.syncadapter.App
import com.etesync.syncadapter.BuildConfig import com.etesync.syncadapter.BuildConfig
import com.etesync.syncadapter.R import com.etesync.syncadapter.R
@ -43,7 +44,6 @@ class AppSettingsActivity : BaseActivity() {
internal lateinit var settings: Settings internal lateinit var settings: Settings
internal lateinit var prefResetHints: Preference internal lateinit var prefResetHints: Preference
internal lateinit var prefResetCertificates: Preference
internal lateinit var prefOverrideProxy: SwitchPreferenceCompat internal lateinit var prefOverrideProxy: SwitchPreferenceCompat
internal lateinit var prefDistrustSystemCerts: SwitchPreferenceCompat internal lateinit var prefDistrustSystemCerts: SwitchPreferenceCompat
@ -125,7 +125,15 @@ class AppSettingsActivity : BaseActivity() {
prefDistrustSystemCerts = findPreference("distrust_system_certs") as SwitchPreferenceCompat prefDistrustSystemCerts = findPreference("distrust_system_certs") as SwitchPreferenceCompat
prefDistrustSystemCerts.isChecked = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false) 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 val prefChangeNotification = findPreference("show_change_notification") as SwitchPreferenceCompat
prefChangeNotification.isChecked = context!!.defaultSharedPreferences.getBoolean(App.CHANGE_NOTIFICATION, true) prefChangeNotification.isChecked = context!!.defaultSharedPreferences.getBoolean(App.CHANGE_NOTIFICATION, true)
@ -143,8 +151,6 @@ class AppSettingsActivity : BaseActivity() {
resetHints() resetHints()
else if (preference === prefDistrustSystemCerts) else if (preference === prefDistrustSystemCerts)
setDistrustSystemCerts(preference.isChecked) setDistrustSystemCerts(preference.isChecked)
else if (preference === prefResetCertificates)
resetCertificates()
else else
return false return false
return true return true
@ -157,14 +163,10 @@ class AppSettingsActivity : BaseActivity() {
private fun setDistrustSystemCerts(distrust: Boolean) { private fun setDistrustSystemCerts(distrust: Boolean) {
settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust) settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust)
// re-initialize certificate manager
val app = context!!.applicationContext as App
app.reinitCertManager()
} }
private fun resetCertificates() { private fun resetCertificates() {
(context!!.applicationContext as App).certManager?.resetCertificates() if (CustomCertManager.resetCertificates(activity!!))
Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show()
} }

View File

@ -15,22 +15,4 @@ open class BaseActivity : AppCompatActivity() {
} }
return false 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
}
} }

View File

@ -61,7 +61,7 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() {
fun changePasswordDo(old_password: String, new_password: String) { fun changePasswordDo(old_password: String, new_password: String) {
val settings = AccountSettings(this, account) val settings = AccountSettings(this, account)
val httpClient = HttpClient.create(this, settings) val httpClient = HttpClient.Builder(this, settings).build().okHttpClient
doAsync { doAsync {
Logger.log.info("Started deriving old key") Logger.log.info("Started deriving old key")

View File

@ -53,7 +53,7 @@ class CollectionMembersListFragment : ListFragment(), AdapterView.OnItemClickLis
asyncTask = doAsync { asyncTask = doAsync {
try { try {
val settings = AccountSettings(context!!, account) 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 journalsManager = JournalManager(httpClient, HttpUrl.get(settings.uri!!)!!)
val journal = JournalManager.Journal.fakeWithUid(journalEntity.uid) val journal = JournalManager.Journal.fakeWithUid(journalEntity.uid)

View File

@ -101,7 +101,8 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
val settings = AccountSettings(context, account) val settings = AccountSettings(context, account)
val principal = HttpUrl.get(settings.uri!!) 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 var uid = info.uid
if (uid == null) { if (uid == null) {

View File

@ -88,7 +88,8 @@ class DeleteCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
val settings = AccountSettings(context, account) val settings = AccountSettings(context, account)
val principal = HttpUrl.get(settings.uri!!) 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!!) val crypto = Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid!!)
journalManager.delete(JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid!!)) journalManager.delete(JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid!!))

View File

@ -27,7 +27,7 @@ class RemoveMemberFragment : DialogFragment() {
memberEmail = arguments!!.getString(KEY_MEMBER) memberEmail = arguments!!.getString(KEY_MEMBER)
try { try {
settings = AccountSettings(context!!, account!!) settings = AccountSettings(context!!, account!!)
httpClient = HttpClient.create(context!!, settings!!) httpClient = HttpClient.Builder(context, settings).build().okHttpClient
} catch (e: InvalidAccountException) { } catch (e: InvalidAccountException) {
e.printStackTrace() e.printStackTrace()
} }

View File

@ -12,6 +12,7 @@ import com.etesync.syncadapter.HttpClient
import com.etesync.syncadapter.journalmanager.Crypto import com.etesync.syncadapter.journalmanager.Crypto
import com.etesync.syncadapter.journalmanager.Exceptions import com.etesync.syncadapter.journalmanager.Exceptions
import com.etesync.syncadapter.journalmanager.JournalAuthenticator import com.etesync.syncadapter.journalmanager.JournalAuthenticator
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.log.StringHandler import com.etesync.syncadapter.log.StringHandler
import com.etesync.syncadapter.model.CollectionInfo import com.etesync.syncadapter.model.CollectionInfo
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -20,22 +21,14 @@ import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.net.URI import java.net.URI
import java.util.* import java.util.*
import java.util.logging.Level
import java.util.logging.Logger
class BaseConfigurationFinder(protected val context: Context, protected val credentials: LoginCredentials) { class BaseConfigurationFinder(protected val context: Context, protected val credentials: LoginCredentials) {
protected val log: Logger
protected val logBuffer = StringHandler() protected val logBuffer = StringHandler()
protected var httpClient: OkHttpClient protected var httpClient: OkHttpClient
init { init {
httpClient = HttpClient.Builder(context).build().okHttpClient
log = Logger.getLogger("syncadapter.BaseConfigurationFinder")
log.level = Level.FINEST
log.addHandler(logBuffer)
httpClient = HttpClient.create(context, log)
} }
@ -50,11 +43,11 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred
try { try {
authtoken = authenticator.getAuthToken(credentials.userName, credentials.password) authtoken = authenticator.getAuthToken(credentials.userName, credentials.password)
} catch (e: Exceptions.HttpException) { } catch (e: Exceptions.HttpException) {
log.warning(e.message) Logger.log.warning(e.message)
failed = true failed = true
} catch (e: IOException) { } catch (e: IOException) {
log.warning(e.message) Logger.log.warning(e.message)
failed = true failed = true
} }
@ -69,7 +62,7 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred
protected fun findInitialConfiguration(service: CollectionInfo.Type): Configuration.ServiceInfo { protected fun findInitialConfiguration(service: CollectionInfo.Type): Configuration.ServiceInfo {
// put discovered information here // put discovered information here
val config = Configuration.ServiceInfo() val config = Configuration.ServiceInfo()
log.info("Finding initial " + service.toString() + " service configuration") Logger.log.info("Finding initial " + service.toString() + " service configuration")
return config return config
} }

View File

@ -95,7 +95,7 @@ class SetupEncryptionFragment : DialogFragment() {
try { try {
val cryptoManager: Crypto.CryptoManager 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 userInfoManager = UserInfoManager(httpClient, HttpUrl.get(config.url)!!)
val userInfo = userInfoManager.fetch(config.userName) val userInfo = userInfoManager.fetch(config.userName)

View File

@ -51,7 +51,7 @@ class SetupUserInfoFragment : DialogFragment() {
override fun doInBackground(vararg accounts: Account): SetupUserInfo.SetupUserInfoResult { override fun doInBackground(vararg accounts: Account): SetupUserInfo.SetupUserInfoResult {
try { try {
val cryptoManager: Crypto.CryptoManager 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!!)!!) val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(settings.uri!!)!!)
var userInfo: UserInfoManager.UserInfo? = userInfoManager.fetch(account.name) var userInfo: UserInfoManager.UserInfo? = userInfoManager.fetch(account.name)

@ -1 +1 @@
Subproject commit af1ae810e8aceddd79fed17e6af8a88cb726bd55 Subproject commit f57a8ee9b11a74ca48c067b42dc0411259c567c8