diff --git a/app/src/main/java/com/etesync/syncadapter/HttpClient.kt b/app/src/main/java/com/etesync/syncadapter/HttpClient.kt index 4bef5439..a4f7128f 100644 --- a/app/src/main/java/com/etesync/syncadapter/HttpClient.kt +++ b/app/src/main/java/com/etesync/syncadapter/HttpClient.kt @@ -11,15 +11,11 @@ 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.* import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.logging.HttpLoggingInterceptor import java.io.File @@ -32,10 +28,7 @@ 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 javax.net.ssl.* import java.util.logging.Logger as LoggerType class HttpClient private constructor( @@ -44,8 +37,11 @@ class HttpClient private constructor( ): AutoCloseable { companion object { + /** max. size of disk cache (10 MB) */ + const val DISK_CACHE_MAX_SIZE: Long = 10*1024*1024 + /** [OkHttpClient] singleton to build all clients from */ - val sharedClient = OkHttpClient.Builder() + val sharedClient: OkHttpClient = OkHttpClient.Builder() // set timeouts .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) @@ -60,7 +56,9 @@ class HttpClient private constructor( .build() } + override fun close() { + okHttpClient.cache()?.close() certManager?.close() } @@ -71,7 +69,7 @@ class HttpClient private constructor( ) { private var certManager: CustomCertManager? = null private var certificateAlias: String? = null - private var foreground = true + private var cache: Cache? = null private val orig = sharedClient.newBuilder() @@ -103,8 +101,12 @@ class HttpClient private constructor( } catch (e: Exception) { Logger.log.log(Level.SEVERE, "Can't set proxy, ignoring", e) } finally { - dbHelper.close() + // dbHelper.close() } + + //if (BuildConfig.customCerts) + customCertManager(CustomCertManager(context, true, + !(settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES,false)), true)) } // use account settings for authentication @@ -124,19 +126,23 @@ class HttpClient private constructor( val cacheDir = File(dir, "HttpClient") cacheDir.mkdir() Logger.log.fine("Using disk cache: $cacheDir") - orig.cache(Cache(cacheDir, 10*1024*1024)) + orig.cache(Cache(cacheDir, DISK_CACHE_MAX_SIZE)) break } } return this } + fun followRedirects(follow: Boolean): Builder { + orig.followRedirects(follow) + return this + } + fun customCertManager(manager: CustomCertManager) { certManager = manager } - fun setForeground(foreground: Boolean): Builder { - this.foreground = foreground + certManager?.appInForeground = foreground return this } @@ -172,16 +178,6 @@ class HttpClient private constructor( } fun build(): HttpClient { - //if (BuildConfig.customCerts) - context?.let { - val dbHelper = ServiceDB.OpenHelper(context) - val settings = Settings(dbHelper.readableDatabase) - - // Only make it interactive if app is in foreground - customCertManager(CustomCertManager(context, foreground, !settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false), foreground)) - dbHelper.close() - } - val trustManager = certManager ?: { val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) factory.init(null as KeyStore?) @@ -192,14 +188,14 @@ class HttpClient private constructor( ?: OkHostnameVerifier.INSTANCE var keyManager: KeyManager? = null - try { - certificateAlias?.let { alias -> + certificateAlias?.let { alias -> + try { val context = requireNotNull(context) - // get client certificate and private key + // get provider 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})") + logger.fine("Using provider 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") @@ -222,12 +218,21 @@ class HttpClient private constructor( override fun getPrivateKey(forAlias: String?) = key.takeIf { forAlias == alias } } + + // HTTP/2 doesn't support client certificates (yet) + // see https://tools.ietf.org/html/draft-ietf-httpbis-http2-secondary-certs-04 + orig.protocols(listOf(Protocol.HTTP_1_1)) + } catch (e: Exception) { + logger.log(Level.SEVERE, "Couldn't set up provider certificate authentication", e) } - } catch (e: Exception) { - logger.log(Level.SEVERE, "Couldn't set up client certificate authentication", e) } - orig.sslSocketFactory(CertTlsSocketFactory(keyManager, trustManager), trustManager) + val sslContext = SSLContext.getInstance("TLS") + sslContext.init( + if (keyManager != null) arrayOf(keyManager) else null, + arrayOf(trustManager), + null) + orig.sslSocketFactory(sslContext.socketFactory, trustManager) orig.hostnameVerifier(hostnameVerifier) return HttpClient(orig.build(), certManager) diff --git a/cert4android b/cert4android index f57a8ee9..0a451ad0 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit f57a8ee9b11a74ca48c067b42dc0411259c567c8 +Subproject commit 0a451ad044ff50b488135cb91207d5a9a071c3b6