HttpClient generates SSLException after acquiring new domain name - android

Recently I acquired a new domain name to use with an existing Android application. I also bought SSL certificate from a trusted CA (Comodo). When I browse to the new domain with a web browser, everything works as expected - no errors about SSL certificate. Same with HttpUrlConnection, but for some reason Apaches HttpClient generates an SSLException:
javax.net.ssl.SSLException: hostname in certificate didn't match: my.new.domain != my.old.domain OR my.old.domain
What's more interesting, some of the devices that I tried with worked fine for about a week after the change, and stopped working after that. Other devices stopped working right away.
If I use the old domain name in client code, everything works correctly.
I'm using version 4.3.3 from here of the HttpClient for Android. I realize that converting to HttpUrlConnection would indeed solve my problem, but I'm interested in WHY this is happening - from where does the HttpClient pick up the old domain name? Is it some misconfiguration on the server, or does Apaches HttpClient have some sort of internal dns cache? Testing with a fresh emulator instance raised the same exception, so the problem is not related to caching.
My own investigations got stuck - all I could find was instructions for disabling hostname verification completely, or instructions for self-signed certificates.

That's probably because Apache HttpClient does not support SNI (server name indication), where you can have multiple certificates behind the same IP address. This means, that it does not send the target hostname inside the SSL handshake and thus the server has only the target IP address to decide which certificate it should use and just uses the default certificate for the IP - which is probably the wrong one.

Related

Unexpected exception while opening secure websocket connection

I'm trying to open a secure websocket connection from a Xamarin Android application to an IIS server. It works fine and dandy on every device I've ever used except Samsung Tab E. Even other Samsung devices work fine.
I'm using the Websockets.PCL library and the regular code:
var _webSocket = WebSocketFactory.Create();
_webSocket.OnOpened += _webSocket_Opened;
_webSocket.OnError += _webSocket_Error;
_webSocket.Open("wss://server.name.here/path/also/");
In this case the error handler is called with:
[websockets] javax.net.ssl.SSLException
[websockets] javax.net.ssl.SSLException: Error occured in delegated task:javax.net.ssl.SSLException: Unexpected exception
The device has no issues with SSL connections via Chrome or other apps, not to this server or others. It also has no issues connecting via SignalR to the same IIS server from this application.
It does have an issue when another third party library is trying to check license from a separate website and an error is logged:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x803b3b28: Failure in SSL library, usually a protocol error
I don't know which address it's trying to contact so can't check what is happening there. But this also works from other devices.
This leads me to believe the Android in the Tab E (v4.4.4, latest available) has something wrong with its SSL implementation since these two connection attempts through javax.net.ssl are failing. But the unexpected exception isn't giving much information.
How to get around this? I wouldn't mind using the websockets implementation SignalR uses (since it works), but as far as I know it's not really exposed as a general use system ready to use.
Additional info
The server does talk TLS1 nicely and sends the whole certificate path as far as I know it, so that shouldn't be the issue, unless the root isn't known (and I would expect to get the handshake failure if that was the case). Testing with openssl shows:
Certificate chain
0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=our.domain.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA
Server public key is 2048 bit
Unfortunately openssl doesn't support the ยด-ssl2` flag anymore. Online tools say that SSL2 is disabled, SSL3, TLS1, TLS1.1 and TLS1.2 are enabled.
I could try the custom SSLSocketFactory route but have to see how it would go through Xamarin and Websockets.PCL.
I found a solution for my case after searching for SSL issues in Android. Florian Krauthan has written about these things and talks about using the SSLSocketFactory on Android 4.1+ to allow use of TLS1.1 etc.
This led me to another post where he explains how Google Play Services can be used to inject a newer OpenSSL library into the whole application. This method also worked for my issue.
The only thing that needs to be done is to add the following code to the beginning of the main activity's OnCreate() method:
Android.Gms.Security.ProviderInstaller.InstallIfNeeded(this);
After this the application will use Google Play Services' OpenSSL library so all connections work without issues and possible known bugs in the built-in Android OpenSSL library get mitigated since a newer version is used.
Of course this might not be a solution for anyone not already using Google Play Services since it increases the application size tremendously.

Android Paho SSL with server sent CA certificate, including Hostname Verification

Duplication: There are a few similar questions (How Can I Access an SSL Connection Through Android?, "Trust anchor for certification path not found" in Android SSL Socket client, Trust anchor for certification path not found using SSL)
with no applicable answers to my issue. I try to give a specific context (e.g. paho and specific test sites) and problem.
Main Issue: I'm using Android Paho, as client, and everything's fine. Now I want to add SSL connection. The broker is the certified entity, and sends its CA-issued certificate during handshake.
(Please note that I'm a security newbie, I just read a few tutorials on the subject.)
Most Java and Paho examples I can find deal with having a local certificate (self-issued, or less known CA, etc.), while I need instead to manage CA-issued certificates sent to my app by the broker server during handshake.
From Android documentation (https://developer.android.com/training/articles/security-ssl.html), the idea seems to be that you just have to use SSLSocketFactory.getDefault(), since root CA certifications for well-known CAs are included with the system. So, simply:
MqttConnectOptions options;
// ...
options.setSocketFactory(SSLSocketFactory.getDefault());
But I tested on both:
ssl://iot.eclipse.org:8883
ssl://test.mosquitto.org:8883
and I always got the dreaded javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
The possible reasons given by the aforementioned android documentation, and Stack Overflow answers, presumably don't apply to such well-known and used test sites. I presume that both sites have updated certificates, issued by well-known Certification Authorities, with no lesser known CAs in the chain. (While I cannot find clear confirmation, usage examples around the net do seem to imply it.)
Any pointers? I really have no idea where to go from here (and I presume I'm missing something obvious that it's not clearly stated.)
Secondary Issue: Furthermore, the Android documentation warns that SSLSocket doesn't do any Hostname Verification: https://developer.android.com/training/articles/security-ssl.html#WarningsSslSocket
And hence it suggests:
SocketFactory sf = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
SSLSession s = socket.getSession();
// Verify that the certicate hostname is for mail.google.com
// This is due to lack of SNI support in the current SSLSocket.
if (!hv.verify("mail.google.com", s)) {
throw new SSLHandshakeException("Expected mail.google.com, "
"found " + s.getPeerPrincipal());
}
Please note that you need a reference to the actual socket to pass to verify() (no overloaded alternative), you cannot just configure the SSLSocketFactory.
Paho doesn't seem to expose access to the socket it's using, not to set a custom verifier. You can only set the SSLSocketFactory.
If I understand correctly, there seem to be a Paho bug reported about it: https://bugs.eclipse.org/bugs/show_bug.cgi?id=425195
Is it actually the same issue?
Finally, if so, is there any way to work around this and actually have Android Paho over SSL with Hostname Verification?
EDIT, Partial answer / findings: I record here my partial provisional findings.
My assumption about iot.eclipse.org:8883 and test.mosquitto.org:8883 seems to be wrong: I found doc for both that provide certificates to explicitly be used by the clients (I presume self-signed, it's not specified). Some examples omitting this misled me.
ssl://iot.eclipse.org:8883 : iot.eclipse.org.crt as indicated by http://iot.eclipse.org/getting-started
ssl://test.mosquitto.org:8883 : mosquitto.org.crt as indicated by http://test.mosquitto.org/
Loading the certificates in a custom TrustManager, everything work. Mystery solved on this point. Our own broker with CA certificates isn't ready yet to be tested.

Why does android get the wrong ssl certificate? (two domains, one server)

I have two domains: foo.net and bar.com. They both have SSL certificates, and they work well in all desktop and mobile browsers. They are hosted on the same server configured with nginx.
However, when I make a request to a domain from within a native android app, it somehow gets the certificate from the wrong domain! This results in an IO Exception:
request = new HttpPost("https://foo.net/api/v1/baz");
request.setHeader("Authorization", "user:pass");
response = httpClient.execute(request);
...
javax.net.ssl.SSLException: hostname in certificate didn't match: <foo.net> != <bar.com> OR <bar.com> OR <www.bar.com>
What would cause android/java to try using the certificate from bar.com when every other measure seems to indicate that the server is correctly configured? Nothing appears in the nginx access or error log. There is no mention of bar.com anywhere in my android project.
Edit: I'm not sure why, but it appears that the server is using the certificate for bar.com for the server IP https://198.245.xx.xxx
The most likely cause for this problem is that the server uses Server Name Indication to choose which certificate to send. If the client doesn't support SNI, the server cannot choose which certificate to send during the SSL/TLS handshake (before any HTTP traffic is sent). SNI is required when you want to use multiple certificates on the same IP address and port, but not all clients support it (notoriously, IE on any version of Windows XP, and a number of mobile browsers).
You're also visibly using the Apache HTTP Client library (not HttpsURLConnection, for which there can be SNI support with some Android versions.
Support for SNI in the Apache HTTP Client library is quite recent, and certainly hasn't made it into the Android stack.
You may find the workaround described in this article useful (although it seems only to work for Android 4.2+).
Another two options would be:
to use a distinct IP address for each host (so as not to need SNI), if you're in control of server, or
to use another HTTP Client library (e.g. HttpsURLConnection).
A solution for Apache, more like a trick:
the SSL certificates are loaded based on the vhost name from /etc/apache2/sites-enabled. So, to trick that check make sure the problematic certificate is loaded first (remember that the vhosts are loaded by name).
It looks like the certificate of foo.net is misconfigured, and is using the same hostname as bar.com
Try to run an online certificate validation tool, like https://www.digicert.com/help/ on foo.net, just to be sure.
I think that you need to regenerate the certificate of foo.net with the right hostname, or reconfigure ngix to make sure that nginx serve the right certificate for the right host.

Android ssl connection using HttpsURLConnection and verifying hostname using .pem files?

In android app I am developing I need to make connection to https server.
Client has provided me with 2 files mycert.pem and mykey.pem, which I think is certificate and public keystrore of server.
I need to make secure connection using HttpsURLConnection and verify host name of server to avoid man in middle attack. I have little understanding about making https connection and cryptography terms. Most of tutorial I came across uses HttpClient and they trust all host. Can someone point me in right direction how to use .pem files to make a secure https connection using HttpsURLConnection and verify hostname.
Thanks in advance.
OK done by using following
httpsConn.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
I am using BROWSER_COMPATIBLE_HOSTNAME_VERIFIER, I hope it works well.

No peer cert. Not sure which route to take

I'm trying to hit an https url with my app. I've followed this tutorial. The store I've created has the correct CA. However, when I try to make the connection, I get the following error:
ERROR/IOException(1843): webPost: javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
I've done some reading about this error and people have decided to allow their apps to accept all certificates. I don't think that's really acceptable. What should I check next to see what the issue is?
You can configure your device to ignore SSL certificates:
http://www.virtualzone.de/2011-02-27/how-to-use-apache-httpclient-with-httpsssl-on-android/(dead link)
UPDATE: Much better answer is visible here:
Trusting all certificates using HttpClient over HTTPS
Or do things specific to your HttpClient version:
HttpGet with HTTPS : SSLPeerUnverifiedException

Categories

Resources