Today, an Android hint. Note for myself for future :)
So, what we have:
So, what we have:
- Server
REST (JAX-RS+akka), published to Tomcat. Tomcat serves app via HTTPS having a self-signed certificate (official tomcat docs).
No CA, no chain - just a single certificate done in a really usual way.
Tomcat is published on a box accessible only via IP, no DNS here.
The actual server is https://46.4.224.49 just in case someone wants to take a look @cert. - Client
Android, trying to talk to the server above in JSON. Should use HTTPS for both traffic encryption and identity confirmation.
Generally, implementation seems pretty straightforward - get the public certificate, and make Android https facilities to trust it.
I decided to use HttpsUrlConnection rather than HttpClient as the documentation suggest the former was only preferred for small operations in Android versions prior to 3. Trick is that Android is not shipped with the JKS keystore implementation which seems to belong to Oracle (Sun before).
After some googling I combined 3 articles to make a good working example:
- http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html
use first 2 steps, how to obtain public certificate off the server, pack it to the BKS keystore and some clues on how to setup BouncyCastle
(hint - as of early 2013 we need version bcprov-jdk15on-146.jar, though newer is there)
Added correct line to jre/lib/security/java.security to support another one provider, and dropped the jar into jre/lib/ext. This is only needed for keytool to understand new format. - http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html
Feed HttpsUrlConnection with the SslSocketFactory relying on that keystore. Keystore is put into the res/raw and loaded via context.getResources().openRawResource(int id)
And this is where the trouble started. The test code was producing error. After getting the certificate in the error has changed from 'Not trusted certificate' to 'Can not verify hostname'. Which is progress.
The very major of the solutions on the Internet were just allowing all the hostname, and even deeper, accepting all the certificates -- which was inacceptable due to security reason.
The certificate had an IP address in CN field. After some research, it was clear that certificate is there, it is trusted, however hostname check fails. That's when DefaultHostnameVerifier was spotted (yes i know javadoc is old, but debugger shows this very class, which is hidden in library-no source). However, old javadoc says clearly that all the checks are declined!
Surely we can not have this check passed as nothing is allowed.
The issue is resolved easily with setting conn.setHostnameVerifier(new BrowserCompatHostnameVerifier()) - this triggers HttpsUrlConnection to the same mode as the browser (passing *.domain CNs, and IP addresses, etc), which is perfectly OK for our case.