How to solve SSLHandshakeException in Android : SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version ?


Being android developers, we might have at least one time come across this issue >>

javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8df4b50: Failure in SSL library, usually a protocol error
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x8d92d990:0x00000000)

or sometimes a similar variant of the above issue. What this actually means is that >>
"The server you are talking to is supporting only a specific protocol version which your client(your android app) is unaware of. That is, your client does not support that particular version"
The issue is mostly encountered in devices below lollipop i.e, kitkat and below devices. Let us see why the issue occurs.

Example Case :

Try getting some response from the host https://api.github.com/ like shown below

Retrofit retrofit1 = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();


        GithubServise githubServise = retrofit1.create(GithubServise.class);

        Call<ResponseBody> call = githubServise.getGithub();
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Toast.makeText(MainActivity.this, "got response" , Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Toast.makeText(MainActivity.this, "SSL error?" , Toast.LENGTH_SHORT).show();
            }
        });


GithubServise.java

public interface GithubServise {
    @GET("/users/{pass_any_github_username_here}/repos")
    Call<ResponseBody> getGithub();
}

pass your github username in the interface as shown so that you could fetch your repos

You could make the following  observations while running this :


  1. This code would run successfully without any issues from android api levels 20+(from lollipop onwards) and will fetch us a successful response with list of repos.
  2.  This will not return any successfull response in the case of android devices below 20(kitkat(19) and below). The onFailure method will be executed with an error like this
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8df4b50: Failure in SSL library, usually a protocol errorerror:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x8d92d990:0x00000000)
The reason why this is working only in devices with api levels 20+ is that the host  https://api.github.com/ supports only TLS 1.2 which is by default disabled in android devices below 20.

You can see the list of protocols supported by your web server by going here https://www.ssllabs.com/ssltest/ and typing in https://api.github.com/ inside hostname field.

check the list of protocols supported by the website

Now hit submit. See the test results, in the section protocols inside configuration, you can see that TLS 1.2 is the only supported protocol.

ssllabs : supported protocols test result


But the protocol TLS 1.2  is supported in android from api level 16. So we just have to enable it for devices with api levels from 16 to 20.

So you can use the following TLSSocketFactory to enable TLS 1.2 for devices below 20

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.2"});
        }
        return socket;
    }
}

Using this TLSSocketFactory, the protocol will never fallback to ssl2 or ssl3 as we have enabled only TLS 1.2

Now apply this to your retrofit call using an OkHttpClient()

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

        Retrofit retrofit1 = new Retrofit.Builder()
                .client(client)
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();


        GithubServise githubServise = retrofit1.create(GithubServise.class);

        Call<ResponseBody> call = githubServise.getGithub();
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Toast.makeText(MainActivity.this, "got response" , Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Toast.makeText(MainActivity.this, "SSL error?" , Toast.LENGTH_SHORT).show();
            }
        });
    }

Thats it! you have now successfully enabled TLS 1.2  on devices from 16 to 20

You may also check similar issue in StackOverflow



No comments:

Post a Comment