diff --git a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt index d21fd0ff2..cd85d9df7 100644 --- a/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt +++ b/app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt @@ -1,18 +1,23 @@ package com.orgzly.android.repos import android.net.Uri +import android.os.Build import com.orgzly.android.BookName +import com.orgzly.android.util.TLSSocketFactory import com.orgzly.android.util.UriUtils import com.thegrizzlylabs.sardineandroid.DavResource import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine +import okhttp3.CipherSuite +import okhttp3.ConnectionSpec import okhttp3.OkHttpClient +import okhttp3.TlsVersion import okio.Buffer import java.io.File import java.io.FileOutputStream import java.io.InputStream import java.security.KeyStore import java.security.cert.CertificateFactory -import java.util.* +import java.util.Arrays import javax.net.ssl.* @@ -29,57 +34,55 @@ class WebdavRepo( } private fun client(certificates: String?): OkHttpSardine { - return if (certificates.isNullOrEmpty()) { - OkHttpSardine() + return OkHttpSardine(okHttpClient(certificates)) + } + + private fun okHttpClient(certificates: String?): OkHttpClient { + val trustManager = x509TrustManager(certificates) + val builder = OkHttpClient.Builder() + if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) { + builder.sslSocketFactory(TLSSocketFactory(trustManager), trustManager) + .connectionSpecs(arrayListOf( + ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .allEnabledTlsVersions() + .allEnabledCipherSuites() + .build(), + ConnectionSpec.COMPATIBLE_TLS, + ConnectionSpec.CLEARTEXT + )) } else { - OkHttpSardine(okHttpClientWithTrustedCertificates(certificates)) + val sslContext = SSLContext.getInstance("TLS").apply { + init(null, arrayOf(trustManager), null) + } + builder.sslSocketFactory(sslContext.socketFactory, trustManager) } + return builder.build() } - private fun okHttpClientWithTrustedCertificates(certificates: String): OkHttpClient { - val trustManager = trustManagerForCertificates(certificates) - - val sslContext = SSLContext.getInstance("TLS").apply { - init(null, arrayOf(trustManager), null) + private fun x509TrustManager(certificates: String?): X509TrustManager { + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore(certificates)) + if (trustManagers.isNotEmpty() && trustManagers[0] is X509TrustManager) { + return trustManagers[0] as X509TrustManager + } + throw IllegalStateException("Unexpected default trust manager: " + Arrays.toString(trustManagers)) } - - val sslSocketFactory = sslContext.socketFactory - - return OkHttpClient.Builder() - .sslSocketFactory(sslSocketFactory, trustManager) - .build() } - private fun trustManagerForCertificates(str: String): X509TrustManager { - // Read certificates - val certificates = Buffer().writeUtf8(str).inputStream().use { stream -> + private fun keyStore(certificates: String?): KeyStore? { + if (certificates.isNullOrEmpty()) { + return null + } + val certs = Buffer().writeUtf8(certificates).inputStream().use { stream -> CertificateFactory.getInstance("X.509").generateCertificates(stream) } - -// require(!certificates.isEmpty()) { -// "Expected non-empty set of trusted certificates" -// } - - // Create new key store val password = "password".toCharArray() // Any password will work val keyStore = newEmptyKeyStore(password) - for ((index, certificate) in certificates.withIndex()) { + for ((index, cert) in certs.withIndex()) { val certificateAlias = index.toString() - keyStore.setCertificateEntry(certificateAlias, certificate) - } - - val trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm() - ).apply { - init(keyStore) + keyStore.setCertificateEntry(certificateAlias, cert) } - - val trustManagers = trustManagerFactory.trustManagers - check(trustManagers.size == 1 && trustManagers[0] is X509TrustManager) { - "Unexpected default trust managers: ${Arrays.toString(trustManagers)}" - } - - return trustManagers[0] as X509TrustManager + return keyStore } private fun newEmptyKeyStore(password: CharArray): KeyStore { diff --git a/app/src/main/java/com/orgzly/android/util/TLSSocketFactory.kt b/app/src/main/java/com/orgzly/android/util/TLSSocketFactory.kt new file mode 100644 index 000000000..4c9ab06e5 --- /dev/null +++ b/app/src/main/java/com/orgzly/android/util/TLSSocketFactory.kt @@ -0,0 +1,85 @@ +package com.orgzly.android.util + +import java.io.IOException +import java.net.InetAddress +import java.net.Socket +import java.net.UnknownHostException +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager + + +class TLSSocketFactory (trustManager: X509TrustManager) : SSLSocketFactory() { + private val internalSSLSocketFactory: SSLSocketFactory + + init { + val context = SSLContext.getInstance("TLS") + context.init(null, arrayOf(trustManager), null) + internalSSLSocketFactory = context.socketFactory + } + + override fun getDefaultCipherSuites(): Array { + return internalSSLSocketFactory.defaultCipherSuites + } + + override fun getSupportedCipherSuites(): Array { + return internalSSLSocketFactory.supportedCipherSuites + } + + @Throws(IOException::class) + override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)) + } + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(host: String, port: Int): Socket { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)) + } + + @Throws(IOException::class) + override fun createSocket( + host: String, + port: Int, + localHost: InetAddress, + localPort: Int + ): Socket { + return enableTLSOnSocket( + internalSSLSocketFactory.createSocket( + host, + port, + localHost, + localPort + ) + ) + } + + @Throws(IOException::class) + override fun createSocket(host: InetAddress, port: Int): Socket { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)) + } + + @Throws(IOException::class) + override fun createSocket( + address: InetAddress, + port: Int, + localAddress: InetAddress, + localPort: Int + ): Socket { + return enableTLSOnSocket( + internalSSLSocketFactory.createSocket( + address, + port, + localAddress, + localPort + ) + ) + } + + private fun enableTLSOnSocket(socket: Socket): Socket { + if (socket is SSLSocket) { + socket.enabledProtocols = arrayOf("TLSv1.2", "TLSv1.1") + } + return socket + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5bf8f55c1..bc85b11fe 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ buildscript { versions.glide = '4.10.0' - versions.sardine = '0.5' + versions.sardine = '0.4' versions.jgit = '4.4.1.201607150455-r'