Android pre-lollipop devices giving error “SSL han

2019-01-07 20:05发布

问题:

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

回答1:

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;

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

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

}

and this is how i used it with okhttp and retrofit

 OkHttpClient client=new OkHttpClient();
    try {
        client = new OkHttpClient.Builder()
                .sslSocketFactory(new TLSSocketFactory())
                .build();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

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

You may also check this for more info



回答2:

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.



回答3:

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.



回答4:

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.