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:
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.
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:
How do I inspect the information of the SSL Certificate that the device is seeing when hitting my endpoint?
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)
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With