Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for TLSv1.2 for WebDAV in Android 4.x #1054

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 42 additions & 39 deletions app/src/main/java/com/orgzly/android/repos/WebdavRepo.kt
Original file line number Diff line number Diff line change
@@ -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.*


Expand All @@ -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 {
Expand Down
85 changes: 85 additions & 0 deletions app/src/main/java/com/orgzly/android/util/TLSSocketFactory.kt
Original file line number Diff line number Diff line change
@@ -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<String> {
return internalSSLSocketFactory.defaultCipherSuites
}

override fun getSupportedCipherSuites(): Array<String> {
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
}
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ buildscript {

versions.glide = '4.10.0'

versions.sardine = '0.5'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version 0.5 brings an upgrade to OKHTTP 4.0, which breaks connections on older Android versions.

versions.sardine = '0.4'

versions.jgit = '4.4.1.201607150455-r'

Expand Down