mdoery ~ software development

adventures in coding

an android hiccup

| Comments

Summary: if you switch to https watch out for this Android bug

One of the Android projects that I’m working on uses the Apache HTTP library, which is located in a library – android.jar – that comes with Android. The project uses the library to connect to an external server using an HTTP GET command. I’ve experienced a couple of problems with this project due to this connection requirement.

(Note: this project is not mine, but belongs to one of my customers, so I’m not the original developer who wrote this code! I’m just dealing with the fallout.)

The first problem occurred when the IP address for the server was changed by the hosting service. I do not know why someone would hard-code an IP address when making a connection, but that was done in this app. The problem was obvious, and the fix was simple enough: change the line of code to point to the domain name (like, “http://example.com”) rather than the IP address. Problem fixed!

The second problem occurred more recently, when the hosting server was switched to use HTTPS. The URL had to be changed – again! – to read something like “https://example.com”. This happened months ago, and no issues were noticed… until a couple days ago, when I was adding a new feature to the app, and it suddenly stopped working!

I checked the logs and noticed an Exception with a lengthy stack trace. Here is the stack trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
08-23 08:11:23.863 W/System.err(25119): javax.net.ssl.SSLException: hostname in certificate didn't match: <example.com> != <*.hostmonster.com> OR <*.hostmonster.com> OR <hostmonster.com>
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:185)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:114)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:95)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:165)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
08-23 08:11:23.863 W/System.err(25119):   at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
...
08-23 08:11:23.863 W/System.err(25119):   at android.os.Handler.handleCallback(Handler.java:615)
08-23 08:11:23.863 W/System.err(25119):   at android.os.Handler.dispatchMessage(Handler.java:92)
08-23 08:11:23.863 W/System.err(25119):   at android.os.Looper.loop(Looper.java:137)
...

The first line is key: “javax.net.ssl.SSLException: hostname in certificate didn’t match: <example.com> != <.hostmonster.com> OR <.hostmonster.com> OR <hostmonster.com>”. My customer is using hostmonster to host their external server. What’s going on?

When I saw this message, I thought the server might be down. The logs contained the URL being requested, so I accessed that URL in a browser, and found it was working just fine.

A simple search for javax.net.ssl.SSLException did not reveal a simple explanation for this error. After substantial hunting, I found a StackOverflow question which provided a clue – it seemed to be related to SNI (server name indication) support. That page pointed to another question at StackOverflow – Why does android get the wrong ssl certificate? (two domains, one server) which made things more clear.

The answer is that the client (the app, in this case) needs to support SNI, and if it does not, the server may send the wrong SSL certificate. That definitely seemed to be happening here. As recommended in the answer to that SO article, I rewrote the app code to use java.net.HttpURLConnection, and the app was fixed immediately. You might think it would be required to use HttpsURLConnection, but it worked perfectly fine with HttpURLConnection.

I did not research the problem much further. I noticed another open-ended question at SO asking what’s the deal with SNI support in Android.

Most of these questions and responses are fairly old. I found a discussion at Google’s issue tracker with the following remarks (from 2010 and 2011!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[bd...@google.com says:]
The main problem with supporting SNI in the browser is that many sites choke on TLS extensions, especially lesser known ones. Chrome and other browsers try to retry the connection without TLS (and without compression, which technically is SSL and not TLS) 

We have moved forward with this in 2.3 (Gingerbread) and javax.net.ssl.HttpsURLConnection does attempt to handshake with SNI (and automatically falls back to SSL w/o compression if there are problems). Unfortunately the browser uses Apache HTTP Client and there was not a simple local fix to make it retry failed connections, so it is overly conservative and does not include SNI information.

[vi...@gmail.com says:]
Non "corporate gibberish" summary : 
 * Gingerbread (2.3) Will never get this update officially. 
 * Honeycomb (3.0) contains the fix but isn't available on phones. 
 * Ice Cream Sandwich (4.0) will provide this feature to phones but isn't already available (soon). 

Also : 
 * The fix from Honeycomb could have been backported by the manufacturers that got it's source code but none that I know off did it. 
 * As Honeycomb is a closed source release none of the alternative distribution could merge the patch without re-developing it. 
 * As Ice Cream Sandwitch will include this patch it will be in all distributions and could even be backported to Gingerbread. 

Sadly coupled with the fact that google don't control at all the updates of previous phones it lock SNI in an "Unusable for most of Android users" state for 2+ Years at least for environements where you don't control users hardware. 

At least now for companies you could plan an upgrade (software or hardware depending on the phone models your employees got) as soon as ICS is released

I still don’t know why this problem appeared so suddenly. It could be due to some change at hostmonster (seems most likey), or it could be due to a change in the OS on my test device.

For the record, here’s the fixed code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
URL testUrl = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection)testUrl.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
int code = urlConnection.getResponseCode();
InputStream in = urlConnection.getInputStream();
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
String inputLine;
StringBuffer sBuffer = new StringBuffer();
while ((inputLine = bin.readLine()) != null) {
  sBuffer.append(inputLine);
}
in.close();
String result = sBuffer.toString();
// do something with the result...

Comments