Skip to content
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