Okhttp: support enabling TLSv1.2 on Android 4.1-4.4.

Created on 25 Feb 2016  路  60Comments  路  Source: square/okhttp

Our lawyers and security consultants claim that for PCI compliance*, we must disable TLS 1.0 and 1.1 on our servers. For some confusing reason, Android has _supported_ TLS 1.2 since API 16 (android 4.1) but enabled it by default only since API 20 (android "4.4W").

With okhttp 2.6, we were able to force use of TLS 1.2 with:

OkHttpClient cli = new OkHttpClient();
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, null, null);
cli.setSslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));
ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_2)
        .build();
cli.setConnectionSpecs(ImmutableList.of(cs));

where Tls12SocketFactory is this.

However, okhttp 3.1 uses some kind of reflection on internal implementation details of the SSLSocketFactory, so the above implementation no longer works. And, indeed, it's a bit silly to make callers write so much code anyway. Specifying TLS_1_2 in the ConnectionSpec should be enough to get TLSv1.2 whenever it is supported.

As far as I can tell, the only reason why the custom socket factory is needed in the first place is that ConnectionSpec.supportedSpec() calls SSLSocket.getEnabledProtocols() to learn the list of protocols supported by the system, so on Android 4._x_ where TLS 1.2 is supported but not enabled by default, OkHttp thinks 1.2 is not supported at all.

Sorry for this long bug report: I _think_ the fix is as simple as changing getEnabledProtocols() above to getSupportedProtocols() but wanted to submit this bug for discussion before making a PR with such a change, in case there is some affirmative reason why it's the other way now.

* Originally I understood the PCI compliance deadline to be June 2016; however, it seems like it has since been changed to be June 2018. Regardless, OkHttp should support this change for users that want it.

android

Most helpful comment

Had the same issue on Android < 5.0 (16 <= API < 20). Thanks to your posts, I was able to make this work, so for anyone who gets here, this is the out-of-the-box solution. At the time of this writing, I'm using OkHttp 3.4.1.

Edit:
I've done some tests and the same issue also happens on some Samsung devices with API 21. Solved by applying the solution also for API 21

Add Tls12SocketFactory.java with the following content:

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;

/**
 * Enables TLS v1.2 when creating SSLSockets.
 * <p/>
 * 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
 */
public class Tls12SocketFactory extends SSLSocketFactory {
    private static final String[] TLS_V12_ONLY = {"TLSv1.2"};

    final SSLSocketFactory delegate;

    public Tls12SocketFactory(SSLSocketFactory base) {
        this.delegate = base;
    }

    @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(TLS_V12_ONLY);
        }
        return s;
    }
}

Then, add this method somewhere in your code:

public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);
            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);
            specs.add(ConnectionSpec.CLEARTEXT);

            client.connectionSpecs(specs);
        } catch (Exception exc) {
            Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
        }
    }

    return client;
}

And when you create your OkHttp instance, use it for example like this:

private OkHttpClient getNewHttpClient() {
    OkHttpClient.Builder client = new OkHttpClient.Builder()
            .followRedirects(true)
            .followSslRedirects(true)
            .retryOnConnectionFailure(true)
            .cache(null)
            .connectTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS);

    return enableTls12OnPreLollipop(client).build();
}

Also, be sure to use well known CAs for your server side certificates when targeting older Androids.

Credits to @StuStirling and @techiebrij:

To check your server side certificates:
https://developer.android.com/reference/javax/net/ssl/SSLEngine.html

Or:
https://gist.github.com/gotev/f1a8a221e2d1d09bcb93e823b8e5a05a

For anyone else that may be struggling with this, the thing that fixed mine was to install the latest security fixes that are bundled with Google Play Services.

ProviderInstaller.installIfNeeded(context);

After doing this, the solution for enabling TLS1.2 worked.

All 60 comments

As a quick fix, try renaming the SSLSocketFactory field in Tls12SocketFactory to delegate. It鈥檚 a gross hack, and it鈥檚 sad, and it鈥檚 how we cope with the absence of the APIs we need doing fancy TLS in Java.

No action for us to take here.

Had the same issue on Android < 5.0 (16 <= API < 20). Thanks to your posts, I was able to make this work, so for anyone who gets here, this is the out-of-the-box solution. At the time of this writing, I'm using OkHttp 3.4.1.

Edit:
I've done some tests and the same issue also happens on some Samsung devices with API 21. Solved by applying the solution also for API 21

Add Tls12SocketFactory.java with the following content:

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;

/**
 * Enables TLS v1.2 when creating SSLSockets.
 * <p/>
 * 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
 */
public class Tls12SocketFactory extends SSLSocketFactory {
    private static final String[] TLS_V12_ONLY = {"TLSv1.2"};

    final SSLSocketFactory delegate;

    public Tls12SocketFactory(SSLSocketFactory base) {
        this.delegate = base;
    }

    @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(TLS_V12_ONLY);
        }
        return s;
    }
}

Then, add this method somewhere in your code:

public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);
            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);
            specs.add(ConnectionSpec.CLEARTEXT);

            client.connectionSpecs(specs);
        } catch (Exception exc) {
            Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
        }
    }

    return client;
}

And when you create your OkHttp instance, use it for example like this:

private OkHttpClient getNewHttpClient() {
    OkHttpClient.Builder client = new OkHttpClient.Builder()
            .followRedirects(true)
            .followSslRedirects(true)
            .retryOnConnectionFailure(true)
            .cache(null)
            .connectTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS);

    return enableTls12OnPreLollipop(client).build();
}

Also, be sure to use well known CAs for your server side certificates when targeting older Androids.

Credits to @StuStirling and @techiebrij:

To check your server side certificates:
https://developer.android.com/reference/javax/net/ssl/SSLEngine.html

Or:
https://gist.github.com/gotev/f1a8a221e2d1d09bcb93e823b8e5a05a

For anyone else that may be struggling with this, the thing that fixed mine was to install the latest security fixes that are bundled with Google Play Services.

ProviderInstaller.installIfNeeded(context);

After doing this, the solution for enabling TLS1.2 worked.

@gotev thanks for sharing, any particular reason you've added the COMPATIBLE_TLS and CLEARTEXT in your connection specs? aren't these necessary only if you want to allow fallbacks to older TLS versions / cleartext http?

It's like that to be as generic as possibile. You could always remove them from the client if you only use TLS 1.2

Seems like client.sslSocketFactory(sslSocketFactory) is deprecated and the documentation recommends using client.sslSocketFactory(sslSocketFactory, X509TrustManager trustManager). How do i get an X509TrustManager object? just create new ?

https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager-

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManager[] trustManagers = new TrustManager[] { new TrustManagerManipulator() };
sslContext.init(null, trustManagers, new SecureRandom());
SSLSocketFactory noSSLv3Factory = new TLSSocketFactory(sslContext.getSocketFactory());
urlConnection.setSSLSocketFactory(noSSLv3Factory);

https://github.com/IKANOW/Infinit.e/blob/master/core/infinit.e.data_model/src/com/ikanow/infinit/e/data_model/utils/TrustManagerManipulator.java

Slightly unrelated. But once I make the changes, how do I actually confirm that app and server are indeed using TLS 1.2 (on my server TLS 1.0, 1.1 and 1.2 all are enabled). Is there a hint in the HTTP packet ?

Check the Handshake object on the Response.

Has anyone actually come up with a working solution for KitKat?

Struggling with same issue with TLS1.2 support on API 19? Does anyone have working solution yet?
Still seeing exception after assigning Tls12SocketFactory socketFactory,

com.parse.ParseRequest$ParseRequestException: i/o failure Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x63c74398: Failure in SSL library, usually a protocol error error:14094438:SSL routines:SSL3_READ_BYTES:tlsv1 alert internal error (external/openssl/ssl/s3_pkt.c:1290 0x6793e4c8:0x00000003)

Same problem here...

@techiebrij any luck with the error? I am experiencing something similiar.

W: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x5cb59b30: Failure in SSL library, usually a protocol error
W: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x5cbd77e8:0x00000000)
W: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x65211a70: Failure in SSL library, usually a protocol error
W: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x5cbd77e8:0x00000000)
W:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:449)

Tried @gotev solution and also get SSL23_GET_SERVER_HELLO:sslv3 , why does it fallback to sslv3?

@StuStirling, sorry for responding late. @gotev code will work only when web server supports right cipher. You can get the list of supported ciphers here https://developer.android.com/reference/javax/net/ssl/SSLEngine.html

API-19 out of code can't do anything to make it work as it has dependency on network call (web server with different SSL spec).

Thanks @techiebrij.

For anyone else that may be struggling with this, the thing that fixed mine was to install the latest security fixes that are bundled with Google Play Services.

ProviderInstaller.installIfNeeded(context);

After doing this, the solution for enabling TLS1.2 worked.

API < 22 was used because of some samsung devices which needed those settings, too. Bear in mind the certificate problem is not only a client side thing. Ensure you have good certificates from well known CAs if you plan to support API < 19.

You may find this useful to check server-side certificates: https://gist.github.com/gotev/f1a8a221e2d1d09bcb93e823b8e5a05a

@gotev i used your code you gave above but almost %1 of my pre-lolliop user's gets exception while setting TLS1.2. is it normal? do you suggest any workaround to handle this problem?

I've seen this happening on a bunch of old samsung devices with API < 19. Solved by using a well known CA for my server side certificates, no changes in actual code.

Is there another fix available for on the sslv3 fallback occurring without having to install the Google Play Services? Our server supports all the ciphers mentioned by @gotev but still no luck.

I give up. After doing everything listed in this thread (Using TLS12SocketFactory, updating google play services, forcing security fixes, adding safe ciphers to the server side), several devices (mainly old Samsung and old rooted devices) are still getting this problem.

Now I'm using Fabric to log devices having issues connecting to our server, as well as their cipher suites. Worst case scenario, I'll just block these devices from being able to download the app from the store. Shitty solution, but it is what it is.

The problem is that those devices don't have what's needed for TLS 1.2. What I ended up doing, and I'm not the only one, is to add older ciphers to the certificate and display a big warning on those devices when I detected they did not support TLS 1.2, informing users that in N months from that message, new TLS 1.2 will be the only standard allowed. After that period, I still allowed users to download the app, but once started it does all the necessary checks and informs the users about the security issue. Maybe it's not the best possible solution, but all depends on how much audience are you impacting.

Any updates on this?

Hi @perrosnk. I solved it by using the bounty castle jar file. Give it a chance :)

Works on my side using @StuStirling solution. API 17, HTC X One.

@mniami This is not a solution. What happens if someone cannot access google play services for a reason? :-)

Do you have @kounalem working solution without forcing user to update GPS? Tls12SocketFactory does not work on my end

@StuStirling Where do you pull the context from ?
Can you share an example of how to use this code?

@kounalem Thank you for your help. Your solution seems nice, but I have no idea how to use it. Actually I am developing in react-native.

Could this be solution? Using bouncy castle to enable TLS1.2? https://stackoverflow.com/questions/33517476/tls-1-2-java-1-6-bouncycastle
It looks like a lot of untrusted code to implement. I think I may end up going down the route of showing an error if I can't enable TLS1.2 with google play service...

Any effective fix on this? I tried multiple approaches, concretely every one described in this thread plus many of the ones described in the issues that link to this one. And still no luck.

My use case is that I would like to restrict TLS to 1.2 only and I would prefer to do this without having to include Google Play Services (currently not using and the less dependencies the better). Also updated OkHttp to 3.10.0

It's crazy that the newer TLS versions were introduced without a proper non-hacky way of enabling them being added as well. Also it's crazy that even when you specify that SSL 3.0 is not supported anymore, devices still fallback to it

You could figure out how to repackage Conscrypt and ship that.

i am trying to connect android device with meteor server everything looks good on devices above marshmallow

Following is the library i have used For connection

https://github.com/TakahikoKawasaki/nv-websocket-client

but i am also facing error on all devices running below nougat

javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb4068200: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:770 0xabeeddd4:0x00000000)

Our server is not supporing SSLv3 so i am using

SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, null, null);
client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

to remove SSLV3 protocol before handshakking but still i am getting above error.

can any one help me ??

trying to get fixes but unable to do. any help would be very highly appreciated.

@rizwan321 Please ask usage questions like this on stackoverflow.

IF you think you have found a bug, please include a test case or code to reproduce it, e.g. connecting against a test server we can access.

The patch in https://github.com/square/okhttp/issues/2372#issuecomment-331623598 actually worked for us.

i see

For those who still getting SSL handshake error even with the fix provided by @gotev. I suggested to use the correct cipher suite that your webserver support and specify it in the ConnectionSpec builder.
e.g.

// written in Kotlin
val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .cipherSuites(
                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
                    .build()

You can check your ssl details here https://www.ssllabs.com/ssltest/

Hello. I tried solution provided by @gotev and @lucaskuan
I tested TLS on
https://api.fastly.com/public-ip-list
https://tlstest.paypal.com/
However on my website I still receive error

javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb950bc70: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x9c8367f1:0x00000000)

My urls works fine on android 5. I found clipper on my api use the same clipper as tested ones TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 So it should work but it's not.
Does anyone know what can be the reason?

@Zo2m4bie I have the same issue too. Because according to this file.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 cipher suite is not supported by Android 4.4 and Java 7 馃槥
So looks like we should use this fix suggested here

@lucaskuan
But I can't understand how these work:
https://api.fastly.com/public-ip-list
https://tlstest.paypal.com/
They use TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 too

I was facing that very same issue while using Glide. I tried to combine solutions from this discussions and some others, but was unsuccessfull.
However i found a working solution for enabling TSL v1.2 for okhttp 3 with glide, here it is (as i see also based on thought from this thread):
https://developer.android.com/reference/javax/net/ssl/SSLSocket

I am not sure whether my comment is appropriate since it is an okhttp rep, but despite glide references one can still extract a working okhttp-only solution.

@gotev Thank you for your example

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManager[] trustManagers = new TrustManager[] { new TrustManagerManipulator() };
sslContext.init(null, trustManagers, new SecureRandom());
SSLSocketFactory noSSLv3Factory = new TLSSocketFactory(sslContext.getSocketFactory());
urlConnection.setSSLSocketFactory(noSSLv3Factory);

https://github.com/IKANOW/Infinit.e/blob/master/core/infinit.e.data_model/src/com/ikanow/infinit/e/data_model/utils/TrustManagerManipulator.java

"urlConnection.setSSLSocketFactory(noSSLv3Factory);" Hi @cantek41 , where do you initialize urlConnection before using to setSSLSocketFactory???

@malikhzr
I do it this way

                SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, null, null);
                client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

                ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                        .tlsVersions(TlsVersion.TLS_1_2)
                        .build();

                List<ConnectionSpec> specs = new ArrayList<>();
                specs.add(cs);
                specs.add(ConnectionSpec.COMPATIBLE_TLS);
                specs.add(ConnectionSpec.CLEARTEXT);

                client.connectionSpecs( Collections.unmodifiableList( specs));

@skauss Thank you for reply. But isn't client.sslSocketFactory deprecated? I am gonna try your code anyway.

React Native version 0.55.4
I implemented everything inside MainActivity.java. I used your code but still it didn't enable TLS version 1.2. Still it is not working on Android Lollipop 5.0.1

import com.facebook.react.ReactActivity;
import org.devio.rn.splashscreen.SplashScreen;
import android.os.Bundle;
import java.util.*;

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;

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;

public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this, R.style.SplashTheme); // here
super.onCreate(savedInstanceState);
getNewHttpClient();
}

/**
 * Returns the name of the main component registered from JavaScript.
 * This is used to schedule rendering of the component.
 */
@Override
protected String getMainComponentName() {
    return "demo";
}

private OkHttpClient getNewHttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder()
        .followRedirects(true)
        .followSslRedirects(true)
        .retryOnConnectionFailure(true)
        .cache(null)
        .connectTimeout(5, TimeUnit.SECONDS)
        .writeTimeout(5, TimeUnit.SECONDS)
        .readTimeout(5, TimeUnit.SECONDS);

return enableTls12OnPreLollipop(client).build();
}


public  OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    // if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);
            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);
            specs.add(ConnectionSpec.CLEARTEXT);

            client.connectionSpecs( Collections.unmodifiableList( specs));
        } catch (Exception exc) {
            Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
        }
    // }

    return client;
}

class Tls12SocketFactory extends SSLSocketFactory {
    private String[] TLS_V12_ONLY = {"TLSv1.2"};

    final SSLSocketFactory delegate;

    public Tls12SocketFactory(SSLSocketFactory base) {
        this.delegate = base;
    }

    @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(TLS_V12_ONLY);
        }
        return s;
    }
}

}

@skauss am I missing something ?

@malikhzr
Yes sslSocketFactory is deprecated.
Hm your code look similar to my one. My code work on 4.4.2.
Are you sure you use your patched OkHttpClient ?

@malikhzr
Yes sslSocketFactory is deprecated.
Hm your code look similar to my one. My code work on 4.4.2.
Are you sure you use your patched OkHttpClient ?

@skauss How to ckeck if I am using patched OkHttpClient ? Or can you reference me some?

@malikhzr
Use the debugger to be sure your client is used or add Log to your code.

Is there any code segment to check whether it's attached to the client.
I'm using the following code segment

SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); TrustManager[] trustManagers = new TrustManager[] { new TrustManagerManipulator() }; sslContext.init(null, trustManagers, new SecureRandom()); SSLSocketFactory noSSLv3Factory = new TLSSocketFactory(sslContext.getSocketFactory()); client.sslSocketFactory(noSSLv3Factory);

@skauss How to patch the OkHttpClient.

I'm calling the getNewHttpClient() method in onCreate function.

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getNewHttpClient();

    }

Thanks to gotev's solution (on Sep 5th 2016), allowed me to make https work on Android 4.x up-to 5.0.

That said, it didn't work out of the box because this solution doesn't support servers having self-signed certificates. I had to change the init into this:

sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

Also, the connectionSpecs() call turned out to be useless.

@3c71 instead of self signed, use https://letsencrypt.org/

@gotev, thanks, however I don't use okhttp to connect to my own site, which I have a valid certificate for. I'm obviously referring to all those sites with self-signed certificates, like all those routers or NAS out there which can't actually be accessed with HTTPs unless certificate is either trusted automatically or manually confirmed. In both cases, it's mandatory to change the init() call.

No action for us to take here.

Ehrm.. I don't want to be that guy, but why did you decide to drop older android versions altogether? (rather than enabling this)
Is it just because of your policy of "only defaults"?
Or were those 200 lines of code becoming that much of a burden (despite even the possibility of the conscrypt embedding)?

And I mean, it's not even like the market for this wouldn't be there given the number of referenced issues above.

Square doesn't ship software on Android 4.4 and I don鈥檛 want to do the work to test & support a platform I don鈥檛 use. In addition to 200 lines of code, there鈥檚 also code to integrate Conscrypt. Conscrypt is native code and there鈥檚 cost to making that work reliably everywhere.

I expect that if anyone really cared enough, they鈥檇 maintain an unofficial backport! @mirh is that you?

We have slammed into this problem as well because we are providing a free healthcare app to a poor part of the world where many suffering users can only afford a Samsung J1 Ace that is stuck on an non-upgradeable Android 4.4. So lack of support for older Androids turns out to be a rest-of-the-world-problem rather than a first-world-problem.

@c4augustus some options to consider:

  • Get TLSv1.2 via the Google Play Services TLS provider. This requires your users have Google Play devices. You may also need to put UI in your app to prompt users to update Play Services if necessary. And then you just configure it before making an HTTPS call.

  • Get TLSv1.2 via Conscrypt. This requires you ship the TLS native code in your application, and configure it before you make an HTTPS call.

  • Change your HTTPS server to accept TLSv1.1. There are potential security consequences to this. But if your server supports both TLSv1.1 and TLSv1.2 it鈥檚 likely to not harm security for the TLSv1.2-capable devices.

Was this page helpful?
0 / 5 - 0 ratings