Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Log the SSL Certificate programmatically

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)
like image 728
Richard Le Mesurier Avatar asked Sep 10 '18 08:09

Richard Le Mesurier


1 Answers

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:

  • Unsafe SSL Client (do not do this in production)
like image 105
Richard Le Mesurier Avatar answered Oct 25 '22 06:10

Richard Le Mesurier