Skip to content

Instantly share code, notes, and snippets.

@alwarren
Last active March 24, 2018 17:58
Show Gist options
  • Save alwarren/978e9f412320bfc4a52b7f56164eb2e0 to your computer and use it in GitHub Desktop.
Save alwarren/978e9f412320bfc4a52b7f56164eb2e0 to your computer and use it in GitHub Desktop.
Handle TLS in OkHttp3 client on pre-Lollipop versions of Android
import android.os.Build
import android.util.Log
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.TlsVersion
import java.io.IOException
import java.net.InetAddress
import java.net.Socket
import java.net.UnknownHostException
import java.security.KeyStore
import java.util.*
import java.util.concurrent.TimeUnit
import javax.net.ssl.*
/**
* Handle TLS on pre-Lollipop versions of Android
*
* @link
*/
class SSLUtils {
/**
* Enables TLS v1.2 when creating SSLSockets.
*
*
* For some reason, android supports TLS v1.2 from API 16, but enables it by
* default only from API 20.
* @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
* @see SSLSocketFactory
*/
private class Tls12SocketFactory(internal val delegate: SSLSocketFactory) : SSLSocketFactory() {
override fun getDefaultCipherSuites(): Array<String> {
return delegate.defaultCipherSuites
}
override fun getSupportedCipherSuites(): Array<String> {
return delegate.supportedCipherSuites
}
@Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? {
return patch(delegate.createSocket(s, host, port, autoClose))
}
@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int): Socket? {
return patch(delegate.createSocket(host, port))
}
@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? {
return patch(delegate.createSocket(host, port, localHost, localPort))
}
@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket? {
return patch(delegate.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? {
return patch(delegate.createSocket(address, port, localAddress, localPort))
}
private fun patch(s: Socket): Socket {
if (s is SSLSocket) {
s.enabledProtocols = TLS_VERSIONS
}
return s
}
companion object {
private val TLS_VERSIONS = arrayOf("TLSv1.2", "TLSv1.1")
}
}
companion object {
fun newHttpClient(): OkHttpClient.Builder {
val client = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.cache(null)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
return enableTls12OnPreLollipop(client)
}
private fun trustManager(): X509TrustManager {
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers
if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
throw IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers))
}
return trustManagers[0] as X509TrustManager
}
private fun enableTls12OnPreLollipop(client: OkHttpClient.Builder): OkHttpClient.Builder {
if (Build.VERSION.SDK_INT in 16..21) {
try {
val sc = SSLContext.getInstance("TLSv1.2")
sc.init(null, null, null)
client.sslSocketFactory(Tls12SocketFactory(sc.socketFactory), trustManager())
val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs = mutableListOf<ConnectionSpec>()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
client.connectionSpecs(specs)
} catch (exc: Exception) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
}
}
return client
}
}
}
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = SSLUtils.newHttpClient()
.addInterceptor(loggingInterceptor)
.build()
val github = "https://api.github.com/users/iammert/repos"
val request = Request.Builder().url(github).build()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call?, response: Response?) {
runOnUiThread{ updateUI("Enqueued and executed") }
}
override fun onFailure(call: Call?, e: IOException) {
runOnUiThread{ updateUI(e.message.toString()) }
println(e.message)
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment