Issue #184

Get error javax.net.ssl.SSLPeerUnverifiedException: No peer certificate in Android API 16 to API 19

Getting started

Read about HTTPS and SSL https://developer.android.com/training/articles/security-ssl Check backend TLS https://www.ssllabs.com/index.html TLS by default in Android P https://android-developers.googleblog.com/2018/04/protecting-users-with-tls-by-default-in.html

TLS version

Read https://developer.android.com/reference/javax/net/ssl/SSLSocket.html

This class extends Sockets and provides secure socket using protocols such as the “Secure Sockets Layer” (SSL) or IETF “Transport Layer Security” (TLS) protocols.

TLS 1.1 and 1.2 are supported from API 16, but not enabled by default until API 20.

Install TLS 1.2 when needed

Read https://medium.com/tech-quizlet/working-with-tls-1-2-on-android-4-4-and-lower-f4f5205629a

The first thing we realized was that despite documentation suggesting otherwise, not all devices on Android 4.1+ actually support TLS 1.2. Even though it is likely due to device manufacturers not fully following the official Android specs, we had to do what we could to ensure this would work for our users.

Luckily, Google Play Services provides a way to do this. The solution is to use ProviderInstaller from Google Play Services to try to update the device to support the latest and greatest security protocols.

fun Context.installTls12() {
  try {
      ProviderInstaller.installIfNeeded(this)
  } catch (e: GooglePlayServicesRepairableException) {
      // Prompt the user to install/update/enable Google Play services.
      GoogleApiAvailability.getInstance()
          .showErrorNotification(this, e.connectionStatusCode)
  } catch (e: GooglePlayServicesNotAvailableException) {
      // Indicates a non-recoverable error: let the user know.
  }
}

Does not seem to work, as the root problem was that TLS was not enabled

Try normal HttpsUrlConnection

If we use any networking library and suspect it is the cause, then try using normal HttpsUrlConnection to check.

class MyHttpRequestTask extends AsyncTask<String,Integer,String> {

    @Override
    protected String doInBackground(String... params) {
        String my_url = params[0];
        try {
            URL url = new URL(my_url);
            HttpsURLConnection httpURLConnection = (HttpsURLConnection) url.openConnection();
            httpURLConnection.setSSLSocketFactory(new MyFactory());
            // setting the  Request Method Type
            httpURLConnection.setRequestMethod("GET");
            // adding the headers for request
            httpURLConnection.setRequestProperty("Content-Type", "application/json");


            String result = readStream(httpURLConnection.getInputStream());
            Log.e("HttpsURLConnection", "data" + result.toString());


        }catch (Exception e){
            e.printStackTrace();
            Log.e("HttpsURLConnection ", "error" + e.toString());
        }

        return null;
    }

    private static String readStream(InputStream is) throws IOException {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("US-ASCII")));
        StringBuilder total = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            total.append(line);
        }
        if (reader != null) {
            reader.close();
        }
        return total.toString();
    }
}

class MyFactory extends SSLSocketFactory {

    private javax.net.ssl.SSLSocketFactory internalSSLSocketFactory;

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

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

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

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

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

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

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

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

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return enableTLSOnSocket(internalSSLSocketFactory.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;
    }
}

The key is setEnabledProtocols. Then use

String url = "https://www.myserver.com/data"
new MyHttpRequestTask().execute(url);

Use custom SSLSocketFactory in some networking libraries

If our custom MyFactory works for HttpsUrlConnection, then the problem lies in some 3rd party networking libraries.

Read https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/

The Android documentation for SSLSocket says that TLS 1.1 and TLS 1.2 is supported within android starting API level 16+ (Android 4.1, Jelly Bean). But it is by default disabled but starting with API level 20+ (Android 4.4 for watch, Kitkat Watch and Android 5.0 for phone, Lollipop) they are enabled. But it is very hard to find any documentation about how to enable it for phones running 4.1 for example.

The first thing you need to do is to make sure that your minimum required API level is 16 to have the following code working in your project.

To enable TLS 1.1 and 1.2 you need to create a custom SSLSocketFactory that is going to proxy all calls to a default SSLSocketFactory implementation. In addition to that do we have to override all createSocket methods and callsetEnabledProtocols on the returned SSLSocket to enable TLS 1.1 and TLS 1.2. For an example implementation just follow the link below.

import javax.net.ssl.SSLSocketFactory;

class MyFactory extends org.apache.http.conn.ssl.SSLSocketFactory {

    public static KeyStore getKeyStore() {
        KeyStore trustStore = null;
        try {
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return trustStore;
    }


    private SSLSocketFactory internalSSLSocketFactory;

    public MyFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, null, null);
        internalSSLSocketFactory = context.getSocketFactory();
    }


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

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

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

Then maybe use it in a library, for example the ancient AsyncHttpClient

asyncHttpClient = new AsyncHttpClient();
asyncHttpClient.setTimeout(HTTP_GET_TIMEOUT);
asyncHttpClient.setSSLSocketFactory(new MyFactory(MyFactory.getKeyStore()));

Updated at 2020-08-11 16:27:22