Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Karewan/4b0270755e7053b471fdca4419467216 to your computer and use it in GitHub Desktop.
Save Karewan/4b0270755e7053b471fdca4419467216 to your computer and use it in GitHub Desktop.
Android: TLS 1.3 with OkHttp and Conscrypt on all Android versions (Tested on 4.1+)
// Android 4.1+
dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
}
// Android 5.0+
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
}
// Init Conscrypt
Provider conscrypt = Conscrypt.newProvider();
// Add as provider
Security.insertProviderAt(conscrypt, 1);
// Init OkHttp
OkHttpClient.Builder okHttpBuilder = new OkHttpClient()
.newBuilder()
.connectionSpecs(Collections.singletonList(ConnectionSpec.RESTRICTED_TLS));
// OkHttp 3.12.x
// ConnectionSpec.COMPATIBLE_TLS = TLS1.0
// ConnectionSpec.MODERN_TLS = TLS1.0 + TLS1.1 + TLS1.2 + TLS 1.3
// ConnectionSpec.RESTRICTED_TLS = TLS 1.2 + TLS 1.3
// OkHttp 3.13+
// ConnectionSpec.COMPATIBLE_TLS = TLS1.0 + TLS1.1 + TLS1.2 + TLS 1.3
// ConnectionSpec.MODERN_TLS = TLS1.2 + TLS 1.3
// ConnectionSpec.RESTRICTED_TLS = TLS 1.2 + TLS 1.3
try {
X509TrustManager tm = Conscrypt.getDefaultX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS", conscrypt);
sslContext.init(null, new TrustManager[] { tm }, null);
okHttpBuilder.sslSocketFactory(new InternalSSLSocketFactory(sslContext.getSocketFactory()), tm);
} catch (Exception e) {
e.printStackTrace();
}
// Build OkHttp
OkHttpClient okHttpClient = okHttpBuilder.build();
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 final class InternalSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory mSSLSocketFactory;
public InternalSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
this.mSSLSocketFactory = sslSocketFactory;
}
@Override
public String[] getDefaultCipherSuites() {
return mSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return mSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(mSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
//if(socket instanceof SSLSocket) ((SSLSocket) socket).setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"});
if(socket instanceof SSLSocket) ((SSLSocket) socket).setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.3"});
return socket;
}
}
Request request = new Request.Builder()
.url("https://tls13.1d.pw") // You can try another TLS 1.3 capable HTTPS server
.build();
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(final Call call, IOException e) {
e.printStackTrace();
Log.d(LOG, "onFailure()");
}
@Override
public void onResponse(Call call,final Response response) throws IOException {
Log.d(LOG, "onResponse() tlsVersion=" + response.handshake().tlsVersion());
Log.d(LOG, "onResponse() cipherSuite=" + response.handshake().cipherSuite().toString());
// D/TestApp##: onResponse() tlsVersion=TLS_1_3
// D/TestApp##: onResponse() cipherSuite=TLS_AES_256_GCM_SHA384
}
});
@kris520
Copy link

kris520 commented Nov 17, 2020

Sorry, my fault, it is work now, thank you!

@kris520
Copy link

kris520 commented Nov 17, 2020

I have another question, why do I need to add this line of code? And I found that deleting this line of code, the program can also work properly.

okHttpBuilder.sslSocketFactory(new InternalSSLSocketFactory(), new InternalX509TrustManager());

@Karewan
Copy link
Author

Karewan commented Nov 17, 2020

If you remove this line, the HTTPS request will fail with an SSL protocol error on Android 4.4 and 5.0.
Seem to work fine without this line on Android 5.1+ but I recommend to keep it to have best result across all devices (even on Android 5.1+).

@kris520
Copy link

kris520 commented Nov 19, 2020

Can you help to explain the effects of these two classes? Are there any side effects? Thank you!

InternalSSLSocketFactory
InternalX509TrustManager

@Karewan
Copy link
Author

Karewan commented Nov 20, 2020

Fist of all, I updated this gist before my answer.

InternalX509TrustManager: This class handle certificate validation but my implementation was bad, so I replaced it with the default system implementation provided by "Conscrypt.getDefaultX509TrustManager()". A X509TrustManager is required by the "okHttpBuilder.sslSocketFactory" method. With default X509TrustManager no side effect, with my previous implementation maybe too friendly with some bad certificates.

InternalSSLSocketFactory: This class extends "SSLSocketFactory" which is used to create TCP SSL sockets for HTTPS connections. My implementation aims to enable the missing TLS protocols which are not implemented in a device or not activated ("TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"). No side effects with this class.

I use TLS 1.3 in production in Android for more than one year with no issues reported.

@kris520
Copy link

kris520 commented Nov 25, 2020

Great, thank you!

@kris520
Copy link

kris520 commented Dec 9, 2020

Hello, after we launched this function, there was a crash in Conscrypt. Have you met it?

google/conscrypt#935

@mohsaleh04
Copy link

Hi, Can I use this for retrofit library?!

@Karewan
Copy link
Author

Karewan commented Feb 12, 2021

I'm not sure, I don't use retrofit.
But according to the doc, you can pass an okhttpclient to the retrofit builder.
So in theory it should work.

@Karewan
Copy link
Author

Karewan commented Feb 12, 2021

Hello, after we launched this function, there was a crash in Conscrypt. Have you met it?

google/conscrypt#935

In the OkHttp changelog for the v3.12.13:

  • Fix: Work around a crash in Android 10 and 11 that may be triggered when two threads concurrently close an SSL socket. This would have appeared in crash logs as NullPointerException: bio == null.

@mohsaleh04
Copy link

mohsaleh04 commented Feb 13, 2021

I'm not sure, I don't use retrofit.
But according to the doc, you can pass an okhttpclient to the retrofit builder.
So in theory it should work.

Sorry, Do you know any reference to solve this problem?
I used these scripts that you wrote but threw an exception to me when pass this code to my retrofit builder.
I checked many other scripts to solve this problem but I did not get any answer.

@mohsaleh04
Copy link

mohsaleh04 commented Feb 13, 2021

This is my block code that throwing a java.lang.NullPointerException in android 4.4.2 kitkat:
Caused by: java.lang.IllegalStateException: Expected Android API level 21+ but was 19

       Security.insertProviderAt(Conscrypt.newProvider(), 1);
        try {
            OkHttpClient.Builder okHttpBuilder = new OkHttpClient().newBuilder()
                                        .connectionSpecs(Collections.singletonList(ConnectionSpec.MODERN_TLS));
        } catch (ExceptionInInitializerError e) {
            e.printStackTrace();
        }
        try {
            X509TrustManager tm = Conscrypt.getDefaultX509TrustManager();
            SSLContext sslContext = SSLContext.getInstance("TLS", "Conscrypt");
            sslContext.init(null, new TrustManager[] { tm }, null);
            okHttpBuilder.sslSocketFactory(new TLS13SocketFactory(sslContext.getSocketFactory()),
                    tm);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Retrofit retrofitSystem = new Retrofit.Builder()
                    .baseUrl(APIConnector.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpBuilder.build()) // Will be occured an unknown error here.
                    .build();

@Karewan
Copy link
Author

Karewan commented Feb 13, 2021

This is my block code that throwing a java.lang.NullPointerException in android 4.4.2 kitkat:
Caused by: java.lang.IllegalStateException: Expected Android API level 21+ but was 19

       Security.insertProviderAt(Conscrypt.newProvider(), 1);
        try {
            OkHttpClient.Builder okHttpBuilder = new OkHttpClient().newBuilder()
                                        .connectionSpecs(Collections.singletonList(ConnectionSpec.MODERN_TLS));
        } catch (ExceptionInInitializerError e) {
            e.printStackTrace();
        }
        try {
            X509TrustManager tm = Conscrypt.getDefaultX509TrustManager();
            SSLContext sslContext = SSLContext.getInstance("TLS", "Conscrypt");
            sslContext.init(null, new TrustManager[] { tm }, null);
            okHttpBuilder.sslSocketFactory(new TLS13SocketFactory(sslContext.getSocketFactory()),
                    tm);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Retrofit retrofitSystem = new Retrofit.Builder()
                    .baseUrl(APIConnector.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpBuilder.build()) // Will be occured an unknown error here.
                    .build();

OkHttp 3.13+ require Android 5.0+, for Android 4.4 you must use OkHttp 3.12.x (the 3.12.x branch is still updated).
Retrofit 2.70+ require Android 5.0+, for Android 4.4 you must use Retrofit 2.6.4.

@mohsaleh04
Copy link

Thank you so much, sir!!
I have just a question!
Can I use this method for android API 16+ (Jellybean 4.1+)?

@Karewan
Copy link
Author

Karewan commented Feb 13, 2021

It seems to me that in the past I tested on Android 4.3 and it didn't work, but you can try anyway.

@mohsaleh04
Copy link

mohsaleh04 commented Feb 13, 2021

It seems to me that in the past I tested on Android 4.3 and it didn't work, but you can try anyway.

But I tested my application in android 4.2 and it worked well.
I did downgrade the version of the libraries and I think for this reason that worked for me.

@soroushLotfi
Copy link

This was life savior thanks. Tested on android 4.1 and works like a charm.

@jdavidag
Copy link

jdavidag commented May 21, 2022

como puedo hacerlo reconocible osea si el servidor no es TLSv1.3 que conecte con TLSv1.2. por que en mi caso pongo todos los protocolos y solo me esta funcionando el TLSv1.3 pero quiero que me funcione con todos los protocolos dependiendo de la conexión...

@mendhak
Copy link

mendhak commented Jun 16, 2022

For the SSLContext I had to use the .newProvider() method rather than the string "Conscrypt". Like so:

SSLContext sslContext = SSLContext.getInstance("TLS", Conscrypt.newProvider());

When using just the string I was getting NoSuchProviderException: Conscrypt

@zedxxx
Copy link

zedxxx commented Nov 14, 2022

Thanks a lot! Works great on Android 4.1.

@CyxouD
Copy link

CyxouD commented Oct 19, 2023

It doesn't work in my case on my specific API 19 device, but works in emulator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment