Last active
March 24, 2018 17:58
-
-
Save alwarren/978e9f412320bfc4a52b7f56164eb2e0 to your computer and use it in GitHub Desktop.
Handle TLS in OkHttp3 client on pre-Lollipop versions of Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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