Log the SSL Certificate programmatically - android

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:
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)

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)

Related

javax.net.ssl.SSLHandshakeException on Android 4.3

Unfortunately I've stucked on getting my app able to use TLS 1.2 protocol on Android version < 5.0.
I use okhttp3 library for creating and firing up proper requests. Everything works perfect on devices running Lollipop +.
I am building OkHttpClient instance like this:
SSLContext context = SSLContext.getInstance("TLS"); // tried TLSv1.2
context.init(null, trustManagers, null);
List<ConnectionSpec> specs = new ArrayList<>();
specs.add(ConnectionSpec.MODERN_TLS);
specs.add(ConnectionSpec.CLEARTEXT);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(new TlsSocketFactory(context.getSocketFactory()), (X509TrustManager)trustManagers[0])
.connectionSpecs(specs)
.build();
Inside TlsSocketFactory class I am setting enabled protocols:
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
}
return socket;
}
After executing request i get this issue:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x67531570: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744 0x5dfc97e8:0x00000000)
I have made a lot of research about this kind of issue, but unfortunately none of presented solutions did the trick. I have already tried:
Installing proper security provider link
Creating TLSSocketFactory class (similar is presented here: link)
Creating NoSSLv3SocketFactory class (presented as solution here: link)
Checking if sever uses proper ciphers (and it does)
Curious thing is calling getSupportedSSLParameters().getProtocols() returns [SSLv3, TLSv1, TLSv1.1, TLSv1.2] but calling getDefaultSSLParameters().getProtocols() returns only [SSLv3, TLSv1]
Do you guys have any ideas about this problem?
Replace MODERN_TLS with COMPATIBLE_TLS. That is necessary to enable obsolete TLS versions.
See OkHttp’s HTTPS page for details.

Message Security with Mutual Certificates for Android & iOS

I would like to ask the following. We have a mobile app both for Android & iOS that exchanges data with a .NET server.
For Android the ksoap2 library is used, while for iOS the Alamofire with AEXML libraries are used.
We would like to enable encryption for the communication between the server and the apps, specifically Message Security with Mutual Certificates (https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-mutual-certificates)
I am not able to find any information how either the Android or the iOS client could encrypt/decrypt the requests/responses.
Can you please provide any relative information?
Thanks in advance!
For the iOS Part.
By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework.
While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities.
In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the ServerTrustPolicy.
ServerTrustPolicy
The ServerTrustPolicy enumeration evaluates the server trust generally provided by an URLAuthenticationChallenge when connecting to a server over a secure HTTPS connection.
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
There are many different cases of server trust evaluation giving you complete control over the validation process:
performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to validate the host provided by the challenge.
pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned certificates match one of the server certificates.
pinPublicKeys: Uses the pinned public keys to validate the server trust.
The server trust is considered valid if one of the pinned public keys match one of the server certificate public keys.
disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
customEvaluation: Uses the associated closure to evaluate the validity of the server trust thus giving you complete control over the validation process. Use with caution.
AlamoFire documentation
For Android part, i am not experienced with but i came across someone asking about the same thing and got an answer with
what you need to do is only to install your certificate into the webserver and call the webservice URL like https://my.webservice.url/ instead of http://my.webservice.url/.
If your certificate is a self-signed certificate, means you did not
bought it from a certificate authority, you will need to set the
SSLSocketFactory. You can check the project wiki on how to do that:
http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#How_to_set_the_SSLSocketFactory_on_a_https_connection__in_order
Check Here.
This might be Helpful too
UPDATE: i've found this framework SOAPEEngine
this one.
Supports Basic, Digest and NTLM Authentication, WS-Security, Client side Certificate and custom security header.
you check its example for more clarifications too.
Message encryption with WCF is done through the WS-Security protocol, by setting the security attribute mode to Message. As you have undoubtedly realized by now, WS-Security is not exactly popular on the Android and iOS platforms, mostly due to it having been superseded by other technologies (like HTTPS,) so your choices in terms of existing libraries are not abundant. The fact that not even Microsoft-owned Xamarin supports it says a lot.
First, a word on WS-Security, This protocol provides three main means of enhancing message security:
Authentication through security tokens
Signing SOAP messages
Encryption of SOAP messages
So a conforming implementation should really provide all three of these functions, but we are mostly interested in encryption here, as from the question and comments it seems like you have the authentication part working.
Therefore, assuming we are looking for a mobile platform library providing minimal WCF compatibility with WS-Security signing and encryption:
Android
On Android, the closes to your needs is WSS-Client for Android. This project:
... implements the OASIS Web Service Security (WSS) standard for
Android platforms and makes XML Encryption and XML Signature available
for tablets and smartphones.
Note that this is GPL-licensed software. The readme says to contact the author for commercial license details. However, it seems to do what you're looking for. Once you have negotiated the key exchange with the server, to encrypt a previously constructed SOAP message using the SOAPUtil class you would do something like:
SOAPMessage message = SOAPUtil.createSOAPMessage();
//... Populate the message (possibly with another library)
SecCrypto serverCrypto = new SecCrypto(serverCertificate, null);
SecCrypto clientCrypto = new SecCrypto(clientPublicKey, clientPrivateKey);
SOAPMessage securedMessage = SOAPUtil.secureSOAPMessage(message, new HashMap<String,String>(SecConstants.REQ_ENCRYPT_SIGN, "yes"), clientCrypto, serverCrypto);
//...
SOAPMessage returnedSecuredMessage = SOAPUtil.sendSOAPMessage(context, securedMessage, endpoint, cryptoParams);
SOAPMessage returnedMessage = SOAPUtil.validateSOAPMessage(returnedSecuredMessage, new HashMap<String,String>(SecConstants.RES_DECRYPT_VERIFY, "yes", decCrypto);
Nevertheless, be prepared to do quite a bit of configuration work and debugging to make it match your server's needs.
If you are looking for a more current and actively developed product, Quasar Development offers a WS-Security implementation for Android.
iOS
Things look a lot more bleak on the iOS side. There are a few libraries claiming varying degrees of support for WSS, but none of them seem to match your needs:
At first SOAPEngine looks the most promising, as it claims
support for WS-Security. However, in a footnote it says that it has a
limitation that it only supports the WCF basicHttpBinding. This
would actually be OK if true. The binding used in the sample code you
linked to in the question is wsHttpBinding however it's important
to note that both wsHttpBinding and basicHttpBinding have support
for encryption though WS-Security. The difference is that
wsHttpBinding supports WS-Security by default (whereas it needs to
be enabled with basicHttpBinding) and it also supports
WS-ReliableMessaging and some other features you may or may not
care about. But the basicHttpBinding is the one intended for
compatibility with other technologies. So in order to have
WS-Security encryption on your WCF server and maximize compatibility
with other technologies at the same time, it would be OK to use
basicHttpBinding and enable WS-Security signing and encryption by
setting the mode security attribute to Message. With the
Message attribute, from the docs:
Security is provided using SOAP message security. By default, the body
is encrypted and signed. For this binding, the system requires that
the server certificate be provided to the client out of band. The only
valid ClientCredentialType for this binding is Certificate.
But this is of no use as SOAPEngine does not have any support for
encrypting messages (or at least I could not find any support for it
in the API). The only WS-Security function it supports is
authentication. So the claim that it supports WS-Security seems
misleading as the support is quite limited.
ServiceNow offers very limited support for WS-Security. It only
supports verifying server signatures. No encryption or signing on the
client side.
Chilkat has some rudimentary XML support and there is sample
code for WS-Security authentication. I didn't see any support or
sample code for WS-Security encryption.
Therefore for iOS, to the best of my knowledge, your two options are:
Pick one of the existing libraries that best matches your other needs
and reach out to the developer and see if you can get them to add the
WS-Security features you need.
Implement the bare minimum features
you need yourself. The spec is actually not that complicated and
there is sample code out there (for non-mobile platforms) that you
can use as guide, like WSS4J for example.
In Android:
I use kasoap2 to call the web services, but before the call, to enable mutual authentication with client certificate you need to initialize a SSLContext with the client authentication keys (KeyManager).
To do that you have to load your certificate and the corresponding password in a KeyStore object, my certificate is a *.pfx file. I cerate a "PKCS12" KeyStore instance. Then you need a KeyManagerFactory object to obtain the KeyManager array. I use a "PKIX" KeyManagerFactory instance. The KeyManager array is needed to init the SSLContext.
Here is an example:
public void enableMutualAuthentication(String filename, String password) {
try {
// InputStream to read the certificate file
FileInputStream cert = new FileInputStream(filename);
char[] pass = password.toCharArray();
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(cert ,pass);
cert.close();
KeyManagerFactory keymanagerfactory = javax.net.ssl.KeyManagerFactory.getInstance("PKIX");
keymanagerfactory.init(keystore, pass);
KeyManager[] keymanagers = keymanagerfactory.getKeyManagers();
// This is not for the mutual authentication.
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
// Install the mutual authentication manager
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(keymanagers, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
}
Check those links, was what help me most.
https://chariotsolutions.com/blog/post/https-with-client-certificates-on/
http://callistaenterprise.se/blogg/teknik/2011/11/24/android-tlsssl-mutual-authentication/

okhttp doesn't validate pins correctly

Using com.squareup.retrofit2:retrofit:2.0.1 with com.squareup.okhttp3:okhttp:3.2.0 on an AVD with Android 6.0.
I'm trying to implement public key pinning using a self signed certificate that is signed by a Root CA. That Root CA is in the system CA trust store.
Using the example provided by okhttp wiki with some small changes:
OkHttpClient client = new OkHttpClient.Builder().certificatePinner(
new CertificatePinner.Builder()
.add(pinningUrl, "sha256/invalidPIN")
.build()).build();
Request request = new Request.Builder()
.url(pinningUrl)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
What happens is that response.isSuccessful returns true, no exception is thrown, although the pin isn't correct. The only thing that is done correctly is the validation of the certificate with the Root CAs in systems CA trust store.
What I've found to be working, is adding this line before the for loop. But that isn't the right approach because the request is already sent, the pinning should work before TLS negotiation is finished. Also this line isn't mentioned in any sample code I've found.
client.certificatePinner().check(pinningUrl, response.handshake().peerCertificates());
throws
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Is there a bug in the sample code provided by okhttp or am I doing something wrong?
You’re configuring it incorrectly. Replace pinningUrl with the hostname of the pinning URL. For example, you want example.com instead of http://example.com/. If you’d like to send a PR to make hostname validation more strict, it would be quite welcome.

Retrofit not working with maps api of sha256 test url

I have used retrofit for google Places and direction API integration.Recently I got update from Google Maps for Work Support Team that In order to make sure your applications are not impacted you need to verify that the HTTPS client you are using supports SHA-256.
They have provided one test url(https://cert-test.sandbox.google.com) to validate that http-client is compatible or not
I have used https://cert-test.sandbox.google.com to validate with Retrofit but It is failing and giving me exception as mentioned below:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x1027ce0: Failure in SSL library, usually a protocol error
To be clear, I have used plain okhttpclient with this integration.
please do needful if anybody have fix for it.
Looks that server is using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 cipher. It only started being supported in API 20 (L Preview). You can see a list of supported ciphers by API level on the SSLSocket docs.
Try running your test on a 5.0 or above device. For example, the following code is successful on a device running 5.0, but gets an SSL exception on 4.4.4 --
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().get().url("https://cert-test.sandbox.google.com/")
.build();
response = client.newCall(request).execute();
Log.d(TAG, "code = " + response.code());
but the same is also true for the equivalent URLConnection code --
HttpsURLConnection con = (HttpsURLConnection) new URL("https://cert-test.sandbox.google.com/").openConnection();
con.connect();
Log.d(TAG, "code = " + con.getResponseCode());
The issue is not retrofit or okhttp, but the limitations of the default security providers provided on older phones.
You can work around it by installing a new provider. Google makes one available through google play services, and is easy to install. One line (plus exception handling, etc).
try {
ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) {
// Fix it
} catch (GooglePlayServicesNotAvailableException e) {
// Skip it
}
For a full example, see "Updating Your Security Provider to Protect Against SSL Exploits" from Google.
Doing this will allow the above two code blocks to run on lower API versions if the phone has Google Play installed.

Retrofit failed response callback, java.security.cert.CertificateException

Im trying to use Retrofit to make a call to this api: https://api.wheretheiss.at/v1/satellites/25544
Here is the code where the connection is being made:
retrieveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASEURL)
.addConverterFactory(GsonConverterFactory.create())
.build();
ISSApi api = retrofit.create(ISSApi.class);
Call<ISS> ISS = api.getLocation();
ISS.enqueue(new Callback<ISS>() {
#Override
public void onResponse(Response<ISS> response) {
System.out.println("booya");
}
#Override
public void onFailure(Throwable t) {
System.out.println("failure");
System.out.println(t.getMessage().toString());
t.printStackTrace();
}
});
So it seems to at least be able to find the API as the onFailure callback is being fired, but im getting this error:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Some notes:
I've used OkHTTP before with an API that had SSL enabled and I never had to deal with certificates. Also, something i noticed, if i go to the API endpoint on my Nexus 5, i get this: http://i.imgur.com/he8821o.png. Perhaps thats related to the issue?
Any help appreciated. Using Retrofit 2.0.0.
The server you are trying to connect to does not have a valid SSL certificate for Android.
This is weird, because I can access it in my computer with no issues, but apparently Android is missing the CA that signed this certificate.
What you can do:
Manually add the certificate in your device -- not good if you are deploying it to many people.
Create a custom SslSocketFactory that accepts this server certificate.
The second approach is the best, but it brings a lot of problems to the table.
For example, if the server changes the certificate (they normally do from time to time), you will get these same errors everytime until you update your app.
An alternative is finding who is the certification authority of this server certificate and add it instead (it would work with #1 and #2).
Check this answer to learn how to override certificate validation using OkHttp.
EDIT: the certificate issuer seems to be RapidSSL (info here).

Categories

Resources