7

I have 2 devices in the wild that are not able to connect to my TLS v1.2 endpoint. All others seem able to, including browsers, PostMan and iOS devices.

The devices are running Android 5 & 7 (so there should not be a problem with the TLS v1.2 support).

Note: This is not a self-signed certificate. It is signed by Amazon.

Immediate thoughts were:

  1. Android fragmentation - perhaps the devices (one is a Kindle Fire 7) are not including the correct certificates into the OS. It wouldn't be the first time that a device manufacturer made an odd decision that breaks functionality.

  2. API is being accessed via a proxy, and there actually is a Man-In-The-Middle, correctly being detected.


Fixing (1) means bundling our certificate, and leads to the usual problems when our cert expires.

I would prefer to get the user to install a debug build that confirms whether (1) or (2) is the problem. Such build would inspect the SSL Certificate provided by the server/proxy, and log that back to me.


Web Frameworks:

  • Retrofit v2.3.0
  • OkHttp v3.9.1

Question:

How do I inspect the information of the SSL Certificate that the device is seeing when hitting my endpoint?


Update per comment from @SangeetSuresh:

It turns out there are 2 different exceptions being thrown.

The Kindle Fire 7" Tablet (KFAUWI, OS 5.1.1) is throwing the one I have started to investigate, which this question is meant to have focused on. i.e. basic SSL failure.

java.security.cert.CertPathValidatorException: 
    Trust anchor for certification path not found.
       at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:331)
       at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:232)
       at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)

The LG device (LG-SP200, OS 7.1.2) is having the connection closed by the peer, which should be addressed under a new question if not solved here:

javax.net.ssl.SSLHandshakeException: 
    Connection closed by peer
       at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(NativeCrypto.java)
       at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
       at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:299)
Cœur
  • 37,241
  • 25
  • 195
  • 267
Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • 1
    TLS 1.2 ist only supported by Android 4.1 and up. Additionally it is disabled by default on devices running pre 5.0 – Christopher Sep 10 '18 at 09:03
  • 1
    @Christopher noted and already checked on my side. `OkHttp` or `Retrofit` actually took care of that for me on the older devices. Now it is just Android 5 and 7 devices that are failing. – Richard Le Mesurier Sep 10 '18 at 09:07
  • So what are you able to hook into? The OkHttp response? In that case I think the information should be available from `response.handshake().peerCertificates()`. – Robby Cornelissen Sep 10 '18 at 09:12
  • 1
    I have no idea. You could have your debug app just trust all certificates and check what you receive. – Robby Cornelissen Sep 10 '18 at 09:19
  • 1
    Go ahead. Since you have the test code available, you'll be able to put together a better answer than me. – Robby Cornelissen Sep 10 '18 at 09:54
  • @RichardLeMesurier Have you added any certificate pinning for okhttp ? – Sangeet Suresh Sep 10 '18 at 10:23
  • @Sangeet No I have not yet. This is an endpoint that I _expect to be able to work_ (since it does on almost all devices, iOS, Android, PC). I'm trying to focus this question on _how to debug what the device is seeing_ - in order to determine if pinning would be required for these offending devices, or what the best route forward would be. – Richard Le Mesurier Sep 10 '18 at 10:26
  • @RichardLeMesurier are you getting SSL handshake exception or timeout? – Sangeet Suresh Sep 10 '18 at 10:28
  • @RichardLeMesurier `response?.raw()?.handshake()` I think this will help you – Sangeet Suresh Sep 10 '18 at 10:33
  • @RichardLeMesurier One thing you can do is that if your certificate is not trusted by the device, you can add certificate pinning and test whether it's working fine or not. Because if you add certificate pinning, it will only check that certificate – Sangeet Suresh Sep 10 '18 at 10:48

1 Answers1

3

Robby Cornelissen provided the basic answer in a comment referencing the OkHttp Response:

the information should be available from response.handshake().peerCertificates().

A simple Interceptor was implemented to inspect the certificates, given a valid handshake:

private static class SslCertificateLogger implements Interceptor {

    public static final String TAG = "SSL";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response;
        try {
            response = chain.proceed(request);
        } catch (Exception e) {
            Log.d(TAG, "<-- HTTP FAILED: " + e);
            throw e;
        }

        Handshake handshake = response.handshake();
        if (handshake == null) {
            Log.d(TAG, "no handshake");
            return response;
        }


        Log.d(TAG, "handshake success");
        List<Certificate> certificates = handshake.peerCertificates();
        if (certificates == null) {
            Log.d(TAG, "no peer certificates");
            return response;
        }

        String s;
        for (Certificate certificate : certificates) {
            s = certificate.toString();
            Log.d(TAG, s);
        }

        return response;
    }
}

This gets added to the OkHttpClient as per normal:

OkHttpClient.Builder builder = new OkHttpClient.Builder()
        .addInterceptor(new SslCertificateLogger())
        .build();

A similar solution was proposed by Sangeet Suresh that references the Retrofit Response object:

response?.raw()?.handshake() I think this will help you

Here the important information being the fact that Retrofit gives access to the raw OkHttp response in this manner.

This would not be used in an Interceptor but rather at a higher level, in the actual Retrofit handling code, after getting a Retrofit Response<> from the API.

Converting his Kotlin solution back to Java could yield something like this:

okhttp3.Response raw = httpResponse.raw();
if (raw != null) {
    Handshake handshake = raw.handshake();
    if (handshake != null) {
        List<Certificate> certificates = handshake.peerCertificates();
        if (certificates != null) {
            for (Certificate certificate : certificates) {
                Log.d(TAG, certificate.toString());
            }
        }
    }
}

Both solutions work fine, provided the handshake() is not null i.e. when the handshake succeeds.

Given that this is an investigation into failed handshakes, a further step was required to "trust all certificates" (NB debug builds only!).

This has been documented many times - here is one such version:

Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • 2
    This handshake object just saved my life today! I was searching for an interceptor to check the certificates from the server and wow, I am saved now! Thank you very much! – sunlover3 Nov 04 '19 at 17:02