mirror of
https://github.com/etesync/android
synced 2024-12-23 15:18:14 +00:00
Upgrade cert4android and refactor httpclient based on upstream
This commit is contained in:
parent
75020c1841
commit
4134f78da4
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
class HttpClient private constructor(
|
||||
val okHttpClient: OkHttpClient,
|
||||
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)
|
||||
|
||||
init {
|
||||
userAgent = "${App.appName}/${BuildConfig.VERSION_NAME} (okhttp3) Android/${Build.VERSION.RELEASE}"
|
||||
// don't allow redirects by default, because it would break PROPFIND handling
|
||||
.followRedirects(false)
|
||||
|
||||
// add User-Agent to every request
|
||||
.addNetworkInterceptor(UserAgentInterceptor)
|
||||
|
||||
.build()
|
||||
}
|
||||
|
||||
fun create(context: Context?, logger: LoggerType, host: String?, token: String): OkHttpClient {
|
||||
var builder = defaultBuilder(context, logger)
|
||||
|
||||
// use account settings for authentication
|
||||
builder = addAuthentication(builder, host, token)
|
||||
|
||||
return builder.build()
|
||||
override fun close() {
|
||||
certManager?.close()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun create(context: Context?, settings: AccountSettings, logger: LoggerType = Logger.log): OkHttpClient {
|
||||
return create(context, logger, settings.uri!!.host, settings.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
|
||||
|
||||
@JvmOverloads
|
||||
fun create(context: Context?, logger: LoggerType = Logger.log): OkHttpClient {
|
||||
return defaultBuilder(context, logger).build()
|
||||
}
|
||||
private val orig = sharedClient.newBuilder()
|
||||
|
||||
fun create(context: Context?, uri: URI, authToken: String): OkHttpClient {
|
||||
return create(context, Logger.log, uri.host, authToken)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
try {
|
||||
context?.let {
|
||||
val dbHelper = ServiceDB.OpenHelper(context)
|
||||
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 distrustSystemCerts = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)
|
||||
|
||||
val proxy = Proxy(Proxy.Type.HTTP, address)
|
||||
builder.proxy(proxy)
|
||||
Logger.log.log(Level.INFO, "Using proxy", proxy)
|
||||
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()
|
||||
}
|
||||
} 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()
|
||||
|
||||
//if (BuildConfig.customCerts)
|
||||
customCertManager(CustomCertManager(context, !distrustSystemCerts))
|
||||
}
|
||||
|
||||
// use account settings for authentication
|
||||
accountSettings?.let {
|
||||
addAuthentication(accountSettings.uri!!.host, accountSettings.authToken)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
constructor(context: Context, host: String?, authToken: String): this(context) {
|
||||
addAuthentication(host, authToken)
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
private fun addAuthentication(builder: OkHttpClient.Builder, host: String?, token: String): OkHttpClient.Builder {
|
||||
val authHandler = TokenAuthenticator(host, token)
|
||||
|
||||
return builder.addNetworkInterceptor(authHandler)
|
||||
}
|
||||
|
||||
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()
|
||||
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 chain.proceed(request)
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected val HEADER_AUTHORIZATION = "Authorization"
|
||||
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)
|
||||
|
||||
orig.addNetworkInterceptor(authHandler)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
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<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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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!!)!!)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -60,7 +60,7 @@ class AddMemberFragment : DialogFragment() {
|
||||
private inner class MemberAdd : AsyncTask<Void, Void, MemberAdd.AddResult>() {
|
||||
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
|
||||
|
@ -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<Void, Void, LanguageUtils.LocaleList>() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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!!))
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit af1ae810e8aceddd79fed17e6af8a88cb726bd55
|
||||
Subproject commit f57a8ee9b11a74ca48c067b42dc0411259c567c8
|
Loading…
Reference in New Issue
Block a user