From b875f0edfdc8c114d524edc685c8924a2ce9becd Mon Sep 17 00:00:00 2001 From: Alex Palaistras Date: Fri, 1 Mar 2024 23:57:57 +0000 Subject: [PATCH] Add support for TLSv1.2 for WebDAV in Android 4.x Android 4.1 to 5.0 have support for TLSv1.2 in creating encrypted sockets, but this is (for some reason) disabled by default. This commit adds conditional support for using a TLS socket override with added support for TLSv1.2 in these SDK build versions, in making WebDAV connections. --- .../com/orgzly/android/repos/WebdavRepo.kt | 81 +++++++++--------- .../orgzly/android/util/TLSSocketFactory.kt | 85 +++++++++++++++++++ build.gradle | 2 +- 3 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/com/orgzly/android/util/TLSSocketFactory.kt 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'