TaskRabbit is Hiring!

We’re a tight-knit team that’s passionate about building a solution that helps people by maximizing their time, talent and skills. We are actively hiring for our Engineering and Design teams. Click To Learn more

Jean-Richard Lai

Enable TLSv1.1 for Android with React Native

@ 17 Jun 2016

javascript react android security tls ssl


At TaskRabbit for security purposes and for PCI compliance (which is a set of security standards to follow when processing credit cards), we needed to enforce TLSv1.1+.

Unfortunately in Android, until API 20, TLSv1.1 was not enabled by default even though it is available from API 16 😞.

How to set up the Android app

In React Native 0.27, OkHttp was upgraded to V3. It’s great, this means multiple issues are fixed for HTTP/2. OkHttp2 features are frozen at this point.

With previous versions of React Native, you could get the client with OkHttpClientProvider.getOkHttpClient(); and then set the SSL Socket Factory and connection Specs; however, with OKHttp3 the client is now immutable. Here are some notes on how to enable TLSv1.1 for your app. You can change to TLSv1.2 with the same technique.

You will need to use a SSL Socket Factory. Here is an implementation inspired by this gist:

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
 * Taken from https://gist.github.com/mlc/549409f649251897ebef
 *
 * Enables TLS when creating SSLSockets.
 *
 * @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
 * @see SSLSocketFactory
 */
class TLSSocketFactory extends SSLSocketFactory {
    final SSLSocketFactory delegate;

    public TLSSocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return patch(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return patch(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return patch(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return patch(delegate.createSocket(address, port, localAddress, localPort));
    }

    private Socket patch(Socket s) {
        if (s instanceof SSLSocket) {
            ((SSLSocket) s).setEnabledProtocols(((SSLSocket) s).getSupportedProtocols());
        }
        return s;
    }
}

Create a TLSSetup class:

import android.util.Log;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;

import javax.net.ssl.*;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

public class TLSSetup {

    static String TAG = "TLSSetup";

    public static void configure(){
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.1");
            sc.init(null, null, null);
            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1)
                    .build();
            // Taken from OkHttpClientProvider.java
            // Set no timeout by default
            OkHttpClient sClient = new OkHttpClient.Builder()
                    .connectTimeout(0, TimeUnit.MILLISECONDS)
                    .readTimeout(0, TimeUnit.MILLISECONDS)
                    .writeTimeout(0, TimeUnit.MILLISECONDS)
                    .cookieJar(new ReactCookieJarContainer())
                    // set sslSocketFactory
                    .sslSocketFactory(new TLSSocketFactory(sc.getSocketFactory()))
                    // set connectionSpecs
                    .connectionSpecs(Arrays.asList(cs, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT))
                    .build();

            OkHttpClientProvider.replaceOkHttpClient(sClient);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

}

In your MainActivity.java call TLSSetup.configure();

Prepare to the TLS change

Now you have an app that supports TLSv1.1. If the app has yet to be released you are good to go 😊. However, if you currently have users of your app, it is a little more complex because you have to migrate your users to the new version of the app.

Here are some steps you can take to make the migration a smooth one.

Notes: Those points are depending on how much time you have before disabling TLSv1.

  1. Release a build with the TLS change.
  2. If you can, add an inside app notification about the upgrade of the app.
  3. Let users update organically for few days.
  4. Notify users about the requirements to upgrade the app by email and pushes (target if possible users that you detect will face the issue, this could be done by collecting the users which TLS used is TLSv1)
  5. Let users update organically for few days.
  6. If you can, enable force upgrade of the App.
  7. 1 or 2 weeks before the TLS change repeat step 4 to 5.
  8. Disable TLSv1.

Et voilà.

Comments

Coments Loading...