21

Iam having this strange issue in which the retrofit keeps throwing me

"SSL handshake aborted: ssl=0x618d9c18: I/O error during system call, Connection reset by peer"

in kitkat, whereas the same code working fine in lollipop devices. Iam using an OkHttpClient client like the following

public OkHttpClient getUnsafeOkHttpClient() {
    try {
        final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] chain,
                    String authType) {
            }
            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] chain,
                    String authType) {
            }
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[0];
            }
        } };

        int cacheSize = 10 * 1024 * 1024; // 10 MB
        Cache cache = new Cache(getCacheDir(), cacheSize);
        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(null, trustAllCerts,
                new java.security.SecureRandom());
        final SSLSocketFactory sslSocketFactory = sslContext
                .getSocketFactory();
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient = okHttpClient.newBuilder()
                .cache(cache)
                .sslSocketFactory(sslSocketFactory)
                .hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER).build();
        return okHttpClient;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

Iam using this client in retrofit like this

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .client(getUnsafeOkHttpClient())
            .addConverterFactory(GsonConverterFactory.create())
            .build();

EDIT : adding the getUnsafeOkHttpClient() has no effect here and it is not at all recommended to bypass the ssl check by using getUnsafeOkHttpClient()

FYI : The issue was because the api endpoint supports only TLS 1.2 which was disabled by default on android devices 16<device<20 . So for 16<device<20, create a custom SSLSocketFactory

Navneet Krishna
  • 4,667
  • 5
  • 21
  • 40

6 Answers6

30

Finally found a solution to this issue, its not a complete solution as it is a hack mentioned by Jesse Wilson from okhttp, square here. As i mentioned it was a simple hack where i had to rename my SSLSocketFactory variable to

private SSLSocketFactory delegate;

notice that it would throw error if you give any name other than delegate. Iam posting my complete solution below

This is my TLSSocketFactory class

public class TLSSocketFactory extends SSLSocketFactory {

private SSLSocketFactory delegate;
private TrustManager[] trustManagers;

public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
    generateTrustManagers();
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, trustManagers, null);
    delegate = context.getSocketFactory();
}

private void generateTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }

    this.trustManagers = trustManagers;
}


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

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

@Override
public Socket createSocket() throws IOException {
    return enableTLSOnSocket(delegate.createSocket());
}

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

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

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

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

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

private Socket enableTLSOnSocket(Socket socket) {
    if(socket != null && (socket instanceof SSLSocket)) {
        ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
    }
    return socket;
}

@Nullable
public X509TrustManager getTrustManager() {
    return  (X509TrustManager) trustManagers[0];
}

}

and this is how i used it with okhttp and retrofit

 OkHttpClient client=new OkHttpClient();
    try {
        TLSSocketFactory tlsSocketFactory=new TLSSocketFactory();
        if (tlsSocketFactory.getTrustManager()!=null) {
            client = new OkHttpClient.Builder()
                    .sslSocketFactory(tlsSocketFactory, tlsSocketFactory.getTrustManager())
                    .build();
        }
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

EDIT : The method public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) is now deprecated and we should use public Builder sslSocketFactory( SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) as i have updated in the answer. This is because X509TrustManager is a field that OkHttp needs to build a clean certificate chain, which was not paased in the deprecated method.

You may also check this for more info

Navneet Krishna
  • 4,667
  • 5
  • 21
  • 40
9

I modified @Navneet Krishna answer because method OkHttpClient.Builder. builder.sslSocketFactory(tlsSocketFactory) is now deprecated.

public class TLSSocketFactory extends SSLSocketFactory {

private final SSLSocketFactory delegate;
private TrustManager[] trustManagers;

public TLSSocketFactory() throws KeyStoreException, KeyManagementException, NoSuchAlgorithmException {
    generateTrustManagers();
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, trustManagers, null);
    delegate = context.getSocketFactory();
}

private void generateTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }

    this.trustManagers = trustManagers;
}

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

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

@Override
public Socket createSocket() throws IOException {
    return enableTLSOnSocket(delegate.createSocket());
}

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

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

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

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

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

private Socket enableTLSOnSocket(Socket socket) {
    if (socket instanceof SSLSocket) {
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"});
    }
    return socket;
}

@Nullable
public X509TrustManager getTrustManager() {
    return  (X509TrustManager) trustManagers[0];
}
}

You need to assign it like this:

TLSSocketFactory tlsTocketFactory = new TLSSocketFactory();
client = new OkHttpClient.Builder()
            .sslSocketFactory(tlsSocketFactory, tlsSocketFactory.getTrustManager());
            .build();
Paweł Dedio
  • 1,138
  • 10
  • 18
  • I had to remove @Inject at the constructor of the TLSSocketFactory for this to work. Maybe it was the wrong import (for me only 1 option was possible: import javax.inject.Inject, which doesn't make sense to me). ps. including imports in your answer really helps! – P Kuijpers Oct 18 '19 at 15:07
  • Sorry, it was annotation used in Dagger DI. I've edited my answer. Thank you for noticing that. – Paweł Dedio Oct 19 '19 at 16:40
5

In addition to Navneet Krishna I had to do the next in my App's class:

ProviderInstaller.installIfNeededAsync

According to https://developer.android.com/training/articles/security-gms-provider, and this because I needed to update the security provider to protect against SSL exploits.

My App Class:

public class AppClass extends MultiDexApplication {

private static final String TAG = AppClass.class.getName();

private static Context context;
private static AuthAPI authAPI;
private static RestAPI buyersAPI;

@Override
public void onCreate() {
    super.onCreate();
    /* enable SSL compatibility in pre-lollipop devices */
    upgradeSecurityProvider();

    createAuthAPI();
    createRestAPI();
}

private void upgradeSecurityProvider() {
    try{
        ProviderInstaller.installIfNeededAsync(this, new ProviderInstaller.ProviderInstallListener() {
            @Override
            public void onProviderInstalled() {
                Log.e(TAG, "New security provider installed.");
            }

            @Override
            public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
                GooglePlayServicesUtil.showErrorNotification(errorCode, BuyersApp.this);
                Log.e(TAG, "New security provider install failed.");
            }
        });
    }catch (Exception ex){
        Log.e(TAG, "Unknown issue trying to install a new security provider", ex);
    }

}

private void createAuthAPI() {
    OkHttpClient.Builder authAPIHttpClientBuilder = new OkHttpClient.Builder();

    Retrofit retrofit = new Retrofit.Builder()
            .client(enableTls12OnPreLollipop(authAPIHttpClientBuilder).build())
            .baseUrl(DomainLoader.getInstance(context).getAuthDomain())
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    authAPI = retrofit.create(AuthAPI.class);
}

private static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()), trustManager);

            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;
}

private void createRestAPI() {
    OkHttpClient.Builder restAPIHttpClientBuilder = new OkHttpClient.Builder();
    buyersAPIHttpClientBuilder.readTimeout(60, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.connectTimeout(60, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.writeTimeout(600, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.addInterceptor(new NetworkErrorInterceptor());
    buyersAPIHttpClientBuilder.addInterceptor(new TokenVerificationInterceptor());

    Retrofit retrofit = new Retrofit.Builder()
            .client(enableTls12OnPreLollipop(restAPIHttpClientBuilder).build())
            .baseUrl(DomainLoader.getInstance(context).getDomain())
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
            .addConverterFactory(ScalarsConverterFactory.create())
            .build();

    buyersAPI = retrofit.create(RestAPI.class);
}
}

And My Tls12SocketFactory class:

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;
}
}

And it's working like a charm in all devices with KitKat and above.

Carlos Daniel
  • 1,572
  • 17
  • 21
3

I got SSL/TLS info for api.data.gov.in here - https://www.ssllabs.com/ssltest/analyze.html?d=api.data.gov.in

It looks like it supports TLSv1.2 only. Old Android versions indeed have issues with the newest TLS versions. In "Handshake Simulation" section on the ssllabs page you can even see your problems.

See How to enable TLS 1.2 support in an Android application (running on Android 4.1 JB) for available solutions.

algrid
  • 4,204
  • 2
  • 24
  • 31
  • its not working for me,its now crashing with `Caused by: java.lang.IllegalStateException: Unable to extract the trust manager on okhttp3.internal.platform.AndroidPlatform@422663d0, sslSocketFactory is class navneet.com.devinered.service.TLSSocketFactory` – Navneet Krishna Sep 03 '17 at 16:23
3

I think my solution might help someone.

In my project, I had a need to do a JSON request over SSL on an older Android (4.4) and I kept getting the issue as mentioned at the top of the thread.

To fix it all I had to do was to add the class Tls12SocketFactory exactly as above.

However, I added a modified code to my project class

I added this to my oncreate

upgradeSecurityProvider();

and modified the function for context as below, and that is all. No more issues of SSL connection

private void upgradeSecurityProvider() {
        try{
            ProviderInstaller.installIfNeededAsync(this, new ProviderInstaller.ProviderInstallListener() {
                @Override
                public void onProviderInstalled() {
                    Log.e("SSLFix", "New security provider installed.");
                }

                @Override
                public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
                   // GooglePlayServicesUtil.showErrorNotification(errorCode, BuyersApp.this);
                    Log.e("SSLFix", "New security provider install failed.");
                }
            });
        }catch (Exception ex){
            Log.e("SSLFix", "Unknown issue trying to install a new security provider", ex);
        }

}

That is all and no more issues.

Suraj Rao
  • 28,186
  • 10
  • 88
  • 94
2

In my Case I just add a dependency in build.gradle (module app) and add the code to my first activity solved my problem . Dependency :

implementation 'com.google.android.gms:play-services-base:11.0.0'

My code :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
        && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    try {
        ProviderInstaller.installIfNeeded(this);
    } catch (GooglePlayServicesRepairableException e) {
        GooglePlayServicesUtil.showErrorNotification(e.getConnectionStatusCode(), this);
        return;
    } catch (GooglePlayServicesNotAvailableException e) {
        return;
    }
}

This problem occurs in below lollipop version . By default TLS not enabled so programmatically have to enable .