As described by the PCI Security Standards Council, early versions of the TLS security protocol are susceptible to widespread exploits that could expose potentially sensitive data. On June 30th, we upgraded Quizlet’s security to require TLS 1.2 or higher as part of our continued following of best-practice data security recommendations.
While our web client and iOS app were largely unaffected by this transition, we had to do a bit of work to make sure our Android app was up to the job.
This was due to a few reasons:
- There is a lack of clarity surrounding TLS 1.2 support on older Android devices,
- Device manufacturers have differing commitments to the official Android specs for shipping TLS 1.2 on their devices
- Carriers and device manufacturers have differing commitments to providing software and security updates to their customers
There’s unfortunately no simple way to ensure all of our users are seamlessly able to use TLS 1.2 to access Quizlet, but we’ve tried our best to minimize disruption to our users as we made the transition.
In this post, I want to share how we went about doing it.
Installing TLS 1.2 Where Needed
We support Android 4.1–4.3 (API 16–18) with a long-term-support version of our app, and Android 4.4+ (API 19+) in our latest builds. Our goal was to minimize disruption for users in both of those builds, so our target was ensuring TLS 1.2 support for Android 4.1 and higher.
The first thing we realized was that despite documentation suggesting otherwise, not all devices on Android 4.1+ actually support TLS 1.2. Even though it is likely due to device manufacturers not fully following the official Android specs, we had to do what we could to ensure this would work for our users.
Luckily, Google Play Services provides a way to do this. The solution is to use ProviderInstaller
from Google Play Services to try to update the device to support the latest and greatest security protocols.
We do this as follows:
For devices without Google Play services, we unfortunately don’t have an alternative way to patch the device’s security Provider
to support TLS 1.2. For these users, we display a message so they know the app may not work properly for them.
In the Quizlet Android app, we call this in a background thread if our first API call fails with an SSLHandshakeException
. Calling ProviderInstaller.installIfNeeded
only after an SSLException
allows us to support users with TLS 1.2 already installed with minimal disruption (i.e., if they have an old version of Google Play Services, or don’t have it installed at all). In your apps, it might make sense to handle this logic in a similar manner.
Calling ProviderInstaller.installIfNeeded
only after an SSLException
allows us to support users with TLS 1.2 already installed with minimal disruption
Alternatives to this approach include Google’s official suggestions: either blocking network transactions by calling installIfNeeded
upon creation of all network-specific threads (it’s a no-op if the security Provider
is already up-to-date, so the performance impact should be minimal), or using the asynchronous variant to start a call from the main thread.
Originally, we thought we were done after doing this step. However, after further testing, we noticed something peculiar. Many devices succeeded in updating the security Provider
via installIfNeeded
, but were still throwing SSLHandshakeExceptions
when the server required TLS 1.2!
Forcing TLS 1.2 To Be Enabled
Our initial understanding of OkHttp’s HTTPS strategy was that it uses the highest-installed version of TLS, and it falls back to older protocols if newer ones are not installed. This is false.
OkHttp’s default behavior is to use an SSLSocketFactory
to create SSLSocket
s with default parameters, and select from protocols that are enabled for a given SSLSocket
. Here’s the kicker: The latest protocols installed on a device are not necessarily enabled on its default SSLSocket
s!
The latest protocols installed on a device are not necessarily enabled on its default SSLSocket
s!
The docs for SSLSocket
on Android state that TLS 1.2 is only enabled as a default client protocol starting in Android 4.3. To make things more confusing, developers have found that certain Samsung devices on Android 4.4 don’t have it enabled either.
To fix this, we need to override OkHttp’s SSLSocketFactory
to enable TLS 1.2 on all SSLSocket
s. We adapted the helper class from this Github issue, and cleaned it up a bit to make it more Kotlin-tastic. We cleared up deprecation warnings by explicitly passing in an X509TrustManager
and made use of Kotlin’s extension functions to simplify some of the code, but the actual logic is largely the same.
The new-and-improved Tls12SocketFactory
is a bit too long to embed, but here’s a link.
Now, we can simply call the enableTls12()
extension function in our OkHttpClient.Builder
chain in Dagger, and there we have it!
Or so we thought.
Forcing TLS 1.2 To Be Enabled, Take 2: Glide
It turns out this still wasn’t enough! Our regular-old API calls were working just fine, but all of our images were still failing to load through Glide due to even more SSLHandshakeExceptions
.
By default, the Glide OkHttp3 integration uses a vanilla OkHttpClient
to load images from URLs. This means it uses the regular SSLSocketFactory
that does not forcefully enable TLS 1.2.
Lucky for us, this fix wasn’t too complicated for Glide. We set up our AppGlideModule
to use our Tls12SocketFactory
for its OkHttpClient
as well:
If you use Glide or any other libraries that manage their own network calls, you should make sure that their SSLSocket
s are also configured to enable TLS 1.2.
If you use Glide or any other libraries that manage their own network calls, you should make sure that their SSLSocket
s are also configured to enable TLS 1.2.
Whew. Now we’re done!
In Summary
A device can be in one of several states with regards to supporting TLS 1.2:
- Device does not have TLS 1.2 installed at all.
- Device has TLS 1.2 installed, [but not enabled by default.](https://developer.android.com/reference/javax/net/ssl/SSLSocket#default-configuration-for-different-android-versions)
- Device has TLS 1.2 installed and enabled by default.
It’s important to make sure you handle not only installing TLS 1.2 support (via ProviderInstaller
) but also ensuring that it’s enabled for all of your network connections — even ones that are handled automagically by other libraries!