Update cert4android and adjust HttpClient accordingly

pull/96/head
Tom Hacohen 5 years ago
parent d64e6ba33f
commit c26c2c5332

@ -11,15 +11,11 @@ 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 android.security.KeyChain
import at.bitfire.cert4android.CertTlsSocketFactory
import at.bitfire.cert4android.CustomCertManager import at.bitfire.cert4android.CustomCertManager
import com.etesync.syncadapter.log.Logger 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.*
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import java.io.File import java.io.File
@ -32,10 +28,7 @@ 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.*
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
class HttpClient private constructor( class HttpClient private constructor(
@ -44,8 +37,11 @@ class HttpClient private constructor(
): AutoCloseable { ): AutoCloseable {
companion object { 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 */ /** [OkHttpClient] singleton to build all clients from */
val sharedClient = OkHttpClient.Builder() val sharedClient: OkHttpClient = OkHttpClient.Builder()
// set timeouts // set timeouts
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
@ -60,7 +56,9 @@ class HttpClient private constructor(
.build() .build()
} }
override fun close() { override fun close() {
okHttpClient.cache()?.close()
certManager?.close() certManager?.close()
} }
@ -71,7 +69,7 @@ class HttpClient private constructor(
) { ) {
private var certManager: CustomCertManager? = null private var certManager: CustomCertManager? = null
private var certificateAlias: String? = null private var certificateAlias: String? = null
private var foreground = true private var cache: Cache? = null
private val orig = sharedClient.newBuilder() private val orig = sharedClient.newBuilder()
@ -103,8 +101,12 @@ class HttpClient private constructor(
} catch (e: Exception) { } catch (e: Exception) {
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, true,
!(settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES,false)), true))
} }
// use account settings for authentication // use account settings for authentication
@ -124,19 +126,23 @@ class HttpClient private constructor(
val cacheDir = File(dir, "HttpClient") val cacheDir = File(dir, "HttpClient")
cacheDir.mkdir() cacheDir.mkdir()
Logger.log.fine("Using disk cache: $cacheDir") Logger.log.fine("Using disk cache: $cacheDir")
orig.cache(Cache(cacheDir, 10*1024*1024)) orig.cache(Cache(cacheDir, DISK_CACHE_MAX_SIZE))
break break
} }
} }
return this return this
} }
fun followRedirects(follow: Boolean): Builder {
orig.followRedirects(follow)
return this
}
fun customCertManager(manager: CustomCertManager) { fun customCertManager(manager: CustomCertManager) {
certManager = manager certManager = manager
} }
fun setForeground(foreground: Boolean): Builder { fun setForeground(foreground: Boolean): Builder {
this.foreground = foreground certManager?.appInForeground = foreground
return this return this
} }
@ -172,16 +178,6 @@ class HttpClient private constructor(
} }
fun build(): HttpClient { 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 trustManager = certManager ?: {
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
factory.init(null as KeyStore?) factory.init(null as KeyStore?)
@ -192,14 +188,14 @@ class HttpClient private constructor(
?: OkHostnameVerifier.INSTANCE ?: OkHostnameVerifier.INSTANCE
var keyManager: KeyManager? = null var keyManager: KeyManager? = null
try { certificateAlias?.let { alias ->
certificateAlias?.let { alias -> try {
val context = requireNotNull(context) 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 certs = KeyChain.getCertificateChain(context, alias) ?: return@let
val key = KeyChain.getPrivateKey(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) // create Android KeyStore (performs key operations without revealing secret data to DAVx5)
val keyStore = KeyStore.getInstance("AndroidKeyStore") val keyStore = KeyStore.getInstance("AndroidKeyStore")
@ -222,12 +218,21 @@ class HttpClient private constructor(
override fun getPrivateKey(forAlias: String?) = override fun getPrivateKey(forAlias: String?) =
key.takeIf { forAlias == alias } 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) orig.hostnameVerifier(hostnameVerifier)
return HttpClient(orig.build(), certManager) return HttpClient(orig.build(), certManager)

@ -1 +1 @@
Subproject commit f57a8ee9b11a74ca48c067b42dc0411259c567c8 Subproject commit 0a451ad044ff50b488135cb91207d5a9a071c3b6
Loading…
Cancel
Save